]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Merge tag 'for-3.8-rc1' of git://gitorious.org/linux-pwm/linux-pwm
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 19 Dec 2012 16:19:07 +0000 (08:19 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 19 Dec 2012 16:19:07 +0000 (08:19 -0800)
Pull pwm changes from Thierry Reding:
 "A new driver has been added for the SPEAr platform and the
  TWL4030/6030 driver has been replaced by two drivers that control the
  regular PWMs and the PWM driven LEDs provided by the chips.

  The vt8500, tiecap, tiehrpwm, i.MX, LPC32xx and Samsung drivers have
  all been improved and the device tree bindings now support the PWM
  signal polarity."

Fix up trivial conflicts due to __devinit/exit removal.

* tag 'for-3.8-rc1' of git://gitorious.org/linux-pwm/linux-pwm: (21 commits)
  pwm: samsung: add missing s3c->pwm_id assignment
  pwm: lpc32xx: Set the chip base for dynamic allocation
  pwm: lpc32xx: Properly disable the clock on device removal
  pwm: lpc32xx: Fix the PWM polarity
  pwm: i.MX: eliminate build warning
  pwm: Export of_pwm_xlate_with_flags()
  pwm: Remove pwm-twl6030 driver
  pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of PMICs
  pwm: New driver to support PWMs on TWL4030/6030 series of PMICs
  pwm: pwm-tiehrpwm: pinctrl support
  pwm: tiehrpwm: Add device-tree binding
  pwm: pwm-tiehrpwm: Adding TBCLK gating support.
  pwm: pwm-tiecap: pinctrl support
  pwm: tiecap: Add device-tree binding
  pwm: Add TI PWM subsystem driver
  pwm: Device tree support for PWM polarity
  pwm: vt8500: Ensure PWM clock is enabled during pwm_config
  pwm: vt8500: Fix build error
  pwm: spear: Staticize spear_pwm_config()
  pwm: Add SPEAr PWM chip driver support
  ...

24 files changed:
Documentation/devicetree/bindings/pwm/pwm-tiecap.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/pwm.txt
Documentation/devicetree/bindings/pwm/spear-pwm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/ti,twl-pwmled.txt [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/vt8500-pwm.txt [new file with mode: 0644]
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/core.c
drivers/pwm/pwm-imx.c
drivers/pwm/pwm-lpc32xx.c
drivers/pwm/pwm-samsung.c
drivers/pwm/pwm-spear.c [new file with mode: 0644]
drivers/pwm/pwm-tiecap.c
drivers/pwm/pwm-tiehrpwm.c
drivers/pwm/pwm-tipwmss.c [new file with mode: 0644]
drivers/pwm/pwm-tipwmss.h [new file with mode: 0644]
drivers/pwm/pwm-twl-led.c [new file with mode: 0644]
drivers/pwm/pwm-twl.c [new file with mode: 0644]
drivers/pwm/pwm-twl6030.c [deleted file]
drivers/pwm/pwm-vt8500.c
include/linux/pwm.h

diff --git a/Documentation/devicetree/bindings/pwm/pwm-tiecap.txt b/Documentation/devicetree/bindings/pwm/pwm-tiecap.txt
new file mode 100644 (file)
index 0000000..131e8c1
--- /dev/null
@@ -0,0 +1,23 @@
+TI SOC ECAP based APWM controller
+
+Required properties:
+- compatible: Must be "ti,am33xx-ecap"
+- #pwm-cells: Should be 3. Number of cells being used to specify PWM property.
+  First cell specifies the per-chip index of the PWM to use, the second
+  cell is the period in nanoseconds and bit 0 in the third cell is used to
+  encode the polarity of PWM output. Set bit 0 of the third in PWM specifier
+  to 1 for inverse polarity & set to 0 for normal polarity.
+- reg: physical base address and size of the registers map.
+
+Optional properties:
+- ti,hwmods: Name of the hwmod associated to the ECAP:
+  "ecap<x>", <x> being the 0-based instance number from the HW spec
+
+Example:
+
+ecap0: ecap@0 {
+       compatible = "ti,am33xx-ecap";
+       #pwm-cells = <3>;
+       reg = <0x48300100 0x80>;
+       ti,hwmods = "ecap0";
+};
diff --git a/Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt b/Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt
new file mode 100644 (file)
index 0000000..4fc7079
--- /dev/null
@@ -0,0 +1,23 @@
+TI SOC EHRPWM based PWM controller
+
+Required properties:
+- compatible : Must be "ti,am33xx-ehrpwm"
+- #pwm-cells: Should be 3. Number of cells being used to specify PWM property.
+  First cell specifies the per-chip index of the PWM to use, the second
+  cell is the period in nanoseconds and bit 0 in the third cell is used to
+  encode the polarity of PWM output. Set bit 0 of the third in PWM specifier
+  to 1 for inverse polarity & set to 0 for normal polarity.
+- reg: physical base address and size of the registers map.
+
+Optional properties:
+- ti,hwmods: Name of the hwmod associated to the EHRPWM:
+  "ehrpwm<x>", <x> being the 0-based instance number from the HW spec
+
+Example:
+
+ehrpwm0: ehrpwm@0 {
+       compatible = "ti,am33xx-ehrpwm";
+       #pwm-cells = <3>;
+       reg = <0x48300200 0x100>;
+       ti,hwmods = "ehrpwm0";
+};
diff --git a/Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt b/Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt
new file mode 100644 (file)
index 0000000..f7eae77
--- /dev/null
@@ -0,0 +1,31 @@
+TI SOC based PWM Subsystem
+
+Required properties:
+- compatible: Must be "ti,am33xx-pwmss";
+- reg: physical base address and size of the registers map.
+- address-cells: Specify the number of u32 entries needed in child nodes.
+                 Should set to 1.
+- size-cells: specify number of u32 entries needed to specify child nodes size
+               in reg property. Should set to 1.
+- ranges: describes the address mapping of a memory-mapped bus. Should set to
+          physical address map of child's base address, physical address within
+          parent's address  space and length of the address map. For am33xx,
+          3 set of child register maps present, ECAP register space, EQEP
+          register space, EHRPWM register space.
+
+Also child nodes should also populated under PWMSS DT node.
+
+Example:
+pwmss0: pwmss@48300000 {
+       compatible = "ti,am33xx-pwmss";
+       reg = <0x48300000 0x10>;
+       ti,hwmods = "epwmss0";
+       #address-cells = <1>;
+       #size-cells = <1>;
+       status = "disabled";
+       ranges = <0x48300100 0x48300100 0x80   /* ECAP */
+                 0x48300180 0x48300180 0x80   /* EQEP */
+                 0x48300200 0x48300200 0x80>; /* EHRPWM */
+
+       /* child nodes go here */
+};
index 73ec962bfe8cfb4911c00b296bd741b74ccd6ffa..06e67247859aa7eef3bc253d11cf6f18ca810680 100644 (file)
@@ -37,10 +37,21 @@ device:
                pwm-names = "backlight";
        };
 
+Note that in the example above, specifying the "pwm-names" is redundant
+because the name "backlight" would be used as fallback anyway.
+
 pwm-specifier typically encodes the chip-relative PWM number and the PWM
-period in nanoseconds. Note that in the example above, specifying the
-"pwm-names" is redundant because the name "backlight" would be used as
-fallback anyway.
+period in nanoseconds.
+
+Optionally, the pwm-specifier can encode a number of flags in a third cell:
+- bit 0: PWM signal polarity (0: normal polarity, 1: inverse polarity)
+
+Example with optional PWM specifier for inverse polarity
+
+       bl: backlight {
+               pwms = <&pwm 0 5000000 1>;
+               pwm-names = "backlight";
+       };
 
 2) PWM controller nodes
 -----------------------
