2012-11-07 14:44:56

by Peter Ujfalusi

[permalink] [raw]
Subject: [PATCH 0/3] pwm: Drivers for twl4030/6030 PWMs and LEDs

Hello,

The currently available pwm-twl6030.c driver only supports TWL6030's Charging
indication LED.
Remove this driver and add two new ones which implements support for all PWM
driven outputs:
pwm-twl driver supports twl4030 (PWM 0/1) and twl6030 (PWM 1/2) instances
pwm-twl-led driver supports twl4030 (PWM driven LED A/B ports) and twl6030's
Charging indication LED (PWM driven).

Regards,
Peter
---
Peter Ujfalusi (3):
pwm: Remove pwm-twl6030 driver
pwm: New driver to support PWMs on TWL4030/6030 series of PMICs
pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of
PMICs

drivers/pwm/Kconfig | 19 ++-
drivers/pwm/Makefile | 3 +-
drivers/pwm/pwm-twl-led.c | 287 +++++++++++++++++++++++++++++++++++++++++++
drivers/pwm/pwm-twl.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/pwm/pwm-twl6030.c | 184 ----------------------------
5 files changed, 608 insertions(+), 189 deletions(-)
create mode 100644 drivers/pwm/pwm-twl-led.c
create mode 100644 drivers/pwm/pwm-twl.c
delete mode 100644 drivers/pwm/pwm-twl6030.c

--
1.8.0


2012-11-07 14:44:58

by Peter Ujfalusi

[permalink] [raw]
Subject: [PATCH 2/3] pwm: New driver to support PWMs on TWL4030/6030 series of PMICs

The driver supports the following PWM outputs:
TWL4030 PWM0 and PWM1
TWL6030 PWM1 and PWM2

On TWL4030 the PWM signals are muxed. Upon requesting the PWM the driver
will select the correct mux so the PWM can be used. When the PWM has been
freed the original configuration going to be restored.

Signed-off-by: Peter Ujfalusi <[email protected]>
---
drivers/pwm/Kconfig | 10 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-twl.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 315 insertions(+)
create mode 100644 drivers/pwm/pwm-twl.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index e678005..c577db9 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -142,6 +142,16 @@ config PWM_TIEHRPWM
To compile this driver as a module, choose M here: the module
will be called pwm-tiehrpwm.

