]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/pwm/pwm-lpss.c
Merge tag 'media/v4.5-5' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
[karo-tx-linux.git] / drivers / pwm / pwm-lpss.c
index 25044104003b01a7e3879fe6b94e7e5e7d477681..295b963dbddb7bdf59b159c6de03ba701d4efc9e 100644 (file)
  * published by the Free Software Foundation.
  */
 
+#include <linux/delay.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
+#include <linux/time.h>
 
 #include "pwm-lpss.h"
 
 #define PWM_ENABLE                     BIT(31)
 #define PWM_SW_UPDATE                  BIT(30)
 #define PWM_BASE_UNIT_SHIFT            8
-#define PWM_BASE_UNIT_MASK             0x00ffff00
 #define PWM_ON_TIME_DIV_MASK           0x000000ff
 #define PWM_DIVISION_CORRECTION                0x2
-#define PWM_LIMIT                      (0x8000 + PWM_DIVISION_CORRECTION)
-#define NSECS_PER_SEC                  1000000000UL
 
 /* Size of each PWM register space if multiple */
 #define PWM_SIZE                       0x400
 struct pwm_lpss_chip {
        struct pwm_chip chip;
        void __iomem *regs;
-       unsigned long clk_rate;
+       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);
 
@@ -50,6 +50,7 @@ EXPORT_SYMBOL_GPL(pwm_lpss_byt_info);
 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);
 
@@ -57,6 +58,7 @@ EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info);
 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);
 
@@ -79,28 +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)
+{
+       pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_SW_UPDATE);
+       /* Give it some time to propagate */
+       usleep_range(10, 50);
+}
+
 static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
                           int duty_ns, int period_ns)
 {
        struct pwm_lpss_chip *lpwm = to_lpwm(chip);
        u8 on_time_div;
-       unsigned long c;
-       unsigned long long base_unit, freq = NSECS_PER_SEC;
+       unsigned long c, base_unit_range;
+       unsigned long long base_unit, freq = NSEC_PER_SEC;
        u32 ctrl;
 
        do_div(freq, period_ns);
 
-       /* The equation is: base_unit = ((freq / c) * 65536) + correction */
-       base_unit = freq * 65536;
+       /*
+        * The equation is:
+        * base_unit = ((freq / c) * base_unit_range) + correction
+        */
+       base_unit_range = BIT(lpwm->info->base_unit_bits);
+       base_unit = freq * base_unit_range;
 
-       c = lpwm->clk_rate;
+       c = lpwm->info->clk_rate;
        if (!c)
                return -EINVAL;
 
        do_div(base_unit, c);
        base_unit += PWM_DIVISION_CORRECTION;
-       if (base_unit > PWM_LIMIT)
-               return -EINVAL;
 
        if (duty_ns <= 0)
                duty_ns = 1;
@@ -109,13 +120,20 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
        pm_runtime_get_sync(chip->dev);
 
        ctrl = pwm_lpss_read(pwm);
-       ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK);
-       ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT;
+       ctrl &= ~PWM_ON_TIME_DIV_MASK;
+       ctrl &= ~((base_unit_range - 1) << PWM_BASE_UNIT_SHIFT);
+       base_unit &= (base_unit_range - 1);
+       ctrl |= (u32) base_unit << PWM_BASE_UNIT_SHIFT;
        ctrl |= on_time_div;
-       /* request PWM to update on next cycle */
-       ctrl |= PWM_SW_UPDATE;
        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;
@@ -124,6 +142,12 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
 static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
        pm_runtime_get_sync(chip->dev);
+
+       /*
+        * 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;
 }
@@ -135,7 +159,6 @@ static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 }
 
 static const struct pwm_ops pwm_lpss_ops = {
-       .free = pwm_lpss_disable,
        .config = pwm_lpss_config,
        .enable = pwm_lpss_enable,
        .disable = pwm_lpss_disable,
@@ -156,7 +179,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
        if (IS_ERR(lpwm->regs))
                return ERR_CAST(lpwm->regs);
 
-       lpwm->clk_rate = info->clk_rate;
+       lpwm->info = info;
        lpwm->chip.dev = dev;
        lpwm->chip.ops = &pwm_lpss_ops;
        lpwm->chip.base = -1;