aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/pwm-atmel.c
diff options
context:
space:
mode:
authorAlexandre Belloni <alexandre.belloni@free-electrons.com>2015-05-25 12:11:49 -0400
committerThierry Reding <thierry.reding@gmail.com>2015-06-12 05:16:12 -0400
commit472ac3dcac108d6648ee28616c6de1e3b0bb361f (patch)
treec8e8c98f9737977e5442f45797a6b88c61a2f995 /drivers/pwm/pwm-atmel.c
parent4c027f7ba8520df088d34ae045205a6f8e2a1d76 (diff)
pwm: atmel: Fix incorrect CDTY value after disabling
pwm-leds calls .config() and .disable() in a row. This exhibits that it may happen that the channel gets disabled before CDTY has been updated with CUPD. The issue gets quite worse with long periods. So, ensure that at least one period has past before disabling the channel by polling ISR. Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Tested-by: Gaƫl PORTAY <gael.portay@gmail.com> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm/pwm-atmel.c')
-rw-r--r--drivers/pwm/pwm-atmel.c28
1 files changed, 28 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
index 89f9ca41d9af..a947c9095d9d 100644
--- a/drivers/pwm/pwm-atmel.c
+++ b/drivers/pwm/pwm-atmel.c
@@ -8,9 +8,11 @@
8 */ 8 */
9 9
10#include <linux/clk.h> 10#include <linux/clk.h>
11#include <linux/delay.h>
11#include <linux/err.h> 12#include <linux/err.h>
12#include <linux/io.h> 13#include <linux/io.h>
13#include <linux/module.h> 14#include <linux/module.h>
15#include <linux/mutex.h>
14#include <linux/of.h> 16#include <linux/of.h>
15#include <linux/of_device.h> 17#include <linux/of_device.h>
16#include <linux/platform_device.h> 18#include <linux/platform_device.h>
@@ -21,6 +23,7 @@
21#define PWM_ENA 0x04 23#define PWM_ENA 0x04
22#define PWM_DIS 0x08 24#define PWM_DIS 0x08
23#define PWM_SR 0x0C 25#define PWM_SR 0x0C
26#define PWM_ISR 0x1C
24/* Bit field in SR */ 27/* Bit field in SR */
25#define PWM_SR_ALL_CH_ON 0x0F 28#define PWM_SR_ALL_CH_ON 0x0F
26 29
@@ -60,6 +63,9 @@ struct atmel_pwm_chip {
60 struct clk *clk; 63 struct clk *clk;
61 void __iomem *base; 64 void __iomem *base;
62 65
66 unsigned int updated_pwms;
67 struct mutex isr_lock; /* ISR is cleared when read, ensure only one thread does that */
68
63 void (*config)(struct pwm_chip *chip, struct pwm_device *pwm, 69 void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
64 unsigned long dty, unsigned long prd); 70 unsigned long dty, unsigned long prd);
65}; 71};
@@ -144,6 +150,10 @@ static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
144 val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK); 150 val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
145 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); 151 atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
146 atmel_pwm->config(chip, pwm, dty, prd); 152 atmel_pwm->config(chip, pwm, dty, prd);
153 mutex_lock(&atmel_pwm->isr_lock);
154 atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
155 atmel_pwm->updated_pwms &= ~(1 << pwm->hwpwm);
156 mutex_unlock(&atmel_pwm->isr_lock);
147 157
148 clk_disable(atmel_pwm->clk); 158 clk_disable(atmel_pwm->clk);
149 return ret; 159 return ret;
@@ -243,7 +253,22 @@ static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
243static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 253static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
244{ 254{
245 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); 255 struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
256 unsigned long timeout = jiffies + 2 * HZ;
257
258 /*
259 * Wait for at least a complete period to have passed before disabling a
260 * channel to be sure that CDTY has been updated
261 */
262 mutex_lock(&atmel_pwm->isr_lock);
263 atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
264
265 while (!(atmel_pwm->updated_pwms & (1 << pwm->hwpwm)) &&
266 time_before(jiffies, timeout)) {
267 usleep_range(10, 100);
268 atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
269 }
246 270
271 mutex_unlock(&atmel_pwm->isr_lock);
247 atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm); 272 atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
248 273
249 clk_disable(atmel_pwm->clk); 274 clk_disable(atmel_pwm->clk);
@@ -358,6 +383,8 @@ static int atmel_pwm_probe(struct platform_device *pdev)
358 atmel_pwm->chip.npwm = 4; 383 atmel_pwm->chip.npwm = 4;
359 atmel_pwm->chip.can_sleep = true; 384 atmel_pwm->chip.can_sleep = true;
360 atmel_pwm->config = data->config; 385 atmel_pwm->config = data->config;
386 atmel_pwm->updated_pwms = 0;
387 mutex_init(&atmel_pwm->isr_lock);
361 388
362 ret = pwmchip_add(&atmel_pwm->chip); 389 ret = pwmchip_add(&atmel_pwm->chip);
363 if (ret < 0) { 390 if (ret < 0) {
@@ -379,6 +406,7 @@ static int atmel_pwm_remove(struct platform_device *pdev)
379 struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev); 406 struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev);
380 407
381 clk_unprepare(atmel_pwm->clk); 408 clk_unprepare(atmel_pwm->clk);
409 mutex_destroy(&atmel_pwm->isr_lock);
382 410
383 return pwmchip_remove(&atmel_pwm->chip); 411 return pwmchip_remove(&atmel_pwm->chip);
384} 412}