+config PWM_TWL
+ tristate "TWL4030/6030 PWM support"
+ select HAVE_PWM
+ 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_VT8500
tristate "vt8500 pwm support"
depends on ARCH_VT8500
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 29cf57e..3324c06 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -11,4 +11,5 @@ obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.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_TWL) += pwm-twl.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
diff --git a/drivers/pwm/pwm-twl.c b/drivers/pwm/pwm-twl.c
new file mode 100644
index 0000000..0821ffe
--- /dev/null
+++ b/drivers/pwm/pwm-twl.c
@@ -0,0 +1,304 @@
+/*
+ * Driver for TWL4030/6030 Generic Pulse Width Modulator
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Peter Ujfalusi <[email protected]>
+ *
+ * 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 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;
+ u8 twl6030_toggle3;
+ u8 twl4030_pwm_mux;
+};
+
+static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ int duty_ns, int period_ns)
+{
+ int duty_cycle = (duty_ns * TWL_PWM_MAX) / period_ns;
+ u8 on_time;
+ u8 pwm_config[2];
+ int base, ret;
+
+ if (duty_cycle >= TWL_PWM_MAX)
+ on_time = TWL_PWM_MAX;
+ else if (!duty_cycle)
+ on_time = TWL_PWM_MAX - 1;
+ else
+ on_time = TWL_PWM_MAX - duty_cycle;
+
+ base = pwm->hwpwm * 3;
+
+ pwm_config[0] = on_time;
+ pwm_config[1] = TWL_PWM_MAX;
+
+ 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)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return ret;
+ }
+
+ val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_BITS);
+
+ 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);
+
+ return ret;
+}
+
+static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return;
+ }
+
+ val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_BITS);
+
+ 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);
+}
+
+static int twl4030_pwm_request(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, bits;
+
+ 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);
+ return ret;
+ }
+
+ if (pwm->hwpwm) {
+ /* PWM 1 */
+ mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
+ bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
+ } else {
+ /* PWM 0 */
+ mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
+ bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
+ }
+
+ /* 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);
+
+ 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;
+
+ 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);
+ return;
+ }
+
+ if (pwm->hwpwm)
+ /* PWM 1 */
+ mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
+ else
+ /* PWM 0 */
+ mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
+
+ /* 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);
+}
+
+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;
+
+ 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);
+ return ret;
+ }
+
+ twl->twl6030_toggle3 = val;
+
+ 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;
+
+ 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);
+ return;
+ }
+
+ 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);
+ return;
+ }
+
+ twl->twl6030_toggle3 = val;
+}
+
+static struct pwm_ops twl_pwm_ops = {
+ .config = twl_pwm_config,
+};
+
+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_pwm_ops.enable = twl4030_pwm_enable;
+ twl_pwm_ops.disable = twl4030_pwm_disable;
+ twl_pwm_ops.request = twl4030_pwm_request;
+ twl_pwm_ops.free = twl4030_pwm_free;
+ } else {
+ twl_pwm_ops.enable = twl6030_pwm_enable;
+ twl_pwm_ops.disable = twl6030_pwm_disable;
+ }
+
+ twl->chip.dev = &pdev->dev;
+ twl->chip.ops = &twl_pwm_ops;
+ twl->chip.base = -1;
+ twl->chip.npwm = 2;
+
+ 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);
+}
+
+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);
+
+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 = __devexit_p(twl_pwm_remove),
+};
+module_platform_driver(twl_pwm_driver);
+
+MODULE_AUTHOR("Peter Ujfalusi <[email protected]>");
+MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030");
+MODULE_ALIAS("platform:twl-pwm");
+MODULE_LICENSE("GPL");
--
1.8.0

2012-11-07 14:45:45

by Peter Ujfalusi

[permalink] [raw]
Subject: [PATCH 3/3] pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of PMICs

The driver supports the following LED outputs as generic PWM driver:
TWL4030 LEDA and LEDB (PWMA and PWMB)
TWL6030 Charging indicator LED (PWM LED)

On TWL6030 when the PWM requested LED is configured to be controlled by SW.
In this case the user can enable/disable and set the duty period freely.
When the PWM has been freed, the LED driver is put back to HW control.

Signed-off-by: Peter Ujfalusi <[email protected]>
---
drivers/pwm/Kconfig | 10 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-twl-led.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 298 insertions(+)
create mode 100644 drivers/pwm/pwm-twl-led.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index c577db9..49c2082 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -152,6 +152,16 @@ config PWM_TWL
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"
+ select HAVE_PWM
+ depends on TWL4030_CORE
+ help
+ Generic PWM framework driver for TWL4030/6030 LED.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-twl-led.
+
config PWM_VT8500
tristate "vt8500 pwm support"
depends on ARCH_VT8500
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 3324c06..5f26134 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -12,4 +12,5 @@ obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
obj-$(CONFIG_PWM_TWL) += pwm-twl.o
+obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c
new file mode 100644
index 0000000..4d6ddc9
--- /dev/null
+++ b/drivers/pwm/pwm-twl-led.c
@@ -0,0 +1,287 @@
+/*
+ * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Peter Ujfalusi <[email protected]>
+ *
+ * This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
+ * Hemanth V <[email protected]>
+ *
+ * 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 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;
+};
+
+static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ int duty_ns, int period_ns)
+{
+ int duty_cycle = (duty_ns * TWL4030_LED_MAX) / period_ns;
+ u8 on_time;
+ u8 pwm_config[2];
+ int base, ret;
+
+ if (duty_cycle >= TWL4030_LED_MAX)
+ on_time = TWL4030_LED_MAX;
+ else if (!duty_cycle)
+ on_time = TWL4030_LED_MAX - 1;
+ else
+ on_time = TWL4030_LED_MAX - duty_cycle;
+
+ base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
+
+ pwm_config[0] = on_time;
+ pwm_config[1] = TWL4030_LED_MAX;
+
+ 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)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return ret;
+ }
+
+ 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);
+
+ return ret;
+}
+
+static void twl4030_pwmled_disable(struct pwm_chip *chip,
+ struct pwm_device *pwm)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return;
+ }
+
+ 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);
+}
+
+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)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return ret;
+ }
+
+ 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);
+
+ return ret;
+}
+
+static void twl6030_pwmled_disable(struct pwm_chip *chip,
+ struct pwm_device *pwm)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return;
+ }
+
+ 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);
+}
+
+static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return ret;
+ }
+
+ 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);
+
+ return ret;
+}
+
+static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ int ret;
+ u8 val;
+
+ 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);
+ return;
+ }
+
+ 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);
+}
+
+static struct pwm_ops twl_pwmled_ops;
+
+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_pwmled_ops.enable = twl4030_pwmled_enable;
+ twl_pwmled_ops.disable = twl4030_pwmled_disable;
+ twl_pwmled_ops.config = twl4030_pwmled_config;
+ twl->chip.npwm = 2;
+ } else {
+ twl_pwmled_ops.enable = twl6030_pwmled_enable;
+ twl_pwmled_ops.disable = twl6030_pwmled_disable;
+ twl_pwmled_ops.config = twl6030_pwmled_config;
+ twl_pwmled_ops.request = twl6030_pwmled_request;
+ twl_pwmled_ops.free = twl6030_pwmled_free;
+ twl->chip.npwm = 1;
+ }
+
+ twl->chip.dev = &pdev->dev;
+ twl->chip.ops = &twl_pwmled_ops;
+ twl->chip.base = -1;
+
+ 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);
+}
+
+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);
+
+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 = __devexit_p(twl_pwmled_remove),
+};
+module_platform_driver(twl_pwmled_driver);
+
+MODULE_AUTHOR("Peter Ujfalusi <[email protected]>");
+MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs");
+MODULE_ALIAS("platform:twl-pwm");
+MODULE_LICENSE("GPL");
--
1.8.0

2012-11-07 14:46:10

by Peter Ujfalusi

[permalink] [raw]
Subject: [PATCH 1/3] pwm: Remove pwm-twl6030 driver

This driver only supported the Charging indicator LED.
New set of drivers going to provide support for both PWMs and LEDs for twl4030
and twl6030 series of PMICs.

Signed-off-by: Peter Ujfalusi <[email protected]>
---
drivers/pwm/Kconfig | 9 ---
drivers/pwm/Makefile | 1 -
drivers/pwm/pwm-twl6030.c | 184 ----------------------------------------------
3 files changed, 194 deletions(-)
delete mode 100644 drivers/pwm/pwm-twl6030.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index ed81720..e678005 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -142,15 +142,6 @@ 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"
- depends on TWL4030_CORE
- help
- Generic PWM framework driver for TWL6030.
-
- To compile this driver as a module, choose M here: the module
- will be called pwm-twl6030.
-
config PWM_VT8500
tristate "vt8500 pwm support"
depends on ARCH_VT8500
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index acfe482..29cf57e 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -11,5 +11,4 @@ obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.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_VT8500) += pwm-vt8500.o
diff --git a/drivers/pwm/pwm-twl6030.c b/drivers/pwm/pwm-twl6030.c
deleted file mode 100644
index 8e63878..0000000
--- a/drivers/pwm/pwm-twl6030.c
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * twl6030_pwm.c
- * Driver for PHOENIX (TWL6030) Pulse Width Modulator
- *
- * Copyright (C) 2010 Texas Instruments
- * Author: Hemanth V <[email protected]>
- *
- * 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 = __devexit_p(twl6030_pwm_remove),
-};
-module_platform_driver(twl6030_pwm_driver);
-
-MODULE_ALIAS("platform:twl6030-pwm");
-MODULE_LICENSE("GPL");
--
1.8.0

2012-11-07 17:50:56

by Grazvydas Ignotas

[permalink] [raw]
Subject: Re: [PATCH 2/3] pwm: New driver to support PWMs on TWL4030/6030 series of PMICs

On Wed, Nov 7, 2012 at 4:44 PM, Peter Ujfalusi <[email protected]> wrote:
> The driver supports the following PWM outputs:
> TWL4030 PWM0 and PWM1
> TWL6030 PWM1 and PWM2
>
> On TWL4030 the PWM signals are muxed. Upon requesting the PWM the driver
> will select the correct mux so the PWM can be used. When the PWM has been
> freed the original configuration going to be restored.
>
> Signed-off-by: Peter Ujfalusi <[email protected]>
> ---
> drivers/pwm/Kconfig | 10 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-twl.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 315 insertions(+)
> create mode 100644 drivers/pwm/pwm-twl.c
>

<snip>

> --- /dev/null
> +++ b/drivers/pwm/pwm-twl.c

<snip>

> +
> +static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + int ret;
> + u8 val;
> +
> + 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);
> + return ret;
> + }
> +
> + val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_BITS);

In my experience doing it like this doesn't work reliably, i.e.
sometimes it just won't enable. I had to first set CLK_ENABLE bit, and
then ENABLE bit with separate i2c write. Perhaps it needs a cycle or
two of 32k clock or something (that doesn't seem to be documented
though).

> +
> + 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);
> +
> + return ret;
> +}
> +
> +static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + int ret;
> + u8 val;
> +
> + 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);
> + return;
> + }
> +
> + val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_BITS);

Same problem here, I would sometimes get LED stuck at full brightness
after this, first clearing ENABLE and then CLK_ENABLE fixed it (we
have charger LED connected to PWM1 on pandora).

> +
> + 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);
> +}
> +
> +static int twl4030_pwm_request(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, bits;
> +
> + 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);
> + return ret;
> + }
> +
> + if (pwm->hwpwm) {
> + /* PWM 1 */
> + mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
> + bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
> + } else {
> + /* PWM 0 */
> + mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
> + bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
> + }
> +
> + /* Save the current MUX configuration for the PWM */
> + twl->twl4030_pwm_mux &= ~mask;
> + twl->twl4030_pwm_mux |= (val & mask);

