]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
Merge branch 'for-4.8/regulator' into for-next
authorThierry Reding <thierry.reding@gmail.com>
Mon, 25 Jul 2016 14:23:40 +0000 (16:23 +0200)
committerThierry Reding <thierry.reding@gmail.com>
Mon, 25 Jul 2016 14:23:40 +0000 (16:23 +0200)
29 files changed:
Documentation/ABI/testing/sysfs-class-pwm
Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt
Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt
Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt
Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt [new file with mode: 0644]
drivers/platform/chrome/cros_ec_proto.c
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/core.c
drivers/pwm/pwm-atmel-hlcdc.c
drivers/pwm/pwm-atmel.c
drivers/pwm/pwm-bcm-iproc.c [new file with mode: 0644]
drivers/pwm/pwm-cros-ec.c [new file with mode: 0644]
drivers/pwm/pwm-lpc32xx.c
drivers/pwm/pwm-lpss-pci.c
drivers/pwm/pwm-lpss.c
drivers/pwm/pwm-rockchip.c
drivers/pwm/pwm-stmpe.c [new file with mode: 0644]
drivers/pwm/pwm-tegra.c
drivers/pwm/pwm-tiecap.c
drivers/pwm/pwm-tiehrpwm.c
drivers/pwm/pwm-tipwmss.c
drivers/pwm/pwm-tipwmss.h [deleted file]
drivers/pwm/sysfs.c
include/linux/mfd/cros_ec.h
include/linux/mfd/cros_ec_commands.h
include/linux/pwm.h

index c479d77b67c540ab6c643942d992d37d9043d1c8..c20e61354561db0b1023a4a15b4499de06958029 100644 (file)
@@ -77,3 +77,12 @@ Description:
                Enable/disable the PWM signal.
                0 is disabled
                1 is enabled
+
+What:          /sys/class/pwm/pwmchipN/pwmX/capture
+Date:          June 2016
+KernelVersion: 4.8
+Contact:       Lee Jones <lee.jones@linaro.org>
+Description:
+               Capture information about a PWM signal. The output format is a
+               pair unsigned integers (period and duty cycle), separated by a
+               single space.
diff --git a/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt b/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt
new file mode 100644 (file)
index 0000000..21f75bb
--- /dev/null
@@ -0,0 +1,21 @@
+Broadcom iProc PWM controller device tree bindings
+
+This controller has 4 channels.
+
+Required Properties :
+- compatible: must be "brcm,iproc-pwm"
+- reg: physical base address and length of the controller's registers
+- clocks: phandle + clock specifier pair for the external clock
+- #pwm-cells: Should be 3. See pwm.txt in this directory for a
+  description of the cells format.
+
+Refer to clocks/clock-bindings.txt for generic clock consumer properties.
+
+Example:
+
+pwm: pwm@18031000 {
+       compatible = "brcm,iproc-pwm";
+       reg = <0x18031000 0x28>;
+       clocks = <&osc>;
+       #pwm-cells = <3>;
+};
diff --git a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt
new file mode 100644 (file)
index 0000000..472bd46
--- /dev/null
@@ -0,0 +1,23 @@
+* PWM controlled by ChromeOS EC
+
+Google's ChromeOS EC PWM is a simple PWM attached to the Embedded Controller
+(EC) and controlled via a host-command interface.
+
+An EC PWM node should be only found as a sub-node of the EC node (see
+Documentation/devicetree/bindings/mfd/cros-ec.txt).
+
+Required properties:
+- compatible: Must contain "google,cros-ec-pwm"
+- #pwm-cells: Should be 1. The cell specifies the PWM index.
+
+Example:
+       cros-ec@0 {
+               compatible = "google,cros-ec-spi";
+
+               ...
+
+               cros_ec_pwm: ec-pwm {
+                       compatible = "google,cros-ec-pwm";
+                       #pwm-cells = <1>;
+               };
+       };
index c52f03b5032f8fab8616d91e7a705a06318331ca..b4e73778dda3a2e211ec210869d4d78807066122 100644 (file)
@@ -1,10 +1,14 @@
 Tegra SoC PWFM controller
 
 Required properties:
-- compatible: For Tegra20, must contain "nvidia,tegra20-pwm".  For Tegra30,
-  must contain "nvidia,tegra30-pwm".  Otherwise, must contain
-  "nvidia,<chip>-pwm", plus one of the above, where <chip> is tegra114,
-  tegra124, tegra132, or tegra210.
+- compatible: Must be:
+  - "nvidia,tegra20-pwm": for Tegra20
+  - "nvidia,tegra30-pwm", "nvidia,tegra20-pwm": for Tegra30
+  - "nvidia,tegra114-pwm", "nvidia,tegra20-pwm": for Tegra114
+  - "nvidia,tegra124-pwm", "nvidia,tegra20-pwm": for Tegra124
+  - "nvidia,tegra132-pwm", "nvidia,tegra20-pwm": for Tegra132
+  - "nvidia,tegra210-pwm", "nvidia,tegra20-pwm": for Tegra210
+  - "nvidia,tegra186-pwm": for Tegra186
 - reg: physical base address and length of the controller's registers
 - #pwm-cells: should be 2. See pwm.txt in this directory for a description of
   the cells format.
index 9c100b2c5b23c4731cc886b363330b6c1c588f36..0f9ba509ebd6efc5f6a1d94a61363af3814f5892 100644 (file)
@@ -15,14 +15,14 @@ Optional properties:
 
 Example:
 
-ehrpwm0: ehrpwm@0 { /* EHRPWM on am33xx */
+ehrpwm0: pwm@48300200 { /* EHRPWM on am33xx */
        compatible = "ti,am33xx-ehrpwm";
        #pwm-cells = <3>;
        reg = <0x48300200 0x100>;
        ti,hwmods = "ehrpwm0";
 };
 
-ehrpwm0: ehrpwm@0 { /* EHRPWM on da850 */
+ehrpwm0: pwm@300000 { /* EHRPWM on da850 */
        compatible = "ti,da850-ehrpwm", "ti,am33xx-ehrpwm";
        #pwm-cells = <3>;
        reg = <0x300000 0x2000>;
index 0822a083fc577c4b1da34521041728b7034cb568..d6de643350223869dba6fdfa09ef668e17de784c 100644 (file)
@@ -7,6 +7,7 @@ Required Properties:
  - "renesas,pwm-r8a7790": for R-Car H2
  - "renesas,pwm-r8a7791": for R-Car M2-W
  - "renesas,pwm-r8a7794": for R-Car E2
+ - "renesas,pwm-r8a7795": for R-Car H3
 - reg: base address and length of the registers block for the PWM.
 - #pwm-cells: should be 2. See pwm.txt in this directory for a description of
   the cells format.
diff --git a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt
new file mode 100644 (file)
index 0000000..cb20964
--- /dev/null
@@ -0,0 +1,18 @@
+== ST STMPE PWM controller ==
+
+This is a PWM block embedded in the ST Microelectronics STMPE
+(ST Multi-Purpose Expander) chips. The PWM is registered as a
+subdevices of the STMPE MFD device.
+
+Required properties:
+- compatible: should be:
+  - "st,stmpe-pwm"
+- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
+  the cells format.
+
+Example:
+
+pwm0: pwm {
+       compatible = "st,stmpe-pwm";
+       #pwm-cells = <2>;
+};
index b6e161f71b26af834b2366b2d2f9f0e32f61ac0c..6c084b26665113ca0698cf24d0cc7436a974b6ec 100644 (file)
@@ -380,3 +380,20 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
        return ret;
 }
 EXPORT_SYMBOL(cros_ec_cmd_xfer);
+
+int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev,
+                           struct cros_ec_command *msg)
+{
+       int ret;
+
+       ret = cros_ec_cmd_xfer(ec_dev, msg);
+       if (ret < 0) {
+               dev_err(ec_dev->dev, "Command xfer error (err:%d)\n", ret);
+       } else if (msg->result != EC_RES_SUCCESS) {
+               dev_dbg(ec_dev->dev, "Command result (err: %d)\n", msg->result);
+               return -EPROTO;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(cros_ec_cmd_xfer_status);
index c182efc62c7bc3ffe31d81a539c88c35f55abac9..80a566a00d0437dcc8fca7715c1109e7c2d35f67 100644 (file)
@@ -74,6 +74,16 @@ config PWM_ATMEL_TCB
          To compile this driver as a module, choose M here: the module
          will be called pwm-atmel-tcb.
 
+config PWM_BCM_IPROC
+       tristate "iProc PWM support"
+       depends on ARCH_BCM_IPROC
+       help
+         Generic PWM framework driver for Broadcom iProc PWM block. This
+         block is used in Broadcom iProc SoC's.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-bcm-iproc.
+
 config PWM_BCM_KONA
        tristate "Kona PWM support"
        depends on ARCH_BCM_MOBILE
@@ -137,6 +147,13 @@ config PWM_CRC
          Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM
          control.
 
+config PWM_CROS_EC
+       tristate "ChromeOS EC PWM driver"
+       depends on MFD_CROS_EC
+       help
+         PWM driver for exposing a PWM attached to the ChromeOS Embedded
+         Controller.
+
 config PWM_EP93XX
        tristate "Cirrus Logic EP93xx PWM support"
        depends on ARCH_EP93XX
@@ -305,7 +322,7 @@ config PWM_PXA
 
 config PWM_RCAR
        tristate "Renesas R-Car PWM support"
-       depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST
+       depends on ARCH_RENESAS || COMPILE_TEST
        depends on HAS_IOMEM
        help
          This driver exposes the PWM Timer controller found in Renesas
@@ -362,6 +379,13 @@ config PWM_STI
          To compile this driver as a module, choose M here: the module
          will be called pwm-sti.
 
+config PWM_STMPE
+       bool "STMPE expander PWM export"
+       depends on MFD_STMPE
+       help
+         This enables support for the PWMs found in the STMPE I/O
+         expanders.
+
 config PWM_SUN4I
        tristate "Allwinner PWM support"
        depends on ARCH_SUNXI || COMPILE_TEST
index dd35bc121a185e3d6ea0c22df4308158272ba078..feef1dd29f731a187b43bb1af7cd74be15f8e231 100644 (file)
@@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500)        += pwm-ab8500.o
 obj-$(CONFIG_PWM_ATMEL)                += pwm-atmel.o
 obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM)      += pwm-atmel-hlcdc.o
 obj-$(CONFIG_PWM_ATMEL_TCB)    += pwm-atmel-tcb.o
+obj-$(CONFIG_PWM_BCM_IPROC)    += pwm-bcm-iproc.o
 obj-$(CONFIG_PWM_BCM_KONA)     += pwm-bcm-kona.o
 obj-$(CONFIG_PWM_BCM2835)      += pwm-bcm2835.o
 obj-$(CONFIG_PWM_BERLIN)       += pwm-berlin.o
@@ -11,6 +12,7 @@ obj-$(CONFIG_PWM_BFIN)                += pwm-bfin.o
 obj-$(CONFIG_PWM_BRCMSTB)      += pwm-brcmstb.o
 obj-$(CONFIG_PWM_CLPS711X)     += pwm-clps711x.o
 obj-$(CONFIG_PWM_CRC)          += pwm-crc.o
+obj-$(CONFIG_PWM_CROS_EC)      += pwm-cros-ec.o
 obj-$(CONFIG_PWM_EP93XX)       += pwm-ep93xx.o
 obj-$(CONFIG_PWM_FSL_FTM)      += pwm-fsl-ftm.o
 obj-$(CONFIG_PWM_IMG)          += pwm-img.o
@@ -34,6 +36,7 @@ obj-$(CONFIG_PWM_ROCKCHIP)    += pwm-rockchip.o
 obj-$(CONFIG_PWM_SAMSUNG)      += pwm-samsung.o
 obj-$(CONFIG_PWM_SPEAR)                += pwm-spear.o
 obj-$(CONFIG_PWM_STI)          += pwm-sti.o
+obj-$(CONFIG_PWM_STMPE)                += pwm-stmpe.o
 obj-$(CONFIG_PWM_SUN4I)                += pwm-sun4i.o
 obj-$(CONFIG_PWM_TEGRA)                += pwm-tegra.o
 obj-$(CONFIG_PWM_TIECAP)       += pwm-tiecap.o
index dba3843c53b8d2162d10100d2473be79014d4b4b..0dbd29e287dbbf495a28fa4b142fb407773b2f0d 100644 (file)
@@ -457,7 +457,8 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state)
 {
        int err;
 
-       if (!pwm)
+       if (!pwm || !state || !state->period ||
+           state->duty_cycle > state->period)
                return -EINVAL;
 
        if (!memcmp(state, &pwm->state, sizeof(*state)))
@@ -524,6 +525,33 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state)
 }
 EXPORT_SYMBOL_GPL(pwm_apply_state);
 
