These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / pwm / pwm-atmel.c
index d3c22de..0e4bd4e 100644 (file)
@@ -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);
 };
@@ -108,7 +114,7 @@ static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        u32 val;
        int ret;
 
-       if (test_bit(PWMF_ENABLED, &pwm->flags) && (period_ns != pwm->period)) {
+       if (pwm_is_enabled(pwm) && (period_ns != pwm_get_period(pwm))) {
                dev_err(chip->dev, "cannot change PWM period while enabled\n");
                return -EBUSY;
        }
@@ -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 (pwm_is_enabled(pwm))
+               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,
@@ -180,7 +191,7 @@ static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
 {
        struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
 
-       if (test_bit(PWMF_ENABLED, &pwm->flags)) {
+       if (pwm_is_enabled(pwm)) {
                /*
                 * If the PWM channel is enabled, using the duty update register
                 * to update the value.
@@ -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);
 }