diff --git a/Documentation/devicetree/bindings/pwm/spear-pwm.txt b/Documentation/devicetree/bindings/pwm/spear-pwm.txt
new file mode 100644 (file)
index 0000000..3ac779d
--- /dev/null
@@ -0,0 +1,18 @@
+== ST SPEAr SoC PWM controller ==
+
+Required properties:
+- compatible: should be one of:
+  - "st,spear320-pwm"
+  - "st,spear1340-pwm"
+- reg: physical base address and length of the controller's registers
+- #pwm-cells: number of cells used to specify PWM which is fixed to 2 on
+  SPEAr. The first cell specifies the per-chip index of the PWM to use and
+  the second cell is the period in nanoseconds.
+
+Example:
+
+        pwm: pwm@a8000000 {
+            compatible ="st,spear320-pwm";
+            reg = <0xa8000000 0x1000>;
+            #pwm-cells = <2>;
+        };
diff --git a/Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt b/Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt
new file mode 100644 (file)
index 0000000..2943ee5
--- /dev/null
@@ -0,0 +1,17 @@
+Texas Instruments TWL series PWM drivers
+
+Supported PWMs:
+On TWL4030 series: PWM1 and PWM2
+On TWL6030 series: PWM0 and PWM1
+
+Required properties:
+- compatible: "ti,twl4030-pwm" or "ti,twl6030-pwm"
+- #pwm-cells: should be 2.  The first cell specifies the per-chip index
+  of the PWM to use and the second cell is the period in nanoseconds.
+
+Example:
+
+twl_pwm: pwm {
+       compatible = "ti,twl6030-pwm";
+       #pwm-cells = <2>;
+};
diff --git a/Documentation/devicetree/bindings/pwm/ti,twl-pwmled.txt b/Documentation/devicetree/bindings/pwm/ti,twl-pwmled.txt
new file mode 100644 (file)
index 0000000..cb64f3a
--- /dev/null
@@ -0,0 +1,17 @@
+Texas Instruments TWL series PWM drivers connected to LED terminals
+
+Supported PWMs:
+On TWL4030 series: PWMA and PWMB (connected to LEDA and LEDB terminals)
+On TWL6030 series: LED PWM (mainly used as charging indicator LED)
+
+Required properties:
+- compatible: "ti,twl4030-pwmled" or "ti,twl6030-pwmled"
+- #pwm-cells: should be 2.  The first cell specifies the per-chip index
+  of the PWM to use and the second cell is the period in nanoseconds.
+
+Example:
+
+twl_pwmled: pwmled {
+       compatible = "ti,twl6030-pwmled";
+       #pwm-cells = <2>;
+};
diff --git a/Documentation/devicetree/bindings/pwm/vt8500-pwm.txt b/Documentation/devicetree/bindings/pwm/vt8500-pwm.txt
new file mode 100644 (file)
index 0000000..bcc6367
--- /dev/null
@@ -0,0 +1,17 @@
+VIA/Wondermedia VT8500/WM8xxx series SoC PWM controller
+
+Required properties:
+- compatible: should be "via,vt8500-pwm"
+- reg: physical base address and length of the controller's registers
+- #pwm-cells: should be 2.  The first cell specifies the per-chip index
+  of the PWM to use and the second cell is the period in nanoseconds.
+- clocks: phandle to the PWM source clock
+
+Example:
+
+pwm1: pwm@d8220000 {
+       #pwm-cells = <2>;
+       compatible = "via,vt8500-pwm";
+       reg = <0xd8220000 0x1000>;
+       clocks = <&clkpwm>;
+};
index ed81720e7b2bf4e7cd24f19f84c382e0ba7b669b..e513cd99817030753c9a449bfbf90b8dccf89fb8 100644 (file)
@@ -112,6 +112,17 @@ config PWM_SAMSUNG
          To compile this driver as a module, choose M here: the module
          will be called pwm-samsung.
 
+config PWM_SPEAR
+       tristate "STMicroelectronics SPEAr PWM support"
+       depends on PLAT_SPEAR
+       depends on OF
+       help
+         Generic PWM framework driver for the PWM controller on ST
+         SPEAr SoCs.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-spear.
+
 config PWM_TEGRA
        tristate "NVIDIA Tegra PWM support"
        depends on ARCH_TEGRA
@@ -125,6 +136,7 @@ config PWM_TEGRA
 config  PWM_TIECAP
        tristate "ECAP PWM support"
        depends on SOC_AM33XX
+       select PWM_TIPWMSS
        help
          PWM driver support for the ECAP APWM controller found on AM33XX
          TI SOC
@@ -135,6 +147,7 @@ config  PWM_TIECAP
 config  PWM_TIEHRPWM
        tristate "EHRPWM PWM support"
        depends on SOC_AM33XX
+       select PWM_TIPWMSS
        help
          PWM driver support for the EHRPWM controller found on AM33XX
          TI SOC
@@ -142,14 +155,32 @@ config  PWM_TIEHRPWM
          To compile this driver as a module, choose M here: the module
          will be called pwm-tiehrpwm.
 
-config PWM_TWL6030
-       tristate "TWL6030 PWM support"
+config  PWM_TIPWMSS
+       bool
+       depends on SOC_AM33XX && (PWM_TIEHRPWM || PWM_TIECAP)
+       help
+         PWM Subsystem driver support for AM33xx SOC.
+
+         PWM submodules require PWM config space access from submodule
+         drivers and require common parent driver support.
+
+config PWM_TWL
+       tristate "TWL4030/6030 PWM support"
+       depends on TWL4030_CORE
+       help
+         Generic PWM framework driver for TWL4030/6030.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-twl.
+
+config PWM_TWL_LED
+       tristate "TWL4030/6030 PWM support for LED drivers"
        depends on TWL4030_CORE
        help
-         Generic PWM framework driver for TWL6030.
+         Generic PWM framework driver for TWL4030/6030 LED terminals.
 
          To compile this driver as a module, choose M here: the module
-         will be called pwm-twl6030.
+         will be called pwm-twl-led.
 
 config PWM_VT8500
        tristate "vt8500 pwm support"
index acfe4821c58b0daeac507d18a9910e7939fbcd22..62a2963cfe58acd2bdb2b21e0fa32f585bd699d8 100644 (file)
@@ -8,8 +8,11 @@ obj-$(CONFIG_PWM_MXS)          += pwm-mxs.o
 obj-$(CONFIG_PWM_PUV3)         += pwm-puv3.o
 obj-$(CONFIG_PWM_PXA)          += pwm-pxa.o
 obj-$(CONFIG_PWM_SAMSUNG)      += pwm-samsung.o
+obj-$(CONFIG_PWM_SPEAR)                += pwm-spear.o
 obj-$(CONFIG_PWM_TEGRA)                += pwm-tegra.o
 obj-$(CONFIG_PWM_TIECAP)       += pwm-tiecap.o
 obj-$(CONFIG_PWM_TIEHRPWM)     += pwm-tiehrpwm.o
