Merge tag 'pwm/for-5.2-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 10 May 2019 16:57:15 +0000 (12:57 -0400)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 10 May 2019 16:57:15 +0000 (12:57 -0400)
Pull pwm updates from Thierry Reding:
 "Nothing out of the ordinary this cycle.

  The bulk of this is a collection of fixes for existing drivers and
  some cleanups. There's one new driver for i.MX SoCs and addition of
  support for some new variants to existing drivers"

* tag 'pwm/for-5.2-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm:
  pwm: meson: Add clock source configuration for Meson G12A
  dt-bindings: pwm: Update bindings for the Meson G12A Family
  pwm: samsung: Don't uses devm_*() functions in ->request()
  pwm: Clear chip_data in pwm_put()
  pwm: Add i.MX TPM PWM driver support
  dt-bindings: pwm: Add i.MX TPM PWM binding
  pwm: imx27: Use devm_platform_ioremap_resource() to simplify code
  pwm: meson: Use the spin-lock only to protect register modifications
  pwm: meson: Don't disable PWM when setting duty repeatedly
  pwm: meson: Consider 128 a valid pre-divider
  pwm: sysfs: fix typo "its" -> "it's"
  pwm: tiehrpwm: Enable compilation for ARCH_K3
  dt-bindings: pwm: tiehrpwm: Add TI AM654 SoC specific compatible
  pwm: tiehrpwm: Update shadow register for disabling PWMs
  pwm: img: Turn final 'else if' into 'else' in img_pwm_config
  pwm: Fix deadlock warning when removing PWM device

16 files changed:
Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/pwm-meson.txt
Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/core.c
drivers/pwm/pwm-berlin.c
drivers/pwm/pwm-img.c
drivers/pwm/pwm-imx-tpm.c [new file with mode: 0644]
drivers/pwm/pwm-imx27.c
drivers/pwm/pwm-meson.c
drivers/pwm/pwm-pca9685.c
drivers/pwm/pwm-samsung.c
drivers/pwm/pwm-tiehrpwm.c
drivers/pwm/sysfs.c
include/linux/pwm.h

diff --git a/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
new file mode 100644 (file)
index 0000000..3ba958d
--- /dev/null
@@ -0,0 +1,22 @@
+Freescale i.MX TPM PWM controller
+
+Required properties:
+- compatible : Should be "fsl,imx7ulp-pwm".
+- reg: Physical base address and length of the controller's registers.
+- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of the cells format.
+- clocks : The clock provided by the SoC to drive the PWM.
+- interrupts: The interrupt for the PWM controller.
+
+Note: The TPM counter and period counter are shared between multiple channels, so all channels
+should use same period setting.
+
+Example:
+
+tpm4: pwm@40250000 {
+       compatible = "fsl,imx7ulp-pwm";
+       reg = <0x40250000 0x1000>;
+       assigned-clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+       assigned-clock-parents = <&scg1 IMX7ULP_CLK_SOSC_BUS_CLK>;
+       clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+       #pwm-cells = <3>;
+};
index 1fa3f718213342f20e910a224b95e23efede87be..891632354065269d490cab32c08f184b8c2181d2 100644 (file)
@@ -7,6 +7,9 @@ Required properties:
                          or "amlogic,meson-gxbb-ao-pwm"
                          or "amlogic,meson-axg-ee-pwm"
                          or "amlogic,meson-axg-ao-pwm"
+                         or "amlogic,meson-g12a-ee-pwm"
+                         or "amlogic,meson-g12a-ao-pwm-ab"
+                         or "amlogic,meson-g12a-ao-pwm-cd"
 - #pwm-cells: Should be 3. See pwm.txt in this directory for a description of
   the cells format.
 
index 944fe356bb4569600619b3434dafeddadd164b18..31c4577157dd7a43acdb3f3bcc8dea0850731d07 100644 (file)
@@ -4,6 +4,7 @@ Required properties:
 - compatible: Must be "ti,<soc>-ehrpwm".
   for am33xx  - compatible = "ti,am3352-ehrpwm", "ti,am33xx-ehrpwm";
   for am4372  - compatible = "ti,am4372-ehrpwm", "ti-am3352-ehrpwm", "ti,am33xx-ehrpwm";
