]> Pileus Git - ~andy/linux/commitdiff
Merge tag 'for-3.11-rc1' of git://gitorious.org/linux-pwm/linux-pwm
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 10 Jul 2013 18:14:56 +0000 (11:14 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 10 Jul 2013 18:14:56 +0000 (11:14 -0700)
Pull pwm changes from Thierry Reding:
 "A new driver supports driving PWM signals using the TPU unit found on
  various Renesas SoCs.  Furthermore support is added for the NXP
  PCA9685 LED controller.  Another big chunk is the sysfs interface
  which has been in the works for quite some time.

  The remaining patches are a random assortment of cleanups and fixes"

* tag 'for-3.11-rc1' of git://gitorious.org/linux-pwm/linux-pwm:
  pwm: pwm-tiehrpwm: Use clk_enable/disable instead clk_prepare/unprepare.
  pwm: pca9685: Fix wrong argument to set MODE1_SLEEP bit
  pwm: renesas-tpu: Add MODULE_ALIAS to make module auto loading work
  pwm: renesas-tpu: fix return value check in tpu_probe()
  pwm: Add Renesas TPU PWM driver
  pwm: Add sysfs interface
  pwm: Fill in missing .owner fields
  pwm: add pca9685 driver
  pwm: atmel-tcb: prepare clk before calling enable
  pwm: devm: alloc correct pointer size
  pwm: mxs: Let device core handle pinctrl
  MAINTAINERS: Update PWM subsystem entry

21 files changed:
Documentation/ABI/testing/sysfs-class-pwm [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt [new file with mode: 0644]
Documentation/pwm.txt
MAINTAINERS
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/core.c
drivers/pwm/pwm-atmel-tcb.c
drivers/pwm/pwm-bfin.c
drivers/pwm/pwm-imx.c
drivers/pwm/pwm-lpc32xx.c
drivers/pwm/pwm-mxs.c
drivers/pwm/pwm-pca9685.c [new file with mode: 0644]
drivers/pwm/pwm-puv3.c
drivers/pwm/pwm-renesas-tpu.c [new file with mode: 0644]
drivers/pwm/pwm-spear.c
drivers/pwm/pwm-tegra.c
drivers/pwm/pwm-tiehrpwm.c
drivers/pwm/sysfs.c [new file with mode: 0644]
include/linux/platform_data/pwm-renesas-tpu.h [new file with mode: 0644]
include/linux/pwm.h

diff --git a/Documentation/ABI/testing/sysfs-class-pwm b/Documentation/ABI/testing/sysfs-class-pwm
new file mode 100644 (file)
index 0000000..c479d77
--- /dev/null
@@ -0,0 +1,79 @@
+What:          /sys/class/pwm/
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               The pwm/ class sub-directory belongs to the Generic PWM
+               Framework and provides a sysfs interface for using PWM
+               channels.
+
+What:          /sys/class/pwm/pwmchipN/
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               A /sys/class/pwm/pwmchipN directory is created for each
+               probed PWM controller/chip where N is the base of the
+               PWM chip.
+
+What:          /sys/class/pwm/pwmchipN/npwm
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               The number of PWM channels supported by the PWM chip.
+
+What:          /sys/class/pwm/pwmchipN/export
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               Exports a PWM channel from the PWM chip for sysfs control.
+               Value is between 0 and /sys/class/pwm/pwmchipN/npwm - 1.
+
+What:          /sys/class/pwm/pwmchipN/unexport
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               Unexports a PWM channel.
+
+What:          /sys/class/pwm/pwmchipN/pwmX
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               A /sys/class/pwm/pwmchipN/pwmX directory is created for
+               each exported PWM channel where X is the exported PWM
+               channel number.
+
+What:          /sys/class/pwm/pwmchipN/pwmX/period
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               Sets the PWM signal period in nanoseconds.
+
+What:          /sys/class/pwm/pwmchipN/pwmX/duty_cycle
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               Sets the PWM signal duty cycle in nanoseconds.
+
+What:          /sys/class/pwm/pwmchipN/pwmX/polarity
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               Sets the output polarity of the PWM signal to "normal" or
+               "inversed".
+
+What:          /sys/class/pwm/pwmchipN/pwmX/enable
+Date:          May 2013
+KernelVersion: 3.11
+Contact:       H Hartley Sweeten <hsweeten@visionengravers.com>
+Description:
+               Enable/disable the PWM signal.
+               0 is disabled
+               1 is enabled
diff --git a/Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt b/Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt
new file mode 100644 (file)
index 0000000..1e3dfe7
--- /dev/null
@@ -0,0 +1,27 @@
+NXP PCA9685 16-channel 12-bit PWM LED controller
+================================================
+
+Required properties:
+  - compatible: "nxp,pca9685-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.
+    The index 16 is the ALLCALL channel, that sets all PWM channels at the same
+    time.
+
+Optional properties:
+  - invert (bool): boolean to enable inverted logic
+  - open-drain (bool): boolean to configure outputs with open-drain structure;
+                      if omitted use totem-pole structure
+
+Example:
+
+For LEDs that are directly connected to the PCA, the following setting is
+applicable:
+
+pca: pca@41 {
+       compatible = "nxp,pca9685-pwm";
+       #pwm-cells = <2>;
+       reg = <0x41>;
+       invert;
+       open-drain;
+};
index 7d2b4c9b544b3c28596bec44c05a62dfadfdc260..1039b68fe9c62aa773f4e90e68613870c0cd9db3 100644 (file)
@@ -45,6 +45,43 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
 
 To start/stop toggling the PWM output use pwm_enable()/pwm_disable().
 
+Using PWMs with the sysfs interface
+-----------------------------------
+
+If CONFIG_SYSFS is enabled in your kernel configuration a simple sysfs
+interface is provided to use the PWMs from userspace. It is exposed at
+/sys/class/pwm/. Each probed PWM controller/chip will be exported as
+pwmchipN, where N is the base of the PWM chip. Inside the directory you
+will find:
+
+npwm - The number of PWM channels this chip supports (read-only).
+
+export - Exports a PWM channel for use with sysfs (write-only).
+
+unexport - Unexports a PWM channel from sysfs (write-only).
+
+The PWM channels are numbered using a per-chip index from 0 to npwm-1.
+
+When a PWM channel is exported a pwmX directory will be created in the
+pwmchipN directory it is associated with, where X is the number of the
+channel that was exported. The following properties will then be available:
+
+period - The total period of the PWM signal (read/write).
+       Value is in nanoseconds and is the sum of the active and inactive
+       time of the PWM.
+
+duty_cycle - The active time of the PWM signal (read/write).
+       Value is in nanoseconds and must be less than the period.
+
+polarity - Changes the polarity of the PWM signal (read/write).
+       Writes to this property only work if the PWM chip supports changing
+       the polarity. The polarity can only be changed if the PWM is not
+       enabled. Value is the string "normal" or "inversed".
+
+enable - Enable/disable the PWM signal (read/write).
+       0 - disabled
+       1 - enabled
+
 Implementing a PWM driver
 -------------------------
 
index 3c433aec12db7db494f8235f6c1fe7b84b628bfd..37f9a71c744f38c4b8b0e2671eda3871425d0d25 100644 (file)
@@ -6568,8 +6568,8 @@ S:        Maintained
 F:     drivers/media/usb/pwc/*
 
 PWM SUBSYSTEM
-M:     Thierry Reding <thierry.reding@avionic-design.de>
-L:     linux-kernel@vger.kernel.org
+M:     Thierry Reding <thierry.reding@gmail.com>
+L:     linux-pwm@vger.kernel.org
 S:     Maintained
 W:     http://gitorious.org/linux-pwm
 T:     git git://gitorious.org/linux-pwm/linux-pwm.git
index 115b6445349307ece080c3f8dbe7d8699968664b..75840b5cea6dfd978d83c5b4c741af0b5af99122 100644 (file)
@@ -28,6 +28,10 @@ menuconfig PWM
 
 if PWM
 
+config PWM_SYSFS
+       bool
+       default y if SYSFS
+
 config PWM_AB8500
        tristate "AB8500 PWM support"
        depends on AB8500_CORE && ARCH_U8500
@@ -97,6 +101,15 @@ config PWM_MXS
          To compile this driver as a module, choose M here: the module
          will be called pwm-mxs.
 
+config PWM_PCA9685
+       tristate "NXP PCA9685 PWM driver"
+       depends on OF && REGMAP_I2C
+       help
+         Generic PWM framework driver for NXP PCA9685 LED controller.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-pca9685.
+
 config PWM_PUV3
        tristate "PKUnity NetBook-0916 PWM support"
        depends on ARCH_PUV3
@@ -115,6 +128,16 @@ config PWM_PXA
          To compile this driver as a module, choose M here: the module
          will be called pwm-pxa.
 
+config PWM_RENESAS_TPU
+       tristate "Renesas TPU PWM support"
+       depends on ARCH_SHMOBILE
+       help
+         This driver exposes the Timer Pulse Unit (TPU) PWM controller found
+         in Renesas chips through the PWM API.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-renesas-tpu.
+
 config PWM_SAMSUNG
        tristate "Samsung PWM support"
        depends on PLAT_SAMSUNG
index 94ba21e24bd6243f27427488fb03fc7b27eb1d35..77a8c185c5b2b46c6a4afe0e0a45bc8d5dd772d0 100644 (file)
@@ -1,4 +1,5 @@
 obj-$(CONFIG_PWM)              += core.o
+obj-$(CONFIG_PWM_SYSFS)                += sysfs.o
 obj-$(CONFIG_PWM_AB8500)       += pwm-ab8500.o
 obj-$(CONFIG_PWM_ATMEL_TCB)    += pwm-atmel-tcb.o
 obj-$(CONFIG_PWM_BFIN)         += pwm-bfin.o
@@ -6,8 +7,10 @@ obj-$(CONFIG_PWM_IMX)          += pwm-imx.o
 obj-$(CONFIG_PWM_JZ4740)       += pwm-jz4740.o
 obj-$(CONFIG_PWM_LPC32XX)      += pwm-lpc32xx.o
 obj-$(CONFIG_PWM_MXS)          += pwm-mxs.o
+obj-$(CONFIG_PWM_PCA9685)      += pwm-pca9685.o
 obj-$(CONFIG_PWM_PUV3)         += pwm-puv3.o
 obj-$(CONFIG_PWM_PXA)          += pwm-pxa.o
+obj-$(CONFIG_PWM_RENESAS_TPU)  += pwm-renesas-tpu.o
 obj-$(CONFIG_PWM_SAMSUNG)      += pwm-samsung.o
 obj-$(CONFIG_PWM_SPEAR)                += pwm-spear.o
 obj-$(CONFIG_PWM_TEGRA)                += pwm-tegra.o
index 32221cb0cbe713622c8773822fc1f6e5be91e49d..dfbfbc5217683de697d1c834f73c55de0ff850de 100644 (file)
@@ -274,6 +274,8 @@ int pwmchip_add(struct pwm_chip *chip)
        if (IS_ENABLED(CONFIG_OF))
                of_pwmchip_add(chip);
 
+       pwmchip_sysfs_export(chip);
+
 out:
        mutex_unlock(&pwm_lock);
        return ret;
@@ -310,6 +312,8 @@ int pwmchip_remove(struct pwm_chip *chip)
 
        free_pwms(chip);
 
+       pwmchip_sysfs_unexport(chip);
+
 out:
        mutex_unlock(&pwm_lock);
        return ret;
@@ -402,10 +406,19 @@ EXPORT_SYMBOL_GPL(pwm_free);
  */
 int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
 {
+       int err;
+
        if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns)
                return -EINVAL;
 
-       return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);
+       err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);
+       if (err)
+               return err;
+
+       pwm->duty_cycle = duty_ns;
+       pwm->period = period_ns;
+
+       return 0;
 }
 EXPORT_SYMBOL_GPL(pwm_config);
 