Do we really need this mask clearing here? After probe twl4030_pwm_mux
should be zero, and if twl4030_pwm_request is called twice you don't
clear the important bits before |=, I think 'twl4030_pwm_mux = val &
mask' would be better here.


--
Gražvydas

2012-11-07 18:12:57

by Grazvydas Ignotas

[permalink] [raw]
Subject: Re: [PATCH 3/3] pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of PMICs

On Wed, Nov 7, 2012 at 4:44 PM, Peter Ujfalusi <[email protected]> wrote:
> The driver supports the following LED outputs as generic PWM driver:
> TWL4030 LEDA and LEDB (PWMA and PWMB)
> TWL6030 Charging indicator LED (PWM LED)
>
> On TWL6030 when the PWM requested LED is configured to be controlled by SW.
> In this case the user can enable/disable and set the duty period freely.
> When the PWM has been freed, the LED driver is put back to HW control.
>
> Signed-off-by: Peter Ujfalusi <[email protected]>
> ---
> drivers/pwm/Kconfig | 10 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-twl-led.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 298 insertions(+)
> create mode 100644 drivers/pwm/pwm-twl-led.c
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index c577db9..49c2082 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -152,6 +152,16 @@ config PWM_TWL
> 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"
> + select HAVE_PWM
> + depends on TWL4030_CORE
> + help
> + Generic PWM framework driver for TWL4030/6030 LED.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-twl-led.
> +
> config PWM_VT8500
> tristate "vt8500 pwm support"
> depends on ARCH_VT8500
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 3324c06..5f26134 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -12,4 +12,5 @@ obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
> obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
> obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
> obj-$(CONFIG_PWM_TWL) += pwm-twl.o
> +obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
> obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
> diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c
> new file mode 100644
> index 0000000..4d6ddc9
> --- /dev/null
> +++ b/drivers/pwm/pwm-twl-led.c
> @@ -0,0 +1,287 @@
> +/*
> + * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver
> + *
> + * Copyright (C) 2012 Texas Instruments
> + * Author: Peter Ujfalusi <[email protected]>
> + *
> + * This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
> + * Hemanth V <[email protected]>
> + *
> + * 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 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;
> +};
> +
> +static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
> + int duty_ns, int period_ns)
> +{
> + int duty_cycle = (duty_ns * TWL4030_LED_MAX) / period_ns;
> + u8 on_time;
> + u8 pwm_config[2];
> + int base, ret;
> +
> + if (duty_cycle >= TWL4030_LED_MAX)
> + on_time = TWL4030_LED_MAX;
> + else if (!duty_cycle)
> + on_time = TWL4030_LED_MAX - 1;
> + else
> + on_time = TWL4030_LED_MAX - duty_cycle;
> +
> + base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
> +
> + pwm_config[0] = on_time;
> + pwm_config[1] = TWL4030_LED_MAX;
> +
> + ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);

Shouldn't this use TWL4030_MODULE_PWMA and TWL4030_MODULE_PWMB as
first argument? I can guess it works your way too, but
TWL4030_MODULE_PWMx would match the TRM better.


--
Gražvydas

2012-11-08 07:14:43

by Peter Ujfalusi

[permalink] [raw]
Subject: Re: [PATCH 2/3] pwm: New driver to support PWMs on TWL4030/6030 series of PMICs

On 11/07/2012 06:50 PM, Grazvydas Ignotas wrote:
>> +static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
>> +{
>> + int ret;
>> + u8 val;
>> +
>> + 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);
>> + return ret;
>> + }
>> +
>> + val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_BITS);
>
> In my experience doing it like this doesn't work reliably, i.e.
> sometimes it just won't enable. I had to first set CLK_ENABLE bit, and
> then ENABLE bit with separate i2c write. Perhaps it needs a cycle or
> two of 32k clock or something (that doesn't seem to be documented
> though).

Thanks, I'll change to the reliable sequence. I do not have HW where I can
test the twl4030 PWMs.

>
>> +
>> + 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);
>> +
>> + return ret;
>> +}
>> +
>> +static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
>> +{
>> + int ret;
>> + u8 val;
>> +
>> + 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);
>> + return;
>> + }
>> +
>> + val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_BITS);
>
> Same problem here, I would sometimes get LED stuck at full brightness
> after this, first clearing ENABLE and then CLK_ENABLE fixed it (we
> have charger LED connected to PWM1 on pandora).

I would guessed that if we need special care we should have turned off CLK
followed by disabling the PWM.
I'll use the sequence you described in the next version.

>
>> +
>> + 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);
>> +}
>> +
>> +static int twl4030_pwm_request(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, bits;
>> +
>> + 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);
>> + return ret;
>> + }
>> +
>> + if (pwm->hwpwm) {
>> + /* PWM 1 */
>> + mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
>> + bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
>> + } else {
>> + /* PWM 0 */
>> + mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
>> + bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
>> + }
>> +
>> + /* Save the current MUX configuration for the PWM */
>> + twl->twl4030_pwm_mux &= ~mask;
>> + twl->twl4030_pwm_mux |= (val & mask);
>
> Do we really need this mask clearing here? After probe twl4030_pwm_mux
> should be zero, and if twl4030_pwm_request is called twice you don't
> clear the important bits before |=, I think 'twl4030_pwm_mux = val &
> mask' would be better here.

