From bccec89f0a35f65302734d1cdb01479df0f33ac9 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 20 Sep 2016 17:40:56 +0300 Subject: pwm: pca9685: Allow any of the 16 PWMs to be used as a GPIO The PCA9685 controller has full on/off bit for each PWM channel. Setting this bit bypasses the PWM control and the line works just as it would be a GPIO. Furthermore in Intel Galileo it is actually used as GPIO output for discreet muxes on the board. This patch adds GPIO output only support for the driver so that we can control the muxes on Galileo using standard GPIO interfaces available in the kernel. GPIO and PWM functionality is exclusive so only one can be active at a time on a single PWM channel. Signed-off-by: Mika Westerberg Acked-by: Linus Walleij Signed-off-by: Thierry Reding --- drivers/pwm/pwm-pca9685.c | 164 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 117fccf7934a..621656b9aa41 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -20,8 +20,10 @@ */ #include +#include #include #include +#include #include #include #include @@ -81,6 +83,10 @@ struct pca9685 { int active_cnt; int duty_ns; int period_ns; +#if IS_ENABLED(CONFIG_GPIOLIB) + struct mutex lock; + struct gpio_chip gpio; +#endif }; static inline struct pca9685 *to_pca(struct pwm_chip *chip) @@ -88,6 +94,151 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip) return container_of(chip, struct pca9685, chip); } +#if IS_ENABLED(CONFIG_GPIOLIB) +static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + struct pwm_device *pwm; + + mutex_lock(&pca->lock); + + pwm = &pca->chip.pwms[offset]; + + if (pwm->flags & (PWMF_REQUESTED | PWMF_EXPORTED)) { + mutex_unlock(&pca->lock); + return -EBUSY; + } + + pwm_set_chip_data(pwm, (void *)1); + + mutex_unlock(&pca->lock); + return 0; +} + +static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + struct pwm_device *pwm; + + mutex_lock(&pca->lock); + pwm = &pca->chip.pwms[offset]; + pwm_set_chip_data(pwm, NULL); + mutex_unlock(&pca->lock); +} + +static bool pca9685_pwm_is_gpio(struct pca9685 *pca, struct pwm_device *pwm) +{ + bool is_gpio = false; + + mutex_lock(&pca->lock); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) { + unsigned int i; + + /* + * Check if any of the GPIOs are requested and in that case + * prevent using the "all LEDs" channel. + */ + for (i = 0; i < pca->gpio.ngpio; i++) + if (gpiochip_is_requested(&pca->gpio, i)) { + is_gpio = true; + break; + } + } else if (pwm_get_chip_data(pwm)) { + is_gpio = true; + } + + mutex_unlock(&pca->lock); + return is_gpio; +} + +static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + struct pwm_device *pwm = &pca->chip.pwms[offset]; + unsigned int value; + + regmap_read(pca->regmap, LED_N_ON_H(pwm->hwpwm), &value); + + return value & LED_FULL; +} + +static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, + int value) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + struct pwm_device *pwm = &pca->chip.pwms[offset]; + unsigned int on = value ? LED_FULL : 0; + + /* Clear both OFF registers */ + regmap_write(pca->regmap, LED_N_OFF_L(pwm->hwpwm), 0); + regmap_write(pca->regmap, LED_N_OFF_H(pwm->hwpwm), 0); + + /* Set the full ON bit */ + regmap_write(pca->regmap, LED_N_ON_H(pwm->hwpwm), on); +} + +static int pca9685_pwm_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + /* Always out */ + return 0; +} + +static int pca9685_pwm_gpio_direction_input(struct gpio_chip *gpio, + unsigned int offset) +{ + return -EINVAL; +} + +static int pca9685_pwm_gpio_direction_output(struct gpio_chip *gpio, + unsigned int offset, int value) +{ + pca9685_pwm_gpio_set(gpio, offset, value); + + return 0; +} + +/* + * The PCA9685 has a bit for turning the PWM output full off or on. Some + * boards like Intel Galileo actually uses these as normal GPIOs so we + * expose a GPIO chip here which can exclusively take over the underlying + * PWM channel. + */ +static int pca9685_pwm_gpio_probe(struct pca9685 *pca) +{ + struct device *dev = pca->chip.dev; + + mutex_init(&pca->lock); + + pca->gpio.label = dev_name(dev); + pca->gpio.parent = dev; + pca->gpio.request = pca9685_pwm_gpio_request; + pca->gpio.free = pca9685_pwm_gpio_free; + pca->gpio.get_direction = pca9685_pwm_gpio_get_direction; + pca->gpio.direction_input = pca9685_pwm_gpio_direction_input; + pca->gpio.direction_output = pca9685_pwm_gpio_direction_output; + pca->gpio.get = pca9685_pwm_gpio_get; + pca->gpio.set = pca9685_pwm_gpio_set; + pca->gpio.base = -1; + pca->gpio.ngpio = PCA9685_MAXCHAN; + pca->gpio.can_sleep = true; + + return devm_gpiochip_add_data(dev, &pca->gpio, pca); +} +#else +static inline bool pca9685_pwm_is_gpio(struct pca9685 *pca, + struct pwm_device *pwm) +{ + return false; +} + +static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca) +{ + return 0; +} +#endif + static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -264,6 +415,9 @@ static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); + if (pca9685_pwm_is_gpio(pca, pwm)) + return -EBUSY; + if (pca->active_cnt++ == 0) return regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, 0x0); @@ -345,7 +499,15 @@ static int pca9685_pwm_probe(struct i2c_client *client, pca->chip.base = -1; pca->chip.can_sleep = true; - return pwmchip_add(&pca->chip); + ret = pwmchip_add(&pca->chip); + if (ret < 0) + return ret; + + ret = pca9685_pwm_gpio_probe(pca); + if (ret < 0) + pwmchip_remove(&pca->chip); + + return ret; } static int pca9685_pwm_remove(struct i2c_client *client) -- cgit v1.2.3 From 5bf22ff32e736beb8ba91a99a3e116d12e7eabd0 Mon Sep 17 00:00:00 2001 From: Scott Branden Date: Sat, 8 Oct 2016 13:54:05 -0700 Subject: pwm: bcm-iproc: Update dependencies for compile-test Add dependency on COMMON_CLK and allow COMPILE_TEST for broader compile coverage. Default to Y for IPROC SoCs. This allows the driver to simply be enabled by selecting PWM. Signed-off-by: Scott Branden Reviewed-by: Ray Jui [thierry.reding@gmail.com: reword commit message] Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index f92dd41b0395..704b0b8f65c1 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -76,7 +76,9 @@ config PWM_ATMEL_TCB config PWM_BCM_IPROC tristate "iProc PWM support" - depends on ARCH_BCM_IPROC + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on COMMON_CLK + default ARCH_BCM_IPROC help Generic PWM framework driver for Broadcom iProc PWM block. This block is used in Broadcom iProc SoC's. -- cgit v1.2.3 From b2ec9efc1f4fb1dc13a599db88019d5472deef9b Mon Sep 17 00:00:00 2001 From: Bhumika Goyal Date: Tue, 10 Jan 2017 23:42:06 +0530 Subject: pwm: constify pwm_ops structures Declare pwm_ops structures as const as they are only stored in the ops field of a pwm_chip structure. This field is of type const struct pwm_ops *, so pwm_ops structures having this property can be declared as const. Done using Coccinelle: @r1 disable optional_qualifier@ identifier i; position p; @@ static struct pwm_ops i@p={...}; @ok1@ identifier r1.i; position p; struct pxa_pwm_chip pwm; struct bfin_pwm_chip bwm; struct vt8500_chip vp; struct imx_chip icp; @@ ( pwm.chip.ops=&i@p | bwm.chip.ops=&i@p | vp.chip.ops=&i@p | icp.chip.ops=&i@p ) @bad@ position p!={r1.p,ok1.p}; identifier r1.i; @@ i@p @depends on !bad disable optional_qualifier@ identifier r1.i; @@ +const struct pwm_ops i; File size details: text data bss dec hex filename 1646 328 0 1974 7b6 drivers/pwm/pwm-imx.o 1742 224 0 1966 7ae drivers/pwm/pwm-imx.o 1941 296 0 2237 8bd drivers/pwm/pwm-pxa.o 2037 192 0 2229 8b5 drivers/pwm/pwm-pxa.o 1946 296 0 2242 8c2 drivers/pwm/pwm-vt8500.o 2050 192 0 2242 8c2 drivers/pwm/pwm-vt8500.o The drivers/pwm/pwm-bfin.o file did not compile. Signed-off-by: Bhumika Goyal Signed-off-by: Thierry Reding --- drivers/pwm/pwm-bfin.c | 2 +- drivers/pwm/pwm-imx.c | 2 +- drivers/pwm/pwm-pxa.c | 2 +- drivers/pwm/pwm-vt8500.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c index 7631ef194de7..d2ed0a2a18e8 100644 --- a/drivers/pwm/pwm-bfin.c +++ b/drivers/pwm/pwm-bfin.c @@ -103,7 +103,7 @@ static void bfin_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) disable_gptimer(priv->pin); } -static struct pwm_ops bfin_pwm_ops = { +static const struct pwm_ops bfin_pwm_ops = { .request = bfin_pwm_request, .free = bfin_pwm_free, .config = bfin_pwm_config, diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index d600fd5cd4ba..177fb81c916d 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -240,7 +240,7 @@ static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(imx->clk_per); } -static struct pwm_ops imx_pwm_ops = { +static const struct pwm_ops imx_pwm_ops = { .enable = imx_pwm_enable, .disable = imx_pwm_disable, .config = imx_pwm_config, diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index 58b709f29130..4143a46684d2 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -118,7 +118,7 @@ static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(pc->clk); } -static struct pwm_ops pxa_pwm_ops = { +static const struct pwm_ops pxa_pwm_ops = { .config = pxa_pwm_config, .enable = pxa_pwm_enable, .disable = pxa_pwm_disable, diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c index cdb58fd4619d..8141a4984126 100644 --- a/drivers/pwm/pwm-vt8500.c +++ b/drivers/pwm/pwm-vt8500.c @@ -184,7 +184,7 @@ static int vt8500_pwm_set_polarity(struct pwm_chip *chip, return 0; } -static struct pwm_ops vt8500_pwm_ops = { +static const struct pwm_ops vt8500_pwm_ops = { .enable = vt8500_pwm_enable, .disable = vt8500_pwm_disable, .config = vt8500_pwm_config, -- cgit v1.2.3 From 8d254a340efb12b40c4c1ff25a48a4f48f7bbd6b Mon Sep 17 00:00:00 2001 From: Clemens Gruber Date: Tue, 13 Dec 2016 16:52:50 +0100 Subject: pwm: pca9685: Fix period change with same duty cycle When first implementing support for changing the output frequency, an optimization was added to continue the PWM after changing the prescaler without having to reprogram the ON and OFF registers for the duty cycle, in case the duty cycle stayed the same. This was flawed, because we compared the absolute value of the duty cycle in nanoseconds instead of the ratio to the period. Fix the problem by removing the shortcut. Fixes: 01ec8472009c9 ("pwm-pca9685: Support changing the output frequency") Cc: # v4.3+ Signed-off-by: Clemens Gruber Reviewed-by: Mika Westerberg Signed-off-by: Thierry Reding --- drivers/pwm/pwm-pca9685.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 621656b9aa41..5566760f7dc4 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -67,7 +67,6 @@ #define PCA9685_MAXCHAN 0x10 #define LED_FULL (1 << 4) -#define MODE1_RESTART (1 << 7) #define MODE1_SLEEP (1 << 4) #define MODE2_INVRT (1 << 4) #define MODE2_OUTDRV (1 << 2) @@ -268,16 +267,6 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, udelay(500); pca->period_ns = period_ns; - - /* - * If the duty cycle did not change, restart PWM with - * the same duty cycle to period ratio and return. - */ - if (duty_ns == pca->duty_ns) { - regmap_update_bits(pca->regmap, PCA9685_MODE1, - MODE1_RESTART, 0x1); - return 0; - } } else { dev_err(chip->dev, "prescaler not set: period out of bounds!\n"); -- cgit v1.2.3 From 684309e5043efaaa94d9b4e65f53f7fdde1f6675 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sat, 28 Jan 2017 17:10:39 +0200 Subject: pwm: lpss: Avoid potential overflow of base_unit The resolution of base_unit is derived from base_unit_bits and thus must be equal to (2^base_unit_bits - 1). Otherwise frequency and therefore base_unit might potentially overflow. Prevent the above by substracting 1 in all cases where base_unit_bits or derivative is used. Reviewed-by: Mika Westerberg Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 72c0bce5a75c..8642feeb8abd 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -102,7 +102,7 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, * The equation is: * base_unit = round(base_unit_range * freq / c) */ - base_unit_range = BIT(lpwm->info->base_unit_bits); + base_unit_range = BIT(lpwm->info->base_unit_bits) - 1; freq *= base_unit_range; base_unit = DIV_ROUND_CLOSEST_ULL(freq, c); @@ -117,8 +117,8 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, ctrl = pwm_lpss_read(pwm); ctrl &= ~PWM_ON_TIME_DIV_MASK; - ctrl &= ~((base_unit_range - 1) << PWM_BASE_UNIT_SHIFT); - base_unit &= (base_unit_range - 1); + ctrl &= ~(base_unit_range << PWM_BASE_UNIT_SHIFT); + base_unit &= base_unit_range; ctrl |= (u32) base_unit << PWM_BASE_UNIT_SHIFT; ctrl |= on_time_div; pwm_lpss_write(pwm, ctrl); -- cgit v1.2.3 From b5c050c719922901891bdd8580d224e2a0035db5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sat, 28 Jan 2017 17:10:40 +0200 Subject: pwm: lpss: Allow duty cycle to be 0 A duty cycle is represented by values [0..] which reflects [0%..100%]. 0% of the duty cycle means always off (logical "0") on output. Allow this in the driver. Reviewed-by: Mika Westerberg Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 8642feeb8abd..ffa01ab907a6 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -107,8 +107,6 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, base_unit = DIV_ROUND_CLOSEST_ULL(freq, c); - if (duty_ns <= 0) - duty_ns = 1; on_time_div = 255ULL * duty_ns; do_div(on_time_div, period_ns); on_time_div = 255ULL - on_time_div; -- cgit v1.2.3 From b14e8ceff03404cce4d9b85204246d3ed1259ec7 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sat, 28 Jan 2017 17:10:41 +0200 Subject: pwm: lpss: Switch to new atomic API Instead of doing things separately, which is not so reliable on some platforms, switch the driver to use new atomic API, i.e. ->apply() callback. The change has been tested on Intel platforms such as Broxton, BayTrail, and Merrifield. Reviewed-by: Mika Westerberg Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 64 +++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 35 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index ffa01ab907a6..09869f91d2d0 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -82,15 +82,20 @@ static inline void pwm_lpss_write(const struct pwm_device *pwm, u32 value) static void pwm_lpss_update(struct pwm_device *pwm) { + /* + * Set a limit for busyloop since not all implementations correctly + * clear PWM_SW_UPDATE bit (at least it's not visible on OS side). + */ + unsigned int count = 10; + pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_SW_UPDATE); - /* Give it some time to propagate */ - usleep_range(10, 50); + while (pwm_lpss_read(pwm) & PWM_SW_UPDATE && --count) + usleep_range(10, 20); } -static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) +static void pwm_lpss_prepare(struct pwm_lpss_chip *lpwm, struct pwm_device *pwm, + int duty_ns, int period_ns) { - struct pwm_lpss_chip *lpwm = to_lpwm(chip); unsigned long long on_time_div; unsigned long c = lpwm->info->clk_rate, base_unit_range; unsigned long long base_unit, freq = NSEC_PER_SEC; @@ -111,8 +116,6 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, do_div(on_time_div, period_ns); on_time_div = 255ULL - on_time_div; - pm_runtime_get_sync(chip->dev); - ctrl = pwm_lpss_read(pwm); ctrl &= ~PWM_ON_TIME_DIV_MASK; ctrl &= ~(base_unit_range << PWM_BASE_UNIT_SHIFT); @@ -120,42 +123,33 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, ctrl |= (u32) base_unit << PWM_BASE_UNIT_SHIFT; ctrl |= on_time_div; pwm_lpss_write(pwm, ctrl); - - /* - * If the PWM is already enabled we need to notify the hardware - * about the change by setting PWM_SW_UPDATE. - */ - if (pwm_is_enabled(pwm)) - pwm_lpss_update(pwm); - - pm_runtime_put(chip->dev); - - return 0; } -static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm) +static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) { - pm_runtime_get_sync(chip->dev); + struct pwm_lpss_chip *lpwm = to_lpwm(chip); - /* - * Hardware must first see PWM_SW_UPDATE before the PWM can be - * enabled. - */ - pwm_lpss_update(pwm); - pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE); - return 0; -} + if (state->enabled) { + if (!pwm_is_enabled(pwm)) { + pm_runtime_get_sync(chip->dev); + pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); + pwm_lpss_update(pwm); + pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE); + } else { + pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); + pwm_lpss_update(pwm); + } + } else if (pwm_is_enabled(pwm)) { + pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); + pm_runtime_put(chip->dev); + } -static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); - pm_runtime_put(chip->dev); + return 0; } static const struct pwm_ops pwm_lpss_ops = { - .config = pwm_lpss_config, - .enable = pwm_lpss_enable, - .disable = pwm_lpss_disable, + .apply = pwm_lpss_apply, .owner = THIS_MODULE, }; -- cgit v1.2.3 From 10d56a4cb1c6c894c60acbaec0f8aa44aba833b0 Mon Sep 17 00:00:00 2001 From: Ilkka Koskinen Date: Sat, 28 Jan 2017 17:10:42 +0200 Subject: pwm: lpss: Avoid reconfiguring while UPDATE bit is still enabled PWM Configuration register has SW_UPDATE bit that is set when a new configuration is written to the register. The bit is automatically cleared at the start of the next output cycle by the IP block. If one writes a new configuration to the register while it still has the bit enabled, PWM may freeze. That is, while one can still write to the register, it won't have an effect. Thus, we try to sleep long enough that the bit gets cleared and make sure the bit is not enabled while we update the configuration. Reviewed-by: Mika Westerberg Tested-by: Richard Griffiths Signed-off-by: Ilkka Koskinen Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 52 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 09869f91d2d0..46670276690d 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -80,17 +81,37 @@ static inline void pwm_lpss_write(const struct pwm_device *pwm, u32 value) writel(value, lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM); } -static void pwm_lpss_update(struct pwm_device *pwm) +static int pwm_lpss_update(struct pwm_device *pwm) { + struct pwm_lpss_chip *lpwm = to_lpwm(pwm->chip); + const void __iomem *addr = lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM; + const unsigned int ms = 500 * USEC_PER_MSEC; + u32 val; + int err; + + pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_SW_UPDATE); + /* - * Set a limit for busyloop since not all implementations correctly - * clear PWM_SW_UPDATE bit (at least it's not visible on OS side). + * PWM Configuration register has SW_UPDATE bit that is set when a new + * configuration is written to the register. The bit is automatically + * cleared at the start of the next output cycle by the IP block. + * + * If one writes a new configuration to the register while it still has + * the bit enabled, PWM may freeze. That is, while one can still write + * to the register, it won't have an effect. Thus, we try to sleep long + * enough that the bit gets cleared and make sure the bit is not + * enabled while we update the configuration. */ - unsigned int count = 10; + err = readl_poll_timeout(addr, val, !(val & PWM_SW_UPDATE), 40, ms); + if (err) + dev_err(pwm->chip->dev, "PWM_SW_UPDATE was not cleared\n"); - pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_SW_UPDATE); - while (pwm_lpss_read(pwm) & PWM_SW_UPDATE && --count) - usleep_range(10, 20); + return err; +} + +static inline int pwm_lpss_is_updating(struct pwm_device *pwm) +{ + return (pwm_lpss_read(pwm) & PWM_SW_UPDATE) ? -EBUSY : 0; } static void pwm_lpss_prepare(struct pwm_lpss_chip *lpwm, struct pwm_device *pwm, @@ -129,16 +150,29 @@ static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct pwm_lpss_chip *lpwm = to_lpwm(chip); + int ret; if (state->enabled) { if (!pwm_is_enabled(pwm)) { pm_runtime_get_sync(chip->dev); + ret = pwm_lpss_is_updating(pwm); + if (ret) { + pm_runtime_put(chip->dev); + return ret; + } pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); - pwm_lpss_update(pwm); + ret = pwm_lpss_update(pwm); + if (ret) { + pm_runtime_put(chip->dev); + return ret; + } pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE); } else { + ret = pwm_lpss_is_updating(pwm); + if (ret) + return ret; pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); - pwm_lpss_update(pwm); + return pwm_lpss_update(pwm); } } else if (pwm_is_enabled(pwm)) { pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); -- cgit v1.2.3 From 9900073cf5587662df9b7ef59f649ff100229d85 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sat, 28 Jan 2017 17:10:43 +0200 Subject: pwm: lpss: Do not export board infos for different PWM types The PWM LPSS probe drivers just pass a pointer to the exported board info structures to pwm_lpss_probe() based on device PCI or ACPI ID. In order to remove the knowledge of specific devices from library part of the driver and reduce noise in exported namespace just duplicate the board info structures and stop exporting them. Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss-pci.c | 21 +++++++++++++++++++++ drivers/pwm/pwm-lpss-platform.c | 21 +++++++++++++++++++++ drivers/pwm/pwm-lpss.c | 24 ------------------------ drivers/pwm/pwm-lpss.h | 4 ---- 4 files changed, 42 insertions(+), 28 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index 3622f093490e..2e7a9a48060d 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -17,6 +17,27 @@ #include "pwm-lpss.h" +/* BayTrail */ +static const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { + .clk_rate = 25000000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Braswell */ +static const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { + .clk_rate = 19200000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Broxton */ +static const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = { + .clk_rate = 19200000, + .npwm = 4, + .base_unit_bits = 22, +}; + static int pwm_lpss_probe_pci(struct pci_dev *pdev, const struct pci_device_id *id) { diff --git a/drivers/pwm/pwm-lpss-platform.c b/drivers/pwm/pwm-lpss-platform.c index 54433fc6d1a4..b22b6fdadb9a 100644 --- a/drivers/pwm/pwm-lpss-platform.c +++ b/drivers/pwm/pwm-lpss-platform.c @@ -18,6 +18,27 @@ #include "pwm-lpss.h" +/* BayTrail */ +static const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { + .clk_rate = 25000000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Braswell */ +static const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { + .clk_rate = 19200000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Broxton */ +static const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = { + .clk_rate = 19200000, + .npwm = 4, + .base_unit_bits = 22, +}; + static int pwm_lpss_probe_platform(struct platform_device *pdev) { const struct pwm_lpss_boardinfo *info; diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 46670276690d..689d2c1cbead 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -38,30 +38,6 @@ struct pwm_lpss_chip { const struct pwm_lpss_boardinfo *info; }; -/* BayTrail */ -const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { - .clk_rate = 25000000, - .npwm = 1, - .base_unit_bits = 16, -}; -EXPORT_SYMBOL_GPL(pwm_lpss_byt_info); - -/* Braswell */ -const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { - .clk_rate = 19200000, - .npwm = 1, - .base_unit_bits = 16, -}; -EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info); - -/* Broxton */ -const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = { - .clk_rate = 19200000, - .npwm = 4, - .base_unit_bits = 22, -}; -EXPORT_SYMBOL_GPL(pwm_lpss_bxt_info); - static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) { return container_of(chip, struct pwm_lpss_chip, chip); diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h index 04766e0d41aa..c94cd7c2695d 100644 --- a/drivers/pwm/pwm-lpss.h +++ b/drivers/pwm/pwm-lpss.h @@ -24,10 +24,6 @@ struct pwm_lpss_boardinfo { unsigned long base_unit_bits; }; -extern const struct pwm_lpss_boardinfo pwm_lpss_byt_info; -extern const struct pwm_lpss_boardinfo pwm_lpss_bsw_info; -extern const struct pwm_lpss_boardinfo pwm_lpss_bxt_info; - struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, const struct pwm_lpss_boardinfo *info); int pwm_lpss_remove(struct pwm_lpss_chip *lpwm); -- cgit v1.2.3 From ae2520540cb0090eb8cdba559bcb6a82266f0308 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sat, 28 Jan 2017 17:10:44 +0200 Subject: pwm: lpss: Add Intel Gemini Lake PCI ID Intel Gemini Lake PWM is pretty much same as used in Intel Broxton. Add this new PCI ID to the list of supported devices. Signed-off-by: Mika Westerberg Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss-pci.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index 2e7a9a48060d..053088b9b66e 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -101,6 +101,7 @@ static const struct pci_device_id pwm_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, + { PCI_VDEVICE(INTEL, 0x31c8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x5ac8), (unsigned long)&pwm_lpss_bxt_info}, { }, }; -- cgit v1.2.3 From 9fb27fac39f47632aa32ffd787b4de1dbd0b8655 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sun, 29 Jan 2017 22:54:06 +0100 Subject: pwm: imx: Remove ipg clock and enable per clock when required The use of the ipg clock was introduced with commit 7b27c160c681 ("pwm: i.MX: fix clock lookup"). In the commit message it was claimed that the ipg clock is enabled for register accesses. This is true for the ->config() callback, but not for the ->set_enable() callback. Given that the ipg clock is not consistently enabled for all register accesses we can assume that either it is not required at all or that the current code does not work. Remove the ipg clock code for now so that it's no longer in the way of refactoring the driver. On the other hand, the i.MX 7 IP requires the peripheral clock to be enabled before accessing its registers. Since ->config() can be called when the PWM is disabled (in which case, the peripheral clock is also disabled), we need to surround the imx->config() with clk_prepare_enable(per_clk)/clk_disable_unprepare(per_clk) calls. Note that the driver was working fine for the i.MX 7 IP so far because the ipg and peripheral clock use the same hardware clock gate, which guaranteed peripheral clock activation even when ->config() was called when the PWM was disabled. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon Cc: Philipp Zabel Reviewed-by: Stefan Agner Tested-by: Stefan Agner Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 177fb81c916d..bb64122a74be 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -49,7 +49,6 @@ struct imx_chip { struct clk *clk_per; - struct clk *clk_ipg; void __iomem *mmio_base; @@ -206,13 +205,13 @@ static int imx_pwm_config(struct pwm_chip *chip, struct imx_chip *imx = to_imx_chip(chip); int ret; - ret = clk_prepare_enable(imx->clk_ipg); + ret = clk_prepare_enable(imx->clk_per); if (ret) return ret; ret = imx->config(chip, pwm, duty_ns, period_ns); - clk_disable_unprepare(imx->clk_ipg); + clk_disable_unprepare(imx->clk_per); return ret; } @@ -293,13 +292,6 @@ static int imx_pwm_probe(struct platform_device *pdev) return PTR_ERR(imx->clk_per); } - imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); - if (IS_ERR(imx->clk_ipg)) { - dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", - PTR_ERR(imx->clk_ipg)); - return PTR_ERR(imx->clk_ipg); - } - imx->chip.ops = &imx_pwm_ops; imx->chip.dev = &pdev->dev; imx->chip.base = -1; -- cgit v1.2.3 From 0038922954236a6af424e2604ece88404dfbc41c Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:07 +0100 Subject: pwm: imx: Add separate set of PWM ops for v1 and v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch provides separate set of PWM operations utilized by i.MX's v1 and v2 of the PWM hardware. Signed-off-by: Lothar Waßmann Signed-off-by: Bhuvanchandra DV Signed-off-by: Lukasz Majewski Acked-by: Shawn Guo Reviewed-by: Sascha Hauer Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index bb64122a74be..c405cb777808 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -239,7 +239,14 @@ static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(imx->clk_per); } -static const struct pwm_ops imx_pwm_ops = { +static const struct pwm_ops imx_pwm_ops_v1 = { + .enable = imx_pwm_enable, + .disable = imx_pwm_disable, + .config = imx_pwm_config, + .owner = THIS_MODULE, +}; + +static const struct pwm_ops imx_pwm_ops_v2 = { .enable = imx_pwm_enable, .disable = imx_pwm_disable, .config = imx_pwm_config, @@ -250,16 +257,19 @@ struct imx_pwm_data { int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); void (*set_enable)(struct pwm_chip *chip, bool enable); + const struct pwm_ops *ops; }; static struct imx_pwm_data imx_pwm_data_v1 = { .config = imx_pwm_config_v1, .set_enable = imx_pwm_set_enable_v1, + .ops = &imx_pwm_ops_v1, }; static struct imx_pwm_data imx_pwm_data_v2 = { .config = imx_pwm_config_v2, .set_enable = imx_pwm_set_enable_v2, + .ops = &imx_pwm_ops_v2, }; static const struct of_device_id imx_pwm_dt_ids[] = { @@ -281,6 +291,8 @@ static int imx_pwm_probe(struct platform_device *pdev) if (!of_id) return -ENODEV; + data = of_id->data; + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); if (imx == NULL) return -ENOMEM; @@ -292,7 +304,7 @@ static int imx_pwm_probe(struct platform_device *pdev) return PTR_ERR(imx->clk_per); } - imx->chip.ops = &imx_pwm_ops; + imx->chip.ops = data->ops; imx->chip.dev = &pdev->dev; imx->chip.base = -1; imx->chip.npwm = 1; @@ -303,7 +315,6 @@ static int imx_pwm_probe(struct platform_device *pdev) if (IS_ERR(imx->mmio_base)) return PTR_ERR(imx->mmio_base); - data = of_id->data; imx->config = data->config; imx->set_enable = data->set_enable; -- cgit v1.2.3 From b3c088fe0297d7580bef5d5830fa5fc69ae8443c Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:08 +0100 Subject: pwm: imx: Rewrite v1 code to facilitate switch to atomic PWM The code has been rewritten to remove "generic" calls to imx_pwm_{enable|disable|config}. Such approach would facilitate switch to atomic PWM (a.k.a ->apply()) implementation. Suggested-by: Stefan Agner Suggested-by: Boris Brezillon Reviewed-by: Stefan Agner Signed-off-by: Lukasz Majewski Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index c405cb777808..5c712104066a 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -90,19 +90,33 @@ static int imx_pwm_config_v1(struct pwm_chip *chip, return 0; } -static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) +static int imx_pwm_enable_v1(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); u32 val; + int ret; + + ret = clk_prepare_enable(imx->clk_per); + if (ret < 0) + return ret; val = readl(imx->mmio_base + MX1_PWMC); + val |= MX1_PWMC_EN; + writel(val, imx->mmio_base + MX1_PWMC); - if (enable) - val |= MX1_PWMC_EN; - else - val &= ~MX1_PWMC_EN; + return 0; +} + +static void imx_pwm_disable_v1(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_chip *imx = to_imx_chip(chip); + u32 val; + val = readl(imx->mmio_base + MX1_PWMC); + val &= ~MX1_PWMC_EN; writel(val, imx->mmio_base + MX1_PWMC); + + clk_disable_unprepare(imx->clk_per); } static int imx_pwm_config_v2(struct pwm_chip *chip, @@ -240,9 +254,9 @@ static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) } static const struct pwm_ops imx_pwm_ops_v1 = { - .enable = imx_pwm_enable, - .disable = imx_pwm_disable, - .config = imx_pwm_config, + .enable = imx_pwm_enable_v1, + .disable = imx_pwm_disable_v1, + .config = imx_pwm_config_v1, .owner = THIS_MODULE, }; @@ -261,8 +275,6 @@ struct imx_pwm_data { }; static struct imx_pwm_data imx_pwm_data_v1 = { - .config = imx_pwm_config_v1, - .set_enable = imx_pwm_set_enable_v1, .ops = &imx_pwm_ops_v1, }; -- cgit v1.2.3 From 970247a486751c79903c7d853198d0106805c641 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:09 +0100 Subject: pwm: imx: Move PWMv2 software reset code to a separate function The software reset code has been extracted from imx_pwm_config_v2 function and moved to new one - imx_pwm_sw_reset(). This change reduces the overall size of imx_pwm_config_v2() and prepares it for atomic PWM operation. Suggested-by: Stefan Agner Suggested-by: Boris Brezillon Signed-off-by: Lukasz Majewski Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 5c712104066a..c944f15f574c 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -119,6 +119,25 @@ static void imx_pwm_disable_v1(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(imx->clk_per); } +static void imx_pwm_sw_reset(struct pwm_chip *chip) +{ + struct imx_chip *imx = to_imx_chip(chip); + struct device *dev = chip->dev; + int wait_count = 0; + u32 cr; + + writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR); + do { + usleep_range(200, 1000); + cr = readl(imx->mmio_base + MX3_PWMCR); + } while ((cr & MX3_PWMCR_SWR) && + (wait_count++ < MX3_PWM_SWR_LOOP)); + + if (cr & MX3_PWMCR_SWR) + dev_warn(dev, "software reset timeout\n"); +} + + static int imx_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -128,7 +147,7 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, unsigned long period_cycles, duty_cycles, prescale; unsigned int period_ms; bool enable = pwm_is_enabled(pwm); - int wait_count = 0, fifoav; + int fifoav; u32 cr, sr; /* @@ -151,15 +170,7 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, dev_warn(dev, "there is no free FIFO slot\n"); } } else { - writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR); - do { - usleep_range(200, 1000); - cr = readl(imx->mmio_base + MX3_PWMCR); - } while ((cr & MX3_PWMCR_SWR) && - (wait_count++ < MX3_PWM_SWR_LOOP)); - - if (cr & MX3_PWMCR_SWR) - dev_warn(dev, "software reset timeout\n"); + imx_pwm_sw_reset(chip); } c = clk_get_rate(imx->clk_per); -- cgit v1.2.3 From 73b1ff1f3e5ccc22b31fa44c98460ef05393181c Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:10 +0100 Subject: pwm: imx: Move PWMv2 wait for fifo slot code to a separate function The code, which waits for fifo slot, has been extracted from imx_pwm_config_v2 function and moved to new one - imx_pwm_wait_fifo_slot(). This change reduces the overall size of imx_pwm_config_v2() and prepares it for atomic PWM operation. Suggested-by: Stefan Agner Suggested-by: Boris Brezillon Signed-off-by: Lukasz Majewski Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index c944f15f574c..d557279446ed 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -137,18 +137,36 @@ static void imx_pwm_sw_reset(struct pwm_chip *chip) dev_warn(dev, "software reset timeout\n"); } +static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct imx_chip *imx = to_imx_chip(chip); + struct device *dev = chip->dev; + unsigned int period_ms; + int fifoav; + u32 sr; + + sr = readl(imx->mmio_base + MX3_PWMSR); + fifoav = sr & MX3_PWMSR_FIFOAV_MASK; + if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) { + period_ms = DIV_ROUND_UP(pwm_get_period(pwm), + NSEC_PER_MSEC); + msleep(period_ms); + + sr = readl(imx->mmio_base + MX3_PWMSR); + if (fifoav == (sr & MX3_PWMSR_FIFOAV_MASK)) + dev_warn(dev, "there is no free FIFO slot\n"); + } +} static int imx_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct imx_chip *imx = to_imx_chip(chip); - struct device *dev = chip->dev; unsigned long long c; unsigned long period_cycles, duty_cycles, prescale; - unsigned int period_ms; bool enable = pwm_is_enabled(pwm); - int fifoav; - u32 cr, sr; + u32 cr; /* * i.MX PWMv2 has a 4-word sample FIFO. @@ -157,21 +175,10 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, * wait for a full PWM cycle to get a relinquished FIFO slot * when the controller is enabled and the FIFO is fully loaded. */ - if (enable) { - sr = readl(imx->mmio_base + MX3_PWMSR); - fifoav = sr & MX3_PWMSR_FIFOAV_MASK; - if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) { - period_ms = DIV_ROUND_UP(pwm_get_period(pwm), - NSEC_PER_MSEC); - msleep(period_ms); - - sr = readl(imx->mmio_base + MX3_PWMSR); - if (fifoav == (sr & MX3_PWMSR_FIFOAV_MASK)) - dev_warn(dev, "there is no free FIFO slot\n"); - } - } else { + if (enable) + imx_pwm_wait_fifo_slot(chip, pwm); + else imx_pwm_sw_reset(chip); - } c = clk_get_rate(imx->clk_per); c = c * period_ns; -- cgit v1.2.3 From 0ca1a11a1d816c8fb0bb29b985666cef912958c1 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:11 +0100 Subject: pwm: imx: Provide atomic PWM support for i.MX PWMv2 This commit provides apply() callback implementation for i.MX's PWMv2. Suggested-by: Stefan Agner Suggested-by: Boris Brezillon Signed-off-by: Lukasz Majewski Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index d557279446ed..34f3031d5c40 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -248,6 +248,72 @@ static int imx_pwm_config(struct pwm_chip *chip, return ret; } +static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + unsigned long period_cycles, duty_cycles, prescale; + struct imx_chip *imx = to_imx_chip(chip); + struct pwm_state cstate; + unsigned long long c; + int ret; + + pwm_get_state(pwm, &cstate); + + if (state->enabled) { + c = clk_get_rate(imx->clk_per); + c *= state->period; + + do_div(c, 1000000000); + 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); + duty_cycles = c; + + /* + * according to imx pwm RM, the real period value should be + * PERIOD value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + /* + * Wait for a free FIFO slot if the PWM is already enabled, and + * flush the FIFO if the PWM was disabled and is about to be + * enabled. + */ + if (cstate.enabled) { + imx_pwm_wait_fifo_slot(chip, pwm); + } else { + ret = clk_prepare_enable(imx->clk_per); + if (ret) + return ret; + + imx_pwm_sw_reset(chip); + } + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + writel(MX3_PWMCR_PRESCALER(prescale) | + MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | + MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH | + MX3_PWMCR_EN, + imx->mmio_base + MX3_PWMCR); + } else if (cstate.enabled) { + writel(0, imx->mmio_base + MX3_PWMCR); + + clk_disable_unprepare(imx->clk_per); + } + + return 0; +} + static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); @@ -279,6 +345,7 @@ static const struct pwm_ops imx_pwm_ops_v1 = { }; static const struct pwm_ops imx_pwm_ops_v2 = { + .apply = imx_pwm_apply_v2, .enable = imx_pwm_enable, .disable = imx_pwm_disable, .config = imx_pwm_config, -- cgit v1.2.3 From 871985b154ada5a0edaf1aa22d6a1de115b0213d Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:12 +0100 Subject: pwm: imx: Remove redundant i.MX PWMv2 code The code providing functionality surpassed by the atomic PWM is not needed anymore and hence can be removed. Suggested-by: Stefan Agner Suggested-by: Boris Brezillon Signed-off-by: Lukasz Majewski Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 127 -------------------------------------------------- 1 file changed, 127 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 34f3031d5c40..0a81c028cb11 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -53,10 +53,6 @@ struct imx_chip { void __iomem *mmio_base; struct pwm_chip chip; - - int (*config)(struct pwm_chip *chip, - struct pwm_device *pwm, int duty_ns, int period_ns); - void (*set_enable)(struct pwm_chip *chip, bool enable); }; #define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) @@ -159,95 +155,6 @@ static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip, } } -static int imx_pwm_config_v2(struct pwm_chip *chip, - struct pwm_device *pwm, int duty_ns, int period_ns) -{ - struct imx_chip *imx = to_imx_chip(chip); - unsigned long long c; - unsigned long period_cycles, duty_cycles, prescale; - bool enable = pwm_is_enabled(pwm); - u32 cr; - - /* - * i.MX PWMv2 has a 4-word sample FIFO. - * In order to avoid FIFO overflow issue, we do software reset - * to clear all sample FIFO if the controller is disabled or - * wait for a full PWM cycle to get a relinquished FIFO slot - * when the controller is enabled and the FIFO is fully loaded. - */ - if (enable) - imx_pwm_wait_fifo_slot(chip, pwm); - else - imx_pwm_sw_reset(chip); - - c = clk_get_rate(imx->clk_per); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - prescale = period_cycles / 0x10000 + 1; - - period_cycles /= prescale; - c = (unsigned long long)period_cycles * duty_ns; - do_div(c, period_ns); - duty_cycles = c; - - /* - * according to imx pwm RM, the real period value should be - * PERIOD value in PWMPR plus 2. - */ - if (period_cycles > 2) - period_cycles -= 2; - else - period_cycles = 0; - - writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); - writel(period_cycles, imx->mmio_base + MX3_PWMPR); - - cr = MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH; - - if (enable) - cr |= MX3_PWMCR_EN; - - writel(cr, imx->mmio_base + MX3_PWMCR); - - return 0; -} - -static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) -{ - struct imx_chip *imx = to_imx_chip(chip); - u32 val; - - val = readl(imx->mmio_base + MX3_PWMCR); - - if (enable) - val |= MX3_PWMCR_EN; - else - val &= ~MX3_PWMCR_EN; - - writel(val, imx->mmio_base + MX3_PWMCR); -} - -static int imx_pwm_config(struct pwm_chip *chip, - struct pwm_device *pwm, int duty_ns, int period_ns) -{ - struct imx_chip *imx = to_imx_chip(chip); - int ret; - - ret = clk_prepare_enable(imx->clk_per); - if (ret) - return ret; - - ret = imx->config(chip, pwm, duty_ns, period_ns); - - clk_disable_unprepare(imx->clk_per); - - return ret; -} - static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { @@ -314,29 +221,6 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct imx_chip *imx = to_imx_chip(chip); - int ret; - - ret = clk_prepare_enable(imx->clk_per); - if (ret) - return ret; - - imx->set_enable(chip, true); - - return 0; -} - -static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct imx_chip *imx = to_imx_chip(chip); - - imx->set_enable(chip, false); - - clk_disable_unprepare(imx->clk_per); -} - static const struct pwm_ops imx_pwm_ops_v1 = { .enable = imx_pwm_enable_v1, .disable = imx_pwm_disable_v1, @@ -346,16 +230,10 @@ static const struct pwm_ops imx_pwm_ops_v1 = { static const struct pwm_ops imx_pwm_ops_v2 = { .apply = imx_pwm_apply_v2, - .enable = imx_pwm_enable, - .disable = imx_pwm_disable, - .config = imx_pwm_config, .owner = THIS_MODULE, }; struct imx_pwm_data { - int (*config)(struct pwm_chip *chip, - struct pwm_device *pwm, int duty_ns, int period_ns); - void (*set_enable)(struct pwm_chip *chip, bool enable); const struct pwm_ops *ops; }; @@ -364,8 +242,6 @@ static struct imx_pwm_data imx_pwm_data_v1 = { }; static struct imx_pwm_data imx_pwm_data_v2 = { - .config = imx_pwm_config_v2, - .set_enable = imx_pwm_set_enable_v2, .ops = &imx_pwm_ops_v2, }; @@ -412,9 +288,6 @@ static int imx_pwm_probe(struct platform_device *pdev) if (IS_ERR(imx->mmio_base)) return PTR_ERR(imx->mmio_base); - imx->config = data->config; - imx->set_enable = data->set_enable; - ret = pwmchip_add(&imx->chip); if (ret < 0) return ret; -- cgit v1.2.3 From 326ed314fefebb259563926c8c6110a009562e07 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Sun, 29 Jan 2017 22:54:15 +0100 Subject: pwm: imx: Add polarity inversion support to i.MX's PWMv2 With this patch the polarity settings for i.MX's PWMv2 is now supported on top of atomic PWM setting Signed-off-by: Bhuvanchandra DV Signed-off-by: Lukasz Majewski Reviewed-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 0a81c028cb11..957099e82ed3 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -38,6 +38,7 @@ #define MX3_PWMCR_DOZEEN (1 << 24) #define MX3_PWMCR_WAITEN (1 << 23) #define MX3_PWMCR_DBGEN (1 << 22) +#define MX3_PWMCR_POUTC (1 << 18) #define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) #define MX3_PWMCR_CLKSRC_IPG (1 << 16) #define MX3_PWMCR_SWR (1 << 3) @@ -163,6 +164,7 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state cstate; unsigned long long c; int ret; + u32 cr; pwm_get_state(pwm, &cstate); @@ -207,11 +209,15 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); writel(period_cycles, imx->mmio_base + MX3_PWMPR); - writel(MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH | - MX3_PWMCR_EN, - imx->mmio_base + MX3_PWMCR); + cr = MX3_PWMCR_PRESCALER(prescale) | + MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | + MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH | + MX3_PWMCR_EN; + + if (state->polarity == PWM_POLARITY_INVERSED) + cr |= MX3_PWMCR_POUTC; + + writel(cr, imx->mmio_base + MX3_PWMCR); } else if (cstate.enabled) { writel(0, imx->mmio_base + MX3_PWMCR); @@ -234,6 +240,7 @@ static const struct pwm_ops imx_pwm_ops_v2 = { }; struct imx_pwm_data { + bool polarity_supported; const struct pwm_ops *ops; }; @@ -242,6 +249,7 @@ static struct imx_pwm_data imx_pwm_data_v1 = { }; static struct imx_pwm_data imx_pwm_data_v2 = { + .polarity_supported = true, .ops = &imx_pwm_ops_v2, }; @@ -283,6 +291,12 @@ static int imx_pwm_probe(struct platform_device *pdev) imx->chip.npwm = 1; imx->chip.can_sleep = true; + if (data->polarity_supported) { + dev_dbg(&pdev->dev, "PWM supports output inversion\n"); + imx->chip.of_xlate = of_pwm_xlate_with_flags; + imx->chip.of_pwm_n_cells = 3; + } + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(imx->mmio_base)) -- cgit v1.2.3