@@ -418,6 +431,8 @@ EXPORT_SYMBOL_GPL(pwm_config);
  */
 int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
 {
+       int err;
+
        if (!pwm || !pwm->chip->ops)
                return -EINVAL;
 
@@ -427,7 +442,13 @@ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
        if (test_bit(PWMF_ENABLED, &pwm->flags))
                return -EBUSY;
 
-       return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity);
+       err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity);
+       if (err)
+               return err;
+
+       pwm->polarity = polarity;
+
+       return 0;
 }
 EXPORT_SYMBOL_GPL(pwm_set_polarity);
 
@@ -694,7 +715,7 @@ struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
 {
        struct pwm_device **ptr, *pwm;
 
-       ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL);
+       ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL);
        if (!ptr)
                return ERR_PTR(-ENOMEM);
 
@@ -724,7 +745,7 @@ struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
 {
        struct pwm_device **ptr, *pwm;
 
-       ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL);
+       ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL);
        if (!ptr)
                return ERR_PTR(-ENOMEM);
 
index 0a7b6582edb109f58ce1ce26189d37600fde1173..ba6ce01035e4feae2c1f40dcf8b24e0ad4923ffa 100644 (file)
@@ -76,7 +76,7 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
        if (!tcbpwm)
                return -ENOMEM;
 