+/**
+ * pwm_capture() - capture and report a PWM signal
+ * @pwm: PWM device
+ * @result: structure to fill with capture result
+ * @timeout: time to wait, in milliseconds, before giving up on capture
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
+               unsigned long timeout)
+{
+       int err;
+
+       if (!pwm || !pwm->chip->ops)
+               return -EINVAL;
+
+       if (!pwm->chip->ops->capture)
+               return -ENOSYS;
+
+       mutex_lock(&pwm_lock);
+       err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout);
+       mutex_unlock(&pwm_lock);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(pwm_capture);
+
 /**
  * pwm_adjust_config() - adjust the current PWM config to the PWM arguments
  * @pwm: PWM device
index f994c7eaf41c6c3d67a0c688247561dadee49863..14fc011faa32fee990c7ab9253d5faabe67465d4 100644 (file)
@@ -272,7 +272,7 @@ static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
        chip->chip.of_pwm_n_cells = 3;
        chip->chip.can_sleep = 1;
 
-       ret = pwmchip_add(&chip->chip);
+       ret = pwmchip_add_with_polarity(&chip->chip, PWM_POLARITY_INVERSED);
        if (ret) {
                clk_disable_unprepare(hlcdc->periph_clk);
                return ret;
index 0e4bd4e8e5823727c03b7701ad893b4cae1f7e7e..e6b8b1b7e6ba7ddcbb2fa6142b2ba33ff0af777e 100644 (file)
@@ -64,7 +64,8 @@ struct atmel_pwm_chip {
        void __iomem *base;
 
        unsigned int updated_pwms;
-       struct mutex isr_lock; /* ISR is cleared when read, ensure only one thread does that */
+       /* ISR is cleared when read, ensure only one thread does that */
+       struct mutex isr_lock;
 
        void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
                       unsigned long dty, unsigned long prd);
@@ -271,6 +272,16 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
        mutex_unlock(&atmel_pwm->isr_lock);
        atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
 
+       /*
+        * Wait for the PWM channel disable operation to be effective before
+        * stopping the clock.
+        */
+       timeout = jiffies + 2 * HZ;
+
+       while ((atmel_pwm_readl(atmel_pwm, PWM_SR) & (1 << pwm->hwpwm)) &&
+              time_before(jiffies, timeout))
+               usleep_range(10, 100);
+
        clk_disable(atmel_pwm->clk);
 }
 
@@ -324,21 +335,14 @@ MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
 static inline const struct atmel_pwm_data *
 atmel_pwm_get_driver_data(struct platform_device *pdev)
 {
-       if (pdev->dev.of_node) {
-               const struct of_device_id *match;
-
-               match = of_match_device(atmel_pwm_dt_ids, &pdev->dev);
-               if (!match)
-                       return NULL;
+       const struct platform_device_id *id;
 
-               return match->data;
-       } else {
-               const struct platform_device_id *id;
+       if (pdev->dev.of_node)
+               return of_device_get_match_data(&pdev->dev);
 
-               id = platform_get_device_id(pdev);
+       id = platform_get_device_id(pdev);
 
-               return (struct atmel_pwm_data *)id->driver_data;
-       }
+       return (struct atmel_pwm_data *)id->driver_data;
 }
 
 static int atmel_pwm_probe(struct platform_device *pdev)
diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c
new file mode 100644 (file)
index 0000000..d961a82
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define IPROC_PWM_CTRL_OFFSET                  0x00
+#define IPROC_PWM_CTRL_TYPE_SHIFT(ch)          (15 + (ch))
+#define IPROC_PWM_CTRL_POLARITY_SHIFT(ch)      (8 + (ch))
+#define IPROC_PWM_CTRL_EN_SHIFT(ch)            (ch)
+
+#define IPROC_PWM_PERIOD_OFFSET(ch)            (0x04 + ((ch) << 3))
+#define IPROC_PWM_PERIOD_MIN                   0x02
+#define IPROC_PWM_PERIOD_MAX                   0xffff
+
+#define IPROC_PWM_DUTY_CYCLE_OFFSET(ch)                (0x08 + ((ch) << 3))
+#define IPROC_PWM_DUTY_CYCLE_MIN               0x00
+#define IPROC_PWM_DUTY_CYCLE_MAX               0xffff
+
+#define IPROC_PWM_PRESCALE_OFFSET              0x24
+#define IPROC_PWM_PRESCALE_BITS                        0x06
+#define IPROC_PWM_PRESCALE_SHIFT(ch)           ((3 - (ch)) * \
+                                                IPROC_PWM_PRESCALE_BITS)
+#define IPROC_PWM_PRESCALE_MASK(ch)            (IPROC_PWM_PRESCALE_MAX << \
+                                                IPROC_PWM_PRESCALE_SHIFT(ch))
+#define IPROC_PWM_PRESCALE_MIN                 0x00
+#define IPROC_PWM_PRESCALE_MAX                 0x3f
+
+struct iproc_pwmc {
+       struct pwm_chip chip;
+       void __iomem *base;
+       struct clk *clk;
+};
+
+static inline struct iproc_pwmc *to_iproc_pwmc(struct pwm_chip *chip)
+{
+       return container_of(chip, struct iproc_pwmc, chip);
+}
+
+static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel)
+{
+       u32 value;
+
+       value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
+       value |= 1 << IPROC_PWM_CTRL_EN_SHIFT(channel);
+       writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       /* must be a 400 ns delay between clearing and setting enable bit */
+       ndelay(400);
+}
+
+static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel)
+{
+       u32 value;
+
+       value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
+       value &= ~(1 << IPROC_PWM_CTRL_EN_SHIFT(channel));
+       writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       /* must be a 400 ns delay between clearing and setting enable bit */
+       ndelay(400);
+}
+
+static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+                                struct pwm_state *state)
+{
+       struct iproc_pwmc *ip = to_iproc_pwmc(chip);
+       u64 tmp, multi, rate;
+       u32 value, prescale;
+
+       rate = clk_get_rate(ip->clk);
+
+       value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm)))
+               state->enabled = true;
+       else
+               state->enabled = false;
+
+       if (value & BIT(IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm)))
+               state->polarity = PWM_POLARITY_NORMAL;
+       else
+               state->polarity = PWM_POLARITY_INVERSED;
+
+       value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET);
+       prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm);
+       prescale &= IPROC_PWM_PRESCALE_MAX;
+
+       multi = NSEC_PER_SEC * (prescale + 1);
+
+       value = readl(ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm));
+       tmp = (value & IPROC_PWM_PERIOD_MAX) * multi;
+       state->period = div64_u64(tmp, rate);
+
+       value = readl(ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm));
+       tmp = (value & IPROC_PWM_PERIOD_MAX) * multi;
+       state->duty_cycle = div64_u64(tmp, rate);
+}
+
+static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+                           struct pwm_state *state)
+{
+       unsigned long prescale = IPROC_PWM_PRESCALE_MIN;
+       struct iproc_pwmc *ip = to_iproc_pwmc(chip);
+       u32 value, period, duty;
+       u64 rate;
+
+       rate = clk_get_rate(ip->clk);
+
+       /*
+        * Find period count, duty count and prescale to suit duty_cycle and
+        * period. This is done according to formulas described below:
+        *
+        * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE
+        * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
+        *
+        * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
+        * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
+        */
+       while (1) {
+               u64 value, div;
+
+               div = NSEC_PER_SEC * (prescale + 1);
+               value = rate * state->period;
+               period = div64_u64(value, div);
+               value = rate * state->duty_cycle;
+               duty = div64_u64(value, div);
+
+               if (period < IPROC_PWM_PERIOD_MIN ||
+                   duty < IPROC_PWM_DUTY_CYCLE_MIN)
+                       return -EINVAL;
+
+               if (period <= IPROC_PWM_PERIOD_MAX &&
+                    duty <= IPROC_PWM_DUTY_CYCLE_MAX)
+                       break;
+
+               /* Otherwise, increase prescale and recalculate counts */
+               if (++prescale > IPROC_PWM_PRESCALE_MAX)
+                       return -EINVAL;
+       }
+
+       iproc_pwmc_disable(ip, pwm->hwpwm);
+
+       /* Set prescale */
+       value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET);
+       value &= ~IPROC_PWM_PRESCALE_MASK(pwm->hwpwm);
+       value |= prescale << IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm);
+       writel(value, ip->base + IPROC_PWM_PRESCALE_OFFSET);
+
+       /* set period and duty cycle */
+       writel(period, ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm));
+       writel(duty, ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm));
+
+       /* set polarity */
+       value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       if (state->polarity == PWM_POLARITY_NORMAL)
+               value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm);
+       else
+               value &= ~(1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm));
+
+       writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       if (state->enabled)
+               iproc_pwmc_enable(ip, pwm->hwpwm);
+
+       return 0;
+}
+
+static const struct pwm_ops iproc_pwm_ops = {
+       .apply = iproc_pwmc_apply,
+       .get_state = iproc_pwmc_get_state,
+};
+
+static int iproc_pwmc_probe(struct platform_device *pdev)
+{
+       struct iproc_pwmc *ip;
+       struct resource *res;
+       unsigned int i;
+       u32 value;
+       int ret;
+
+       ip = devm_kzalloc(&pdev->dev, sizeof(*ip), GFP_KERNEL);
+       if (!ip)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, ip);
+
+       ip->chip.dev = &pdev->dev;
+       ip->chip.ops = &iproc_pwm_ops;
+       ip->chip.base = -1;
+       ip->chip.npwm = 4;
+       ip->chip.of_xlate = of_pwm_xlate_with_flags;
+       ip->chip.of_pwm_n_cells = 3;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       ip->base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(ip->base))
+               return PTR_ERR(ip->base);
+
+       ip->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(ip->clk)) {
+               dev_err(&pdev->dev, "failed to get clock: %ld\n",
+                       PTR_ERR(ip->clk));
+               return PTR_ERR(ip->clk);
+       }
+
+       ret = clk_prepare_enable(ip->clk);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
+               return ret;
+       }
+
+       /* Set full drive and normal polarity for all channels */
+       value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       for (i = 0; i < ip->chip.npwm; i++) {
+               value &= ~(1 << IPROC_PWM_CTRL_TYPE_SHIFT(i));
+               value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(i);
+       }
+
+       writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
+
+       ret = pwmchip_add(&ip->chip);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
+               clk_disable_unprepare(ip->clk);
+       }
+
+       return ret;
+}
+
+static int iproc_pwmc_remove(struct platform_device *pdev)
+{
+       struct iproc_pwmc *ip = platform_get_drvdata(pdev);
+
+       clk_disable_unprepare(ip->clk);
+
+       return pwmchip_remove(&ip->chip);
+}
+
+static const struct of_device_id bcm_iproc_pwmc_dt[] = {
+       { .compatible = "brcm,iproc-pwm" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, bcm_iproc_pwmc_dt);
+
+static struct platform_driver iproc_pwmc_driver = {
+       .driver = {
+               .name = "bcm-iproc-pwm",
+               .of_match_table = bcm_iproc_pwmc_dt,
+       },
+       .probe = iproc_pwmc_probe,
+       .remove = iproc_pwmc_remove,
+};
+module_platform_driver(iproc_pwmc_driver);
+
+MODULE_AUTHOR("Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom iProc PWM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c
new file mode 100644 (file)
index 0000000..99b9acc
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2, as published by
+ * the Free Software Foundation.
+ *
+ * Expose a PWM controlled by the ChromeOS EC to the host processor.
+ */
+
+#include <linux/module.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+
+/**
+ * struct cros_ec_pwm_device - Driver data for EC PWM
+ *
+ * @dev: Device node
+ * @ec: Pointer to EC device
+ * @chip: PWM controller chip
+ */
+struct cros_ec_pwm_device {
+       struct device *dev;
+       struct cros_ec_device *ec;
+       struct pwm_chip chip;
+};
+
+static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *c)
+{
+       return container_of(c, struct cros_ec_pwm_device, chip);
+}
+
+static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
+{
+       struct {
+               struct cros_ec_command msg;
+               struct ec_params_pwm_set_duty params;
+       } buf;
+       struct ec_params_pwm_set_duty *params = &buf.params;
+       struct cros_ec_command *msg = &buf.msg;
+
+       memset(&buf, 0, sizeof(buf));
+
+       msg->version = 0;
+       msg->command = EC_CMD_PWM_SET_DUTY;
+       msg->insize = 0;
+       msg->outsize = sizeof(*params);
+
+       params->duty = duty;
+       params->pwm_type = EC_PWM_TYPE_GENERIC;
+       params->index = index;
+
+       return cros_ec_cmd_xfer_status(ec, msg);
+}
+
+static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index,
+                                 u32 *result)
+{
+       struct {
+               struct cros_ec_command msg;
+               union {
+                       struct ec_params_pwm_get_duty params;
+                       struct ec_response_pwm_get_duty resp;
+               };
+       } buf;
+       struct ec_params_pwm_get_duty *params = &buf.params;
+       struct ec_response_pwm_get_duty *resp = &buf.resp;
+       struct cros_ec_command *msg = &buf.msg;
+       int ret;
+
+       memset(&buf, 0, sizeof(buf));
+
+       msg->version = 0;
+       msg->command = EC_CMD_PWM_GET_DUTY;
+       msg->insize = sizeof(*params);
+       msg->outsize = sizeof(*resp);
+
+       params->pwm_type = EC_PWM_TYPE_GENERIC;
+       params->index = index;
+
+       ret = cros_ec_cmd_xfer_status(ec, msg);
+       if (result)
+               *result = msg->result;
+       if (ret < 0)
+               return ret;
+
+       return resp->duty;
+}
+
+static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index)
+{
+       return __cros_ec_pwm_get_duty(ec, index, NULL);
+}
+
+static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+                            struct pwm_state *state)
+{
+       struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
+       int duty_cycle;
+
+       /* The EC won't let us change the period */
+       if (state->period != EC_PWM_MAX_DUTY)
+               return -EINVAL;
+
+       /*
+        * EC doesn't separate the concept of duty cycle and enabled, but
+        * kernel does. Translate.
+        */
+       duty_cycle = state->enabled ? state->duty_cycle : 0;
+
+       return cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle);
+}
+
+static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+                                 struct pwm_state *state)
+{
+       struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
+       int ret;
+
+       ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm);
+       if (ret < 0) {
+               dev_err(chip->dev, "error getting initial duty: %d\n", ret);
+               return;
+       }
+
+       state->enabled = (ret > 0);
+       state->period = EC_PWM_MAX_DUTY;
+
+       /* Note that "disabled" and "duty cycle == 0" are treated the same */
+       state->duty_cycle = ret;
+}
+
+static struct pwm_device *
+cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
+{
+       struct pwm_device *pwm;
+
+       if (args->args[0] >= pc->npwm)
+               return ERR_PTR(-EINVAL);
+
+       pwm = pwm_request_from_chip(pc, args->args[0], NULL);
+       if (IS_ERR(pwm))
+               return pwm;
+
+       /* The EC won't let us change the period */
+       pwm->args.period = EC_PWM_MAX_DUTY;
+
+       return pwm;
+}
+
+static const struct pwm_ops cros_ec_pwm_ops = {
+       .get_state      = cros_ec_pwm_get_state,
+       .apply          = cros_ec_pwm_apply,
+       .owner          = THIS_MODULE,
+};
+
+static int cros_ec_num_pwms(struct cros_ec_device *ec)
+{
+       int i, ret;
+
+       /* The index field is only 8 bits */
+       for (i = 0; i <= U8_MAX; i++) {
+               u32 result = 0;
+
+               ret = __cros_ec_pwm_get_duty(ec, i, &result);
+               /* We want to parse EC protocol errors */
+               if (ret < 0 && !(ret == -EPROTO && result))
+                       return ret;
+
+               /*
+                * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM
+                * responses; everything else is treated as an error.
+                */
+               if (result == EC_RES_INVALID_COMMAND)
+                       return -ENODEV;
+               else if (result == EC_RES_INVALID_PARAM)
+                       return i;
+               else if (result)
+                       return -EPROTO;
+       }
+
+       return U8_MAX;
+}
+
+static int cros_ec_pwm_probe(struct platform_device *pdev)
+{
+       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+       struct device *dev = &pdev->dev;
+       struct cros_ec_pwm_device *ec_pwm;
+       struct pwm_chip *chip;
+       int ret;
+
+       if (!ec) {
+               dev_err(dev, "no parent EC device\n");
+               return -EINVAL;
+       }
+
+       ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL);
+       if (!ec_pwm)
+               return -ENOMEM;
+       chip = &ec_pwm->chip;
+       ec_pwm->ec = ec;
+
+       /* PWM chip */
+       chip->dev = dev;
+       chip->ops = &cros_ec_pwm_ops;
+       chip->of_xlate = cros_ec_pwm_xlate;
+       chip->of_pwm_n_cells = 1;
+       chip->base = -1;
+       ret = cros_ec_num_pwms(ec);
+       if (ret < 0) {
+               dev_err(dev, "Couldn't find PWMs: %d\n", ret);
+               return ret;
+       }
+       chip->npwm = ret;
+       dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);
+
+       ret = pwmchip_add(chip);
+       if (ret < 0) {
+               dev_err(dev, "cannot register PWM: %d\n", ret);
+               return ret;
+       }
+
+       platform_set_drvdata(pdev, ec_pwm);
+
+       return ret;
+}
+
+static int cros_ec_pwm_remove(struct platform_device *dev)
+{
+       struct cros_ec_pwm_device *ec_pwm = platform_get_drvdata(dev);
+       struct pwm_chip *chip = &ec_pwm->chip;
+
+       return pwmchip_remove(chip);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id cros_ec_pwm_of_match[] = {
+       { .compatible = "google,cros-ec-pwm" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
+#endif
+
+static struct platform_driver cros_ec_pwm_driver = {
+       .probe = cros_ec_pwm_probe,
+       .remove = cros_ec_pwm_remove,
+       .driver = {
+               .name = "cros-ec-pwm",
+               .of_match_table = of_match_ptr(cros_ec_pwm_of_match),
+       },
+};
+module_platform_driver(cros_ec_pwm_driver);
+
+MODULE_ALIAS("platform:cros-ec-pwm");
+MODULE_DESCRIPTION("ChromeOS EC PWM driver");
+MODULE_LICENSE("GPL v2");
index 4d470c1a406a470081961db9d452f8c31bf70055..a9b3cff96aaca1c1a4429f58be7129fa82ec8827 100644 (file)
@@ -25,6 +25,7 @@ struct lpc32xx_pwm_chip {
 };
 
 #define PWM_ENABLE     BIT(31)
+#define PWM_PIN_LEVEL  BIT(30)
 
 #define to_lpc32xx_pwm_chip(_chip) \
        container_of(_chip, struct lpc32xx_pwm_chip, chip)
@@ -103,6 +104,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
        struct lpc32xx_pwm_chip *lpc32xx;
        struct resource *res;
        int ret;
+       u32 val;
 
        lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL);
        if (!lpc32xx)
@@ -128,6 +130,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
                return ret;
        }
 
+       /* When PWM is disable, configure the output to the default value */
+       val = readl(lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2));
+       val &= ~PWM_PIN_LEVEL;
+       writel(val, lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2));
+
        platform_set_drvdata(pdev, lpc32xx);
 
        return 0;
