From db539cb928c0abe3b6c96ab16ed19b90c5dd110d Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 11 Apr 2020 17:35:28 +0200 Subject: pwm: Add missing '\n' in log messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Message logged by 'dev_xxx()' or 'pr_xxx()' should end with a '\n'. Fixes: 3ad1f3a33286 ("pwm: Implement some checks for lowlevel drivers") Signed-off-by: Christophe JAILLET Acked-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 9973c442b455..bca04965bfe6 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -537,7 +537,7 @@ static void pwm_apply_state_debug(struct pwm_device *pwm, if (!state->enabled && s2.enabled && s2.duty_cycle > 0) dev_warn(chip->dev, - "requested disabled, but yielded enabled with duty > 0"); + "requested disabled, but yielded enabled with duty > 0\n"); /* reapply the state that the driver reported being configured. */ err = chip->ops->apply(chip, pwm, &s1); -- cgit v1.2.3 From 07b053ff1c6ee7c972e7e3d0915d05f1b1280f14 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 10 Apr 2020 18:14:17 +0900 Subject: dt-bindings: pwm: rcar: add r8a77961 support Add support for r8a77961 (R-Car M3-W+). Signed-off-by: Yoshihiro Shimoda Reviewed-by: Geert Uytterhoeven Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml index 945c14e1be35..620a2ff40fc7 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml +++ b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml @@ -27,6 +27,7 @@ properties: - renesas,pwm-r8a7794 # R-Car E2 - renesas,pwm-r8a7795 # R-Car H3 - renesas,pwm-r8a7796 # R-Car M3-W + - renesas,pwm-r8a77961 # R-Car M3-W+ - renesas,pwm-r8a77965 # R-Car M3-N - renesas,pwm-r8a77970 # R-Car V3M - renesas,pwm-r8a77980 # R-Car V3H -- cgit v1.2.3 From 6f0841a8197b20d01de9d41cb1eeed09b7b31f9b Mon Sep 17 00:00:00 2001 From: Jeff LaBundy Date: Sun, 12 Apr 2020 21:15:16 -0500 Subject: pwm: Add support for Azoteq IQS620A PWM generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for the Azoteq IQS620A, capable of generating a 1-kHz PWM output with duty cycle between ~0.4% and 100% (inclusive). Signed-off-by: Jeff LaBundy Reviewed-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-iqs620a.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 drivers/pwm/pwm-iqs620a.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index eebbc917ac97..c13d146cdde5 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -232,6 +232,16 @@ config PWM_IMX_TPM To compile this driver as a module, choose M here: the module will be called pwm-imx-tpm. +config PWM_IQS620A + tristate "Azoteq IQS620A PWM support" + depends on MFD_IQS62X || COMPILE_TEST + help + Generic PWM framework driver for the Azoteq IQS620A multi-function + sensor. + + To compile this driver as a module, choose M here: the module will + be called pwm-iqs620a. + config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" depends on MACH_INGENIC diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9a475073dafc..a59c710e98c7 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o +obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c new file mode 100644 index 000000000000..674f0e238ba0 --- /dev/null +++ b/drivers/pwm/pwm-iqs620a.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A PWM Generator + * + * Copyright (C) 2019 Jeff LaBundy + * + * Limitations: + * - The period is fixed to 1 ms and is generated continuously despite changes + * to the duty cycle or enable/disable state. + * - Changes to the duty cycle or enable/disable state take effect immediately + * and may result in a glitch during the period in which the change is made. + * - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256 + * ms, the output is disabled and relies upon an external pull-down resistor + * to hold the GPIO3/LTX pin low. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS620_PWR_SETTINGS 0xD2 +#define IQS620_PWR_SETTINGS_PWM_OUT BIT(7) + +#define IQS620_PWM_DUTY_CYCLE 0xD8 + +#define IQS620_PWM_PERIOD_NS 1000000 + +struct iqs620_pwm_private { + struct iqs62x_core *iqs62x; + struct pwm_chip chip; + struct notifier_block notifier; + struct mutex lock; + bool out_en; + u8 duty_val; +}; + +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + int duty_scale, ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -ENOTSUPP; + + if (state->period < IQS620_PWM_PERIOD_NS) + return -EINVAL; + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + iqs62x = iqs620_pwm->iqs62x; + + /* + * The duty cycle generated by the device is calculated as follows: + * + * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms + * + * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255 + * (inclusive). Therefore the lowest duty cycle the device can generate + * while the output is enabled is 1 / 256 ms. + * + * For lower duty cycles (e.g. 0), the PWM output is simply disabled to + * allow an external pull-down resistor to hold the GPIO3/LTX pin low. + */ + duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS; + + mutex_lock(&iqs620_pwm->lock); + + if (!state->enabled || !duty_scale) { + ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, 0); + if (ret) + goto err_mutex; + } + + if (duty_scale) { + u8 duty_val = min(duty_scale - 1, 0xFF); + + ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, + duty_val); + if (ret) + goto err_mutex; + + iqs620_pwm->duty_val = duty_val; + } + + if (state->enabled && duty_scale) { + ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, 0xFF); + if (ret) + goto err_mutex; + } + + iqs620_pwm->out_en = state->enabled; + +err_mutex: + mutex_unlock(&iqs620_pwm->lock); + + return ret; +} + +static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + + mutex_lock(&iqs620_pwm->lock); + + /* + * Since the device cannot generate a 0% duty cycle, requests to do so + * cause subsequent calls to iqs620_pwm_get_state to report the output + * as disabled with duty cycle equal to that which was in use prior to + * the request. This is not ideal, but is the best compromise based on + * the capabilities of the device. + */ + state->enabled = iqs620_pwm->out_en; + state->duty_cycle = DIV_ROUND_UP((iqs620_pwm->duty_val + 1) * + IQS620_PWM_PERIOD_NS, 256); + + mutex_unlock(&iqs620_pwm->lock); + + state->period = IQS620_PWM_PERIOD_NS; +} + +static int iqs620_pwm_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + int ret; + + if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET))) + return NOTIFY_DONE; + + iqs620_pwm = container_of(notifier, struct iqs620_pwm_private, + notifier); + iqs62x = iqs620_pwm->iqs62x; + + mutex_lock(&iqs620_pwm->lock); + + /* + * The parent MFD driver already prints an error message in the event + * of a device reset, so nothing else is printed here unless there is + * an additional failure. + */ + ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, + iqs620_pwm->duty_val); + if (ret) + goto err_mutex; + + ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, + iqs620_pwm->out_en ? 0xFF : 0); + +err_mutex: + mutex_unlock(&iqs620_pwm->lock); + + if (ret) { + dev_err(iqs620_pwm->chip.dev, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + +static const struct pwm_ops iqs620_pwm_ops = { + .apply = iqs620_pwm_apply, + .get_state = iqs620_pwm_get_state, + .owner = THIS_MODULE, +}; + +static void iqs620_pwm_notifier_unregister(void *context) +{ + struct iqs620_pwm_private *iqs620_pwm = context; + int ret; + + ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) + dev_err(iqs620_pwm->chip.dev, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs620_pwm_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs620_pwm_private *iqs620_pwm; + unsigned int val; + int ret; + + iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL); + if (!iqs620_pwm) + return -ENOMEM; + + platform_set_drvdata(pdev, iqs620_pwm); + iqs620_pwm->iqs62x = iqs62x; + + ret = regmap_read(iqs62x->regmap, IQS620_PWR_SETTINGS, &val); + if (ret) + return ret; + iqs620_pwm->out_en = val & IQS620_PWR_SETTINGS_PWM_OUT; + + ret = regmap_read(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, &val); + if (ret) + return ret; + iqs620_pwm->duty_val = val; + + iqs620_pwm->chip.dev = &pdev->dev; + iqs620_pwm->chip.ops = &iqs620_pwm_ops; + iqs620_pwm->chip.base = -1; + iqs620_pwm->chip.npwm = 1; + + mutex_init(&iqs620_pwm->lock); + + iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier; + ret = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs620_pwm_notifier_unregister, + iqs620_pwm); + if (ret) + return ret; + + ret = pwmchip_add(&iqs620_pwm->chip); + if (ret) + dev_err(&pdev->dev, "Failed to add device: %d\n", ret); + + return ret; +} + +static int iqs620_pwm_remove(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&iqs620_pwm->chip); + if (ret) + dev_err(&pdev->dev, "Failed to remove device: %d\n", ret); + + return ret; +} + +static struct platform_driver iqs620_pwm_platform_driver = { + .driver = { + .name = "iqs620a-pwm", + }, + .probe = iqs620_pwm_probe, + .remove = iqs620_pwm_remove, +}; +module_platform_driver(iqs620_pwm_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620A PWM Generator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs620a-pwm"); -- cgit v1.2.3 From 856c45d8c1a733e10ad82be817202eb32ea4e570 Mon Sep 17 00:00:00 2001 From: Peter Vasil Date: Tue, 28 Apr 2020 18:41:50 +0200 Subject: pwm: sun4i: Support direct clock output on Allwinner A64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allwinner A64 is capable of a direct clock output on PWM (see A64 User Manual chapter 3.10). Add support for this in the sun4i PWM driver. Signed-off-by: Peter Vasil Acked-by: Maxime Ripard Acked-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/pwm-sun4i.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index 5c677c563349..18fbbe3277d0 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -352,6 +352,12 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = { .npwm = 1, }; +static const struct sun4i_pwm_data sun50i_a64_pwm_data = { + .has_prescaler_bypass = true, + .has_direct_mod_clk_output = true, + .npwm = 1, +}; + static const struct sun4i_pwm_data sun50i_h6_pwm_data = { .has_prescaler_bypass = true, .has_direct_mod_clk_output = true, @@ -374,6 +380,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = { }, { .compatible = "allwinner,sun8i-h3-pwm", .data = &sun4i_pwm_single_bypass, + }, { + .compatible = "allwinner,sun50i-a64-pwm", + .data = &sun50i_a64_pwm_data, }, { .compatible = "allwinner,sun50i-h6-pwm", .data = &sun50i_h6_pwm_data, -- cgit v1.2.3 From 01aa905d4791da7d3630f6030ff99d58105cca00 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 May 2020 13:00:44 +0200 Subject: pwm: lpss: Fix get_state runtime-pm reference handling Before commit cfc4c189bc70 ("pwm: Read initial hardware state at request time"), a driver's get_state callback would get called once per PWM from pwmchip_add(). pwm-lpss' runtime-pm code was relying on this, getting a runtime-pm ref for PWMs which are enabled at probe time from within its get_state callback, before enabling runtime-pm. The change to calling get_state at request time causes a number of problems: 1. PWMs enabled at probe time may get runtime suspended before they are requested, causing e.g. a LCD backlight controlled by the PWM to turn off. 2. When the request happens when the PWM has been runtime suspended, the ctrl register will read all 1 / 0xffffffff, causing get_state to store bogus values in the pwm_state. 3. get_state was using an async pm_runtime_get() call, because it assumed that runtime-pm has not been enabled yet. If shortly after the request an apply call is made, then the pwm_lpss_is_updating() check may trigger because the resume triggered by the pm_runtime_get() call is not complete yet, so the ctrl register still reads all 1 / 0xffffffff. This commit fixes these issues by moving the initial pm_runtime_get() call for PWMs which are enabled at probe time to the pwm_lpss_probe() function; and by making get_state take a runtime-pm ref before reading the ctrl reg. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1828927 Fixes: cfc4c189bc70 ("pwm: Read initial hardware state at request time") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 75bbfe5f3bc2..9d965ffe66d1 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -158,7 +158,6 @@ static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -/* This function gets called once from pwmchip_add to get the initial state */ static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { @@ -167,6 +166,8 @@ static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long base_unit, freq, on_time_div; u32 ctrl; + pm_runtime_get_sync(chip->dev); + base_unit_range = BIT(lpwm->info->base_unit_bits); ctrl = pwm_lpss_read(pwm); @@ -187,8 +188,7 @@ static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm, state->polarity = PWM_POLARITY_NORMAL; state->enabled = !!(ctrl & PWM_ENABLE); - if (state->enabled) - pm_runtime_get(chip->dev); + pm_runtime_put(chip->dev); } static const struct pwm_ops pwm_lpss_ops = { @@ -202,7 +202,8 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, { struct pwm_lpss_chip *lpwm; unsigned long c; - int ret; + int i, ret; + u32 ctrl; if (WARN_ON(info->npwm > MAX_PWMS)) return ERR_PTR(-ENODEV); @@ -232,6 +233,12 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, return ERR_PTR(ret); } + for (i = 0; i < lpwm->info->npwm; i++) { + ctrl = pwm_lpss_read(&lpwm->chip.pwms[i]); + if (ctrl & PWM_ENABLE) + pm_runtime_get(dev); + } + return lpwm; } EXPORT_SYMBOL_GPL(pwm_lpss_probe); -- cgit v1.2.3 From b48d49e0d53a15a82545ae68db54e8abe99a840a Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Wed, 27 May 2020 13:52:22 +0200 Subject: pwm: jz4740: Drop dependency on MACH_INGENIC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depending on MACH_INGENIC prevent us from creating a generic kernel that works on more than one MIPS board. Instead, we just depend on MIPS being set. Acked-by: Uwe Kleine-König Signed-off-by: Paul Cercueil Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c13d146cdde5..cb8d739067d2 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -244,7 +244,7 @@ config PWM_IQS620A config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" - depends on MACH_INGENIC + depends on MIPS depends on COMMON_CLK select MFD_SYSCON help -- cgit v1.2.3 From 9017dc4fbd59c09463019ce494cfe36d654495a8 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Wed, 27 May 2020 13:52:23 +0200 Subject: pwm: jz4740: Enhance precision in calculation of duty cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calculating the hardware value for the duty from the hardware value of the period resulted in a precision loss versus calculating it from the clock rate directly. (Also remove a cast that doesn't really need to be here) Fixes: f6b8a5700057 ("pwm: Add Ingenic JZ4740 support") Cc: Suggested-by: Uwe Kleine-König Reviewed-by: Uwe Kleine-König Signed-off-by: Paul Cercueil Signed-off-by: Thierry Reding --- drivers/pwm/pwm-jz4740.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 3cd5c054ad9a..4fe9d99ac9a9 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -158,11 +158,11 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* Calculate period value */ tmp = (unsigned long long)rate * state->period; do_div(tmp, NSEC_PER_SEC); - period = (unsigned long)tmp; + period = tmp; /* Calculate duty value */ - tmp = (unsigned long long)period * state->duty_cycle; - do_div(tmp, state->period); + tmp = (unsigned long long)rate * state->duty_cycle; + do_div(tmp, NSEC_PER_SEC); duty = period - tmp; if (duty >= period) -- cgit v1.2.3 From a020f22a4ff555386f136eb951e16e4cff709e01 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Wed, 27 May 2020 13:52:24 +0200 Subject: pwm: jz4740: Make PWM start with the active part The PWM in Ingenic SoCs starts in inactive state until the internal timer reaches the duty value, then becomes active until the timer reaches the period value. In theory, we should then use (period - duty) as the real duty value, as a high duty value would otherwise result in the PWM pin being inactive most of the time. This is the reason why the duty value was inverted in the driver until now, but it still had the problem that it would not start with the active part. To address this remaining issue, the common trick is to invert the duty, and invert the polarity when the PWM is enabled. Since the duty was already inverted, and we invert it again, we now program the hardware for the requested duty, and simply invert the polarity when the PWM is enabled. Signed-off-by: Paul Cercueil Signed-off-by: Thierry Reding --- drivers/pwm/pwm-jz4740.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 4fe9d99ac9a9..fe06ca8ce30f 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -6,7 +6,6 @@ * Limitations: * - The .apply callback doesn't complete the currently running period before * reconfiguring the hardware. - * - Each period starts with the inactive part. */ #include @@ -163,7 +162,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* Calculate duty value */ tmp = (unsigned long long)rate * state->duty_cycle; do_div(tmp, NSEC_PER_SEC); - duty = period - tmp; + duty = tmp; if (duty >= period) duty = period - 1; @@ -189,18 +188,26 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); - /* Set polarity */ - switch (state->polarity) { - case PWM_POLARITY_NORMAL: + /* + * Set polarity. + * + * The PWM starts in inactive state until the internal timer reaches the + * duty value, then becomes active until the timer reaches the period + * value. In theory, we should then use (period - duty) as the real duty + * value, as a high duty value would otherwise result in the PWM pin + * being inactive most of the time. + * + * Here, we don't do that, and instead invert the polarity of the PWM + * when it is active. This trick makes the PWM start with its active + * state instead of its inactive state. + */ + if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled) regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, 0); - break; - case PWM_POLARITY_INVERSED: + else regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, TCU_TCSR_PWM_INITL_HIGH); - break; - } if (state->enabled) jz4740_pwm_enable(chip, pwm); -- cgit v1.2.3 From 74db728c0b4c7c0781d439afa6ebc43c76a1f952 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Wed, 27 May 2020 13:52:25 +0200 Subject: pwm: jz4740: Add support for the JZ4725B The PWM hardware in the JZ4725B works the same as in the JZ4740, but has only six channels available. Signed-off-by: Paul Cercueil Signed-off-by: Thierry Reding --- drivers/pwm/pwm-jz4740.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index fe06ca8ce30f..5830ac2bdf6a 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -20,7 +20,9 @@ #include #include -#define NUM_PWM 8 +struct soc_info { + unsigned int num_pwms; +}; struct jz4740_pwm_chip { struct pwm_chip chip; @@ -36,7 +38,7 @@ static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz, unsigned int channel) { /* Enable all TCU channels for PWM use by default except channels 0/1 */ - u32 pwm_channels_mask = GENMASK(NUM_PWM - 1, 2); + u32 pwm_channels_mask = GENMASK(jz->chip.npwm - 1, 2); device_property_read_u32(jz->chip.dev->parent, "ingenic,pwm-channels-mask", @@ -226,6 +228,11 @@ static int jz4740_pwm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct jz4740_pwm_chip *jz4740; + const struct soc_info *info; + + info = device_get_match_data(dev); + if (!info) + return -EINVAL; jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL); if (!jz4740) @@ -239,7 +246,7 @@ static int jz4740_pwm_probe(struct platform_device *pdev) jz4740->chip.dev = dev; jz4740->chip.ops = &jz4740_pwm_ops; - jz4740->chip.npwm = NUM_PWM; + jz4740->chip.npwm = info->num_pwms; jz4740->chip.base = -1; jz4740->chip.of_xlate = of_pwm_xlate_with_flags; jz4740->chip.of_pwm_n_cells = 3; @@ -256,9 +263,18 @@ static int jz4740_pwm_remove(struct platform_device *pdev) return pwmchip_remove(&jz4740->chip); } +static const struct soc_info __maybe_unused jz4740_soc_info = { + .num_pwms = 8, +}; + +static const struct soc_info __maybe_unused jz4725b_soc_info = { + .num_pwms = 6, +}; + #ifdef CONFIG_OF static const struct of_device_id jz4740_pwm_dt_ids[] = { - { .compatible = "ingenic,jz4740-pwm", }, + { .compatible = "ingenic,jz4740-pwm", .data = &jz4740_soc_info }, + { .compatible = "ingenic,jz4725b-pwm", .data = &jz4725b_soc_info }, {}, }; MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids); -- cgit v1.2.3 From 1d7796bdb63a63c9cc8087ffc6daeb36a7c005e1 Mon Sep 17 00:00:00 2001 From: Sandipan Patra Date: Mon, 1 Jun 2020 10:50:36 +0530 Subject: pwm: tegra: Support dynamic clock frequency configuration Added support for dynamic clock freq configuration in PWM kernel driver. Earlier the PWM driver used to cache boot time clock rate by PWM clock parent during probe. Hence dynamically changing PWM frequency was not possible for all the possible ranges. With this change, dynamic calculation is enabled and it is able to set the requested period from sysfs knob provided the value is supported by clock source. Changes mainly have 2 parts: - Tegra186 and later chips [1] - Tegra210 and prior chips [2] For [1] - Changes implemented to set pwm period dynamically and also checks added to allow only if requested period(ns) is below or equals to higher range. For [2] - Only checks if the requested period(ns) is below or equals to higher range defined by max clock limit. The limitation in Tegra210 or prior chips are due to the reason of having only one PWM controller supporting multiple channels. But later chips have multiple PWM controller instances each having single channel support. Signed-off-by: Sandipan Patra Reviewed-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index d26ed8f579ff..1daf591025c0 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -4,8 +4,36 @@ * * Tegra pulse-width-modulation controller driver * - * Copyright (c) 2010, NVIDIA Corporation. + * Copyright (c) 2010-2020, NVIDIA Corporation. * Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer + * + * Overview of Tegra Pulse Width Modulator Register: + * 1. 13-bit: Frequency division (SCALE) + * 2. 8-bit : Pulse division (DUTY) + * 3. 1-bit : Enable bit + * + * The PWM clock frequency is divided by 256 before subdividing it based + * on the programmable frequency division value to generate the required + * frequency for PWM output. The maximum output frequency that can be + * achieved is (max rate of source clock) / 256. + * e.g. if source clock rate is 408 MHz, maximum output frequency can be: + * 408 MHz/256 = 1.6 MHz. + * This 1.6 MHz frequency can further be divided using SCALE value in PWM. + * + * PWM pulse width: 8 bits are usable [23:16] for varying pulse width. + * To achieve 100% duty cycle, program Bit [24] of this register to + * 1’b1. In which case the other bits [23:16] are set to don't care. + * + * Limitations: + * - When PWM is disabled, the output is driven to inactive. + * - It does not allow the current PWM period to complete and + * stops abruptly. + * + * - If the register is reconfigured while PWM is running, + * it does not complete the currently running period. + * + * - If the user input duty is beyond acceptible limits, + * -EINVAL is returned. */ #include @@ -41,6 +69,7 @@ struct tegra_pwm_chip { struct reset_control*rst; unsigned long clk_rate; + unsigned long min_period_ns; void __iomem *regs; @@ -68,7 +97,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, { struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); unsigned long long c = duty_ns, hz; - unsigned long rate; + unsigned long rate, required_clk_rate; u32 val = 0; int err; @@ -82,10 +111,48 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, val = (u32)c << PWM_DUTY_SHIFT; + /* + * min period = max clock limit >> PWM_DUTY_WIDTH + */ + if (period_ns < pc->min_period_ns) + return -EINVAL; + /* * Compute the prescaler value for which (1 << PWM_DUTY_WIDTH) * cycles at the PWM clock rate will take period_ns nanoseconds. + * + * num_channels: If single instance of PWM controller has multiple + * channels (e.g. Tegra210 or older) then it is not possible to + * configure separate clock rates to each of the channels, in such + * case the value stored during probe will be referred. + * + * If every PWM controller instance has one channel respectively, i.e. + * nums_channels == 1 then only the clock rate can be modified + * dynamically (e.g. Tegra186 or Tegra194). */ + if (pc->soc->num_channels == 1) { + /* + * Rate is multiplied with 2^PWM_DUTY_WIDTH so that it matches + * with the maximum possible rate that the controller can + * provide. Any further lower value can be derived by setting + * PFM bits[0:12]. + * + * required_clk_rate is a reference rate for source clock and + * it is derived based on user requested period. By setting the + * source clock rate as required_clk_rate, PWM controller will + * be able to configure the requested period. + */ + required_clk_rate = + (NSEC_PER_SEC / period_ns) << PWM_DUTY_WIDTH; + + err = clk_set_rate(pc->clk, required_clk_rate); + if (err < 0) + return -EINVAL; + + /* Store the new rate for further references */ + pc->clk_rate = clk_get_rate(pc->clk); + } + rate = pc->clk_rate >> PWM_DUTY_WIDTH; /* Consider precision in PWM_SCALE_WIDTH rate calculation */ @@ -94,7 +161,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, /* * Since the actual PWM divider is the register's frequency divider - * field minus 1, we need to decrement to get the correct value to + * field plus 1, we need to decrement to get the correct value to * write to the register. */ if (rate > 0) @@ -205,6 +272,10 @@ static int tegra_pwm_probe(struct platform_device *pdev) */ pwm->clk_rate = clk_get_rate(pwm->clk); + /* Set minimum limit of PWM period for the IP */ + pwm->min_period_ns = + (NSEC_PER_SEC / (pwm->soc->max_frequency >> PWM_DUTY_WIDTH)) + 1; + pwm->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm"); if (IS_ERR(pwm->rst)) { ret = PTR_ERR(pwm->rst); @@ -312,5 +383,6 @@ static struct platform_driver tegra_pwm_driver = { module_platform_driver(tegra_pwm_driver); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_AUTHOR("Sandipan Patra "); +MODULE_DESCRIPTION("Tegra PWM controller driver"); MODULE_ALIAS("platform:tegra-pwm"); -- cgit v1.2.3 From ca162ce98110b98e7d97b7157328d34dcfdd40a9 Mon Sep 17 00:00:00 2001 From: Navid Emamdoost Date: Mon, 1 Jun 2020 02:11:16 -0500 Subject: pwm: img: Call pm_runtime_put() in pm_runtime_get_sync() failed case Even in failed case of pm_runtime_get_sync(), the usage_count is incremented. In order to keep the usage_count with correct value call appropriate pm_runtime_put(). Signed-off-by: Navid Emamdoost Signed-off-by: Thierry Reding --- drivers/pwm/pwm-img.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c index c9e57bd109fb..599a0f66a384 100644 --- a/drivers/pwm/pwm-img.c +++ b/drivers/pwm/pwm-img.c @@ -129,8 +129,10 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, duty = DIV_ROUND_UP(timebase * duty_ns, period_ns); ret = pm_runtime_get_sync(chip->dev); - if (ret < 0) + if (ret < 0) { + pm_runtime_put_autosuspend(chip->dev); return ret; + } val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm)); @@ -331,8 +333,10 @@ static int img_pwm_remove(struct platform_device *pdev) int ret; ret = pm_runtime_get_sync(&pdev->dev); - if (ret < 0) + if (ret < 0) { + pm_runtime_put(&pdev->dev); return ret; + } for (i = 0; i < pwm_chip->chip.npwm; i++) { val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); -- cgit v1.2.3 From cad0f2960675261584c14e8f0026cd67c60f4864 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Thu, 19 Sep 2019 11:17:27 +0200 Subject: pwm: rockchip: Simplify rockchip_pwm_get_state() The way state->enabled is computed is rather convoluted and hard to read - both branches of the if() actually do the exact same thing. So remove the if(), and further simplify " ? true : false" to "". Signed-off-by: Rasmus Villemoes Signed-off-by: Thierry Reding --- drivers/pwm/pwm-rockchip.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 73352e6fbccb..eb8c9cb645a6 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -83,12 +83,7 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip, state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); val = readl_relaxed(pc->base + pc->data->regs.ctrl); - if (pc->data->supports_polarity) - state->enabled = ((val & enable_conf) != enable_conf) ? - false : true; - else - state->enabled = ((val & enable_conf) == enable_conf) ? - true : false; + state->enabled = (val & enable_conf) == enable_conf; if (pc->data->supports_polarity && !(val & PWM_DUTY_POSITIVE)) state->polarity = PWM_POLARITY_INVERSED; -- cgit v1.2.3 From aef1a3799b5cb3ba4841f6034497b179646ccc70 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 16 Apr 2020 10:02:45 +0200 Subject: pwm: imx27: Fix rounding behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To not trigger the warnings provided by CONFIG_PWM_DEBUG - use up-rounding in .get_state() - don't divide by the result of a division - don't use the rounded counter value for the period length to calculate the counter value for the duty cycle Signed-off-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx27.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c index a6e40d4c485f..732a6f3701e8 100644 --- a/drivers/pwm/pwm-imx27.c +++ b/drivers/pwm/pwm-imx27.c @@ -150,13 +150,12 @@ static void pwm_imx27_get_state(struct pwm_chip *chip, prescaler = MX3_PWMCR_PRESCALER_GET(val); pwm_clk = clk_get_rate(imx->clk_per); - pwm_clk = DIV_ROUND_CLOSEST_ULL(pwm_clk, prescaler); val = readl(imx->mmio_base + MX3_PWMPR); period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; /* PWMOUT (Hz) = PWMCLK / (PWMPR + 2) */ - tmp = NSEC_PER_SEC * (u64)(period + 2); - state->period = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk); + tmp = NSEC_PER_SEC * (u64)(period + 2) * prescaler; + state->period = DIV_ROUND_UP_ULL(tmp, pwm_clk); /* * PWMSAR can be read only if PWM is enabled. If the PWM is disabled, @@ -167,8 +166,8 @@ static void pwm_imx27_get_state(struct pwm_chip *chip, else val = imx->duty_cycle; - tmp = NSEC_PER_SEC * (u64)(val); - state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk); + tmp = NSEC_PER_SEC * (u64)(val) * prescaler; + state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk); pwm_imx27_clk_disable_unprepare(imx); } @@ -220,22 +219,23 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); struct pwm_state cstate; unsigned long long c; + unsigned long long clkrate; int ret; u32 cr; pwm_get_state(pwm, &cstate); - c = clk_get_rate(imx->clk_per); - c *= state->period; + clkrate = clk_get_rate(imx->clk_per); + c = clkrate * state->period; - do_div(c, 1000000000); + do_div(c, NSEC_PER_SEC); period_cycles = c; prescale = period_cycles / 0x10000 + 1; period_cycles /= prescale; - c = (unsigned long long)period_cycles * state->duty_cycle; - do_div(c, state->period); + c = clkrate * state->duty_cycle; + do_div(c, NSEC_PER_SEC * prescale); duty_cycles = c; /* -- cgit v1.2.3 From d741cb045cb5552d9e636e7e447f237d3b1667ad Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 2 Jun 2020 14:29:01 +0200 Subject: MAINTAINERS: Add Lee Jones as reviewer for the PWM subsystem Lee has kindly offered his help in sharing the patch review workload for the PWM subsystem. If this works out well between Lee, Uwe and myself it may be a good idea to maintain the subsystem as a group. Acked-by: Lee Jones Signed-off-by: Thierry Reding --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index e64e5db31497..b8889bed5d1e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13736,6 +13736,7 @@ F: drivers/media/rc/pwm-ir-tx.c PWM SUBSYSTEM M: Thierry Reding R: Uwe Kleine-König +M: Lee Jones L: linux-pwm@vger.kernel.org S: Maintained Q: https://patchwork.ozlabs.org/project/linux-pwm/list/ -- cgit v1.2.3 From f5641d053d46a9a18fe13f2ecb4a7b4a66d9cdf7 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 3 Jun 2020 15:40:56 -0700 Subject: pwm: Add missing "CONFIG_" prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IS_ENABLED() use was missing the CONFIG_ prefix which would have lead to skipping this code. Fixes: 3ad1f3a33286 ("pwm: Implement some checks for lowlevel drivers") Signed-off-by: Kees Cook Reviewed-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index bca04965bfe6..004b2ea9b5fd 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -121,7 +121,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) pwm->chip->ops->get_state(pwm->chip, pwm, &pwm->state); trace_pwm_get(pwm, &pwm->state); - if (IS_ENABLED(PWM_DEBUG)) + if (IS_ENABLED(CONFIG_PWM_DEBUG)) pwm->last = pwm->state; } -- cgit v1.2.3