-       ret = clk_enable(tc->clk[group]);
+       ret = clk_prepare_enable(tc->clk[group]);
        if (ret) {
                devm_kfree(chip->dev, tcbpwm);
                return ret;
@@ -124,7 +124,7 @@ static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
        struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
        struct atmel_tc *tc = tcbpwmc->tc;
 
-       clk_disable(tc->clk[pwm->hwpwm / 2]);
+       clk_disable_unprepare(tc->clk[pwm->hwpwm / 2]);
        tcbpwmc->pwms[pwm->hwpwm] = NULL;
        devm_kfree(chip->dev, tcbpwm);
 }
@@ -434,6 +434,7 @@ MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids);
 static struct platform_driver atmel_tcb_pwm_driver = {
        .driver = {
                .name = "atmel-tcb-pwm",
+               .owner = THIS_MODULE,
                .of_match_table = atmel_tcb_pwm_dt_ids,
        },
        .probe = atmel_tcb_pwm_probe,
index 7631ef194de7dc80ba28b98da9b340e519ac5095..9985d830e554856dd8e96eb9667da4ad64923793 100644 (file)
@@ -149,6 +149,7 @@ static int bfin_pwm_remove(struct platform_device *pdev)
 static struct platform_driver bfin_pwm_driver = {
        .driver = {
                .name = "bfin-pwm",
+               .owner = THIS_MODULE,
        },
        .probe = bfin_pwm_probe,
        .remove = bfin_pwm_remove,
index c938bae18812ea5768e77b48ee2b0f1a6e39b24f..2b7c4f88b461b788c1d683a141d6a402d958af12 100644 (file)
@@ -295,6 +295,7 @@ static int imx_pwm_remove(struct platform_device *pdev)
 static struct platform_driver imx_pwm_driver = {
        .driver         = {
                .name   = "imx-pwm",
+               .owner = THIS_MODULE,
                .of_match_table = of_match_ptr(imx_pwm_dt_ids),
        },
        .probe          = imx_pwm_probe,
index 8272883c0d05f73e63c887aa7b90160e0346e4ae..efb6c7bf8750f0ffc8f31e956e1fbdaff8358e6b 100644 (file)
@@ -171,6 +171,7 @@ MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids);
 static struct platform_driver lpc32xx_pwm_driver = {
        .driver = {
                .name = "lpc32xx-pwm",
+               .owner = THIS_MODULE,
                .of_match_table = of_match_ptr(lpc32xx_pwm_dt_ids),
        },
        .probe = lpc32xx_pwm_probe,
index 3febdddf71f9aa77a8d240d126c008fccf8827d2..2c77b81da7c4a7fb50c70f94904ad5312a791a85 100644 (file)
@@ -16,7 +16,6 @@
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
-#include <linux/pinctrl/consumer.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
 #include <linux/slab.h>
@@ -130,7 +129,6 @@ static int mxs_pwm_probe(struct platform_device *pdev)
        struct device_node *np = pdev->dev.of_node;
        struct mxs_pwm_chip *mxs;
        struct resource *res;
-       struct pinctrl *pinctrl;
        int ret;
 
        mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL);
@@ -142,10 +140,6 @@ static int mxs_pwm_probe(struct platform_device *pdev)
        if (IS_ERR(mxs->base))
                return PTR_ERR(mxs->base);
 
-       pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
-       if (IS_ERR(pinctrl))
-               return PTR_ERR(pinctrl);
-
        mxs->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(mxs->clk))
                return PTR_ERR(mxs->clk);
@@ -188,6 +182,7 @@ MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids);
 static struct platform_driver mxs_pwm_driver = {
        .driver = {
                .name = "mxs-pwm",
+               .owner = THIS_MODULE,
                .of_match_table = of_match_ptr(mxs_pwm_dt_ids),
        },
        .probe = mxs_pwm_probe,
diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c
new file mode 100644 (file)
index 0000000..3fb775d
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * Driver for PCA9685 16-channel 12-bit PWM LED controller
+ *
+ * Copyright (C) 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de>
+ *
+ * based on the pwm-twl-led.c driver
+ *
+ * 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/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define PCA9685_MODE1          0x00
+#define PCA9685_MODE2          0x01
+#define PCA9685_SUBADDR1       0x02
+#define PCA9685_SUBADDR2       0x03
+#define PCA9685_SUBADDR3       0x04
+#define PCA9685_ALLCALLADDR    0x05
+#define PCA9685_LEDX_ON_L      0x06
+#define PCA9685_LEDX_ON_H      0x07
+#define PCA9685_LEDX_OFF_L     0x08
+#define PCA9685_LEDX_OFF_H     0x09
+
+#define PCA9685_ALL_LED_ON_L   0xFA
+#define PCA9685_ALL_LED_ON_H   0xFB
+#define PCA9685_ALL_LED_OFF_L  0xFC
+#define PCA9685_ALL_LED_OFF_H  0xFD
+#define PCA9685_PRESCALE       0xFE
+
+#define PCA9685_NUMREGS                0xFF
+#define PCA9685_MAXCHAN                0x10
+
+#define LED_FULL               (1 << 4)
+#define MODE1_SLEEP            (1 << 4)
+#define MODE2_INVRT            (1 << 4)
+#define MODE2_OUTDRV           (1 << 2)
+
+#define LED_N_ON_H(N)  (PCA9685_LEDX_ON_H + (4 * (N)))
+#define LED_N_ON_L(N)  (PCA9685_LEDX_ON_L + (4 * (N)))
+#define LED_N_OFF_H(N) (PCA9685_LEDX_OFF_H + (4 * (N)))
+#define LED_N_OFF_L(N) (PCA9685_LEDX_OFF_L + (4 * (N)))
+
+struct pca9685 {
+       struct pwm_chip chip;
+       struct regmap *regmap;
+       int active_cnt;
+};
+
+static inline struct pca9685 *to_pca(struct pwm_chip *chip)
+{
+       return container_of(chip, struct pca9685, chip);
+}
+
+static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                             int duty_ns, int period_ns)
+{
+       struct pca9685 *pca = to_pca(chip);
+       unsigned long long duty;
+       unsigned int reg;
+
+       if (duty_ns < 1) {
+               if (pwm->hwpwm >= PCA9685_MAXCHAN)
+                       reg = PCA9685_ALL_LED_OFF_H;
+               else
+                       reg = LED_N_OFF_H(pwm->hwpwm);
+
+               regmap_write(pca->regmap, reg, LED_FULL);
+
+               return 0;
+       }
+
+       if (duty_ns == period_ns) {
+               if (pwm->hwpwm >= PCA9685_MAXCHAN)
+                       reg = PCA9685_ALL_LED_ON_H;
+               else
+                       reg = LED_N_ON_H(pwm->hwpwm);
+
+               regmap_write(pca->regmap, reg, LED_FULL);
+
+               return 0;
+       }
+
+       duty = 4096 * (unsigned long long)duty_ns;
+       duty = DIV_ROUND_UP_ULL(duty, period_ns);
+
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_OFF_L;
+       else
+               reg = LED_N_OFF_L(pwm->hwpwm);
+
+       regmap_write(pca->regmap, reg, (int)duty & 0xff);
+
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_OFF_H;
+       else
+               reg = LED_N_OFF_H(pwm->hwpwm);
+
+       regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf);
+
+       return 0;
+}
+
+static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct pca9685 *pca = to_pca(chip);
+       unsigned int reg;
+
+       /*
+        * The PWM subsystem does not support a pre-delay.
+        * So, set the ON-timeout to 0
+        */
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_ON_L;
+       else
+               reg = LED_N_ON_L(pwm->hwpwm);
+
+       regmap_write(pca->regmap, reg, 0);
+
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_ON_H;
+       else
+               reg = LED_N_ON_H(pwm->hwpwm);
+
+       regmap_write(pca->regmap, reg, 0);
+
+       /*
+        * Clear the full-off bit.
+        * It has precedence over the others and must be off.
+        */
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_OFF_H;
+       else
+               reg = LED_N_OFF_H(pwm->hwpwm);
+
+       regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0);
+
+       return 0;
+}
+
+static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct pca9685 *pca = to_pca(chip);
+       unsigned int reg;
+
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_OFF_H;
+       else
+               reg = LED_N_OFF_H(pwm->hwpwm);
+
+       regmap_write(pca->regmap, reg, LED_FULL);
+
+       /* Clear the LED_OFF counter. */
+       if (pwm->hwpwm >= PCA9685_MAXCHAN)
+               reg = PCA9685_ALL_LED_OFF_L;
+       else
+               reg = LED_N_OFF_L(pwm->hwpwm);
+
+       regmap_write(pca->regmap, reg, 0x0);
+}
+
+static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct pca9685 *pca = to_pca(chip);
+
+       if (pca->active_cnt++ == 0)
+               return regmap_update_bits(pca->regmap, PCA9685_MODE1,
+                                         MODE1_SLEEP, 0x0);
+
+       return 0;
+}
+
+static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct pca9685 *pca = to_pca(chip);
+
+       if (--pca->active_cnt == 0)
+               regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP,
+                                  MODE1_SLEEP);
+}
+
+static const struct pwm_ops pca9685_pwm_ops = {
+       .enable = pca9685_pwm_enable,
+       .disable = pca9685_pwm_disable,
+       .config = pca9685_pwm_config,
+       .request = pca9685_pwm_request,
+       .free = pca9685_pwm_free,
+       .owner = THIS_MODULE,
+};
+
+static struct regmap_config pca9685_regmap_i2c_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = PCA9685_NUMREGS,
+       .cache_type = REGCACHE_NONE,
+};
+
+static int pca9685_pwm_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       struct device_node *np = client->dev.of_node;
+       struct pca9685 *pca;
+       int ret;
+       int mode2;
+
+       pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL);
+       if (!pca)
+               return -ENOMEM;
+
+       pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config);
+       if (IS_ERR(pca->regmap)) {
+               ret = PTR_ERR(pca->regmap);
+               dev_err(&client->dev, "Failed to initialize register map: %d\n",
+                       ret);
+               return ret;
+       }
+
+       i2c_set_clientdata(client, pca);
+
+       regmap_read(pca->regmap, PCA9685_MODE2, &mode2);
+
+       if (of_property_read_bool(np, "invert"))
+               mode2 |= MODE2_INVRT;
+       else
+               mode2 &= ~MODE2_INVRT;
+
+       if (of_property_read_bool(np, "open-drain"))
+               mode2 &= ~MODE2_OUTDRV;
+       else
+               mode2 |= MODE2_OUTDRV;
+
+       regmap_write(pca->regmap, PCA9685_MODE2, mode2);
+
+       /* clear all "full off" bits */
+       regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0);
+       regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0);
+
+       pca->chip.ops = &pca9685_pwm_ops;
+       /* add an extra channel for ALL_LED */
+       pca->chip.npwm = PCA9685_MAXCHAN + 1;
+
+       pca->chip.dev = &client->dev;
+       pca->chip.base = -1;
+       pca->chip.can_sleep = true;
+
+       return pwmchip_add(&pca->chip);
+}
+
+static int pca9685_pwm_remove(struct i2c_client *client)
+{
+       struct pca9685 *pca = i2c_get_clientdata(client);
+
+       regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP,
+                          MODE1_SLEEP);
+
+       return pwmchip_remove(&pca->chip);
+}
+
+static const struct i2c_device_id pca9685_id[] = {
+       { "pca9685", 0 },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, pca9685_id);
+
+static const struct of_device_id pca9685_dt_ids[] = {
+       { .compatible = "nxp,pca9685-pwm", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pca9685_dt_ids);
+
+static struct i2c_driver pca9685_i2c_driver = {
+       .driver = {
+               .name = "pca9685-pwm",
+               .owner = THIS_MODULE,
+               .of_match_table = pca9685_dt_ids,
+       },
+       .probe = pca9685_pwm_probe,
+       .remove = pca9685_pwm_remove,
+       .id_table = pca9685_id,
+};
+
+module_i2c_driver(pca9685_i2c_driver);
+
+MODULE_AUTHOR("Steffen Trumtrar <s.trumtrar@pengutronix.de>");
+MODULE_DESCRIPTION("PWM driver for PCA9685");
+MODULE_LICENSE("GPL");
index ed6007b27585a9512d5c2470248343b3e80882d4..a9a28083f245ca2f5efa8be46c6b3b79690170db 100644 (file)
@@ -146,6 +146,7 @@ static int pwm_remove(struct platform_device *pdev)
 static struct platform_driver puv3_pwm_driver = {
        .driver = {
                .name = "PKUnity-v3-PWM",
+               .owner = THIS_MODULE,
        },
        .probe = pwm_probe,
        .remove = pwm_remove,
diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c
new file mode 100644 (file)
index 0000000..2600892
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * R-Mobile TPU PWM driver
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * 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
+ *
+ * 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/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/pwm-renesas-tpu.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define TPU_TSTR               0x00    /* Timer start register (shared) */
+
+#define TPU_TCRn               0x00    /* Timer control register */
+#define TPU_TCR_CCLR_NONE      (0 << 5)
+#define TPU_TCR_CCLR_TGRA      (1 << 5)
+#define TPU_TCR_CCLR_TGRB      (2 << 5)
+#define TPU_TCR_CCLR_TGRC      (5 << 5)
+#define TPU_TCR_CCLR_TGRD      (6 << 5)
+#define TPU_TCR_CKEG_RISING    (0 << 3)
+#define TPU_TCR_CKEG_FALLING   (1 << 3)
+#define TPU_TCR_CKEG_BOTH      (2 << 3)
+#define TPU_TMDRn              0x04    /* Timer mode register */
+#define TPU_TMDR_BFWT          (1 << 6)
+#define TPU_TMDR_BFB           (1 << 5)
+#define TPU_TMDR_BFA           (1 << 4)
+#define TPU_TMDR_MD_NORMAL     (0 << 0)
+#define TPU_TMDR_MD_PWM                (2 << 0)
+#define TPU_TIORn              0x08    /* Timer I/O control register */
+#define TPU_TIOR_IOA_0         (0 << 0)
+#define TPU_TIOR_IOA_0_CLR     (1 << 0)
+#define TPU_TIOR_IOA_0_SET     (2 << 0)
+#define TPU_TIOR_IOA_0_TOGGLE  (3 << 0)
+#define TPU_TIOR_IOA_1         (4 << 0)
+#define TPU_TIOR_IOA_1_CLR     (5 << 0)
+#define TPU_TIOR_IOA_1_SET     (6 << 0)
+#define TPU_TIOR_IOA_1_TOGGLE  (7 << 0)
+#define TPU_TIERn              0x0c    /* Timer interrupt enable register */
+#define TPU_TSRn               0x10    /* Timer status register */
+#define TPU_TCNTn              0x14    /* Timer counter */
+#define TPU_TGRAn              0x18    /* Timer general register A */
+#define TPU_TGRBn              0x1c    /* Timer general register B */
+#define TPU_TGRCn              0x20    /* Timer general register C */
+#define TPU_TGRDn              0x24    /* Timer general register D */
+
+#define TPU_CHANNEL_OFFSET     0x10
+#define TPU_CHANNEL_SIZE       0x40
+
+enum tpu_pin_state {
+       TPU_PIN_INACTIVE,               /* Pin is driven inactive */
+       TPU_PIN_PWM,                    /* Pin is driven by PWM */
+       TPU_PIN_ACTIVE,                 /* Pin is driven active */
+};
+
+struct tpu_device;
+
+struct tpu_pwm_device {
+       bool timer_on;                  /* Whether the timer is running */
+
+       struct tpu_device *tpu;
+       unsigned int channel;           /* Channel number in the TPU */
+
+       enum pwm_polarity polarity;
+       unsigned int prescaler;
+       u16 period;
+       u16 duty;
+};
+
+struct tpu_device {
+       struct platform_device *pdev;
+       struct tpu_pwm_platform_data *pdata;
+       struct pwm_chip chip;
+       spinlock_t lock;
+
+       void __iomem *base;
+       struct clk *clk;
+};
+
+#define to_tpu_device(c)       container_of(c, struct tpu_device, chip)
+
+static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value)
+{
+       void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET
+                          + pwm->channel * TPU_CHANNEL_SIZE;
+
+       iowrite16(value, base + reg_nr);
+}
+
+static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm,
+                           enum tpu_pin_state state)
+{
+       static const char * const states[] = { "inactive", "PWM", "active" };
+
+       dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n",
+               pwm->channel, states[state]);
+
+       switch (state) {
+       case TPU_PIN_INACTIVE:
+               tpu_pwm_write(pwm, TPU_TIORn,
+                             pwm->polarity == PWM_POLARITY_INVERSED ?
+                             TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0);
+               break;
+       case TPU_PIN_PWM:
+               tpu_pwm_write(pwm, TPU_TIORn,
+                             pwm->polarity == PWM_POLARITY_INVERSED ?
+                             TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR);
+               break;
+       case TPU_PIN_ACTIVE:
+               tpu_pwm_write(pwm, TPU_TIORn,
+                             pwm->polarity == PWM_POLARITY_INVERSED ?
+                             TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1);
+               break;
+       }
+}
+
+static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start)
+{
+       unsigned long flags;
+       u16 value;
+
+       spin_lock_irqsave(&pwm->tpu->lock, flags);
+       value = ioread16(pwm->tpu->base + TPU_TSTR);
+
+       if (start)
+               value |= 1 << pwm->channel;
+       else
+               value &= ~(1 << pwm->channel);
+
+       iowrite16(value, pwm->tpu->base + TPU_TSTR);
+       spin_unlock_irqrestore(&pwm->tpu->lock, flags);
+}
+
+static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
+{
+       int ret;
+
+       if (!pwm->timer_on) {
+               /* Wake up device and enable clock. */
+               pm_runtime_get_sync(&pwm->tpu->pdev->dev);
+               ret = clk_prepare_enable(pwm->tpu->clk);
+               if (ret) {
+                       dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n");
+                       return ret;
+               }
+               pwm->timer_on = true;
+       }
+
+       /*
+        * Make sure the channel is stopped, as we need to reconfigure it
+        * completely. First drive the pin to the inactive state to avoid
+        * glitches.
+        */
+       tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
+       tpu_pwm_start_stop(pwm, false);
+
+       /*
+        * - Clear TCNT on TGRB match
+        * - Count on rising edge
+        * - Set prescaler
+        * - Output 0 until TGRA, output 1 until TGRB (active low polarity)
+        * - Output 1 until TGRA, output 0 until TGRB (active high polarity
+        * - PWM mode
+        */
+       tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
+                     pwm->prescaler);
+       tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM);
+       tpu_pwm_set_pin(pwm, TPU_PIN_PWM);
+       tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
+       tpu_pwm_write(pwm, TPU_TGRBn, pwm->period);
+
+       dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
+               pwm->channel, pwm->duty, pwm->period);
+
+       /* Start the channel. */
+       tpu_pwm_start_stop(pwm, true);
+
+       return 0;
+}
+
+static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm)
+{
+       if (!pwm->timer_on)
+               return;
+
+       /* Disable channel. */
+       tpu_pwm_start_stop(pwm, false);
+
+       /* Stop clock and mark device as idle. */
+       clk_disable_unprepare(pwm->tpu->clk);
+       pm_runtime_put(&pwm->tpu->pdev->dev);
+
+       pwm->timer_on = false;
+}
+
+/* -----------------------------------------------------------------------------
+ * PWM API
+ */
+
+static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm)
+{
+       struct tpu_device *tpu = to_tpu_device(chip);
+       struct tpu_pwm_device *pwm;
+
+       if (_pwm->hwpwm >= TPU_CHANNEL_MAX)
+               return -EINVAL;
+
+       pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
+       if (pwm == NULL)
+               return -ENOMEM;
+
+       pwm->tpu = tpu;
+       pwm->channel = _pwm->hwpwm;
+       pwm->polarity = tpu->pdata ? tpu->pdata->channels[pwm->channel].polarity
+                     : PWM_POLARITY_NORMAL;
+       pwm->prescaler = 0;
+       pwm->period = 0;
+       pwm->duty = 0;
+
+       pwm->timer_on = false;
+
+       pwm_set_chip_data(_pwm, pwm);
+
+       return 0;
+}
+
+static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm)
+{
+       struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+
+       tpu_pwm_timer_stop(pwm);
+       kfree(pwm);
+}
+
+static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
+                         int duty_ns, int period_ns)
+{
+       static const unsigned int prescalers[] = { 1, 4, 16, 64 };
+       struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+       struct tpu_device *tpu = to_tpu_device(chip);
+       unsigned int prescaler;
+       bool duty_only = false;
+       u32 clk_rate;
+       u32 period;
+       u32 duty;
+       int ret;
+
+       /*
+        * Pick a prescaler to avoid overflowing the counter.
+        * TODO: Pick the highest acceptable prescaler.
+        */
+       clk_rate = clk_get_rate(tpu->clk);
+
+       for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
+               period = clk_rate / prescalers[prescaler]
+                      / (NSEC_PER_SEC / period_ns);
+               if (period <= 0xffff)
+                       break;
+       }
+
+       if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
+               dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
+               return -ENOTSUPP;
+       }
+
+       if (duty_ns) {
+               duty = clk_rate / prescalers[prescaler]
+                    / (NSEC_PER_SEC / duty_ns);
+               if (duty > period)
+                       return -EINVAL;
+       } else {
+               duty = 0;
+       }
+
+       dev_dbg(&tpu->pdev->dev,
+               "rate %u, prescaler %u, period %u, duty %u\n",
+               clk_rate, prescalers[prescaler], period, duty);
+
+       if (pwm->prescaler == prescaler && pwm->period == period)
+               duty_only = true;
+
+       pwm->prescaler = prescaler;
+       pwm->period = period;
+       pwm->duty = duty;
+
+       /* If the channel is disabled we're done. */
+       if (!test_bit(PWMF_ENABLED, &_pwm->flags))
+               return 0;
+
+       if (duty_only && pwm->timer_on) {
+               /*
+                * If only the duty cycle changed and the timer is already
+                * running, there's no need to reconfigure it completely, Just
+                * modify the duty cycle.
+                */
+               tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
+               dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel,
+                       pwm->duty);
+       } else {
+               /* Otherwise perform a full reconfiguration. */
+               ret = tpu_pwm_timer_start(pwm);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (duty == 0 || duty == period) {
+               /*
+                * To avoid running the timer when not strictly required, handle
+                * 0% and 100% duty cycles as fixed levels and stop the timer.
+                */
+               tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
+               tpu_pwm_timer_stop(pwm);
+       }
+
+       return 0;
+}
+
+static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm,
+                               enum pwm_polarity polarity)
+{
+       struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+
+       pwm->polarity = polarity;
+
+       return 0;
+}
+
+static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
+{
+       struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+       int ret;
+
+       ret = tpu_pwm_timer_start(pwm);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * To avoid running the timer when not strictly required, handle 0% and
+        * 100% duty cycles as fixed levels and stop the timer.
+        */
+       if (pwm->duty == 0 || pwm->duty == pwm->period) {
+               tpu_pwm_set_pin(pwm, pwm->duty ?
+                               TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
+               tpu_pwm_timer_stop(pwm);
+       }
+
+       return 0;
+}
+
+static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm)
+{
+       struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+
+       /* The timer must be running to modify the pin output configuration. */
+       tpu_pwm_timer_start(pwm);
+       tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
+       tpu_pwm_timer_stop(pwm);
+}
+
+static const struct pwm_ops tpu_pwm_ops = {
+       .request = tpu_pwm_request,
+       .free = tpu_pwm_free,
+       .config = tpu_pwm_config,
+       .set_polarity = tpu_pwm_set_polarity,
+       .enable = tpu_pwm_enable,
+       .disable = tpu_pwm_disable,
+       .owner = THIS_MODULE,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe and remove
+ */
+
+static int tpu_probe(struct platform_device *pdev)
+{
+       struct tpu_device *tpu;
+       struct resource *res;
+       int ret;
+
+       tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL);
+       if (tpu == NULL) {
+               dev_err(&pdev->dev, "failed to allocate driver data\n");
+               return -ENOMEM;
+       }
+
+       tpu->pdata = pdev->dev.platform_data;
+
+       /* Map memory, get clock and pin control. */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "failed to get I/O memory\n");
+               return -ENXIO;
+       }
+
+       tpu->base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(tpu->base))
+               return PTR_ERR(tpu->base);
+
+       tpu->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(tpu->clk)) {
+               dev_err(&pdev->dev, "cannot get clock\n");
+               return PTR_ERR(tpu->clk);
+       }
+
+       /* Initialize and register the device. */
+       platform_set_drvdata(pdev, tpu);
+
+       spin_lock_init(&tpu->lock);
+       tpu->pdev = pdev;
+
+       tpu->chip.dev = &pdev->dev;
+       tpu->chip.ops = &tpu_pwm_ops;
+       tpu->chip.base = -1;
+       tpu->chip.npwm = TPU_CHANNEL_MAX;
+
+       ret = pwmchip_add(&tpu->chip);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to register PWM chip\n");
+               return ret;
+       }
+
+       dev_info(&pdev->dev, "TPU PWM %d registered\n", tpu->pdev->id);
+
+       pm_runtime_enable(&pdev->dev);
+
+       return 0;
+}
+
+static int tpu_remove(struct platform_device *pdev)
+{
+       struct tpu_device *tpu = platform_get_drvdata(pdev);
+       int ret;
+
+       ret = pwmchip_remove(&tpu->chip);
+       if (ret)
+               return ret;
+
+       pm_runtime_disable(&pdev->dev);
+
+       return 0;
+}
+
+static struct platform_driver tpu_driver = {
+       .probe          = tpu_probe,
+       .remove         = tpu_remove,
+       .driver         = {
+               .name   = "renesas-tpu-pwm",
+               .owner  = THIS_MODULE,
+       }
+};
+
+module_platform_driver(tpu_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas TPU PWM Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:renesas-tpu-pwm");
index 6d99e2cbdc739eb0c4dd84a998d6d69a37013402..a54d21401431d6caeb06b7ecd75da5b15c02fbfe 100644 (file)
@@ -259,6 +259,7 @@ MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
 static struct platform_driver spear_pwm_driver = {
        .driver = {
                .name = "spear-pwm",
+               .owner = THIS_MODULE,
                .of_match_table = spear_pwm_of_match,
        },
        .probe = spear_pwm_probe,
index a5402933001f9cf446192d79b1ddf204e748e2a1..74298c561c4e57f303f675a13987f6b70ecb5dc4 100644 (file)
@@ -239,6 +239,7 @@ MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
 static struct platform_driver tegra_pwm_driver = {
        .driver = {
                .name = "tegra-pwm",
+               .owner = THIS_MODULE,
                .of_match_table = tegra_pwm_of_match,
        },
        .probe = tegra_pwm_probe,
index 48a485c2e4224a334b49dda3f1a8848aaecb2bfa..aa4c5586f53b1c2d11eab6541d256b5ccd812a9b 100644 (file)
@@ -359,7 +359,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
        configure_polarity(pc, pwm->hwpwm);
 
        /* Enable TBCLK before enabling PWM device */
-       ret = clk_prepare_enable(pc->tbclk);
+       ret = clk_enable(pc->tbclk);
        if (ret) {
                pr_err("Failed to enable TBCLK for %s\n",
                                dev_name(pc->chip.dev));
@@ -395,7 +395,7 @@ 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_unprepare(pc->tbclk);
+       clk_disable(pc->tbclk);
 
        /* Stop Time base counter */
        ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT);
@@ -482,6 +482,12 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
                return PTR_ERR(pc->tbclk);
        }
 
+       ret = clk_prepare(pc->tbclk);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret);
+               return ret;
+       }
+
        ret = pwmchip_add(&pc->chip);
        if (ret < 0) {
                dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
@@ -508,6 +514,7 @@ pwmss_clk_failure:
        pm_runtime_put_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
        pwmchip_remove(&pc->chip);
+       clk_unprepare(pc->tbclk);
        return ret;
 }
 