I'm storing both PWM's state in the same variable, but in different offsets:
PWM0: bits 2-3
PWM1: bits 4-5
Probably it is over engineering to clear the relevant bits in the backup
storage, but better to be safe IMHO.
I would leave this part as it is.

--
Péter

2012-11-08 07:34:38

by Peter Ujfalusi

[permalink] [raw]
Subject: Re: [PATCH 3/3] pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of PMICs

On 11/07/2012 07:12 PM, Grazvydas Ignotas wrote:
>> +static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
>> + int duty_ns, int period_ns)
>> +{
>> + int duty_cycle = (duty_ns * TWL4030_LED_MAX) / period_ns;
>> + u8 on_time;
>> + u8 pwm_config[2];
>> + int base, ret;
>> +
>> + if (duty_cycle >= TWL4030_LED_MAX)
>> + on_time = TWL4030_LED_MAX;
>> + else if (!duty_cycle)
>> + on_time = TWL4030_LED_MAX - 1;
>> + else
>> + on_time = TWL4030_LED_MAX - duty_cycle;
>> +
>> + base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
>> +
>> + pwm_config[0] = on_time;
>> + pwm_config[1] = TWL4030_LED_MAX;
>> +
>> + ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);
>
> Shouldn't this use TWL4030_MODULE_PWMA and TWL4030_MODULE_PWMB as
> first argument? I can guess it works your way too, but
> TWL4030_MODULE_PWMx would match the TRM better.