index 7160e8ab38a4ea83f7cd01b21f58b6309b0859fc..3622f093490e73cdc1133978e9314861e76aadd5 100644 (file)
@@ -76,6 +76,7 @@ static const struct pci_device_id pwm_lpss_pci_ids[] = {
        { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info},
        { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info},
        { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info},
+       { PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_bxt_info},
        { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info},
        { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info},
        { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info},
index 295b963dbddb7bdf59b159c6de03ba701d4efc9e..72c0bce5a75cc646dc0094d77774c48cbeb5ce6a 100644 (file)
@@ -27,7 +27,6 @@
 #define PWM_SW_UPDATE                  BIT(30)
 #define PWM_BASE_UNIT_SHIFT            8
 #define PWM_ON_TIME_DIV_MASK           0x000000ff
-#define PWM_DIVISION_CORRECTION                0x2
 
 /* Size of each PWM register space if multiple */
 #define PWM_SIZE                       0x400
@@ -92,8 +91,8 @@ 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, base_unit_range;
+       unsigned long long on_time_div;
+       unsigned long c = lpwm->info->clk_rate, base_unit_range;
        unsigned long long base_unit, freq = NSEC_PER_SEC;
        u32 ctrl;
 
@@ -101,21 +100,18 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
 
        /*
         * The equation is:
-        * base_unit = ((freq / c) * base_unit_range) + correction
+        * base_unit = round(base_unit_range * freq / c)
         */
        base_unit_range = BIT(lpwm->info->base_unit_bits);
-       base_unit = freq * base_unit_range;
+       freq *= base_unit_range;
 
-       c = lpwm->info->clk_rate;
-       if (!c)
-               return -EINVAL;
-
-       do_div(base_unit, c);
-       base_unit += PWM_DIVISION_CORRECTION;
+       base_unit = DIV_ROUND_CLOSEST_ULL(freq, c);
 
        if (duty_ns <= 0)
                duty_ns = 1;
-       on_time_div = 255 - (255 * duty_ns / period_ns);
+       on_time_div = 255ULL * duty_ns;
+       do_div(on_time_div, period_ns);
+       on_time_div = 255ULL - on_time_div;
 
        pm_runtime_get_sync(chip->dev);
 
@@ -169,6 +165,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
                                     const struct pwm_lpss_boardinfo *info)
 {
        struct pwm_lpss_chip *lpwm;
+       unsigned long c;
        int ret;
 
        lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL);
@@ -180,6 +177,11 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
                return ERR_CAST(lpwm->regs);
 
        lpwm->info = info;
+
+       c = lpwm->info->clk_rate;
+       if (!c)
+               return ERR_PTR(-EINVAL);
+
        lpwm->chip.dev = dev;
        lpwm->chip.ops = &pwm_lpss_ops;
        lpwm->chip.base = -1;
index 7d9cc9049522348dd15951f927ed4649b1cdf364..ef89df1f7336c77a70c3a4de42b6d71ec96162ba 100644 (file)
@@ -47,10 +47,14 @@ struct rockchip_pwm_regs {
 struct rockchip_pwm_data {
        struct rockchip_pwm_regs regs;
        unsigned int prescaler;
+       bool supports_polarity;
        const struct pwm_ops *ops;
 
        void (*set_enable)(struct pwm_chip *chip,
-                          struct pwm_device *pwm, bool enable);
+                          struct pwm_device *pwm, bool enable,
+                          enum pwm_polarity polarity);
+       void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
+                         struct pwm_state *state);
 };
 
 static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
@@ -59,7 +63,8 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
 }
 
 static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
-                                      struct pwm_device *pwm, bool enable)
+                                      struct pwm_device *pwm, bool enable,
+                                      enum pwm_polarity polarity)
 {
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
@@ -75,15 +80,29 @@ static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
        writel_relaxed(val, pc->base + pc->data->regs.ctrl);
 }
 
+static void rockchip_pwm_get_state_v1(struct pwm_chip *chip,
+                                     struct pwm_device *pwm,
+                                     struct pwm_state *state)
+{
+       struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
+       u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
+       u32 val;
+
+       val = readl_relaxed(pc->base + pc->data->regs.ctrl);
+       if ((val & enable_conf) == enable_conf)
+               state->enabled = true;
+}
+
 static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
-                                      struct pwm_device *pwm, bool enable)
+                                      struct pwm_device *pwm, bool enable,
+                                      enum pwm_polarity polarity)
 {
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
                          PWM_CONTINUOUS;
        u32 val;
 
-       if (pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED)
+       if (polarity == PWM_POLARITY_INVERSED)
                enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
        else
                enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
@@ -98,13 +117,59 @@ static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
        writel_relaxed(val, pc->base + pc->data->regs.ctrl);
 }
 
+static void rockchip_pwm_get_state_v2(struct pwm_chip *chip,
+                                     struct pwm_device *pwm,
+                                     struct pwm_state *state)
+{
+       struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
+       u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
+                         PWM_CONTINUOUS;
+       u32 val;
+
+       val = readl_relaxed(pc->base + pc->data->regs.ctrl);
+       if ((val & enable_conf) != enable_conf)
+               return;
+
+       state->enabled = true;
+
+       if (!(val & PWM_DUTY_POSITIVE))
+               state->polarity = PWM_POLARITY_INVERSED;
+}
+
+static void rockchip_pwm_get_state(struct pwm_chip *chip,
+                                  struct pwm_device *pwm,
+                                  struct pwm_state *state)
+{
+       struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
+       unsigned long clk_rate;
+       u64 tmp;
+       int ret;
+
+       ret = clk_enable(pc->clk);
+       if (ret)
+               return;
+
+       clk_rate = clk_get_rate(pc->clk);
+
+       tmp = readl_relaxed(pc->base + pc->data->regs.period);
+       tmp *= pc->data->prescaler * NSEC_PER_SEC;
+       state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
+
+       tmp = readl_relaxed(pc->base + pc->data->regs.duty);
+       tmp *= pc->data->prescaler * NSEC_PER_SEC;
+       state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
+
+       pc->data->get_state(chip, pwm, state);
+
+       clk_disable(pc->clk);
+}
+
 static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                               int duty_ns, int period_ns)
 {
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        unsigned long period, duty;
        u64 clk_rate, div;
-       int ret;
 
        clk_rate = clk_get_rate(pc->clk);
 
@@ -114,74 +179,72 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
         * default prescaler value for all practical clock rate values.
         */
        div = clk_rate * period_ns;
-       do_div(div, pc->data->prescaler * NSEC_PER_SEC);
-       period = div;
+       period = DIV_ROUND_CLOSEST_ULL(div,
+                                      pc->data->prescaler * NSEC_PER_SEC);
 
        div = clk_rate * duty_ns;
-       do_div(div, pc->data->prescaler * NSEC_PER_SEC);
-       duty = div;
-
-       ret = clk_enable(pc->clk);
-       if (ret)
-               return ret;
+       duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
 
        writel(period, pc->base + pc->data->regs.period);
        writel(duty, pc->base + pc->data->regs.duty);
-       writel(0, pc->base + pc->data->regs.cntr);
-
-       clk_disable(pc->clk);
-
-       return 0;
-}
-
-static int rockchip_pwm_set_polarity(struct pwm_chip *chip,
-                                    struct pwm_device *pwm,
-                                    enum pwm_polarity polarity)
-{
-       /*
-        * No action needed here because pwm->polarity will be set by the core
-        * and the core will only change polarity when the PWM is not enabled.
-        * We'll handle things in set_enable().
-        */
 
        return 0;
 }
 