+  for am654   - compatible = "ti,am654-ehrpwm", "ti-am3352-ehrpwm";
   for da850   - compatible = "ti,da850-ehrpwm", "ti-am3352-ehrpwm", "ti,am33xx-ehrpwm";
   for dra746 - compatible = "ti,dra746-ehrpwm", "ti-am3352-ehrpwm";
 - #pwm-cells: should be 3. See pwm.txt in this directory for a description of
index 54f8238aac0dece5e2469c0ba02f5e7c7d1d49c0..1311b54089beceb92a75c7e11a073d415b5df8aa 100644 (file)
@@ -210,6 +210,17 @@ config PWM_IMX27
          To compile this driver as a module, choose M here: the module
          will be called pwm-imx27.
 
+config PWM_IMX_TPM
+       tristate "i.MX TPM PWM support"
+       depends on ARCH_MXC || COMPILE_TEST
+       depends on HAVE_CLK && HAS_IOMEM
+       help
+         Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
+         name is Low Power Timer/Pulse Width Modulation Module.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-imx-tpm.
+
 config PWM_JZ4740
        tristate "Ingenic JZ47xx PWM support"
        depends on MACH_INGENIC
@@ -467,10 +478,9 @@ config  PWM_TIECAP
 
 config  PWM_TIEHRPWM
        tristate "EHRPWM PWM support"
-       depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX
+       depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_K3
        help
-         PWM driver support for the EHRPWM controller found on AM33XX
-         TI SOC
+         PWM driver support for the EHRPWM controller found on TI SOCs
 
          To compile this driver as a module, choose M here: the module
          will be called pwm-tiehrpwm.
index 448825e892bc17b1de1549319dcd900be7917901..c368599d36c090148daaf6041d1663da1cb06f8a 100644 (file)
@@ -19,6 +19,7 @@ obj-$(CONFIG_PWM_HIBVT)               += pwm-hibvt.o
 obj-$(CONFIG_PWM_IMG)          += pwm-img.o
 obj-$(CONFIG_PWM_IMX1)         += pwm-imx1.o
 obj-$(CONFIG_PWM_IMX27)                += pwm-imx27.o
+obj-$(CONFIG_PWM_IMX_TPM)      += pwm-imx-tpm.o
 obj-$(CONFIG_PWM_JZ4740)       += pwm-jz4740.o
 obj-$(CONFIG_PWM_LP3943)       += pwm-lp3943.o
 obj-$(CONFIG_PWM_LPC18XX_SCT)  += pwm-lpc18xx-sct.o
index 3149204567f3cc3b64b4ddf15fb75cbdc8a32d85..3998ebd51db46c9582cb079c7b1fd642be9965d9 100644 (file)
@@ -311,10 +311,12 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
        if (IS_ENABLED(CONFIG_OF))
                of_pwmchip_add(chip);
 
-       pwmchip_sysfs_export(chip);
-
 out:
        mutex_unlock(&pwm_lock);
+
+       if (!ret)
+               pwmchip_sysfs_export(chip);
+
        return ret;
 }
 EXPORT_SYMBOL_GPL(pwmchip_add_with_polarity);
@@ -348,7 +350,7 @@ int pwmchip_remove(struct pwm_chip *chip)
        unsigned int i;
        int ret = 0;
 
-       pwmchip_sysfs_unexport_children(chip);
+       pwmchip_sysfs_unexport(chip);
 
        mutex_lock(&pwm_lock);
 
@@ -368,8 +370,6 @@ int pwmchip_remove(struct pwm_chip *chip)
 
        free_pwms(chip);
 
-       pwmchip_sysfs_unexport(chip);
-
 out:
        mutex_unlock(&pwm_lock);
        return ret;
@@ -877,6 +877,7 @@ void pwm_put(struct pwm_device *pwm)
        if (pwm->chip->ops->free)
                pwm->chip->ops->free(pwm->chip, pwm);
 
+       pwm_set_chip_data(pwm, NULL);
        pwm->label = NULL;
 
        module_put(pwm->chip->ops->owner);
index 7c8d6a168ceb254c69420ab293f08e3b43fcdd18..b91c477cc84be90fc29d200a3528fdffd20ae1aa 100644 (file)
@@ -84,7 +84,6 @@ static void berlin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 {
        struct berlin_pwm_channel *channel = pwm_get_chip_data(pwm);
 
-       pwm_set_chip_data(pwm, NULL);
        kfree(channel);
 }
 
