diff options
Diffstat (limited to 'drivers/pwm/pwm-pxa.c')
| -rw-r--r-- | drivers/pwm/pwm-pxa.c | 171 |
1 files changed, 95 insertions, 76 deletions
diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index dc9717551d39..0f5bdb0e395e 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -1,16 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/pwm/pwm-pxa.c * * simple driver for PWM (Pulse Width Modulator) controller * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * * 2008-02-13 initial version - * eric miao <eric.miao@marvell.com> + * eric miao <eric.miao@marvell.com> + * + * Links to reference manuals for some of the supported PWM chips can be found + * in Documentation/arch/arm/marvell.rst. + * + * Limitations: + * - When PWM is stopped, the current PWM period stops abruptly at the next + * input clock (PWMCR_SD is set) and the output is driven to inactive. */ +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> @@ -19,6 +24,8 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/pwm.h> +#include <linux/of.h> +#include <linux/reset.h> #include <asm/div64.h> @@ -43,7 +50,6 @@ MODULE_DEVICE_TABLE(platform, pwm_id_table); #define PWMDCR_FD (1 << 10) struct pxa_pwm_chip { - struct pwm_chip chip; struct device *dev; struct clk *clk; @@ -52,7 +58,7 @@ struct pxa_pwm_chip { static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip) { - return container_of(chip, struct pxa_pwm_chip, chip); + return pwmchip_get_drvdata(chip); } /* @@ -60,13 +66,12 @@ static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip) * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE */ static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) + u64 duty_ns, u64 period_ns) { struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); unsigned long long c; unsigned long period_cycles, prescale, pv, dc; unsigned long offset; - int rc; offset = pwm->hwpwm ? 0x10 : 0; @@ -86,112 +91,126 @@ static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, if (duty_ns == period_ns) dc = PWMDCR_FD; else - dc = (pv + 1) * duty_ns / period_ns; + dc = mul_u64_u64_div_u64(pv + 1, duty_ns, period_ns); - /* NOTE: the clock to PWM has to be enabled first - * before writing to the registers - */ - rc = clk_prepare_enable(pc->clk); - if (rc < 0) - return rc; - - writel(prescale, pc->mmio_base + offset + PWMCR); + writel(prescale | PWMCR_SD, pc->mmio_base + offset + PWMCR); writel(dc, pc->mmio_base + offset + PWMDCR); writel(pv, pc->mmio_base + offset + PWMPCR); - clk_disable_unprepare(pc->clk); return 0; } -static int pxa_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +static int pxa_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) { struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + u64 duty_cycle; + int err; - return clk_prepare_enable(pc->clk); -} + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; -static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + err = clk_prepare_enable(pc->clk); + if (err) + return err; + + duty_cycle = state->enabled ? state->duty_cycle : 0; + + err = pxa_pwm_config(chip, pwm, duty_cycle, state->period); + if (err) { + clk_disable_unprepare(pc->clk); + return err; + } + + if (state->enabled && !pwm->state.enabled) + return 0; clk_disable_unprepare(pc->clk); + + if (!state->enabled && pwm->state.enabled) + clk_disable_unprepare(pc->clk); + + return 0; } -static struct pwm_ops pxa_pwm_ops = { - .config = pxa_pwm_config, - .enable = pxa_pwm_enable, - .disable = pxa_pwm_disable, - .owner = THIS_MODULE, +static const struct pwm_ops pxa_pwm_ops = { + .apply = pxa_pwm_apply, }; +#ifdef CONFIG_OF +/* + * Device tree users must create one device instance for each PWM channel. + * Hence we dispense with the HAS_SECONDARY_PWM and "tell" the original driver + * code that this is a single channel pxa25x-pwm. Currently all devices are + * supported identically. + */ +static const struct of_device_id pwm_of_match[] = { + { .compatible = "marvell,pxa250-pwm", .data = &pwm_id_table[0]}, + { .compatible = "marvell,pxa270-pwm", .data = &pwm_id_table[0]}, + { .compatible = "marvell,pxa168-pwm", .data = &pwm_id_table[0]}, + { .compatible = "marvell,pxa910-pwm", .data = &pwm_id_table[0]}, + { } +}; +MODULE_DEVICE_TABLE(of, pwm_of_match); +#else +#define pwm_of_match NULL +#endif + static int pwm_probe(struct platform_device *pdev) { const struct platform_device_id *id = platform_get_device_id(pdev); - struct pxa_pwm_chip *pwm; - struct resource *r; + struct pwm_chip *chip; + struct pxa_pwm_chip *pc; + struct device *dev = &pdev->dev; + struct reset_control *rst; int ret = 0; - pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } + if (IS_ENABLED(CONFIG_OF) && id == NULL) + id = of_device_get_match_data(dev); - pwm->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(pwm->clk)) - return PTR_ERR(pwm->clk); + if (id == NULL) + return -EINVAL; - pwm->chip.dev = &pdev->dev; - pwm->chip.ops = &pxa_pwm_ops; - pwm->chip.base = -1; - pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1; + chip = devm_pwmchip_alloc(dev, (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1, + sizeof(*pc)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + pc = to_pxa_pwm_chip(chip); - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(pwm->mmio_base)) - return PTR_ERR(pwm->mmio_base); + pc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(pc->clk)) + return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); - ret = pwmchip_add(&pwm->chip); - if (ret < 0) { - dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); - return ret; - } + rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return PTR_ERR(rst); - platform_set_drvdata(pdev, pwm); - return 0; -} + chip->ops = &pxa_pwm_ops; -static int pwm_remove(struct platform_device *pdev) -{ - struct pxa_pwm_chip *chip; + if (IS_ENABLED(CONFIG_OF)) + chip->of_xlate = of_pwm_single_xlate; + + pc->mmio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pc->mmio_base)) + return PTR_ERR(pc->mmio_base); - chip = platform_get_drvdata(pdev); - if (chip == NULL) - return -ENODEV; + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "pwmchip_add() failed\n"); - return pwmchip_remove(&chip->chip); + return 0; } static struct platform_driver pwm_driver = { .driver = { .name = "pxa25x-pwm", - .owner = THIS_MODULE, + .of_match_table = pwm_of_match, }, .probe = pwm_probe, - .remove = pwm_remove, .id_table = pwm_id_table, }; -static int __init pwm_init(void) -{ - return platform_driver_register(&pwm_driver); -} -arch_initcall(pwm_init); - -static void __exit pwm_exit(void) -{ - platform_driver_unregister(&pwm_driver); -} -module_exit(pwm_exit); +module_platform_driver(pwm_driver); +MODULE_DESCRIPTION("PXA Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); |