I don't have strong opinion regarding to this. You mean changing from:

base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);

to:

if (pwm->hwpwm)
module = TWL4030_MODULE_PWMB;
else
module = TWL4030_MODULE_PWMA;
ret = twl_i2c_write(module, pwm_config, 0, 2);

But I want to note that I'm currently trying to clean up the mess around
twl-core. In my view we have quite a bit of redundancy in there. The PWM A/B
is for driving the LED A/B outputs. We should have only these modules for
PWM/LED in twl-core:
TWL_MODULE_PWM - offset for PWM0ON register in twl4030 and PWM1ON for twl6030
TWL_MODULE_LED - offset for LEDEN register in twl4030 and LED_PWM_CTRL1
for twl6030

>From here the driver can figure out what to do IMHO.

There's no need to have separate TWL 'modules' for:
TWL4030_BASEADD_PWM0
TWL4030_BASEADD_PWM1
TWL4030_BASEADD_PWMA
TWL4030_BASEADD_PWMB

--
Péter

2012-11-08 11:53:45

by Grazvydas Ignotas

[permalink] [raw]
Subject: Re: [PATCH 2/3] pwm: New driver to support PWMs on TWL4030/6030 series of PMICs

On Thu, Nov 8, 2012 at 9:14 AM, Péter Ujfalusi <[email protected]> wrote:
> On 11/07/2012 06:50 PM, Grazvydas Ignotas wrote:
>>> + if (pwm->hwpwm) {
>>> + /* PWM 1 */
>>> + mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
>>> + bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
>>> + } else {
>>> + /* PWM 0 */
>>> + mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
>>> + bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
>>> + }
>>> +
>>> + /* Save the current MUX configuration for the PWM */
>>> + twl->twl4030_pwm_mux &= ~mask;
>>> + twl->twl4030_pwm_mux |= (val & mask);
>>
>> Do we really need this mask clearing here? After probe twl4030_pwm_mux
>> should be zero, and if twl4030_pwm_request is called twice you don't
>> clear the important bits before |=, I think 'twl4030_pwm_mux = val &
>> mask' would be better here.
>
> I'm storing both PWM's state in the same variable, but in different offsets:
> PWM0: bits 2-3
> PWM1: bits 4-5
> Probably it is over engineering to clear the relevant bits in the backup
> storage, but better to be safe IMHO.
> I would leave this part as it is.

