|
@@ -8,9 +8,11 @@
|
|
*/
|
|
*/
|
|
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk.h>
|
|
|
|
+#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
|
|
+#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/platform_device.h>
|
|
@@ -21,6 +23,7 @@
|
|
#define PWM_ENA 0x04
|
|
#define PWM_ENA 0x04
|
|
#define PWM_DIS 0x08
|
|
#define PWM_DIS 0x08
|
|
#define PWM_SR 0x0C
|
|
#define PWM_SR 0x0C
|
|
|
|
+#define PWM_ISR 0x1C
|
|
/* Bit field in SR */
|
|
/* Bit field in SR */
|
|
#define PWM_SR_ALL_CH_ON 0x0F
|
|
#define PWM_SR_ALL_CH_ON 0x0F
|
|
|
|
|
|
@@ -60,6 +63,9 @@ struct atmel_pwm_chip {
|
|
struct clk *clk;
|
|
struct clk *clk;
|
|
void __iomem *base;
|
|
void __iomem *base;
|
|
|
|
|
|
|
|
+ unsigned int updated_pwms;
|
|
|
|
+ struct mutex isr_lock; /* ISR is cleared when read, ensure only one thread does that */
|
|
|
|
+
|
|
void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
unsigned long dty, unsigned long prd);
|
|
unsigned long dty, unsigned long prd);
|
|
};
|
|
};
|
|
@@ -144,6 +150,10 @@ static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
|
|
val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
|
|
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
|
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
|
atmel_pwm->config(chip, pwm, dty, prd);
|
|
atmel_pwm->config(chip, pwm, dty, prd);
|
|
|
|
+ mutex_lock(&atmel_pwm->isr_lock);
|
|
|
|
+ atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
|
|
|
|
+ atmel_pwm->updated_pwms &= ~(1 << pwm->hwpwm);
|
|
|
|
+ mutex_unlock(&atmel_pwm->isr_lock);
|
|
|
|
|
|
clk_disable(atmel_pwm->clk);
|
|
clk_disable(atmel_pwm->clk);
|
|
return ret;
|
|
return ret;
|
|
@@ -243,7 +253,22 @@ static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
{
|
|
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
|
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
|
|
|
+ unsigned long timeout = jiffies + 2 * HZ;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Wait for at least a complete period to have passed before disabling a
|
|
|
|
+ * channel to be sure that CDTY has been updated
|
|
|
|
+ */
|
|
|
|
+ mutex_lock(&atmel_pwm->isr_lock);
|
|
|
|
+ atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
|
|
|
|
+
|
|
|
|
+ while (!(atmel_pwm->updated_pwms & (1 << pwm->hwpwm)) &&
|
|
|
|
+ time_before(jiffies, timeout)) {
|
|
|
|
+ usleep_range(10, 100);
|
|
|
|
+ atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ mutex_unlock(&atmel_pwm->isr_lock);
|
|
atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
|
|
atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
|
|
|
|
|
|
clk_disable(atmel_pwm->clk);
|
|
clk_disable(atmel_pwm->clk);
|
|
@@ -358,6 +383,8 @@ static int atmel_pwm_probe(struct platform_device *pdev)
|
|
atmel_pwm->chip.npwm = 4;
|
|
atmel_pwm->chip.npwm = 4;
|
|
atmel_pwm->chip.can_sleep = true;
|
|
atmel_pwm->chip.can_sleep = true;
|
|
atmel_pwm->config = data->config;
|
|
atmel_pwm->config = data->config;
|
|
|
|
+ atmel_pwm->updated_pwms = 0;
|
|
|
|
+ mutex_init(&atmel_pwm->isr_lock);
|
|
|
|
|
|
ret = pwmchip_add(&atmel_pwm->chip);
|
|
ret = pwmchip_add(&atmel_pwm->chip);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
@@ -379,6 +406,7 @@ static int atmel_pwm_remove(struct platform_device *pdev)
|
|
struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev);
|
|
struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev);
|
|
|
|
|
|
clk_unprepare(atmel_pwm->clk);
|
|
clk_unprepare(atmel_pwm->clk);
|
|
|
|
+ mutex_destroy(&atmel_pwm->isr_lock);
|
|
|
|
|
|
return pwmchip_remove(&atmel_pwm->chip);
|
|
return pwmchip_remove(&atmel_pwm->chip);
|
|
}
|
|
}
|