-obj-$(CONFIG_PWM_TWL6030)      += pwm-twl6030.o
+obj-$(CONFIG_PWM_TIPWMSS)      += pwm-tipwmss.o
+obj-$(CONFIG_PWM_TWL)          += pwm-twl.o
+obj-$(CONFIG_PWM_TWL_LED)      += pwm-twl-led.o
 obj-$(CONFIG_PWM_VT8500)       += pwm-vt8500.o
index f5acdaa527077bc1e6e4bd8f67a52b21c5310dfb..903138b18842d5fb94cdc88316419702f7e0ae8c 100644 (file)
@@ -32,6 +32,9 @@
 
 #define MAX_PWMS 1024
 
+/* flags in the third cell of the DT PWM specifier */
+#define PWM_SPEC_POLARITY      (1 << 0)
+
 static DEFINE_MUTEX(pwm_lookup_lock);
 static LIST_HEAD(pwm_lookup_list);
 static DEFINE_MUTEX(pwm_lock);
@@ -129,6 +132,32 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
        return 0;
 }
 
+struct pwm_device *
+of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
+{
+       struct pwm_device *pwm;
+
+       if (pc->of_pwm_n_cells < 3)
+               return ERR_PTR(-EINVAL);
+
+       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;
+
+       pwm_set_period(pwm, args->args[1]);
+
+       if (args->args[2] & PWM_SPEC_POLARITY)
+               pwm_set_polarity(pwm, PWM_POLARITY_INVERSED);
+       else
+               pwm_set_polarity(pwm, PWM_POLARITY_NORMAL);
+
+       return pwm;
+}
+EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags);
+
 static struct pwm_device *
 of_pwm_simple_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
 {
index 8f26e9fcea976e259443dedfcd9997d67009f7f3..65a86bdeabedbdfdf35ff66f976cda00114a09a9 100644 (file)
@@ -235,7 +235,7 @@ static int imx_pwm_probe(struct platform_device *pdev)
 {
        const struct of_device_id *of_id =
                        of_match_device(imx_pwm_dt_ids, &pdev->dev);
-       struct imx_pwm_data *data;
+       const struct imx_pwm_data *data;
        struct imx_chip *imx;
        struct resource *r;
        int ret = 0;
index 015a82235620e648e3b5f49f974ea9f2f7c01ad7..14106440294fa94fd468563f4bed1212d6f356fe 100644 (file)
@@ -49,9 +49,24 @@ static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                c = 0; /* 0 set division by 256 */
        period_cycles = c;
 
+       /* The duty-cycle value is as follows:
+        *
+        *  DUTY-CYCLE     HIGH LEVEL
+        *      1            99.9%
+        *      25           90.0%
+        *      128          50.0%
+        *      220          10.0%
+        *      255           0.1%
+        *      0             0.0%
+        *
+        * In other words, the register value is duty-cycle % 256 with
+        * duty-cycle in the range 1-256.
+        */
        c = 256 * duty_ns;
        do_div(c, period_ns);
-       duty_cycles = c;
+       if (c > 255)
+               c = 255;
+       duty_cycles = 256 - c;
 
        writel(PWM_ENABLE | PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles),
                lpc32xx->base + (pwm->hwpwm << 2));
@@ -106,6 +121,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
        lpc32xx->chip.dev = &pdev->dev;
        lpc32xx->chip.ops = &lpc32xx_pwm_ops;
        lpc32xx->chip.npwm = 2;
+       lpc32xx->chip.base = -1;
 
        ret = pwmchip_add(&lpc32xx->chip);
        if (ret < 0) {
@@ -121,8 +137,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
 static int lpc32xx_pwm_remove(struct platform_device *pdev)
 {
        struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev);
+       unsigned int i;
+
+       for (i = 0; i < lpc32xx->chip.npwm; i++)
+               pwm_disable(&lpc32xx->chip.pwms[i]);
 
-       clk_disable(lpc32xx->clk);
        return pwmchip_remove(&lpc32xx->chip);
 }
 
index e9b15d099c03cb87d668aa500d5752d18e471061..5207e6cd8648d0d89a7ecea039b930a6ff5cf6dc 100644 (file)
@@ -222,6 +222,7 @@ static int s3c_pwm_probe(struct platform_device *pdev)
 
        /* calculate base of control bits in TCON */
        s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4;
+       s3c->pwm_id = id;
        s3c->chip.dev = &pdev->dev;
        s3c->chip.ops = &s3c_pwm_ops;
        s3c->chip.base = -1;
diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c
new file mode 100644 (file)
index 0000000..83b21d9
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * ST Microelectronics SPEAr Pulse Width Modulator driver
+ *
+ * Copyright (C) 2012 ST Microelectronics
+ * Shiraz Hashim <shiraz.hashim@st.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define NUM_PWM                4
+
+/* PWM registers and bits definitions */
+#define PWMCR                  0x00    /* Control Register */
+#define PWMCR_PWM_ENABLE       0x1
+#define PWMCR_PRESCALE_SHIFT   2
+#define PWMCR_MIN_PRESCALE     0x00
+#define PWMCR_MAX_PRESCALE     0x3FFF
+
+#define PWMDCR                 0x04    /* Duty Cycle Register */
+#define PWMDCR_MIN_DUTY                0x0001
+#define PWMDCR_MAX_DUTY                0xFFFF
+
+#define PWMPCR                 0x08    /* Period Register */
+#define PWMPCR_MIN_PERIOD      0x0001
+#define PWMPCR_MAX_PERIOD      0xFFFF
+
+/* Following only available on 13xx SoCs */
+#define PWMMCR                 0x3C    /* Master Control Register */
+#define PWMMCR_PWM_ENABLE      0x1
+
+/**
+ * struct spear_pwm_chip - struct representing pwm chip
+ *
+ * @mmio_base: base address of pwm chip
+ * @clk: pointer to clk structure of pwm chip
+ * @chip: linux pwm chip representation
+ * @dev: pointer to device structure of pwm chip
+ */
+struct spear_pwm_chip {
+       void __iomem *mmio_base;
+       struct clk *clk;
+       struct pwm_chip chip;
+       struct device *dev;
+};
+
+static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
+{
+       return container_of(chip, struct spear_pwm_chip, chip);
+}
+
+static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
+                                 unsigned long offset)
+{
+       return readl_relaxed(chip->mmio_base + (num << 4) + offset);
+}
+
+static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
+                                   unsigned int num, unsigned long offset,
+                                   unsigned long val)
+{
+       writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
+}
+
+static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                           int duty_ns, int period_ns)
+{
+       struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
+       u64 val, div, clk_rate;
+       unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
+       int ret;
+
+       /*
+        * Find pv, dc and prescale to suit duty_ns and period_ns. This is done
+        * according to formulas described below:
+        *
+        * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
+        * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
+        *
+        * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
+        * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
+        */
+       clk_rate = clk_get_rate(pc->clk);
+       while (1) {
+               div = 1000000000;
+               div *= 1 + prescale;
+               val = clk_rate * period_ns;
+               pv = div64_u64(val, div);
+               val = clk_rate * duty_ns;
+               dc = div64_u64(val, div);
+
+               /* if duty_ns and period_ns are not achievable then return */
+               if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
+                       return -EINVAL;
+
+               /*
+                * if pv and dc have crossed their upper limit, then increase
+                * prescale and recalculate pv and dc.
+                */
+               if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
+                       if (++prescale > PWMCR_MAX_PRESCALE)
+                               return -EINVAL;
+                       continue;
+               }
+               break;
+       }
+
+       /*
+        * NOTE: the clock to PWM has to be enabled first before writing to the
+        * registers.
+        */
+       ret = clk_enable(pc->clk);
+       if (ret)
+               return ret;
+
+       spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
+                       prescale << PWMCR_PRESCALE_SHIFT);
+       spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
+       spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
+       clk_disable(pc->clk);
+
+       return 0;
+}
+
+static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
+       int rc = 0;
+       u32 val;
+
+       rc = clk_enable(pc->clk);
+       if (!rc)
+               return rc;
+
+       val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
+       val |= PWMCR_PWM_ENABLE;
+       spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
+
+       return 0;
+}
+
+static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
+       u32 val;
+
+       val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
+       val &= ~PWMCR_PWM_ENABLE;
+       spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
+
+       clk_disable(pc->clk);
+}
+
+static const struct pwm_ops spear_pwm_ops = {
+       .config = spear_pwm_config,
+       .enable = spear_pwm_enable,
+       .disable = spear_pwm_disable,
+       .owner = THIS_MODULE,
+};
+
+static int spear_pwm_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct spear_pwm_chip *pc;
+       struct resource *r;
+       int ret;
+       u32 val;
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!r) {
+               dev_err(&pdev->dev, "no memory resources defined\n");
+               return -ENODEV;
+       }
+
+       pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
+       if (!pc) {
+               dev_err(&pdev->dev, "failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
+       if (!pc->mmio_base)
+               return -EADDRNOTAVAIL;
+
+       pc->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(pc->clk))
+               return PTR_ERR(pc->clk);
+
+       pc->dev = &pdev->dev;
+       platform_set_drvdata(pdev, pc);
+
+       pc->chip.dev = &pdev->dev;
+       pc->chip.ops = &spear_pwm_ops;
+       pc->chip.base = -1;
+       pc->chip.npwm = NUM_PWM;
+
+       ret = clk_prepare(pc->clk);
+       if (!ret)
+               return ret;
+
+       if (of_device_is_compatible(np, "st,spear1340-pwm")) {
+               ret = clk_enable(pc->clk);
+               if (!ret) {
+                       clk_unprepare(pc->clk);
+                       return ret;
+               }
+               /*
+                * Following enables PWM chip, channels would still be
+                * enabled individually through their control register
+                */
+               val = readl_relaxed(pc->mmio_base + PWMMCR);
+               val |= PWMMCR_PWM_ENABLE;
+               writel_relaxed(val, pc->mmio_base + PWMMCR);
+
+               clk_disable(pc->clk);
+       }
+
+       ret = pwmchip_add(&pc->chip);
+       if (!ret) {
+               clk_unprepare(pc->clk);
+               dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
+       }
+
+       return ret;
+}
+
+static int spear_pwm_remove(struct platform_device *pdev)
+{
+       struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
+       int i;
+
+       for (i = 0; i < NUM_PWM; i++)
+               pwm_disable(&pc->chip.pwms[i]);
+
+       /* clk was prepared in probe, hence unprepare it here */
+       clk_unprepare(pc->clk);
+       return pwmchip_remove(&pc->chip);
+}
+
+static struct of_device_id spear_pwm_of_match[] = {
+       { .compatible = "st,spear320-pwm" },
+       { .compatible = "st,spear1340-pwm" },
+       { }
+};
+
+MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
+
+static struct platform_driver spear_pwm_driver = {
+       .driver = {
+               .name = "spear-pwm",
+               .of_match_table = spear_pwm_of_match,
+       },
+       .probe = spear_pwm_probe,
+       .remove = spear_pwm_remove,
+};
+
+module_platform_driver(spear_pwm_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Shiraz Hashim <shiraz.hashim@st.com>");
+MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
+MODULE_ALIAS("platform:spear-pwm");
index 87c091b245cc74a6225504ce4c8d0c0b3e65f2db..5cf016dd982238f4dac9ba5e4da12194d0e614aa 100644 (file)
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/pwm.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+
+#include "pwm-tipwmss.h"
 
 /* ECAP registers and bits definitions */
 #define CAP1                   0x08
@@ -184,12 +188,24 @@ static const struct pwm_ops ecap_pwm_ops = {
        .owner          = THIS_MODULE,
 };
 
+static const struct of_device_id ecap_of_match[] = {
+       { .compatible   = "ti,am33xx-ecap" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, ecap_of_match);
+
 static int ecap_pwm_probe(struct platform_device *pdev)
 {
        int ret;
        struct resource *r;
        struct clk *clk;
        struct ecap_pwm_chip *pc;
+       u16 status;
+       struct pinctrl *pinctrl;
+
+       pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+       if (IS_ERR(pinctrl))
+               dev_warn(&pdev->dev, "unable to select pin group\n");
 
        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
        if (!pc) {
@@ -211,6 +227,8 @@ static int ecap_pwm_probe(struct platform_device *pdev)
 
        pc->chip.dev = &pdev->dev;
        pc->chip.ops = &ecap_pwm_ops;
+       pc->chip.of_xlate = of_pwm_xlate_with_flags;
+       pc->chip.of_pwm_n_cells = 3;
        pc->chip.base = -1;
        pc->chip.npwm = 1;
 
@@ -231,14 +249,40 @@ 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_put_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
        return pwmchip_remove(&pc->chip);
@@ -246,7 +290,9 @@ static int ecap_pwm_remove(struct platform_device *pdev)
 
 static struct platform_driver ecap_pwm_driver = {
        .driver = {
-               .name = "ecap",
+               .name   = "ecap",
+               .owner  = THIS_MODULE,
+               .of_match_table = ecap_of_match,
        },
        .probe = ecap_pwm_probe,
        .remove = ecap_pwm_remove,
index 9ffd389d0c8b062f651f73be1b46a66dac2fefc3..72a6dd40c9ec5a566ccdffaffe69d8d4cc13f0af 100644 (file)
 #include <linux/err.h>
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+
+#include "pwm-tipwmss.h"
 
 /* EHRPWM registers and bits definitions */
 
@@ -115,6 +119,7 @@ struct ehrpwm_pwm_chip {
        void __iomem    *mmio_base;
        unsigned long period_cycles[NUM_PWM_CHANNEL];
        enum pwm_polarity polarity[NUM_PWM_CHANNEL];
+       struct  clk     *tbclk;
 };
 
 static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
@@ -335,6 +340,9 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
        /* Channels polarity can be configured from action qualifier module */
        configure_polarity(pc, pwm->hwpwm);
 
+       /* Enable TBCLK before enabling PWM device */
+       clk_enable(pc->tbclk);
+
        /* Enable time counter for free_run */
        ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN);
        return 0;
@@ -363,6 +371,9 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 
        ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
 
+       /* Disabling TBCLK on PWM disable */
+       clk_disable(pc->tbclk);
+
        /* Stop Time base counter */
        ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT);
 
@@ -392,12 +403,24 @@ static const struct pwm_ops ehrpwm_pwm_ops = {
        .owner          = THIS_MODULE,
 };
 
+static const struct of_device_id ehrpwm_of_match[] = {
+       { .compatible   = "ti,am33xx-ehrpwm" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
+
 static int ehrpwm_pwm_probe(struct platform_device *pdev)
 {
        int ret;
        struct resource *r;
        struct clk *clk;
        struct ehrpwm_pwm_chip *pc;
+       u16 status;
+       struct pinctrl *pinctrl;
+
+       pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+       if (IS_ERR(pinctrl))
+               dev_warn(&pdev->dev, "unable to select pin group\n");
 
        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
        if (!pc) {
@@ -419,6 +442,8 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 
        pc->chip.dev = &pdev->dev;
        pc->chip.ops = &ehrpwm_pwm_ops;
+       pc->chip.of_xlate = of_pwm_xlate_with_flags;
+       pc->chip.of_pwm_n_cells = 3;
        pc->chip.base = -1;
        pc->chip.npwm = NUM_PWM_CHANNEL;
 
@@ -432,6 +457,13 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
        if (!pc->mmio_base)
                return  -EADDRNOTAVAIL;
 
+       /* Acquire tbclk for Time Base EHRPWM submodule */
+       pc->tbclk = devm_clk_get(&pdev->dev, "tbclk");
+       if (IS_ERR(pc->tbclk)) {
+               dev_err(&pdev->dev, "Failed to get tbclk\n");
+               return PTR_ERR(pc->tbclk);
+       }
+
        ret = pwmchip_add(&pc->chip);
        if (ret < 0) {
                dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
@@ -439,14 +471,40 @@ 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);
+       return ret;
 }
 
 static int ehrpwm_pwm_remove(struct platform_device *pdev)
 {
        struct ehrpwm_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_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);
@@ -454,7 +512,9 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
 
 static struct platform_driver ehrpwm_pwm_driver = {
        .driver = {
-               .name = "ehrpwm",
+               .name   = "ehrpwm",
+               .owner  = THIS_MODULE,
+               .of_match_table = ehrpwm_of_match,
        },
        .probe = ehrpwm_pwm_probe,
        .remove = ehrpwm_pwm_remove,
diff --git a/drivers/pwm/pwm-tipwmss.c b/drivers/pwm/pwm-tipwmss.c
new file mode 100644 (file)
index 0000000..3448a1c
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#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" },
+       {},
+};
+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) {
+               dev_err(&pdev->dev, "failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       mutex_init(&info->pwmss_lock);
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!r) {
+               dev_err(&pdev->dev, "no memory resource defined\n");
+               return -ENODEV;
+       }
+
+       info->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
+       if (!info->mmio_base)
+               return -EADDRNOTAVAIL;
+
+       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);
+       if (ret)
+               dev_err(&pdev->dev, "no child node found\n");
+
+       return ret;
+}
+
+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;
+}
+
+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;
+}
+
+static SIMPLE_DEV_PM_OPS(pwmss_pm_ops, pwmss_suspend, pwmss_resume);
+
+static struct platform_driver pwmss_driver = {
+       .driver = {
+               .name   = "pwmss",
+               .owner  = THIS_MODULE,
+               .pm     = &pwmss_pm_ops,
+               .of_match_table = pwmss_of_match,
+       },
+       .probe  = pwmss_probe,
+       .remove = pwmss_remove,
+};
+
+module_platform_driver(pwmss_driver);
+
+MODULE_DESCRIPTION("PWM Subsystem driver");
+MODULE_AUTHOR("Texas Instruments");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-tipwmss.h b/drivers/pwm/pwm-tipwmss.h
new file mode 100644 (file)
index 0000000..11f76a1
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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
+
+#ifdef CONFIG_PWM_TIPWMSS
+/* 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)
+
+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 */
diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c
new file mode 100644 (file)
index 0000000..9dfa0f3
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
+ * Hemanth V <hemanthv@ti.com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/i2c/twl.h>
+#include <linux/slab.h>
+
+/*
+ * This driver handles the PWM driven LED terminals of TWL4030 and TWL6030.
+ * To generate the signal on TWL4030:
+ *  - LEDA uses PWMA
+ *  - LEDB uses PWMB
+ * TWL6030 has one LED pin with dedicated LEDPWM
+ */
+
+#define TWL4030_LED_MAX                0x7f
+#define TWL6030_LED_MAX                0xff
+
+/* Registers, bits and macro for TWL4030 */
+#define TWL4030_LEDEN_REG      0x00
+#define TWL4030_PWMA_REG       0x01
+
+#define TWL4030_LEDXON         (1 << 0)
+#define TWL4030_LEDXPWM                (1 << 4)
+#define TWL4030_LED_PINS       (TWL4030_LEDXON | TWL4030_LEDXPWM)
+#define TWL4030_LED_TOGGLE(led, x)     ((x) << (led))
+
+/* Register, bits and macro for TWL6030 */
+#define TWL6030_LED_PWM_CTRL1  0xf4
+#define TWL6030_LED_PWM_CTRL2  0xf5
+
+#define TWL6040_LED_MODE_HW    0x00
+#define TWL6040_LED_MODE_ON    0x01
+#define TWL6040_LED_MODE_OFF   0x02
+#define TWL6040_LED_MODE_MASK  0x03
+
+struct twl_pwmled_chip {
+       struct pwm_chip chip;
+       struct mutex mutex;
+};
+
+static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip)
+{
+       return container_of(chip, struct twl_pwmled_chip, chip);
+}
+
+static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                             int duty_ns, int period_ns)
+{
+       int duty_cycle = DIV_ROUND_UP(duty_ns * TWL4030_LED_MAX, period_ns) + 1;
+       u8 pwm_config[2] = { 1, 0 };
+       int base, ret;
+
+       /*
+        * To configure the duty period:
+        * On-cycle is set to 1 (the minimum allowed value)
+        * The off time of 0 is not configurable, so the mapping is:
+        * 0 -> off cycle = 2,
+        * 1 -> off cycle = 2,
+        * 2 -> off cycle = 3,
+        * 126 - > off cycle 127,
+        * 127 - > off cycle 1
+        * When on cycle == off cycle the PWM will be always on
+        */
+       if (duty_cycle == 1)
+               duty_cycle = 2;
+       else if (duty_cycle > TWL4030_LED_MAX)
+               duty_cycle = 1;
+
+       base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
+
+       pwm_config[1] = duty_cycle;
+
+       ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
+
+       return ret;
+}
+
+static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwmled_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label);
+               goto out;
+       }
+
+       val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+       return ret;
+}
+
+static void twl4030_pwmled_disable(struct pwm_chip *chip,
+                                  struct pwm_device *pwm)
+{
+       struct twl_pwmled_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label);
+               goto out;
+       }
+
+       val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+}
+
+static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                             int duty_ns, int period_ns)
+{
+       int duty_cycle = (duty_ns * TWL6030_LED_MAX) / period_ns;
+       u8 on_time;
+       int ret;
+
+       on_time = duty_cycle & 0xff;
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, on_time,
+                              TWL6030_LED_PWM_CTRL1);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
+
+       return ret;
+}
+
+static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwmled_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
+                       pwm->label);
+               goto out;
+       }
+
+       val &= ~TWL6040_LED_MODE_MASK;
+       val |= TWL6040_LED_MODE_ON;
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+       return ret;
+}
+
+static void twl6030_pwmled_disable(struct pwm_chip *chip,
+                                  struct pwm_device *pwm)
+{
+       struct twl_pwmled_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
+                       pwm->label);
+               goto out;
+       }
+
+       val &= ~TWL6040_LED_MODE_MASK;
+       val |= TWL6040_LED_MODE_OFF;
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+}
+
+static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwmled_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
+                       pwm->label);
+               goto out;
+       }
+
+       val &= ~TWL6040_LED_MODE_MASK;
+       val |= TWL6040_LED_MODE_OFF;
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+       return ret;
+}
+
+static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwmled_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
+                       pwm->label);
+               goto out;
+       }
+
+       val &= ~TWL6040_LED_MODE_MASK;
+       val |= TWL6040_LED_MODE_HW;
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+}
+
+static const struct pwm_ops twl4030_pwmled_ops = {
+       .enable = twl4030_pwmled_enable,
+       .disable = twl4030_pwmled_disable,
+       .config = twl4030_pwmled_config,
+};
+
+static const struct pwm_ops twl6030_pwmled_ops = {
+       .enable = twl6030_pwmled_enable,
+       .disable = twl6030_pwmled_disable,
+       .config = twl6030_pwmled_config,
+       .request = twl6030_pwmled_request,
+       .free = twl6030_pwmled_free,
+};
+
+static int twl_pwmled_probe(struct platform_device *pdev)
+{
+       struct twl_pwmled_chip *twl;
+       int ret;
+
+       twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
+       if (!twl)
+               return -ENOMEM;
+
+       if (twl_class_is_4030()) {
+               twl->chip.ops = &twl4030_pwmled_ops;
+               twl->chip.npwm = 2;
+       } else {
+               twl->chip.ops = &twl6030_pwmled_ops;
+               twl->chip.npwm = 1;
+       }
+
+       twl->chip.dev = &pdev->dev;
+       twl->chip.base = -1;
+
+       mutex_init(&twl->mutex);
+
+       ret = pwmchip_add(&twl->chip);
+       if (ret < 0)
+               return ret;
+
+       platform_set_drvdata(pdev, twl);
+
+       return 0;
+}
+
+static int twl_pwmled_remove(struct platform_device *pdev)
+{
+       struct twl_pwmled_chip *twl = platform_get_drvdata(pdev);
+
+       return pwmchip_remove(&twl->chip);
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id twl_pwmled_of_match[] = {
+       { .compatible = "ti,twl4030-pwmled" },
+       { .compatible = "ti,twl6030-pwmled" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, twl_pwmled_of_match);
+#endif
+
+static struct platform_driver twl_pwmled_driver = {
+       .driver = {
+               .name = "twl-pwmled",
+               .of_match_table = of_match_ptr(twl_pwmled_of_match),
+       },
+       .probe = twl_pwmled_probe,
+       .remove = twl_pwmled_remove,
+};
+module_platform_driver(twl_pwmled_driver);
+
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
+MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs");
+MODULE_ALIAS("platform:twl-pwmled");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-twl.c b/drivers/pwm/pwm-twl.c
new file mode 100644 (file)
index 0000000..e65db95
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ * Driver for TWL4030/6030 Generic Pulse Width Modulator
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/i2c/twl.h>
+#include <linux/slab.h>
+
+/*
+ * This driver handles the PWMs of TWL4030 and TWL6030.
+ * The TRM names for the PWMs on TWL4030 are: PWM0, PWM1
+ * TWL6030 also have two PWMs named in the TRM as PWM1, PWM2
+ */
+
+#define TWL_PWM_MAX            0x7f
+
+/* Registers, bits and macro for TWL4030 */
+#define TWL4030_GPBR1_REG      0x0c
+#define TWL4030_PMBR1_REG      0x0d
+
+/* GPBR1 register bits */
+#define TWL4030_PWMXCLK_ENABLE (1 << 0)
+#define TWL4030_PWMX_ENABLE    (1 << 2)
+#define TWL4030_PWMX_BITS      (TWL4030_PWMX_ENABLE | TWL4030_PWMXCLK_ENABLE)
+#define TWL4030_PWM_TOGGLE(pwm, x)     ((x) << (pwm))
+
+/* PMBR1 register bits */
+#define TWL4030_GPIO6_PWM0_MUTE_MASK           (0x03 << 2)
+#define TWL4030_GPIO6_PWM0_MUTE_PWM0           (0x01 << 2)
+#define TWL4030_GPIO7_VIBRASYNC_PWM1_MASK      (0x03 << 4)
+#define TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1      (0x03 << 4)
+
+/* Register, bits and macro for TWL6030 */
+#define TWL6030_TOGGLE3_REG    0x92
+
+#define TWL6030_PWMXR          (1 << 0)
+#define TWL6030_PWMXS          (1 << 1)
+#define TWL6030_PWMXEN         (1 << 2)
+#define TWL6030_PWM_TOGGLE(pwm, x)     ((x) << (pwm * 3))
+
+struct twl_pwm_chip {
+       struct pwm_chip chip;
+       struct mutex mutex;
+       u8 twl6030_toggle3;
+       u8 twl4030_pwm_mux;
+};
+
+static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip)
+{
+       return container_of(chip, struct twl_pwm_chip, chip);
+}
+
+static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                             int duty_ns, int period_ns)
+{
+       int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1;
+       u8 pwm_config[2] = { 1, 0 };
+       int base, ret;
+
+       /*
+        * To configure the duty period:
+        * On-cycle is set to 1 (the minimum allowed value)
+        * The off time of 0 is not configurable, so the mapping is:
+        * 0 -> off cycle = 2,
+        * 1 -> off cycle = 2,
+        * 2 -> off cycle = 3,
+        * 126 - > off cycle 127,
+        * 127 - > off cycle 1
+        * When on cycle == off cycle the PWM will be always on
+        */
+       if (duty_cycle == 1)
+               duty_cycle = 2;
+       else if (duty_cycle > TWL_PWM_MAX)
+               duty_cycle = 1;
+
+       base = pwm->hwpwm * 3;
+
+       pwm_config[1] = duty_cycle;
+
+       ret = twl_i2c_write(TWL_MODULE_PWM, pwm_config, base, 2);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
+
+       return ret;
+}
+
+static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwm_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
+               goto out;
+       }
+
+       val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
+
+       val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+       return ret;
+}
+
+static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwm_chip *twl = to_twl(chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
+               goto out;
+       }
+
+       val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
+
+       val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+}
+
+static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwm_chip *twl = to_twl(chip);
+       int ret;
+       u8 val, mask, bits;
+
+       if (pwm->hwpwm == 1) {
+               mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
+               bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
+       } else {
+               mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
+               bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
+       }
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
+               goto out;
+       }
+
+       /* Save the current MUX configuration for the PWM */
+       twl->twl4030_pwm_mux &= ~mask;
+       twl->twl4030_pwm_mux |= (val & mask);
+
+       /* Select PWM functionality */
+       val &= ~mask;
+       val |= bits;
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+       return ret;
+}
+
+static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
+                                               chip);
+       int ret;
+       u8 val, mask;
+
+       if (pwm->hwpwm == 1)
+               mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
+       else
+               mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
+
+       mutex_lock(&twl->mutex);
+       ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
+               goto out;
+       }
+
+       /* Restore the MUX configuration for the PWM */
+       val &= ~mask;
+       val |= (twl->twl4030_pwm_mux & mask);
+
+       ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
+       if (ret < 0)
+               dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
+
+out:
+       mutex_unlock(&twl->mutex);
+}
+
+static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
+                                               chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       val = twl->twl6030_toggle3;
+       val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
+       val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
+               goto out;
+       }
+
+       twl->twl6030_toggle3 = val;
+out:
+       mutex_unlock(&twl->mutex);
+       return 0;
+}
+
+static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
+                                               chip);
+       int ret;
+       u8 val;
+
+       mutex_lock(&twl->mutex);
+       val = twl->twl6030_toggle3;
+       val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
+       val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label);
+               goto out;
+       }
+
+       val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
+
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
+       if (ret < 0) {
+               dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
+               goto out;
+       }
+
+       twl->twl6030_toggle3 = val;
+out:
+       mutex_unlock(&twl->mutex);
+}
+
+static const struct pwm_ops twl4030_pwm_ops = {
+       .config = twl_pwm_config,
+       .enable = twl4030_pwm_enable,
+       .disable = twl4030_pwm_disable,
+       .request = twl4030_pwm_request,
+       .free = twl4030_pwm_free,
+};
+
+static const struct pwm_ops twl6030_pwm_ops = {
+       .config = twl_pwm_config,
+       .enable = twl6030_pwm_enable,
+       .disable = twl6030_pwm_disable,
+};
+
+static int twl_pwm_probe(struct platform_device *pdev)
+{
+       struct twl_pwm_chip *twl;
+       int ret;
+
+       twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
+       if (!twl)
+               return -ENOMEM;
+
+       if (twl_class_is_4030())
+               twl->chip.ops = &twl4030_pwm_ops;
+       else
+               twl->chip.ops = &twl6030_pwm_ops;
+
+       twl->chip.dev = &pdev->dev;
+       twl->chip.base = -1;
+       twl->chip.npwm = 2;
+
+       mutex_init(&twl->mutex);
+
+       ret = pwmchip_add(&twl->chip);
+       if (ret < 0)
+               return ret;
+
+       platform_set_drvdata(pdev, twl);
+
+       return 0;
+}
+
+static int twl_pwm_remove(struct platform_device *pdev)
+{
+       struct twl_pwm_chip *twl = platform_get_drvdata(pdev);
+
+       return pwmchip_remove(&twl->chip);
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id twl_pwm_of_match[] = {
+       { .compatible = "ti,twl4030-pwm" },
+       { .compatible = "ti,twl6030-pwm" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, twl_pwm_of_match);
+#endif
+
+static struct platform_driver twl_pwm_driver = {
+       .driver = {
+               .name = "twl-pwm",
+               .of_match_table = of_match_ptr(twl_pwm_of_match),
+       },
+       .probe = twl_pwm_probe,
+       .remove = twl_pwm_remove,
+};
+module_platform_driver(twl_pwm_driver);
+
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
+MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030");
+MODULE_ALIAS("platform:twl-pwm");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-twl6030.c b/drivers/pwm/pwm-twl6030.c
deleted file mode 100644 (file)
index 378a7e2..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * twl6030_pwm.c
- * Driver for PHOENIX (TWL6030) Pulse Width Modulator
- *
- * Copyright (C) 2010 Texas Instruments
- * Author: Hemanth V <hemanthv@ti.com>
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/pwm.h>
-#include <linux/i2c/twl.h>
-#include <linux/slab.h>
-
-#define LED_PWM_CTRL1  0xF4
-#define LED_PWM_CTRL2  0xF5
-
-/* Max value for CTRL1 register */
-#define PWM_CTRL1_MAX  255
-
-/* Pull down disable */
-#define PWM_CTRL2_DIS_PD       (1 << 6)
-
-/* Current control 2.5 milli Amps */
-#define PWM_CTRL2_CURR_02      (2 << 4)
-
-/* LED supply source */
-#define PWM_CTRL2_SRC_VAC      (1 << 2)
-
-/* LED modes */
-#define PWM_CTRL2_MODE_HW      (0 << 0)
-#define PWM_CTRL2_MODE_SW      (1 << 0)
-#define PWM_CTRL2_MODE_DIS     (2 << 0)
-
-#define PWM_CTRL2_MODE_MASK    0x3
-
-struct twl6030_pwm_chip {
-       struct pwm_chip chip;
-};
-
-static int twl6030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
-{
-       int ret;
-       u8 val;
-
-       /* Configure PWM */
-       val = PWM_CTRL2_DIS_PD | PWM_CTRL2_CURR_02 | PWM_CTRL2_SRC_VAC |
-             PWM_CTRL2_MODE_HW;
-
-       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, LED_PWM_CTRL2);
-       if (ret < 0) {
-               dev_err(chip->dev, "%s: Failed to configure PWM, Error %d\n",
-                       pwm->label, ret);
-               return ret;
-       }
-
-       return 0;
-}
-
-static int twl6030_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
-                             int duty_ns, int period_ns)
-{
-       u8 duty_cycle = (duty_ns * PWM_CTRL1_MAX) / period_ns;
-       int ret;
-
-       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, duty_cycle, LED_PWM_CTRL1);
-       if (ret < 0) {
-               pr_err("%s: Failed to configure PWM, Error %d\n",
-                       pwm->label, ret);
-               return ret;
-       }
-
-       return 0;
-}
-
-static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
-       int ret;
-       u8 val;
-
-       ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, LED_PWM_CTRL2);
-       if (ret < 0) {
-               dev_err(chip->dev, "%s: Failed to enable PWM, Error %d\n",
-                       pwm->label, ret);
-               return ret;
-       }
-
-       /* Change mode to software control */
-       val &= ~PWM_CTRL2_MODE_MASK;
-       val |= PWM_CTRL2_MODE_SW;
-
-       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, LED_PWM_CTRL2);
-       if (ret < 0) {
-               dev_err(chip->dev, "%s: Failed to enable PWM, Error %d\n",
-                       pwm->label, ret);
-               return ret;
-       }
-
-       twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, LED_PWM_CTRL2);
-       return 0;
-}
-
-static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
-       int ret;
-       u8 val;
-
-       ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, LED_PWM_CTRL2);
-       if (ret < 0) {
-               dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n",
-                       pwm->label, ret);
-               return;
-       }
-
-       val &= ~PWM_CTRL2_MODE_MASK;
-       val |= PWM_CTRL2_MODE_HW;
-
-       ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, LED_PWM_CTRL2);
-       if (ret < 0) {
-               dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n",
-                       pwm->label, ret);
-       }
-}
-
-static const struct pwm_ops twl6030_pwm_ops = {
-       .request = twl6030_pwm_request,
-       .config = twl6030_pwm_config,
-       .enable = twl6030_pwm_enable,
-       .disable = twl6030_pwm_disable,
-};
-
-static int twl6030_pwm_probe(struct platform_device *pdev)
-{
-       struct twl6030_pwm_chip *twl6030;
-       int ret;
-
-       twl6030 = devm_kzalloc(&pdev->dev, sizeof(*twl6030), GFP_KERNEL);
-       if (!twl6030)
-               return -ENOMEM;
-
-       twl6030->chip.dev = &pdev->dev;
-       twl6030->chip.ops = &twl6030_pwm_ops;
-       twl6030->chip.base = -1;
-       twl6030->chip.npwm = 1;
-
-       ret = pwmchip_add(&twl6030->chip);
-       if (ret < 0)
-               return ret;
-
-       platform_set_drvdata(pdev, twl6030);
-
-       return 0;
-}
-
-static int twl6030_pwm_remove(struct platform_device *pdev)
-{
-       struct twl6030_pwm_chip *twl6030 = platform_get_drvdata(pdev);
-
-       return pwmchip_remove(&twl6030->chip);
-}
-
-static struct platform_driver twl6030_pwm_driver = {
-       .driver = {
-               .name = "twl6030-pwm",
-       },
-       .probe = twl6030_pwm_probe,
-       .remove = twl6030_pwm_remove,
-};
-module_platform_driver(twl6030_pwm_driver);
-
-MODULE_ALIAS("platform:twl6030-pwm");
-MODULE_LICENSE("GPL");
index ad14389b7144a29f142ed9b8e0c9d6df3b2793c0..b0ba2d403439b0a77b889f88cb210f0eaae789ad 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * drivers/pwm/pwm-vt8500.c
  *