Oh, it should be good then.


--
Gražvydas

2012-11-08 12:29:54

by Grazvydas Ignotas

[permalink] [raw]
Subject: Re: [PATCH 3/3] pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of PMICs

On Thu, Nov 8, 2012 at 9:34 AM, Péter Ujfalusi <[email protected]> wrote:
> On 11/07/2012 07:12 PM, Grazvydas Ignotas wrote:
>>> +static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
>>> + int duty_ns, int period_ns)
>>> +{
>>> + int duty_cycle = (duty_ns * TWL4030_LED_MAX) / period_ns;
>>> + u8 on_time;
>>> + u8 pwm_config[2];
>>> + int base, ret;
>>> +
>>> + if (duty_cycle >= TWL4030_LED_MAX)
>>> + on_time = TWL4030_LED_MAX;
>>> + else if (!duty_cycle)
>>> + on_time = TWL4030_LED_MAX - 1;
>>> + else
>>> + on_time = TWL4030_LED_MAX - duty_cycle;
>>> +
>>> + base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
>>> +
>>> + pwm_config[0] = on_time;
>>> + pwm_config[1] = TWL4030_LED_MAX;
>>> +
>>> + ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);
>>
>> Shouldn't this use TWL4030_MODULE_PWMA and TWL4030_MODULE_PWMB as
>> first argument? I can guess it works your way too, but
>> TWL4030_MODULE_PWMx would match the TRM better.
>
> I don't have strong opinion regarding to this. You mean changing from:
>
> base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
> ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);
>
> to:
>
> if (pwm->hwpwm)
> module = TWL4030_MODULE_PWMB;
> else
> module = TWL4030_MODULE_PWMA;
> ret = twl_i2c_write(module, pwm_config, 0, 2);
>
> But I want to note that I'm currently trying to clean up the mess around
> twl-core. In my view we have quite a bit of redundancy in there. The PWM A/B
> is for driving the LED A/B outputs. We should have only these modules for
> PWM/LED in twl-core:
> TWL_MODULE_PWM - offset for PWM0ON register in twl4030 and PWM1ON for twl6030
> TWL_MODULE_LED - offset for LEDEN register in twl4030 and LED_PWM_CTRL1
> for twl6030
>
> From here the driver can figure out what to do IMHO.
>
> There's no need to have separate TWL 'modules' for:
> TWL4030_BASEADD_PWM0
> TWL4030_BASEADD_PWM1
> TWL4030_BASEADD_PWMA
> TWL4030_BASEADD_PWMB