-static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+                             struct pwm_state *state)
 {
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
+       struct pwm_state curstate;
+       bool enabled;
        int ret;
 
+       pwm_get_state(pwm, &curstate);
+       enabled = curstate.enabled;
+
        ret = clk_enable(pc->clk);
        if (ret)
                return ret;
 
-       pc->data->set_enable(chip, pwm, true);
+       if (state->polarity != curstate.polarity && enabled) {
+               pc->data->set_enable(chip, pwm, false, state->polarity);
+               enabled = false;
+       }
 
-       return 0;
-}
+       ret = rockchip_pwm_config(chip, pwm, state->duty_cycle, state->period);
+       if (ret) {
+               if (enabled != curstate.enabled)
+                       pc->data->set_enable(chip, pwm, !enabled,
+                                            state->polarity);
 
-static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
-       struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
+               goto out;
+       }
+
+       if (state->enabled != enabled)
+               pc->data->set_enable(chip, pwm, state->enabled,
+                                    state->polarity);
 
-       pc->data->set_enable(chip, pwm, false);
+       /*
+        * Update the state with the real hardware, which can differ a bit
+        * because of period/duty_cycle approximation.
+        */
+       rockchip_pwm_get_state(chip, pwm, state);
 
+out:
        clk_disable(pc->clk);
+
+       return ret;
 }
 
 static const struct pwm_ops rockchip_pwm_ops_v1 = {
-       .config = rockchip_pwm_config,
-       .enable = rockchip_pwm_enable,
-       .disable = rockchip_pwm_disable,
+       .get_state = rockchip_pwm_get_state,
+       .apply = rockchip_pwm_apply,
        .owner = THIS_MODULE,
 };
 
 static const struct pwm_ops rockchip_pwm_ops_v2 = {
-       .config = rockchip_pwm_config,
-       .set_polarity = rockchip_pwm_set_polarity,
-       .enable = rockchip_pwm_enable,
-       .disable = rockchip_pwm_disable,
+       .get_state = rockchip_pwm_get_state,
+       .apply = rockchip_pwm_apply,
        .owner = THIS_MODULE,
 };
 
@@ -195,6 +258,7 @@ static const struct rockchip_pwm_data pwm_data_v1 = {
        .prescaler = 2,
        .ops = &rockchip_pwm_ops_v1,
        .set_enable = rockchip_pwm_set_enable_v1,
+       .get_state = rockchip_pwm_get_state_v1,
 };
 
 static const struct rockchip_pwm_data pwm_data_v2 = {
@@ -205,8 +269,10 @@ static const struct rockchip_pwm_data pwm_data_v2 = {
                .ctrl = 0x0c,
        },
        .prescaler = 1,
+       .supports_polarity = true,
        .ops = &rockchip_pwm_ops_v2,
        .set_enable = rockchip_pwm_set_enable_v2,
+       .get_state = rockchip_pwm_get_state_v2,
 };
 
 static const struct rockchip_pwm_data pwm_data_vop = {
@@ -217,8 +283,10 @@ static const struct rockchip_pwm_data pwm_data_vop = {
                .ctrl = 0x00,
        },
        .prescaler = 1,
+       .supports_polarity = true,
        .ops = &rockchip_pwm_ops_v2,
        .set_enable = rockchip_pwm_set_enable_v2,
+       .get_state = rockchip_pwm_get_state_v2,
 };
 
 static const struct of_device_id rockchip_pwm_dt_ids[] = {
@@ -253,7 +321,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
        if (IS_ERR(pc->clk))
                return PTR_ERR(pc->clk);
 
-       ret = clk_prepare(pc->clk);
+       ret = clk_prepare_enable(pc->clk);
        if (ret)
                return ret;
 
@@ -265,7 +333,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
        pc->chip.base = -1;
        pc->chip.npwm = 1;
 
-       if (pc->data->ops->set_polarity) {
+       if (pc->data->supports_polarity) {
                pc->chip.of_xlate = of_pwm_xlate_with_flags;
                pc->chip.of_pwm_n_cells = 3;
        }
@@ -276,6 +344,10 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
                dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
        }
 
+       /* Keep the PWM clk enabled if the PWM appears to be up and running. */
+       if (!pwm_is_enabled(pc->chip.pwms))
+               clk_disable(pc->clk);
+
        return ret;
 }
 
@@ -283,6 +355,20 @@ static int rockchip_pwm_remove(struct platform_device *pdev)
 {
        struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev);
 
+       /*
+        * Disable the PWM clk before unpreparing it if the PWM device is still
+        * running. This should only happen when the last PWM user left it
+        * enabled, or when nobody requested a PWM that was previously enabled
+        * by the bootloader.
+        *
+        * FIXME: Maybe the core should disable all PWM devices in
+        * pwmchip_remove(). In this case we'd only have to call
+        * clk_unprepare() after pwmchip_remove().
+        *
+        */
+       if (pwm_is_enabled(pc->chip.pwms))
+               clk_disable(pc->clk);
+
        clk_unprepare(pc->clk);
 
        return pwmchip_remove(&pc->chip);
diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c
new file mode 100644 (file)
index 0000000..e464582
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/mfd/stmpe.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+
+#define STMPE24XX_PWMCS                0x30
+#define PWMCS_EN_PWM0          BIT(0)
+#define PWMCS_EN_PWM1          BIT(1)
+#define PWMCS_EN_PWM2          BIT(2)
+#define STMPE24XX_PWMIC0       0x38
+#define STMPE24XX_PWMIC1       0x39
+#define STMPE24XX_PWMIC2       0x3a
+
+#define STMPE_PWM_24XX_PINBASE 21
+
+struct stmpe_pwm {
+       struct stmpe *stmpe;
+       struct pwm_chip chip;
+       u8 last_duty;
+};
+
+static inline struct stmpe_pwm *to_stmpe_pwm(struct pwm_chip *chip)
+{
+       return container_of(chip, struct stmpe_pwm, chip);
+}
+
+static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
+       u8 value;
+       int ret;
+
+       ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS);
+       if (ret < 0) {
+               dev_err(chip->dev, "error reading PWM#%u control\n",
+                       pwm->hwpwm);
+               return ret;
+       }
+
+       value = ret | BIT(pwm->hwpwm);
+
+       ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value);
+       if (ret) {
+               dev_err(chip->dev, "error writing PWM#%u control\n",
+                       pwm->hwpwm);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void stmpe_24xx_pwm_disable(struct pwm_chip *chip,
+                                  struct pwm_device *pwm)
+{
+       struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
+       u8 value;
+       int ret;
+
+       ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS);
+       if (ret < 0) {
+               dev_err(chip->dev, "error reading PWM#%u control\n",
+                       pwm->hwpwm);
+               return;
+       }
+
+       value = ret & ~BIT(pwm->hwpwm);
+
+       ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value);
+       if (ret) {
+               dev_err(chip->dev, "error writing PWM#%u control\n",
+                       pwm->hwpwm);
+               return;
+       }
+}
+
+/* STMPE 24xx PWM instructions */
+#define SMAX           0x007f
+#define SMIN           0x00ff
+#define GTS            0x0000
+#define LOAD           BIT(14) /* Only available on 2403 */
+#define RAMPUP         0x0000
+#define RAMPDOWN       BIT(7)
+#define PRESCALE_512   BIT(14)
+#define STEPTIME_1     BIT(8)
+#define BRANCH         (BIT(15) | BIT(13))
+
+static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                                int duty_ns, int period_ns)
+{
+       struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
+       unsigned int i, pin;
+       u16 program[3] = {
+               SMAX,
+               GTS,
+               GTS,
+       };
+       u8 offset;
+       int ret;
+
+       /* Make sure we are disabled */
+       if (pwm_is_enabled(pwm)) {
+               stmpe_24xx_pwm_disable(chip, pwm);
+       } else {
+               /* Connect the PWM to the pin */
+               pin = pwm->hwpwm;
+
+               /* On STMPE2401 and 2403 pins 21,22,23 are used */
+               if (stmpe_pwm->stmpe->partnum == STMPE2401 ||
+                   stmpe_pwm->stmpe->partnum == STMPE2403)
+                       pin += STMPE_PWM_24XX_PINBASE;
+
+               ret = stmpe_set_altfunc(stmpe_pwm->stmpe, BIT(pin),
+                                       STMPE_BLOCK_PWM);
+               if (ret) {
+                       dev_err(chip->dev, "unable to connect PWM#%u to pin\n",
+                               pwm->hwpwm);
+                       return ret;
+               }
+       }
+
+       /* STMPE24XX */
+       switch (pwm->hwpwm) {
+       case 0:
+               offset = STMPE24XX_PWMIC0;
+               break;
+
+       case 1:
+               offset = STMPE24XX_PWMIC1;
+               break;
+
+       case 2:
+               offset = STMPE24XX_PWMIC1;
+               break;
+
+       default:
+               /* Should not happen as npwm is 3 */
+               return -ENODEV;
+       }
+
+       dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n",
+               pwm->hwpwm, duty_ns, period_ns);
+
+       if (duty_ns == 0) {
+               if (stmpe_pwm->stmpe->partnum == STMPE2401)
+                       program[0] = SMAX; /* off all the time */
+
+               if (stmpe_pwm->stmpe->partnum == STMPE2403)
+                       program[0] = LOAD | 0xff; /* LOAD 0xff */
+
+               stmpe_pwm->last_duty = 0x00;
+       } else if (duty_ns == period_ns) {
+               if (stmpe_pwm->stmpe->partnum == STMPE2401)
+                       program[0] = SMIN; /* on all the time */
+
+               if (stmpe_pwm->stmpe->partnum == STMPE2403)
+                       program[0] = LOAD | 0x00; /* LOAD 0x00 */
+
+               stmpe_pwm->last_duty = 0xff;
+       } else {
+               u8 value, last = stmpe_pwm->last_duty;
+               unsigned long duty;
+
+               /*
+                * Counter goes from 0x00 to 0xff repeatedly at 32768 Hz,
+                * (means a period of 30517 ns) then this is compared to the
+                * counter from the ramp, if this is >= PWM counter the output
+                * is high. With LOAD we can define how much of the cycle it
+                * is on.
+                *
+                * Prescale = 0 -> 2 kHz -> T = 1/f = 488281.25 ns
+                */
+
+               /* Scale to 0..0xff */
+               duty = duty_ns * 256;
+               duty = DIV_ROUND_CLOSEST(duty, period_ns);
+               value = duty;
+
+               if (value == last) {
+                       /* Run the old program */
+                       if (pwm_is_enabled(pwm))
+                               stmpe_24xx_pwm_enable(chip, pwm);
+
+                       return 0;
+               } else if (stmpe_pwm->stmpe->partnum == STMPE2403) {
+                       /* STMPE2403 can simply set the right PWM value */
+                       program[0] = LOAD | value;
+                       program[1] = 0x0000;
+               } else if (stmpe_pwm->stmpe->partnum == STMPE2401) {
+                       /* STMPE2401 need a complex program */
+                       u16 incdec = 0x0000;
+
+                       if (last < value)
+                               /* Count up */
+                               incdec = RAMPUP | (value - last);
+                       else
+                               /* Count down */
+                               incdec = RAMPDOWN | (last - value);
+
+                       /* Step to desired value, smoothly */
+                       program[0] = PRESCALE_512 | STEPTIME_1 | incdec;
+
+                       /* Loop eternally to 0x00 */
+                       program[1] = BRANCH;
+               }
+
+               dev_dbg(chip->dev,
+                       "PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n",
+                       pwm->hwpwm, value, last, program[0], program[1],
+                       program[2]);
+               stmpe_pwm->last_duty = value;
+       }
+
+       /*
+        * We can write programs of up to 64 16-bit words into this channel.
+        */
+       for (i = 0; i < ARRAY_SIZE(program); i++) {
+               u8 value;
+
+               value = (program[i] >> 8) & 0xff;
+
+               ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value);
+               if (ret) {
+                       dev_err(chip->dev, "error writing register %02x: %d\n",
+                               offset, ret);
+                       return ret;
+               }
+
+               value = program[i] & 0xff;
+
+               ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value);
+               if (ret) {
+                       dev_err(chip->dev, "error writing register %02x: %d\n",
+                               offset, ret);
+                       return ret;
+               }
+       }
+
+       /* If we were enabled, re-enable this PWM */
+       if (pwm_is_enabled(pwm))
+               stmpe_24xx_pwm_enable(chip, pwm);
+
+       /* Sleep for 200ms so we're sure it will take effect */
+       msleep(200);
+
+       dev_dbg(chip->dev, "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i);
+
+       return 0;
+}
+
+static const struct pwm_ops stmpe_24xx_pwm_ops = {
+       .config = stmpe_24xx_pwm_config,
+       .enable = stmpe_24xx_pwm_enable,
+       .disable = stmpe_24xx_pwm_disable,
+       .owner = THIS_MODULE,
+};
+
+static int __init stmpe_pwm_probe(struct platform_device *pdev)
+{
+       struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+       struct stmpe_pwm *pwm;
+       int ret;
+
+       pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
+       if (!pwm)
+               return -ENOMEM;
+
+       pwm->stmpe = stmpe;
+       pwm->chip.dev = &pdev->dev;
+       pwm->chip.base = -1;
+
+       if (stmpe->partnum == STMPE2401 || stmpe->partnum == STMPE2403) {
+               pwm->chip.ops = &stmpe_24xx_pwm_ops;
+               pwm->chip.npwm = 3;
+       } else {
+               if (stmpe->partnum == STMPE1601)
+                       dev_err(&pdev->dev, "STMPE1601 not yet supported\n");
+               else
+                       dev_err(&pdev->dev, "Unknown STMPE PWM\n");
+
+               return -ENODEV;
+       }
+
+       ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM);
+       if (ret)
+               return ret;
+
+       ret = pwmchip_add(&pwm->chip);
+       if (ret) {
+               stmpe_disable(stmpe, STMPE_BLOCK_PWM);
+               return ret;
+       }
+
+       platform_set_drvdata(pdev, pwm);
+
+       return 0;
+}
+
+static struct platform_driver stmpe_pwm_driver = {
+       .driver = {
+               .name = "stmpe-pwm",
+       },
+};
+builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe);
index d4de0607b502a87c03498f7362b731dd44f06d50..e4647840cd6e3129fb94da036e30470714852f14 100644 (file)
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_device.h>
 #include <linux/pwm.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
+#include <linux/reset.h>
 
 #define PWM_ENABLE     (1 << 31)
 #define PWM_DUTY_WIDTH 8
 #define PWM_SCALE_WIDTH        13
 #define PWM_SCALE_SHIFT        0
 
-#define NUM_PWM 4
+struct tegra_pwm_soc {
+       unsigned int num_channels;
+};
 
 struct tegra_pwm_chip {
-       struct pwm_chip         chip;
-       struct device           *dev;
+       struct pwm_chip chip;
+       struct device *dev;
+
+       struct clk *clk;
+       struct reset_control*rst;
 
-       struct clk              *clk;
+       void __iomem *regs;
 
-       void __iomem            *mmio_base;
+       const struct tegra_pwm_soc *soc;
 };
 
 static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
@@ -54,20 +61,20 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
 
 static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num)
 {
-       return readl(chip->mmio_base + (num << 4));
+       return readl(chip->regs + (num << 4));
 }
 
 static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num,
                             unsigned long val)
 {
-       writel(val, chip->mmio_base + (num << 4));
+       writel(val, chip->regs + (num << 4));
 }
 
 static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                            int duty_ns, int period_ns)
 {
        struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
-       unsigned long long c;
+       unsigned long long c = duty_ns;
        unsigned long rate, hz;
        u32 val = 0;
        int err;
@@ -77,7 +84,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
         * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the
         * nearest integer during division.
         */
-       c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1) + period_ns / 2;
+       c *= (1 << PWM_DUTY_WIDTH);
+       c += period_ns / 2;
        do_div(c, period_ns);
 
        val = (u32)c << PWM_DUTY_SHIFT;
@@ -176,12 +184,13 @@ static int tegra_pwm_probe(struct platform_device *pdev)
        if (!pwm)
                return -ENOMEM;
 
+       pwm->soc = of_device_get_match_data(&pdev->dev);
        pwm->dev = &pdev->dev;
 
        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
-       if (IS_ERR(pwm->mmio_base))
-               return PTR_ERR(pwm->mmio_base);
+       pwm->regs = devm_ioremap_resource(&pdev->dev, r);
+       if (IS_ERR(pwm->regs))
+               return PTR_ERR(pwm->regs);
 
        platform_set_drvdata(pdev, pwm);
 
@@ -189,14 +198,24 @@ static int tegra_pwm_probe(struct platform_device *pdev)
        if (IS_ERR(pwm->clk))
                return PTR_ERR(pwm->clk);
 
+       pwm->rst = devm_reset_control_get(&pdev->dev, "pwm");
+       if (IS_ERR(pwm->rst)) {
+               ret = PTR_ERR(pwm->rst);
+               dev_err(&pdev->dev, "Reset control is not found: %d\n", ret);
+               return ret;
+       }
+
+       reset_control_deassert(pwm->rst);
+
        pwm->chip.dev = &pdev->dev;
        pwm->chip.ops = &tegra_pwm_ops;
        pwm->chip.base = -1;
-       pwm->chip.npwm = NUM_PWM;
+       pwm->chip.npwm = pwm->soc->num_channels;
 
        ret = pwmchip_add(&pwm->chip);
        if (ret < 0) {
                dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
+               reset_control_assert(pwm->rst);
                return ret;
        }
 