- *  Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
 #include <linux/io.h>
 #include <linux/pwm.h>
 #include <linux/delay.h>
+#include <linux/clk.h>
 
 #include <asm/div64.h>
 
-#define VT8500_NR_PWMS 4
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+/*
+ * SoC architecture allocates register space for 4 PWMs but only
+ * 2 are currently implemented.
+ */
+#define VT8500_NR_PWMS 2
 
 struct vt8500_chip {
        struct pwm_chip chip;
        void __iomem *base;
+       struct clk *clk;
 };
 
 #define to_vt8500_chip(chip)   container_of(chip, struct vt8500_chip, chip)
@@ -51,8 +62,15 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
        unsigned long long c;
        unsigned long period_cycles, prescale, pv, dc;
+       int err;
 
-       c = 25000000/2; /* wild guess --- need to implement clocks */
+       err = clk_enable(vt8500->clk);
+       if (err < 0) {
+               dev_err(chip->dev, "failed to enable clock\n");
+               return err;
+       }
+
+       c = clk_get_rate(vt8500->clk);
        c = c * period_ns;
        do_div(c, 1000000000);
        period_cycles = c;
@@ -64,8 +82,10 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        if (pv > 4095)
                pv = 4095;
 
-       if (prescale > 1023)
+       if (prescale > 1023) {
+               clk_disable(vt8500->clk);
                return -EINVAL;
+       }
 
        c = (unsigned long long)pv * duty_ns;
        do_div(c, period_ns);