index 815f5333bb8f98dea06e5d4f1969e8f96933b815..1cc5fbe1e1d3f8cc5afb8119769824d4a7b1a472 100644 (file)
@@ -123,7 +123,7 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        } else if (mul <= max_timebase * 512) {
                div = PWM_CTRL_CFG_SUB_DIV0_DIV1;
                timebase = DIV_ROUND_UP(mul, 512);
-       } else if (mul > max_timebase * 512) {
+       } else {
                dev_err(chip->dev,
                        "failed to configure timebase steps/divider value\n");
                return -EINVAL;
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
new file mode 100644 (file)
index 0000000..e8385c1
--- /dev/null
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2019 NXP.
+ *
+ * Limitations:
+ * - The TPM counter and period counter are shared between
+ *   multiple channels, so all channels should use same period
+ *   settings.
+ * - Changes to polarity cannot be latched at the time of the
+ *   next period start.
+ * - Changing period and duty cycle together isn't atomic,
+ *   with the wrong timing it might happen that a period is
+ *   produced with old duty cycle but new period settings.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+
+#define PWM_IMX_TPM_PARAM      0x4
+#define PWM_IMX_TPM_GLOBAL     0x8
+#define PWM_IMX_TPM_SC         0x10
+#define PWM_IMX_TPM_CNT                0x14
+#define PWM_IMX_TPM_MOD                0x18
+#define PWM_IMX_TPM_CnSC(n)    (0x20 + (n) * 0x8)
+#define PWM_IMX_TPM_CnV(n)     (0x24 + (n) * 0x8)
+
+#define PWM_IMX_TPM_PARAM_CHAN                 GENMASK(7, 0)
+
+#define PWM_IMX_TPM_SC_PS                      GENMASK(2, 0)
+#define PWM_IMX_TPM_SC_CMOD                    GENMASK(4, 3)
+#define PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK      FIELD_PREP(PWM_IMX_TPM_SC_CMOD, 1)
+#define PWM_IMX_TPM_SC_CPWMS                   BIT(5)
+
+#define PWM_IMX_TPM_CnSC_CHF   BIT(7)
+#define PWM_IMX_TPM_CnSC_MSB   BIT(5)
+#define PWM_IMX_TPM_CnSC_MSA   BIT(4)
+
+/*
+ * The reference manual describes this field as two separate bits. The
+ * semantic of the two bits isn't orthogonal though, so they are treated
+ * together as a 2-bit field here.
+ */
+#define PWM_IMX_TPM_CnSC_ELS   GENMASK(3, 2)
+#define PWM_IMX_TPM_CnSC_ELS_INVERSED  FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 1)
+#define PWM_IMX_TPM_CnSC_ELS_NORMAL    FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 2)
+
+
+#define PWM_IMX_TPM_MOD_WIDTH  16
+#define PWM_IMX_TPM_MOD_MOD    GENMASK(PWM_IMX_TPM_MOD_WIDTH - 1, 0)
+
+struct imx_tpm_pwm_chip {
+       struct pwm_chip chip;
+       struct clk *clk;
+       void __iomem *base;
+       struct mutex lock;
+       u32 user_count;
+       u32 enable_count;
+       u32 real_period;
+};
+
+struct imx_tpm_pwm_param {
+       u8 prescale;
+       u32 mod;
+       u32 val;
+};
+
+static inline struct imx_tpm_pwm_chip *
+to_imx_tpm_pwm_chip(struct pwm_chip *chip)
+{
+       return container_of(chip, struct imx_tpm_pwm_chip, chip);
+}
+
+/*
+ * This function determines for a given pwm_state *state that a consumer
+ * might request the pwm_state *real_state that eventually is implemented
+ * by the hardware and the necessary register values (in *p) to achieve
+ * this.
+ */
+static int pwm_imx_tpm_round_state(struct pwm_chip *chip,
+                                  struct imx_tpm_pwm_param *p,
+                                  struct pwm_state *real_state,
+                                  struct pwm_state *state)
+{
+       struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+       u32 rate, prescale, period_count, clock_unit;
+       u64 tmp;
+
+       rate = clk_get_rate(tpm->clk);
+       tmp = (u64)state->period * rate;
+       clock_unit = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
+       if (clock_unit <= PWM_IMX_TPM_MOD_MOD)
+               prescale = 0;
+       else
+               prescale = ilog2(clock_unit) + 1 - PWM_IMX_TPM_MOD_WIDTH;
+
+       if ((!FIELD_FIT(PWM_IMX_TPM_SC_PS, prescale)))
+               return -ERANGE;
+       p->prescale = prescale;
+
+       period_count = (clock_unit + ((1 << prescale) >> 1)) >> prescale;
+       p->mod = period_count;
+
+       /* calculate real period HW can support */
+       tmp = (u64)period_count << prescale;
+       tmp *= NSEC_PER_SEC;
+       real_state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+       /*
+        * if eventually the PWM output is inactive, either
+        * duty cycle is 0 or status is disabled, need to
+        * make sure the output pin is inactive.
+        */
+       if (!state->enabled)
+               real_state->duty_cycle = 0;
+       else
+               real_state->duty_cycle = state->duty_cycle;
+
+       tmp = (u64)p->mod * real_state->duty_cycle;
+       p->val = DIV_ROUND_CLOSEST_ULL(tmp, real_state->period);
+
+       real_state->polarity = state->polarity;
+       real_state->enabled = state->enabled;
+
+       return 0;
+}
+
+static void pwm_imx_tpm_get_state(struct pwm_chip *chip,
+                                 struct pwm_device *pwm,
+                                 struct pwm_state *state)
+{
+       struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+       u32 rate, val, prescale;
+       u64 tmp;
+
+       /* get period */
+       state->period = tpm->real_period;
+
+       /* get duty cycle */
+       rate = clk_get_rate(tpm->clk);
+       val = readl(tpm->base + PWM_IMX_TPM_SC);
+       prescale = FIELD_GET(PWM_IMX_TPM_SC_PS, val);
+       tmp = readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm));
+       tmp = (tmp << prescale) * NSEC_PER_SEC;
+       state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+       /* get polarity */
+       val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm));
+       if ((val & PWM_IMX_TPM_CnSC_ELS) == PWM_IMX_TPM_CnSC_ELS_INVERSED)
+               state->polarity = PWM_POLARITY_INVERSED;
+       else
+               /*
+                * Assume reserved values (2b00 and 2b11) to yield
+                * normal polarity.
+                */
+               state->polarity = PWM_POLARITY_NORMAL;
+
+       /* get channel status */
+       state->enabled = FIELD_GET(PWM_IMX_TPM_CnSC_ELS, val) ? true : false;
+}
+
+/* this function is supposed to be called with mutex hold */
+static int pwm_imx_tpm_apply_hw(struct pwm_chip *chip,
+                               struct imx_tpm_pwm_param *p,
+                               struct pwm_state *state,
+                               struct pwm_device *pwm)
+{
+       struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+       bool period_update = false;
+       bool duty_update = false;
+       u32 val, cmod, cur_prescale;
+       unsigned long timeout;
+       struct pwm_state c;
+
+       if (state->period != tpm->real_period) {
+               /*
+                * TPM counter is shared by multiple channels, so
+                * prescale and period can NOT be modified when
+                * there are multiple channels in use with different
+                * period settings.
+                */
+               if (tpm->user_count > 1)
+                       return -EBUSY;
+
+               val = readl(tpm->base + PWM_IMX_TPM_SC);
+               cmod = FIELD_GET(PWM_IMX_TPM_SC_CMOD, val);
+               cur_prescale = FIELD_GET(PWM_IMX_TPM_SC_PS, val);
+               if (cmod && cur_prescale != p->prescale)
+                       return -EBUSY;
+
+               /* set TPM counter prescale */
+               val &= ~PWM_IMX_TPM_SC_PS;
+               val |= FIELD_PREP(PWM_IMX_TPM_SC_PS, p->prescale);
+               writel(val, tpm->base + PWM_IMX_TPM_SC);
+
+               /*
+                * set period count:
+                * if the PWM is disabled (CMOD[1:0] = 2b00), then MOD register
+                * is updated when MOD register is written.
+                *
+                * if the PWM is enabled (CMOD[1:0] â‰  2b00), the period length
+                * is latched into hardware when the next period starts.
+                */
+               writel(p->mod, tpm->base + PWM_IMX_TPM_MOD);
+               tpm->real_period = state->period;
+               period_update = true;
+       }
+
+       pwm_imx_tpm_get_state(chip, pwm, &c);
+
+       /* polarity is NOT allowed to be changed if PWM is active */
+       if (c.enabled && c.polarity != state->polarity)
+               return -EBUSY;
+
+       if (state->duty_cycle != c.duty_cycle) {
+               /*
+                * set channel value:
+                * if the PWM is disabled (CMOD[1:0] = 2b00), then CnV register
+                * is updated when CnV register is written.
+                *
+                * if the PWM is enabled (CMOD[1:0] â‰  2b00), the duty length
+                * is latched into hardware when the next period starts.
+                */
+               writel(p->val, tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm));
+               duty_update = true;
+       }
+
+       /* make sure MOD & CnV registers are updated */
+       if (period_update || duty_update) {
+               timeout = jiffies + msecs_to_jiffies(tpm->real_period /
+                                                    NSEC_PER_MSEC + 1);
+               while (readl(tpm->base + PWM_IMX_TPM_MOD) != p->mod
+                      || readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm))
+                      != p->val) {
+                       if (time_after(jiffies, timeout))
+                               return -ETIME;
+                       cpu_relax();
+               }
+       }
+
+       /*
+        * polarity settings will enabled/disable output status
+        * immediately, so if the channel is disabled, need to
+        * make sure MSA/MSB/ELS are set to 0 which means channel
+        * disabled.
+        */
+       val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm));
+       val &= ~(PWM_IMX_TPM_CnSC_ELS | PWM_IMX_TPM_CnSC_MSA |
+                PWM_IMX_TPM_CnSC_MSB);
+       if (state->enabled) {
+               /*
+                * set polarity (for edge-aligned PWM modes)
+                *
+                * ELS[1:0] = 2b10 yields normal polarity behaviour,
+                * ELS[1:0] = 2b01 yields inversed polarity.
+                * The other values are reserved.
+                */
+               val |= PWM_IMX_TPM_CnSC_MSB;
+               val |= (state->polarity == PWM_POLARITY_NORMAL) ?
+                       PWM_IMX_TPM_CnSC_ELS_NORMAL :
+                       PWM_IMX_TPM_CnSC_ELS_INVERSED;
+       }
+       writel(val, tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm));
+
+       /* control the counter status */
+       if (state->enabled != c.enabled) {
+               val = readl(tpm->base + PWM_IMX_TPM_SC);
+               if (state->enabled) {
+                       if (++tpm->enable_count == 1)
+                               val |= PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK;
+               } else {
+                       if (--tpm->enable_count == 0)
+                               val &= ~PWM_IMX_TPM_SC_CMOD;
+               }
+               writel(val, tpm->base + PWM_IMX_TPM_SC);
+       }
+
+       return 0;
+}
+
+static int pwm_imx_tpm_apply(struct pwm_chip *chip,
+                            struct pwm_device *pwm,
+                            struct pwm_state *state)
+{
+       struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+       struct imx_tpm_pwm_param param;
+       struct pwm_state real_state;
+       int ret;
+
+       ret = pwm_imx_tpm_round_state(chip, &param, &real_state, state);
+       if (ret)
+               return ret;
+
+       mutex_lock(&tpm->lock);
+       ret = pwm_imx_tpm_apply_hw(chip, &param, &real_state, pwm);
+       mutex_unlock(&tpm->lock);
+
+       return ret;
+}
+
+static int pwm_imx_tpm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+
+       mutex_lock(&tpm->lock);
+       tpm->user_count++;
+       mutex_unlock(&tpm->lock);
+
+       return 0;
+}
+
+static void pwm_imx_tpm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+
+       mutex_lock(&tpm->lock);
+       tpm->user_count--;
+       mutex_unlock(&tpm->lock);
+}
+
+static const struct pwm_ops imx_tpm_pwm_ops = {
+       .request = pwm_imx_tpm_request,
+       .free = pwm_imx_tpm_free,
+       .get_state = pwm_imx_tpm_get_state,
+       .apply = pwm_imx_tpm_apply,
+       .owner = THIS_MODULE,
+};
+
+static int pwm_imx_tpm_probe(struct platform_device *pdev)
+{
+       struct imx_tpm_pwm_chip *tpm;
+       int ret;
+       u32 val;
+
+       tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
+       if (!tpm)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, tpm);
+
+       tpm->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(tpm->base))
+               return PTR_ERR(tpm->base);
+
+       tpm->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(tpm->clk)) {
+               ret = PTR_ERR(tpm->clk);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(&pdev->dev,
+                               "failed to get PWM clock: %d\n", ret);
+               return ret;
+       }
+
+       ret = clk_prepare_enable(tpm->clk);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "failed to prepare or enable clock: %d\n", ret);
+               return ret;
+       }
+
+       tpm->chip.dev = &pdev->dev;
+       tpm->chip.ops = &imx_tpm_pwm_ops;
+       tpm->chip.base = -1;
+       tpm->chip.of_xlate = of_pwm_xlate_with_flags;
+       tpm->chip.of_pwm_n_cells = 3;
+
+       /* get number of channels */
+       val = readl(tpm->base + PWM_IMX_TPM_PARAM);
+       tpm->chip.npwm = FIELD_GET(PWM_IMX_TPM_PARAM_CHAN, val);
+
+       mutex_init(&tpm->lock);
+
+       ret = pwmchip_add(&tpm->chip);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
+               clk_disable_unprepare(tpm->clk);
+       }
+
+       return ret;
+}
+
+static int pwm_imx_tpm_remove(struct platform_device *pdev)
+{
+       struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
+       int ret = pwmchip_remove(&tpm->chip);
+
+       clk_disable_unprepare(tpm->clk);
+
+       return ret;
+}
+
+static int __maybe_unused pwm_imx_tpm_suspend(struct device *dev)
+{
+       struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+
+       if (tpm->enable_count > 0)
+               return -EBUSY;
+
+       clk_disable_unprepare(tpm->clk);
+
+       return 0;
+}
+
+static int __maybe_unused pwm_imx_tpm_resume(struct device *dev)
+{
+       struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+       int ret = 0;
+
+       ret = clk_prepare_enable(tpm->clk);
+       if (ret)
+               dev_err(dev,
+                       "failed to prepare or enable clock: %d\n",
+                       ret);
+
+       return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
+                        pwm_imx_tpm_suspend, pwm_imx_tpm_resume);
+
+static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
+       { .compatible = "fsl,imx7ulp-pwm", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
+
+static struct platform_driver imx_tpm_pwm_driver = {
+       .driver = {
+               .name = "imx7ulp-tpm-pwm",
+               .of_match_table = imx_tpm_pwm_dt_ids,
+               .pm = &imx_tpm_pwm_pm,
+       },
+       .probe  = pwm_imx_tpm_probe,
+       .remove = pwm_imx_tpm_remove,
+};
+module_platform_driver(imx_tpm_pwm_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("i.MX TPM PWM Driver");
+MODULE_LICENSE("GPL v2");
index 806130654211f1517c5b91915f04ad8f41122801..434a351fb626be9a790f1a8cafa1e77595bd9dfd 100644 (file)
@@ -291,7 +291,6 @@ MODULE_DEVICE_TABLE(of, pwm_imx27_dt_ids);
 static int pwm_imx27_probe(struct platform_device *pdev)
 {
        struct pwm_imx27_chip *imx;
-       struct resource *r;
 
        imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
        if (imx == NULL)
@@ -326,8 +325,7 @@ static int pwm_imx27_probe(struct platform_device *pdev)
        imx->chip.of_xlate = of_pwm_xlate_with_flags;
        imx->chip.of_pwm_n_cells = 3;
 
-       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
+       imx->mmio_base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(imx->mmio_base))
                return PTR_ERR(imx->mmio_base);
 
index 4ae5d774443ec09eb6f31f4fd2a290ed268493a2..fb5a369b1a8dd2d03a10bdd5626114fe5562ece3 100644 (file)
@@ -111,6 +111,10 @@ struct meson_pwm {
        const struct meson_pwm_data *data;
        void __iomem *base;
        u8 inverter_mask;
+       /*
+        * Protects register (write) access to the REG_MISC_AB register
+        * that is shared between the two PWMs.
+        */
        spinlock_t lock;
 };
 
@@ -184,7 +188,7 @@ static int meson_pwm_calc(struct meson_pwm *meson,
        do_div(fin_ps, fin_freq);
 
        /* Calc pre_div with the period */
-       for (pre_div = 0; pre_div < MISC_CLK_DIV_MASK; pre_div++) {
+       for (pre_div = 0; pre_div <= MISC_CLK_DIV_MASK; pre_div++) {
                cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000,
                                            fin_ps * (pre_div + 1));
                dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n",
@@ -193,7 +197,7 @@ static int meson_pwm_calc(struct meson_pwm *meson,
                        break;
        }
 
-       if (pre_div == MISC_CLK_DIV_MASK) {
+       if (pre_div > MISC_CLK_DIV_MASK) {
                dev_err(meson->chip.dev, "unable to get period pre_div\n");
                return -EINVAL;
        }
@@ -235,6 +239,7 @@ static void meson_pwm_enable(struct meson_pwm *meson,
 {
        u32 value, clk_shift, clk_enable, enable;
        unsigned int offset;
+       unsigned long flags;
 
        switch (id) {
        case 0:
@@ -255,6 +260,8 @@ static void meson_pwm_enable(struct meson_pwm *meson,
                return;
        }
 
+       spin_lock_irqsave(&meson->lock, flags);
+
        value = readl(meson->base + REG_MISC_AB);
        value &= ~(MISC_CLK_DIV_MASK << clk_shift);
        value |= channel->pre_div << clk_shift;
@@ -267,11 +274,14 @@ static void meson_pwm_enable(struct meson_pwm *meson,
        value = readl(meson->base + REG_MISC_AB);
        value |= enable;
        writel(value, meson->base + REG_MISC_AB);
+
+       spin_unlock_irqrestore(&meson->lock, flags);
 }
 
 static void meson_pwm_disable(struct meson_pwm *meson, unsigned int id)
 {
        u32 value, enable;
+       unsigned long flags;
 
        switch (id) {
        case 0:
@@ -286,9 +296,13 @@ static void meson_pwm_disable(struct meson_pwm *meson, unsigned int id)
                return;
        }
 
+       spin_lock_irqsave(&meson->lock, flags);
+
        value = readl(meson->base + REG_MISC_AB);
        value &= ~enable;
        writel(value, meson->base + REG_MISC_AB);
+
+       spin_unlock_irqrestore(&meson->lock, flags);
 }
 
 static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -296,29 +310,21 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 {
        struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
        struct meson_pwm *meson = to_meson_pwm(chip);
-       unsigned long flags;
        int err = 0;
 
        if (!state)
                return -EINVAL;
 
-       spin_lock_irqsave(&meson->lock, flags);
-
        if (!state->enabled) {
                meson_pwm_disable(meson, pwm->hwpwm);
                channel->state.enabled = false;
 
-               goto unlock;
+               return 0;
        }
 
        if (state->period != channel->state.period ||
            state->duty_cycle != channel->state.duty_cycle ||
            state->polarity != channel->state.polarity) {
-               if (channel->state.enabled) {
-                       meson_pwm_disable(meson, pwm->hwpwm);
-                       channel->state.enabled = false;
-               }
-
                if (state->polarity != channel->state.polarity) {
                        if (state->polarity == PWM_POLARITY_NORMAL)
                                meson->inverter_mask |= BIT(pwm->hwpwm);
@@ -329,7 +335,7 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
                err = meson_pwm_calc(meson, channel, pwm->hwpwm,
                                     state->duty_cycle, state->period);
                if (err < 0)
-                       goto unlock;
+                       return err;
 
                channel->state.polarity = state->polarity;
                channel->state.period = state->period;
@@ -341,9 +347,7 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
                channel->state.enabled = true;
        }
 
-unlock:
-       spin_unlock_irqrestore(&meson->lock, flags);
-       return err;
+       return 0;
 }
 
 static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -429,6 +433,24 @@ static const struct meson_pwm_data pwm_axg_ao_data = {
        .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_names),
 };
 
+static const char * const pwm_g12a_ao_cd_parent_names[] = {
+       "aoclk81", "xtal",
+};
+
+static const struct meson_pwm_data pwm_g12a_ao_cd_data = {
+       .parent_names = pwm_g12a_ao_cd_parent_names,
+       .num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_names),
+};
+
+static const char * const pwm_g12a_ee_parent_names[] = {
+       "xtal", "hdmi_pll", "fclk_div4", "fclk_div3"
+};
+
+static const struct meson_pwm_data pwm_g12a_ee_data = {
+       .parent_names = pwm_g12a_ee_parent_names,
+       .num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_names),
+};
+
 static const struct of_device_id meson_pwm_matches[] = {
        {
                .compatible = "amlogic,meson8b-pwm",
@@ -450,6 +472,18 @@ static const struct of_device_id meson_pwm_matches[] = {
                .compatible = "amlogic,meson-axg-ao-pwm",
                .data = &pwm_axg_ao_data
        },
+       {
+               .compatible = "amlogic,meson-g12a-ee-pwm",
+               .data = &pwm_g12a_ee_data
+       },
+       {
+               .compatible = "amlogic,meson-g12a-ao-pwm-ab",
+               .data = &pwm_axg_ao_data
+       },
+       {
+               .compatible = "amlogic,meson-g12a-ao-pwm-cd",
+               .data = &pwm_g12a_ao_cd_data
+       },
        {},
 };
 MODULE_DEVICE_TABLE(of, meson_pwm_matches);
index a7eaf962a95b15725e43426edca3bd0f4d28acf6..567f5e2771c47288488063f49363fd3e0b4eaab7 100644 (file)
@@ -176,7 +176,6 @@ static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset)
        pm_runtime_put(pca->chip.dev);
        mutex_lock(&pca->lock);
        pwm = &pca->chip.pwms[offset];
-       pwm_set_chip_data(pwm, NULL);
        mutex_unlock(&pca->lock);
 }
 
index 062f2cfc45ec69545d3150c212d346597d76335e..6674e1e80175f928784d1f3bbbabf89751f51ccb 100644 (file)
@@ -226,7 +226,7 @@ static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm)
                return -EINVAL;
        }
 
-       our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL);
+       our_chan = kzalloc(sizeof(*our_chan), GFP_KERNEL);
        if (!our_chan)
                return -ENOMEM;
 
@@ -237,8 +237,7 @@ static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm)
 
 static void pwm_samsung_free(struct pwm_chip *chip, struct pwm_device *pwm)
 {
-       devm_kfree(chip->dev, pwm_get_chip_data(pwm));
-       pwm_set_chip_data(pwm, NULL);
+       kfree(pwm_get_chip_data(pwm));
 }
 
 static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm)
