|
@@ -8,9 +8,11 @@
|
|
|
*/
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
+#include <linux/delay.h>
|
|
|
#include <linux/err.h>
|
|
|
#include <linux/io.h>
|
|
|
#include <linux/module.h>
|
|
|
+#include <linux/mutex.h>
|
|
|
#include <linux/of.h>
|
|
|
#include <linux/of_device.h>
|
|
|
#include <linux/platform_device.h>
|
|
@@ -21,6 +23,7 @@
|
|
|
#define PWM_ENA 0x04
|
|
|
#define PWM_DIS 0x08
|
|
|
#define PWM_SR 0x0C
|
|
|
+#define PWM_ISR 0x1C
|
|
|
/* Bit field in SR */
|
|
|
#define PWM_SR_ALL_CH_ON 0x0F
|
|
|
|
|
@@ -60,6 +63,9 @@ struct atmel_pwm_chip {
|
|
|
struct clk *clk;
|
|
|
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,
|
|
|
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);
|
|
|
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
|
|
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);
|
|
|
return ret;
|
|
@@ -155,24 +165,25 @@ static void atmel_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
|
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
|
|
unsigned int val;
|
|
|
|
|
|
- if (test_bit(PWMF_ENABLED, &pwm->flags)) {
|
|
|
- /*
|
|
|
- * If the PWM channel is enabled, using the update register,
|
|
|
- * it needs to set bit 10 of CMR to 0
|
|
|
- */
|
|
|
- atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
|
|
|
|
|
|
- val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
|
|
- val &= ~PWM_CMR_UPD_CDTY;
|
|
|
- atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
|
|
- } else {
|
|
|
- /*
|
|
|
- * If the PWM channel is disabled, write value to duty and
|
|
|
- * period registers directly.
|
|
|
- */
|
|
|
- atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
|
|
|
- atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
|
|
|
- }
|
|
|
+ atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
|
|
|
+
|
|
|
+ val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
|
|
+ val &= ~PWM_CMR_UPD_CDTY;
|
|
|
+ atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If the PWM channel is enabled, only update CDTY by using the update
|
|
|
+ * register, it needs to set bit 10 of CMR to 0
|
|
|
+ */
|
|
|
+ if (test_bit(PWMF_ENABLED, &pwm->flags))
|
|
|
+ return;
|
|
|
+ /*
|
|
|
+ * If the PWM channel is disabled, write value to duty and period
|
|
|
+ * registers directly.
|
|
|
+ */
|
|
|
+ atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
|
|
|
+ atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
|
|
|
}
|
|
|
|
|
|
static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
@@ -242,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)
|
|
|
{
|
|
|
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);
|
|
|
|
|
|
clk_disable(atmel_pwm->clk);
|
|
@@ -357,6 +383,8 @@ static int atmel_pwm_probe(struct platform_device *pdev)
|
|
|
atmel_pwm->chip.npwm = 4;
|
|
|
atmel_pwm->chip.can_sleep = true;
|
|
|
atmel_pwm->config = data->config;
|
|
|
+ atmel_pwm->updated_pwms = 0;
|
|
|
+ mutex_init(&atmel_pwm->isr_lock);
|
|
|
|
|
|
ret = pwmchip_add(&atmel_pwm->chip);
|
|
|
if (ret < 0) {
|
|
@@ -378,6 +406,7 @@ static int atmel_pwm_remove(struct platform_device *pdev)
|
|
|
struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev);
|
|
|
|
|
|
clk_unprepare(atmel_pwm->clk);
|
|
|
+ mutex_destroy(&atmel_pwm->isr_lock);
|
|
|
|
|
|
return pwmchip_remove(&atmel_pwm->chip);
|
|
|
}
|