|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * Copyright (C) 2014 Free Electrons | 
|  | * Copyright (C) 2014 Atmel | 
|  | * | 
|  | * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/mfd/atmel-hlcdc.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/pwm.h> | 
|  | #include <linux/regmap.h> | 
|  |  | 
|  | #define ATMEL_HLCDC_PWMCVAL_MASK	GENMASK(15, 8) | 
|  | #define ATMEL_HLCDC_PWMCVAL(x)		(((x) << 8) & ATMEL_HLCDC_PWMCVAL_MASK) | 
|  | #define ATMEL_HLCDC_PWMPOL		BIT(4) | 
|  | #define ATMEL_HLCDC_PWMPS_MASK		GENMASK(2, 0) | 
|  | #define ATMEL_HLCDC_PWMPS_MAX		0x6 | 
|  | #define ATMEL_HLCDC_PWMPS(x)		((x) & ATMEL_HLCDC_PWMPS_MASK) | 
|  |  | 
|  | struct atmel_hlcdc_pwm_errata { | 
|  | bool slow_clk_erratum; | 
|  | bool div1_clk_erratum; | 
|  | }; | 
|  |  | 
|  | struct atmel_hlcdc_pwm { | 
|  | struct atmel_hlcdc *hlcdc; | 
|  | struct clk *cur_clk; | 
|  | const struct atmel_hlcdc_pwm_errata *errata; | 
|  | }; | 
|  |  | 
|  | static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip) | 
|  | { | 
|  | return pwmchip_get_drvdata(chip); | 
|  | } | 
|  |  | 
|  | static int atmel_hlcdc_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, | 
|  | const struct pwm_state *state) | 
|  | { | 
|  | struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip); | 
|  | struct atmel_hlcdc *hlcdc = atmel->hlcdc; | 
|  | unsigned int status; | 
|  | int ret; | 
|  |  | 
|  | if (state->enabled) { | 
|  | struct clk *new_clk = hlcdc->slow_clk; | 
|  | u64 pwmcval = state->duty_cycle * 256; | 
|  | unsigned long clk_freq; | 
|  | u64 clk_period_ns; | 
|  | u32 pwmcfg; | 
|  | int pres; | 
|  |  | 
|  | if (!atmel->errata || !atmel->errata->slow_clk_erratum) { | 
|  | clk_freq = clk_get_rate(new_clk); | 
|  | if (!clk_freq) | 
|  | return -EINVAL; | 
|  |  | 
|  | clk_period_ns = (u64)NSEC_PER_SEC * 256; | 
|  | do_div(clk_period_ns, clk_freq); | 
|  | } | 
|  |  | 
|  | /* Errata: cannot use slow clk on some IP revisions */ | 
|  | if ((atmel->errata && atmel->errata->slow_clk_erratum) || | 
|  | clk_period_ns > state->period) { | 
|  | new_clk = hlcdc->sys_clk; | 
|  | clk_freq = clk_get_rate(new_clk); | 
|  | if (!clk_freq) | 
|  | return -EINVAL; | 
|  |  | 
|  | clk_period_ns = (u64)NSEC_PER_SEC * 256; | 
|  | do_div(clk_period_ns, clk_freq); | 
|  | } | 
|  |  | 
|  | for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) { | 
|  | /* Errata: cannot divide by 1 on some IP revisions */ | 
|  | if (!pres && atmel->errata && | 
|  | atmel->errata->div1_clk_erratum) | 
|  | continue; | 
|  |  | 
|  | if ((clk_period_ns << pres) >= state->period) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (pres > ATMEL_HLCDC_PWMPS_MAX) | 
|  | return -EINVAL; | 
|  |  | 
|  | pwmcfg = ATMEL_HLCDC_PWMPS(pres); | 
|  |  | 
|  | if (new_clk != atmel->cur_clk) { | 
|  | u32 gencfg = 0; | 
|  | int ret; | 
|  |  | 
|  | ret = clk_prepare_enable(new_clk); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | clk_disable_unprepare(atmel->cur_clk); | 
|  | atmel->cur_clk = new_clk; | 
|  |  | 
|  | if (new_clk == hlcdc->sys_clk) | 
|  | gencfg = ATMEL_HLCDC_CLKPWMSEL; | 
|  |  | 
|  | ret = regmap_update_bits(hlcdc->regmap, | 
|  | ATMEL_HLCDC_CFG(0), | 
|  | ATMEL_HLCDC_CLKPWMSEL, | 
|  | gencfg); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | do_div(pwmcval, state->period); | 
|  |  | 
|  | /* | 
|  | * The PWM duty cycle is configurable from 0/256 to 255/256 of | 
|  | * the period cycle. Hence we can't set a duty cycle occupying | 
|  | * the whole period cycle if we're asked to. | 
|  | * Set it to 255 if pwmcval is greater than 256. | 
|  | */ | 
|  | if (pwmcval > 255) | 
|  | pwmcval = 255; | 
|  |  | 
|  | pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval); | 
|  |  | 
|  | if (state->polarity == PWM_POLARITY_NORMAL) | 
|  | pwmcfg |= ATMEL_HLCDC_PWMPOL; | 
|  |  | 
|  | ret = regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6), | 
|  | ATMEL_HLCDC_PWMCVAL_MASK | | 
|  | ATMEL_HLCDC_PWMPS_MASK | | 
|  | ATMEL_HLCDC_PWMPOL, | 
|  | pwmcfg); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN, | 
|  | ATMEL_HLCDC_PWM); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR, | 
|  | status, | 
|  | status & ATMEL_HLCDC_PWM, | 
|  | 10, 0); | 
|  | if (ret) | 
|  | return ret; | 
|  | } else { | 
|  | ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS, | 
|  | ATMEL_HLCDC_PWM); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR, | 
|  | status, | 
|  | !(status & ATMEL_HLCDC_PWM), | 
|  | 10, 0); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | clk_disable_unprepare(atmel->cur_clk); | 
|  | atmel->cur_clk = NULL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct pwm_ops atmel_hlcdc_pwm_ops = { | 
|  | .apply = atmel_hlcdc_pwm_apply, | 
|  | }; | 
|  |  | 
|  | static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_at91sam9x5_errata = { | 
|  | .slow_clk_erratum = true, | 
|  | }; | 
|  |  | 
|  | static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = { | 
|  | .div1_clk_erratum = true, | 
|  | }; | 
|  |  | 
|  | static int atmel_hlcdc_pwm_suspend(struct device *dev) | 
|  | { | 
|  | struct pwm_chip *chip = dev_get_drvdata(dev); | 
|  | struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip); | 
|  | struct pwm_device *pwm = &chip->pwms[0]; | 
|  |  | 
|  | /* Keep the periph clock enabled if the PWM is still running. */ | 
|  | if (!pwm->state.enabled) | 
|  | clk_disable_unprepare(atmel->hlcdc->periph_clk); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int atmel_hlcdc_pwm_resume(struct device *dev) | 
|  | { | 
|  | struct pwm_chip *chip = dev_get_drvdata(dev); | 
|  | struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip); | 
|  | struct pwm_device *pwm = &chip->pwms[0]; | 
|  | int ret; | 
|  |  | 
|  | /* Re-enable the periph clock it was stopped during suspend. */ | 
|  | if (!pwm->state.enabled) { | 
|  | ret = clk_prepare_enable(atmel->hlcdc->periph_clk); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return atmel_hlcdc_pwm_apply(chip, pwm, &pwm->state); | 
|  | } | 
|  |  | 
|  | static DEFINE_SIMPLE_DEV_PM_OPS(atmel_hlcdc_pwm_pm_ops, | 
|  | atmel_hlcdc_pwm_suspend, atmel_hlcdc_pwm_resume); | 
|  |  | 
|  | static const struct of_device_id atmel_hlcdc_dt_ids[] = { | 
|  | { | 
|  | .compatible = "atmel,at91sam9n12-hlcdc", | 
|  | /* 9n12 has same errata as 9x5 HLCDC PWM */ | 
|  | .data = &atmel_hlcdc_pwm_at91sam9x5_errata, | 
|  | }, | 
|  | { | 
|  | .compatible = "atmel,at91sam9x5-hlcdc", | 
|  | .data = &atmel_hlcdc_pwm_at91sam9x5_errata, | 
|  | }, | 
|  | { | 
|  | .compatible = "atmel,sama5d2-hlcdc", | 
|  | }, | 
|  | { | 
|  | .compatible = "atmel,sama5d3-hlcdc", | 
|  | .data = &atmel_hlcdc_pwm_sama5d3_errata, | 
|  | }, | 
|  | { | 
|  | .compatible = "atmel,sama5d4-hlcdc", | 
|  | .data = &atmel_hlcdc_pwm_sama5d3_errata, | 
|  | }, | 
|  | {	.compatible = "microchip,sam9x60-hlcdc", }, | 
|  | { /* sentinel */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids); | 
|  |  | 
|  | static int atmel_hlcdc_pwm_probe(struct platform_device *pdev) | 
|  | { | 
|  | const struct of_device_id *match; | 
|  | struct device *dev = &pdev->dev; | 
|  | struct pwm_chip *chip; | 
|  | struct atmel_hlcdc_pwm *atmel; | 
|  | struct atmel_hlcdc *hlcdc; | 
|  | int ret; | 
|  |  | 
|  | hlcdc = dev_get_drvdata(dev->parent); | 
|  |  | 
|  | chip = devm_pwmchip_alloc(dev, 1, sizeof(*atmel)); | 
|  | if (IS_ERR(chip)) | 
|  | return PTR_ERR(chip); | 
|  | atmel = to_atmel_hlcdc_pwm(chip); | 
|  |  | 
|  | ret = clk_prepare_enable(hlcdc->periph_clk); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | match = of_match_node(atmel_hlcdc_dt_ids, dev->parent->of_node); | 
|  | if (match) | 
|  | atmel->errata = match->data; | 
|  |  | 
|  | atmel->hlcdc = hlcdc; | 
|  | chip->ops = &atmel_hlcdc_pwm_ops; | 
|  |  | 
|  | ret = pwmchip_add(chip); | 
|  | if (ret) { | 
|  | clk_disable_unprepare(hlcdc->periph_clk); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, chip); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void atmel_hlcdc_pwm_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct pwm_chip *chip = platform_get_drvdata(pdev); | 
|  | struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip); | 
|  |  | 
|  | pwmchip_remove(chip); | 
|  |  | 
|  | clk_disable_unprepare(atmel->hlcdc->periph_clk); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = { | 
|  | { .compatible = "atmel,hlcdc-pwm" }, | 
|  | { /* sentinel */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, atmel_hlcdc_pwm_dt_ids); | 
|  |  | 
|  | static struct platform_driver atmel_hlcdc_pwm_driver = { | 
|  | .driver = { | 
|  | .name = "atmel-hlcdc-pwm", | 
|  | .of_match_table = atmel_hlcdc_pwm_dt_ids, | 
|  | .pm = pm_ptr(&atmel_hlcdc_pwm_pm_ops), | 
|  | }, | 
|  | .probe = atmel_hlcdc_pwm_probe, | 
|  | .remove = atmel_hlcdc_pwm_remove, | 
|  | }; | 
|  | module_platform_driver(atmel_hlcdc_pwm_driver); | 
|  |  | 
|  | MODULE_ALIAS("platform:atmel-hlcdc-pwm"); | 
|  | MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); | 
|  | MODULE_DESCRIPTION("Atmel HLCDC PWM driver"); | 
|  | MODULE_LICENSE("GPL v2"); |