Well all these seem to come from TRM, no hard feelings here too but if
you are going to remove them, probably worth adding a comment.

--
Gražvydas

2012-11-08 12:55:59

by Peter Ujfalusi

[permalink] [raw]
Subject: Re: [PATCH 3/3] pwm: New driver to support PWM driven LEDs on TWL4030/6030 series of PMICs

On 11/08/2012 01:29 PM, Grazvydas Ignotas wrote:
>> But I want to note that I'm currently trying to clean up the mess around
>> twl-core. In my view we have quite a bit of redundancy in there. The PWM A/B
>> is for driving the LED A/B outputs. We should have only these modules for
>> PWM/LED in twl-core:
>> TWL_MODULE_PWM - offset for PWM0ON register in twl4030 and PWM1ON for twl6030
>> TWL_MODULE_LED - offset for LEDEN register in twl4030 and LED_PWM_CTRL1
>> for twl6030
>>
>> From here the driver can figure out what to do IMHO.
>>
>> There's no need to have separate TWL 'modules' for:
>> TWL4030_BASEADD_PWM0
>> TWL4030_BASEADD_PWM1
>> TWL4030_BASEADD_PWMA
>> TWL4030_BASEADD_PWMB
>
> Well all these seem to come from TRM, no hard feelings here too but if
> you are going to remove them, probably worth adding a comment.

>From the 'outside' of twl4030 we have: LEDA, LEDB, PWM0 and PWM1 pins. This is
more important from system integration point of view than what name the TRM
calls the PWM (PWMA) behind of the LEDA terminal for example.

At the end in the board file you will have to use something like this:

static struct pwm_lookup zoom_pwm_lookup[] = {
PWM_LOOKUP("twl-pwm", 0, "leds_pwm", "zoom::keypad"), /* PWM0 */
PWM_LOOKUP("twl-pwm", 1, "pwm-backlight", "backlight"), /* PWM1 */
PWM_LOOKUP("twl-pwm-led", 0, "leds_pwm", "zoom::blinking"), /* LEDA */
};

I'll add comment to both the pwm-twl and pwm-twl-led driver for clarification.

--
Péter