@@ -206,12 +225,17 @@ static int tegra_pwm_probe(struct platform_device *pdev)
 static int tegra_pwm_remove(struct platform_device *pdev)
 {
        struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
-       int i;
+       unsigned int i;
+       int err;
 
        if (WARN_ON(!pc))
                return -ENODEV;
 
-       for (i = 0; i < NUM_PWM; i++) {
+       err = clk_prepare_enable(pc->clk);
+       if (err < 0)
+               return err;
+
+       for (i = 0; i < pc->chip.npwm; i++) {
                struct pwm_device *pwm = &pc->chip.pwms[i];
 
                if (!pwm_is_enabled(pwm))
@@ -223,12 +247,23 @@ static int tegra_pwm_remove(struct platform_device *pdev)
                clk_disable_unprepare(pc->clk);
        }
 
+       reset_control_assert(pc->rst);
+       clk_disable_unprepare(pc->clk);
+
        return pwmchip_remove(&pc->chip);
 }
 
+static const struct tegra_pwm_soc tegra20_pwm_soc = {
+       .num_channels = 4,
+};
+
+static const struct tegra_pwm_soc tegra186_pwm_soc = {
+       .num_channels = 1,
+};
+
 static const struct of_device_id tegra_pwm_of_match[] = {
-       { .compatible = "nvidia,tegra20-pwm" },
-       { .compatible = "nvidia,tegra30-pwm" },
+       { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
+       { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
        { }
 };
 
index 616af764a27682ed0301e897ab731f5ffe8c5a13..6ec342dd3eea48ec71be46e698ab87ba9e70433f 100644 (file)
@@ -27,8 +27,6 @@
 #include <linux/pwm.h>
 #include <linux/of_device.h>
 
-#include "pwm-tipwmss.h"
-
 /* ECAP registers and bits definitions */
 #define CAP1                   0x08
 #define CAP2                   0x0C
@@ -195,6 +193,7 @@ static const struct pwm_ops ecap_pwm_ops = {
 };
 
 static const struct of_device_id ecap_of_match[] = {
+       { .compatible   = "ti,am3352-ecap" },
        { .compatible   = "ti,am33xx-ecap" },
        {},
 };
@@ -202,17 +201,24 @@ MODULE_DEVICE_TABLE(of, ecap_of_match);
 
 static int ecap_pwm_probe(struct platform_device *pdev)
 {
+       struct device_node *np = pdev->dev.of_node;
        int ret;
        struct resource *r;
        struct clk *clk;
        struct ecap_pwm_chip *pc;
-       u16 status;
 
        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
        if (!pc)
                return -ENOMEM;
 
        clk = devm_clk_get(&pdev->dev, "fck");
+       if (IS_ERR(clk)) {
+               if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
+                       dev_warn(&pdev->dev, "Binding is obsolete.\n");
+                       clk = devm_clk_get(pdev->dev.parent, "fck");
+               }
+       }
+
        if (IS_ERR(clk)) {
                dev_err(&pdev->dev, "failed to get clock\n");
                return PTR_ERR(clk);
@@ -243,40 +249,15 @@ static int ecap_pwm_probe(struct platform_device *pdev)
        }
 
        pm_runtime_enable(&pdev->dev);
-       pm_runtime_get_sync(&pdev->dev);
-
-       status = pwmss_submodule_state_change(pdev->dev.parent,
-                       PWMSS_ECAPCLK_EN);
-       if (!(status & PWMSS_ECAPCLK_EN_ACK)) {
-               dev_err(&pdev->dev, "PWMSS config space clock enable failed\n");
-               ret = -EINVAL;
-               goto pwmss_clk_failure;
-       }
-
-       pm_runtime_put_sync(&pdev->dev);
 
        platform_set_drvdata(pdev, pc);
        return 0;
-
-pwmss_clk_failure:
-       pm_runtime_put_sync(&pdev->dev);
-       pm_runtime_disable(&pdev->dev);
-       pwmchip_remove(&pc->chip);
-       return ret;
 }
 
 static int ecap_pwm_remove(struct platform_device *pdev)
 {
        struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
 
-       pm_runtime_get_sync(&pdev->dev);
-       /*
-        * Due to hardware misbehaviour, acknowledge of the stop_req
-        * is missing. Hence checking of the status bit skipped.
-        */
-       pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ);
-       pm_runtime_put_sync(&pdev->dev);
-
        pm_runtime_disable(&pdev->dev);
        return pwmchip_remove(&pc->chip);
 }
index 6a41e66015b67fa84865891cbc3e97e12de6539f..b5c6b06368934044f36be06b11051290d6bfdad7 100644 (file)
@@ -27,8 +27,6 @@
 #include <linux/pm_runtime.h>
 #include <linux/of_device.h>
 
-#include "pwm-tipwmss.h"
-
 /* EHRPWM registers and bits definitions */
 
 /* Time base module registers */
@@ -426,6 +424,7 @@ static const struct pwm_ops ehrpwm_pwm_ops = {
 };
 
 static const struct of_device_id ehrpwm_of_match[] = {
+       { .compatible   = "ti,am3352-ehrpwm" },
        { .compatible   = "ti,am33xx-ehrpwm" },
        {},
 };
@@ -433,17 +432,24 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
 
 static int ehrpwm_pwm_probe(struct platform_device *pdev)
 {
+       struct device_node *np = pdev->dev.of_node;
        int ret;
        struct resource *r;
        struct clk *clk;
        struct ehrpwm_pwm_chip *pc;
-       u16 status;
 
        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
        if (!pc)
                return -ENOMEM;
 
        clk = devm_clk_get(&pdev->dev, "fck");
+       if (IS_ERR(clk)) {
+               if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
+                       dev_warn(&pdev->dev, "Binding is obsolete.\n");
+                       clk = devm_clk_get(pdev->dev.parent, "fck");
+               }
+       }
+
        if (IS_ERR(clk)) {
                dev_err(&pdev->dev, "failed to get clock\n");
                return PTR_ERR(clk);
@@ -487,27 +493,9 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
        }
 
        pm_runtime_enable(&pdev->dev);
-       pm_runtime_get_sync(&pdev->dev);
-
-       status = pwmss_submodule_state_change(pdev->dev.parent,
-                       PWMSS_EPWMCLK_EN);
-       if (!(status & PWMSS_EPWMCLK_EN_ACK)) {
-               dev_err(&pdev->dev, "PWMSS config space clock enable failed\n");
-               ret = -EINVAL;
-               goto pwmss_clk_failure;
-       }
-
-       pm_runtime_put_sync(&pdev->dev);
 
        platform_set_drvdata(pdev, pc);
        return 0;
-
-pwmss_clk_failure:
-       pm_runtime_put_sync(&pdev->dev);
-       pm_runtime_disable(&pdev->dev);
-       pwmchip_remove(&pc->chip);
-       clk_unprepare(pc->tbclk);
-       return ret;
 }
 
 static int ehrpwm_pwm_remove(struct platform_device *pdev)
@@ -516,14 +504,6 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
 
        clk_unprepare(pc->tbclk);
 
-       pm_runtime_get_sync(&pdev->dev);
-       /*
-        * Due to hardware misbehaviour, acknowledge of the stop_req
-        * is missing. Hence checking of the status bit skipped.
-        */
-       pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ);
-       pm_runtime_put_sync(&pdev->dev);
-
        pm_runtime_put_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
        return pwmchip_remove(&pc->chip);
index 5cf65a15d02191b3b8400713133470d5bcc303f8..829f4991c96f5e8a5a6c9af4d3eef76dde2f25e2 100644 (file)
 #include <linux/pm_runtime.h>
 #include <linux/of_device.h>
 