@@ -80,13 +100,21 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 3));
        writel(dc, vt8500->base + 0xc + (pwm->hwpwm << 4));
 
+       clk_disable(vt8500->clk);
        return 0;
 }
 
 static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
+       int err;
        struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
 
+       err = clk_enable(vt8500->clk);
+       if (err < 0) {
+               dev_err(chip->dev, "failed to enable clock\n");
+               return err;
+       }
+
        pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0));
        writel(5, vt8500->base + (pwm->hwpwm << 4));
        return 0;
@@ -98,6 +126,8 @@ static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 
        pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0));
        writel(0, vt8500->base + (pwm->hwpwm << 4));
+
+       clk_disable(vt8500->clk);
 }
 
 static struct pwm_ops vt8500_pwm_ops = {
@@ -107,12 +137,24 @@ static struct pwm_ops vt8500_pwm_ops = {
        .owner = THIS_MODULE,
 };
 
-static int __devinit pwm_probe(struct platform_device *pdev)
+static const struct of_device_id vt8500_pwm_dt_ids[] = {
+       { .compatible = "via,vt8500-pwm", },
+       { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vt8500_pwm_dt_ids);
+
+static int vt8500_pwm_probe(struct platform_device *pdev)
 {
        struct vt8500_chip *chip;
        struct resource *r;
+       struct device_node *np = pdev->dev.of_node;
        int ret;
 
+       if (!np) {
+               dev_err(&pdev->dev, "invalid devicetree node\n");
+               return -EINVAL;
+       }
+
        chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
        if (chip == NULL) {
                dev_err(&pdev->dev, "failed to allocate memory\n");
@@ -124,6 +166,12 @@ static int __devinit pwm_probe(struct platform_device *pdev)
        chip->chip.base = -1;
        chip->chip.npwm = VT8500_NR_PWMS;
 
+       chip->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(chip->clk)) {
+               dev_err(&pdev->dev, "clock source not specified\n");
+               return PTR_ERR(chip->clk);
+       }
+
        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (r == NULL) {
                dev_err(&pdev->dev, "no memory resource defined\n");
@@ -131,18 +179,26 @@ static int __devinit pwm_probe(struct platform_device *pdev)
        }
 
        chip->base = devm_request_and_ioremap(&pdev->dev, r);
-       if (chip->base == NULL)
+       if (!chip->base)
                return -EADDRNOTAVAIL;
 
+       ret = clk_prepare(chip->clk);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to prepare clock\n");
+               return ret;
+       }
+
        ret = pwmchip_add(&chip->chip);
-       if (ret < 0)
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to add PWM chip\n");
                return ret;
+       }
 
        platform_set_drvdata(pdev, chip);
        return ret;
 }
 