index f7b8a86fa5c5e9570a616ccbcdb61e7d427b8ae3..ad4a40c0f27cf45271e57aee9e8ee4e168118695 100644 (file)
@@ -382,6 +382,8 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
        }
 
        /* Update shadow register first before modifying active register */
+       ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
+                     AQSFRC_RLDCSF_ZRO);
        ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
        /*
         * Changes to immediate action on Action Qualifier. This puts
index ceb233dd604840bd2370dbe3c0446a751f22fe6d..719f8fada0a73da85f5f15e8aaadb0f7f08110bd 100644 (file)
@@ -398,7 +398,7 @@ void pwmchip_sysfs_export(struct pwm_chip *chip)
 
        /*
         * If device_create() fails the pwm_chip is still usable by
-        * the kernel its just not exported.
+        * the kernel it's just not exported.
         */
        parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip,
                               "pwmchip%d", chip->base);
@@ -409,19 +409,6 @@ void pwmchip_sysfs_export(struct pwm_chip *chip)
 }
 
 void pwmchip_sysfs_unexport(struct pwm_chip *chip)
-{
-       struct device *parent;
-
-       parent = class_find_device(&pwm_class, NULL, chip,
-                                  pwmchip_sysfs_match);
-       if (parent) {
-               /* for class_find_device() */
-               put_device(parent);
-               device_unregister(parent);
-       }
-}
-
-void pwmchip_sysfs_unexport_children(struct pwm_chip *chip)
 {
        struct device *parent;
        unsigned int i;
@@ -439,6 +426,7 @@ void pwmchip_sysfs_unexport_children(struct pwm_chip *chip)
        }
 
        put_device(parent);
+       device_unregister(parent);
 }
 
 static int __init pwm_sysfs_init(void)
index b628abfffacc26a253214352b7de7b1580e4dfe6..eaa5c6e3fc9fc4465a277767b043612015090421 100644 (file)
@@ -596,7 +596,6 @@ static inline void pwm_remove_table(struct pwm_lookup *table, size_t num)
 #ifdef CONFIG_PWM_SYSFS
 void pwmchip_sysfs_export(struct pwm_chip *chip);
 void pwmchip_sysfs_unexport(struct pwm_chip *chip);
-void pwmchip_sysfs_unexport_children(struct pwm_chip *chip);
 #else
 static inline void pwmchip_sysfs_export(struct pwm_chip *chip)
 {
@@ -605,10 +604,6 @@ static inline void pwmchip_sysfs_export(struct pwm_chip *chip)
 static inline void pwmchip_sysfs_unexport(struct pwm_chip *chip)
 {
 }
-
-static inline void pwmchip_sysfs_unexport_children(struct pwm_chip *chip)
-{
-}
 #endif /* CONFIG_PWM_SYSFS */
 
 #endif /* __LINUX_PWM_H */