-#include "pwm-tipwmss.h"
-
-#define PWMSS_CLKCONFIG                0x8     /* Clock gating reg */
-#define PWMSS_CLKSTATUS                0xc     /* Clock gating status reg */
-
-struct pwmss_info {
-       void __iomem    *mmio_base;
-       struct mutex    pwmss_lock;
-       u16             pwmss_clkconfig;
-};
-
-u16 pwmss_submodule_state_change(struct device *dev, int set)
-{
-       struct pwmss_info *info = dev_get_drvdata(dev);
-       u16 val;
-
-       mutex_lock(&info->pwmss_lock);
-       val = readw(info->mmio_base + PWMSS_CLKCONFIG);
-       val |= set;
-       writew(val , info->mmio_base + PWMSS_CLKCONFIG);
-       mutex_unlock(&info->pwmss_lock);
-
-       return readw(info->mmio_base + PWMSS_CLKSTATUS);
-}
-EXPORT_SYMBOL(pwmss_submodule_state_change);
-
 static const struct of_device_id pwmss_of_match[] = {
        { .compatible   = "ti,am33xx-pwmss" },
        {},
@@ -57,24 +31,10 @@ MODULE_DEVICE_TABLE(of, pwmss_of_match);
 static int pwmss_probe(struct platform_device *pdev)
 {
        int ret;
-       struct resource *r;
-       struct pwmss_info *info;
        struct device_node *node = pdev->dev.of_node;
 
-       info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
-       if (!info)
-               return -ENOMEM;
-
-       mutex_init(&info->pwmss_lock);
-
-       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       info->mmio_base = devm_ioremap_resource(&pdev->dev, r);
-       if (IS_ERR(info->mmio_base))
-               return PTR_ERR(info->mmio_base);
-
        pm_runtime_enable(&pdev->dev);
        pm_runtime_get_sync(&pdev->dev);
-       platform_set_drvdata(pdev, info);
 
        /* Populate all the child nodes here... */
        ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
@@ -86,30 +46,21 @@ static int pwmss_probe(struct platform_device *pdev)
 
 static int pwmss_remove(struct platform_device *pdev)
 {
-       struct pwmss_info *info = platform_get_drvdata(pdev);
-
        pm_runtime_put_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
-       mutex_destroy(&info->pwmss_lock);
        return 0;
 }
 
 #ifdef CONFIG_PM_SLEEP
 static int pwmss_suspend(struct device *dev)
 {
-       struct pwmss_info *info = dev_get_drvdata(dev);
-
-       info->pwmss_clkconfig = readw(info->mmio_base + PWMSS_CLKCONFIG);
        pm_runtime_put_sync(dev);
        return 0;
 }
 
 static int pwmss_resume(struct device *dev)
 {
-       struct pwmss_info *info = dev_get_drvdata(dev);
-
        pm_runtime_get_sync(dev);
-       writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG);
        return 0;
 }
 #endif
diff --git a/drivers/pwm/pwm-tipwmss.h b/drivers/pwm/pwm-tipwmss.h
deleted file mode 100644 (file)
index 10ad804..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * TI PWM Subsystem driver
- *
- * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- */
-
-#ifndef __TIPWMSS_H
-#define __TIPWMSS_H
-
-/* PWM substem clock gating */
-#define PWMSS_ECAPCLK_EN       BIT(0)
-#define PWMSS_ECAPCLK_STOP_REQ BIT(1)
-#define PWMSS_EPWMCLK_EN       BIT(8)
-#define PWMSS_EPWMCLK_STOP_REQ BIT(9)
-
-#define PWMSS_ECAPCLK_EN_ACK   BIT(0)
-#define PWMSS_EPWMCLK_EN_ACK   BIT(8)
-
-#ifdef CONFIG_PWM_TIPWMSS
-extern u16 pwmss_submodule_state_change(struct device *dev, int set);
-#else
-static inline u16 pwmss_submodule_state_change(struct device *dev, int set)
-{
-       /* return success status value */
-       return 0xFFFF;
-}
-#endif
-#endif /* __TIPWMSS_H */
index d98599249a05688137325fcf906415e14aa7aed4..18ed725594c316ab71edafdafb5e1912714de7f1 100644 (file)
@@ -152,7 +152,7 @@ static ssize_t enable_store(struct device *child,
                goto unlock;
        }
 
-       pwm_apply_state(pwm, &state);
+       ret = pwm_apply_state(pwm, &state);
 
 unlock:
        mutex_unlock(&export->lock);
@@ -208,16 +208,33 @@ static ssize_t polarity_store(struct device *child,
        return ret ? : size;
 }
 
+static ssize_t capture_show(struct device *child,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct pwm_device *pwm = child_to_pwm_device(child);
+       struct pwm_capture result;
+       int ret;
+
+       ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ));
+       if (ret)
+               return ret;
+
+       return sprintf(buf, "%u %u\n", result.period, result.duty_cycle);
+}
+
 static DEVICE_ATTR_RW(period);
 static DEVICE_ATTR_RW(duty_cycle);
 static DEVICE_ATTR_RW(enable);
 static DEVICE_ATTR_RW(polarity);
+static DEVICE_ATTR_RO(capture);
 
 static struct attribute *pwm_attrs[] = {
        &dev_attr_period.attr,
        &dev_attr_duty_cycle.attr,
        &dev_attr_enable.attr,
        &dev_attr_polarity.attr,
+       &dev_attr_capture.attr,
        NULL
 };
 ATTRIBUTE_GROUPS(pwm);
index 64184d27e3cd38cb7c2f47a3392df8518581e7b3..d641a18abacb9f1d15b65b1ff5420929a6bd9e92 100644 (file)
@@ -225,6 +225,21 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev,
 int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
                     struct cros_ec_command *msg);
 
+/**
+ * cros_ec_cmd_xfer_status - Send a command to the ChromeOS EC
+ *
+ * This function is identical to cros_ec_cmd_xfer, except it returns success
+ * status only if both the command was transmitted successfully and the EC
+ * replied with success status. It's not necessary to check msg->result when
+ * using this function.
+ *
+ * @ec_dev: EC device
+ * @msg: Message to write
+ * @return: Num. of bytes transferred on success, <0 on failure
+ */
+int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev,
+                           struct cros_ec_command *msg);
+
 /**
  * cros_ec_remove - Remove a ChromeOS EC
  *
index 13b630c10d4c07d16e958bff61f1c6f3787142dd..7e7a8d4b4551149c48007b95a2840fdaf0f0719f 100644 (file)
@@ -949,6 +949,37 @@ struct ec_params_pwm_set_fan_duty {
        uint32_t percent;
 } __packed;
 
+#define EC_CMD_PWM_SET_DUTY 0x25
+/* 16 bit duty cycle, 0xffff = 100% */
+#define EC_PWM_MAX_DUTY 0xffff
+
+enum ec_pwm_type {
+       /* All types, indexed by board-specific enum pwm_channel */
+       EC_PWM_TYPE_GENERIC = 0,
+       /* Keyboard backlight */
+       EC_PWM_TYPE_KB_LIGHT,
+       /* Display backlight */
+       EC_PWM_TYPE_DISPLAY_LIGHT,
+       EC_PWM_TYPE_COUNT,
+};
+
+struct ec_params_pwm_set_duty {
+       uint16_t duty;     /* Duty cycle, EC_PWM_MAX_DUTY = 100% */
+       uint8_t pwm_type;  /* ec_pwm_type */
+       uint8_t index;     /* Type-specific index, or 0 if unique */
+} __packed;
+
+#define EC_CMD_PWM_GET_DUTY 0x26
+
+struct ec_params_pwm_get_duty {
+       uint8_t pwm_type;  /* ec_pwm_type */
+       uint8_t index;     /* Type-specific index, or 0 if unique */
+} __packed;
+
+struct ec_response_pwm_get_duty {
+       uint16_t duty;     /* Duty cycle, EC_PWM_MAX_DUTY = 100% */
+} __packed;
+
 /*****************************************************************************/
 /*
  * Lightbar commands. This looks worse than it is. Since we only use one HOST
index fd1092729ed6d8f2bb98db8110bd20734dc18034..f1bbae014889b67a219a73c1e76156aa53229024 100644 (file)
@@ -5,7 +5,9 @@
 #include <linux/mutex.h>
 #include <linux/of.h>
 
+struct pwm_capture;
 struct seq_file;
+
 struct pwm_chip;
 
 /**
@@ -241,6 +243,7 @@ pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle,
  * @free: optional hook for freeing a PWM
  * @config: configure duty cycles and period length for this PWM
  * @set_polarity: configure the polarity of this PWM
+ * @capture: capture and report PWM signal
  * @enable: enable PWM output toggling
  * @disable: disable PWM output toggling
  * @apply: atomically apply a new PWM config. The state argument
@@ -260,6 +263,8 @@ struct pwm_ops {
                      int duty_ns, int period_ns);
        int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
                            enum pwm_polarity polarity);
+       int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
+                      struct pwm_capture *result, unsigned long timeout);
        int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);
        void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
        int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -300,6 +305,16 @@ struct pwm_chip {
        bool can_sleep;
 };
 
+/**
+ * struct pwm_capture - PWM capture data
+ * @period: period of the PWM signal (in nanoseconds)
+ * @duty_cycle: duty cycle of the PWM signal (in nanoseconds)
+ */
+struct pwm_capture {
+       unsigned int period;
+       unsigned int duty_cycle;
+};
+
 #if IS_ENABLED(CONFIG_PWM)
 /* PWM user APIs */
 struct pwm_device *pwm_request(int pwm_id, const char *label);
@@ -323,6 +338,9 @@ static inline int pwm_config(struct pwm_device *pwm, int duty_ns,
        if (!pwm)
                return -EINVAL;
 
+       if (duty_ns < 0 || period_ns < 0)
+               return -EINVAL;
+
        pwm_get_state(pwm, &state);
        if (state.duty_cycle == duty_ns && state.period == period_ns)
                return 0;
@@ -408,8 +426,9 @@ static inline void pwm_disable(struct pwm_device *pwm)
        pwm_apply_state(pwm, &state);
 }
 
-
 /* PWM provider APIs */
+int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
+               unsigned long timeout);
 int pwm_set_chip_data(struct pwm_device *pwm, void *data);
 void *pwm_get_chip_data(struct pwm_device *pwm);
 
@@ -461,6 +480,13 @@ static inline int pwm_config(struct pwm_device *pwm, int duty_ns,
        return -EINVAL;
 }
 
+static inline int pwm_capture(struct pwm_device *pwm,
+                             struct pwm_capture *result,
+                             unsigned long timeout)
+{
+       return -EINVAL;
+}
+
 static inline int pwm_set_polarity(struct pwm_device *pwm,
                                   enum pwm_polarity polarity)
 {
@@ -549,6 +575,8 @@ static inline bool pwm_can_sleep(struct pwm_device *pwm)
 
 static inline void pwm_apply_args(struct pwm_device *pwm)
 {
+       struct pwm_state state = { };
+
        /*
         * PWM users calling pwm_apply_args() expect to have a fresh config
         * where the polarity and period are set according to pwm_args info.
@@ -561,18 +589,20 @@ static inline void pwm_apply_args(struct pwm_device *pwm)
         * at startup (even if they are actually enabled), thus authorizing
         * polarity setting.
         *
-        * Instead of setting ->enabled to false, we call pwm_disable()
-        * before pwm_set_polarity() to ensure that everything is configured
-        * as expected, and the PWM is really disabled when the user request
-        * it.
+        * To fulfill this requirement, we apply a new state which disables
+        * the PWM device and set the reference period and polarity config.
         *
         * Note that PWM users requiring a smooth handover between the
         * bootloader and the kernel (like critical regulators controlled by
         * PWM devices) will have to switch to the atomic API and avoid calling
         * pwm_apply_args().
         */
-       pwm_disable(pwm);
-       pwm_set_polarity(pwm, pwm->args.polarity);
+
+       state.enabled = false;
+       state.polarity = pwm->args.polarity;
+       state.period = pwm->args.period;
+
+       pwm_apply_state(pwm, &state);
 }
 
 struct pwm_lookup {