-static int __devexit pwm_remove(struct platform_device *pdev)
+static int vt8500_pwm_remove(struct platform_device *pdev)
 {
        struct vt8500_chip *chip;
 
@@ -150,28 +206,22 @@ static int __devexit pwm_remove(struct platform_device *pdev)
        if (chip == NULL)
                return -ENODEV;
 
+       clk_unprepare(chip->clk);
+
        return pwmchip_remove(&chip->chip);
 }
 
-static struct platform_driver pwm_driver = {
+static struct platform_driver vt8500_pwm_driver = {
+       .probe          = vt8500_pwm_probe,
+       .remove         = vt8500_pwm_remove,
        .driver         = {
                .name   = "vt8500-pwm",
                .owner  = THIS_MODULE,
+               .of_match_table = vt8500_pwm_dt_ids,
        },
-       .probe          = pwm_probe,
-       .remove         = __devexit_p(pwm_remove),
 };
+module_platform_driver(vt8500_pwm_driver);
 
-static int __init pwm_init(void)
-{
-       return platform_driver_register(&pwm_driver);
-}
-arch_initcall(pwm_init);
-
-static void __exit pwm_exit(void)
-{
-       platform_driver_unregister(&pwm_driver);
-}
-module_exit(pwm_exit);
-
-MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("VT8500 PWM Driver");
+MODULE_AUTHOR("Tony Prisk <linux@prisktech.co.nz>");
+MODULE_LICENSE("GPL v2");
index 112b31436848d449bdd471374faa4135d910d043..6d661f32e0e4675681cf8fd3b1284e6a684b6dbe 100644 (file)
@@ -171,6 +171,9 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
                                         unsigned int index,
                                         const char *label);
 
+struct pwm_device *of_pwm_xlate_with_flags(struct pwm_chip *pc,
+               const struct of_phandle_args *args);
+
 struct pwm_device *pwm_get(struct device *dev, const char *consumer);
 void pwm_put(struct pwm_device *pwm);