@@ -515,6 +522,8 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
 {
        struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev);
 
+       clk_unprepare(pc->tbclk);
+
        pm_runtime_get_sync(&pdev->dev);
        /*
         * Due to hardware misbehaviour, acknowledge of the stop_req
diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c
new file mode 100644 (file)
index 0000000..8ca5de3
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ * A simple sysfs interface for the generic PWM framework
+ *
+ * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on previous work by Lars Poeschel <poeschel@lemonage.de>
+ *
+ * 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, 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/device.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/kdev_t.h>
+#include <linux/pwm.h>
+
+struct pwm_export {
+       struct device child;
+       struct pwm_device *pwm;
+};
+
+static struct pwm_export *child_to_pwm_export(struct device *child)
+{
+       return container_of(child, struct pwm_export, child);
+}
+
+static struct pwm_device *child_to_pwm_device(struct device *child)
+{
+       struct pwm_export *export = child_to_pwm_export(child);
+
+       return export->pwm;
+}
+
+static ssize_t pwm_period_show(struct device *child,
+                              struct device_attribute *attr,
+                              char *buf)
+{
+       const struct pwm_device *pwm = child_to_pwm_device(child);
+
+       return sprintf(buf, "%u\n", pwm->period);
+}
+
+static ssize_t pwm_period_store(struct device *child,
+                               struct device_attribute *attr,
+                               const char *buf, size_t size)
+{
+       struct pwm_device *pwm = child_to_pwm_device(child);
+       unsigned int val;
+       int ret;
+
+       ret = kstrtouint(buf, 0, &val);
+       if (ret)
+               return ret;
+
+       ret = pwm_config(pwm, pwm->duty_cycle, val);
+
+       return ret ? : size;
+}
+
+static ssize_t pwm_duty_cycle_show(struct device *child,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+       const struct pwm_device *pwm = child_to_pwm_device(child);
+
+       return sprintf(buf, "%u\n", pwm->duty_cycle);
+}
+
+static ssize_t pwm_duty_cycle_store(struct device *child,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t size)
+{
+       struct pwm_device *pwm = child_to_pwm_device(child);
+       unsigned int val;
+       int ret;
+
+       ret = kstrtouint(buf, 0, &val);
+       if (ret)
+               return ret;
+
+       ret = pwm_config(pwm, val, pwm->period);
+
+       return ret ? : size;
+}
+
+static ssize_t pwm_enable_show(struct device *child,
+                              struct device_attribute *attr,
+                              char *buf)
+{
+       const struct pwm_device *pwm = child_to_pwm_device(child);
+       int enabled = test_bit(PWMF_ENABLED, &pwm->flags);
+
+       return sprintf(buf, "%d\n", enabled);
+}
+
+static ssize_t pwm_enable_store(struct device *child,
+                               struct device_attribute *attr,
+                               const char *buf, size_t size)
+{
+       struct pwm_device *pwm = child_to_pwm_device(child);
+       int val, ret;
+
+       ret = kstrtoint(buf, 0, &val);
+       if (ret)
+               return ret;
+
+       switch (val) {
+       case 0:
+               pwm_disable(pwm);
+               break;
+       case 1:
+               ret = pwm_enable(pwm);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret ? : size;
+}
+
+static ssize_t pwm_polarity_show(struct device *child,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       const struct pwm_device *pwm = child_to_pwm_device(child);
+
+       return sprintf(buf, "%s\n", pwm->polarity ? "inversed" : "normal");
+}
+
+static ssize_t pwm_polarity_store(struct device *child,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t size)
+{
+       struct pwm_device *pwm = child_to_pwm_device(child);
+       enum pwm_polarity polarity;
+       int ret;
+
+       if (sysfs_streq(buf, "normal"))
+               polarity = PWM_POLARITY_NORMAL;
+       else if (sysfs_streq(buf, "inversed"))
+               polarity = PWM_POLARITY_INVERSED;
+       else
+               return -EINVAL;
+
+       ret = pwm_set_polarity(pwm, polarity);
+
+       return ret ? : size;
+}
+
+static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store);
+static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store);
+static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store);
+static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
+
+static struct attribute *pwm_attrs[] = {
+       &dev_attr_period.attr,
+       &dev_attr_duty_cycle.attr,
+       &dev_attr_enable.attr,
+       &dev_attr_polarity.attr,
+       NULL
+};
+
+static const struct attribute_group pwm_attr_group = {
+       .attrs          = pwm_attrs,
+};
+
+static const struct attribute_group *pwm_attr_groups[] = {
+       &pwm_attr_group,
+       NULL,
+};
+
+static void pwm_export_release(struct device *child)
+{
+       struct pwm_export *export = child_to_pwm_export(child);
+
+       kfree(export);
+}
+
+static int pwm_export_child(struct device *parent, struct pwm_device *pwm)
+{
+       struct pwm_export *export;
+       int ret;
+
+       if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags))
+               return -EBUSY;
+
+       export = kzalloc(sizeof(*export), GFP_KERNEL);
+       if (!export) {
+               clear_bit(PWMF_EXPORTED, &pwm->flags);
+               return -ENOMEM;
+       }
+
+       export->pwm = pwm;
+
+       export->child.release = pwm_export_release;
+       export->child.parent = parent;
+       export->child.devt = MKDEV(0, 0);
+       export->child.groups = pwm_attr_groups;
+       dev_set_name(&export->child, "pwm%u", pwm->hwpwm);
+
+       ret = device_register(&export->child);
+       if (ret) {
+               clear_bit(PWMF_EXPORTED, &pwm->flags);
+               kfree(export);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int pwm_unexport_match(struct device *child, void *data)
+{
+       return child_to_pwm_device(child) == data;
+}
+
+static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm)
+{
+       struct device *child;
+
+       if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags))
+               return -ENODEV;
+
+       child = device_find_child(parent, pwm, pwm_unexport_match);
+       if (!child)
+               return -ENODEV;
+
+       /* for device_find_child() */
+       put_device(child);
+       device_unregister(child);
+       pwm_put(pwm);
+
+       return 0;
+}
+
+static ssize_t pwm_export_store(struct device *parent,
+                               struct device_attribute *attr,
+                               const char *buf, size_t len)
+{
+       struct pwm_chip *chip = dev_get_drvdata(parent);
+       struct pwm_device *pwm;
+       unsigned int hwpwm;
+       int ret;
+
+       ret = kstrtouint(buf, 0, &hwpwm);
+       if (ret < 0)
+               return ret;
+
+       if (hwpwm >= chip->npwm)
+               return -ENODEV;
+
+       pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
+       if (IS_ERR(pwm))
+               return PTR_ERR(pwm);
+
+       ret = pwm_export_child(parent, pwm);
+       if (ret < 0)
+               pwm_put(pwm);
+
+       return ret ? : len;
+}
+
+static ssize_t pwm_unexport_store(struct device *parent,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t len)
+{
+       struct pwm_chip *chip = dev_get_drvdata(parent);
+       unsigned int hwpwm;
+       int ret;
+
+       ret = kstrtouint(buf, 0, &hwpwm);
+       if (ret < 0)
+               return ret;
+
+       if (hwpwm >= chip->npwm)
+               return -ENODEV;
+
+       ret = pwm_unexport_child(parent, &chip->pwms[hwpwm]);
+
+       return ret ? : len;
+}
+
+static ssize_t pwm_npwm_show(struct device *parent,
+                            struct device_attribute *attr,
+                            char *buf)
+{
+       const struct pwm_chip *chip = dev_get_drvdata(parent);
+
+       return sprintf(buf, "%u\n", chip->npwm);
+}
+
+static struct device_attribute pwm_chip_attrs[] = {
+       __ATTR(export, 0200, NULL, pwm_export_store),
+       __ATTR(unexport, 0200, NULL, pwm_unexport_store),
+       __ATTR(npwm, 0444, pwm_npwm_show, NULL),
+       __ATTR_NULL,
+};
+
+static struct class pwm_class = {
+       .name           = "pwm",
+       .owner          = THIS_MODULE,
+       .dev_attrs      = pwm_chip_attrs,
+};
+
+static int pwmchip_sysfs_match(struct device *parent, const void *data)
+{
+       return dev_get_drvdata(parent) == data;
+}
+
+void pwmchip_sysfs_export(struct pwm_chip *chip)
+{
+       struct device *parent;
+
+       /*
+        * If device_create() fails the pwm_chip is still usable by
+        * the kernel its just not exported.
+        */
+       parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip,
+                              "pwmchip%d", chip->base);
+       if (IS_ERR(parent)) {
+               dev_warn(chip->dev,
+                        "device_create failed for pwm_chip sysfs export\n");
+       }
+}
+
+void pwmchip_sysfs_unexport(struct pwm_chip *chip)
+{
+       struct device *parent;
+
+       parent = class_find_device(&pwm_class, NULL, chip,
+                                  pwmchip_sysfs_match);
+       if (parent) {
+               /* for class_find_device() */
+               put_device(parent);
+               device_unregister(parent);
+       }
+}
+
+static int __init pwm_sysfs_init(void)
+{
+       return class_register(&pwm_class);
+}
+subsys_initcall(pwm_sysfs_init);
diff --git a/include/linux/platform_data/pwm-renesas-tpu.h b/include/linux/platform_data/pwm-renesas-tpu.h
new file mode 100644 (file)
index 0000000..a7220b1
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __PWM_RENESAS_TPU_H__
+#define __PWM_RENESAS_TPU_H__
+
+#include <linux/pwm.h>
+
+#define TPU_CHANNEL_MAX                4
+
+struct tpu_pwm_channel_data {
+       enum pwm_polarity polarity;
+};
+
+struct tpu_pwm_platform_data {
+       struct tpu_pwm_channel_data channels[TPU_CHANNEL_MAX];
+};
+
+#endif /* __PWM_RENESAS_TPU_H__ */
index a4df2042b79c15f75bf53dd8d185fd6c80426b05..f0feafd184a0d655fff326bc979eebd21a7a28ab 100644 (file)
@@ -76,6 +76,7 @@ enum pwm_polarity {
 enum {
        PWMF_REQUESTED = 1 << 0,
        PWMF_ENABLED = 1 << 1,
+       PWMF_EXPORTED = 1 << 2,
 };
 
 struct pwm_device {
@@ -86,7 +87,9 @@ struct pwm_device {
        struct pwm_chip         *chip;
        void                    *chip_data;
 
-       unsigned int            period; /* in nanoseconds */
+       unsigned int            period;         /* in nanoseconds */
+       unsigned int            duty_cycle;     /* in nanoseconds */
+       enum pwm_polarity       polarity;
 };
 
 static inline void pwm_set_period(struct pwm_device *pwm, unsigned int period)
@@ -100,6 +103,17 @@ static inline unsigned int pwm_get_period(struct pwm_device *pwm)
        return pwm ? pwm->period : 0;
 }
 
+static inline void pwm_set_duty_cycle(struct pwm_device *pwm, unsigned int duty)
+{
+       if (pwm)
+               pwm->duty_cycle = duty;
+}
+
+static inline unsigned int pwm_get_duty_cycle(struct pwm_device *pwm)
+{
+       return pwm ? pwm->duty_cycle : 0;
+}
+
 /*
  * pwm_set_polarity - configure the polarity of a PWM signal
  */
@@ -278,4 +292,17 @@ static inline void pwm_add_table(struct pwm_lookup *table, size_t num)
 }
 #endif
 
+#ifdef CONFIG_PWM_SYSFS
+void pwmchip_sysfs_export(struct pwm_chip *chip);
+void pwmchip_sysfs_unexport(struct pwm_chip *chip);
+#else
+static inline void pwmchip_sysfs_export(struct pwm_chip *chip)
+{
+}
+
+static inline void pwmchip_sysfs_unexport(struct pwm_chip *chip)
+{
+}
+#endif /* CONFIG_PWM_SYSFS */
+
 #endif /* __LINUX_PWM_H */