aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@ti.com>2012-11-27 05:09:58 -0500
committerThierry Reding <thierry.reding@avionic-design.de>2012-11-28 09:16:19 -0500
commitaa7656471df6cad1a4ed82ba888a40e9bc46aa19 (patch)
tree72e7b3bc5652e6d4d2c6a02fe8c87e7466de88eb /drivers/pwm
parent3744c26386971db1c7ecf5aa5900882deae60492 (diff)
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 <peter.ujfalusi@ti.com> Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
Diffstat (limited to 'drivers/pwm')
-rw-r--r--drivers/pwm/Kconfig9
-rw-r--r--drivers/pwm/Makefile1
-rw-r--r--drivers/pwm/pwm-twl-led.c344
3 files changed, 354 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index e1792d5e3861..fe788a7c5495 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -182,6 +182,15 @@ config PWM_TWL
182 To compile this driver as a module, choose M here: the module 182 To compile this driver as a module, choose M here: the module
183 will be called pwm-twl. 183 will be called pwm-twl.
184 184
185config PWM_TWL_LED
186 tristate "TWL4030/6030 PWM support for LED drivers"
187 depends on TWL4030_CORE
188 help
189 Generic PWM framework driver for TWL4030/6030 LED terminals.
190
191 To compile this driver as a module, choose M here: the module
192 will be called pwm-twl-led.
193
185config PWM_VT8500 194config PWM_VT8500
186 tristate "vt8500 pwm support" 195 tristate "vt8500 pwm support"
187 depends on ARCH_VT8500 196 depends on ARCH_VT8500
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 3b7efa7a40aa..4b10133452c4 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -15,4 +15,5 @@ obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
15obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o 15obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o
16obj-$(CONFIG_PWM_TWL6030) += pwm-twl6030.o 16obj-$(CONFIG_PWM_TWL6030) += pwm-twl6030.o
17obj-$(CONFIG_PWM_TWL) += pwm-twl.o 17obj-$(CONFIG_PWM_TWL) += pwm-twl.o
18obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
18obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o 19obj-$(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 000000000000..9dfa0f3eca30
--- /dev/null
+++ b/drivers/pwm/pwm-twl-led.c
@@ -0,0 +1,344 @@
1/*
2 * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver
3 *
4 * Copyright (C) 2012 Texas Instruments
5 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
6 *
7 * This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
8 * Hemanth V <hemanthv@ti.com>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License version 2 as published by
12 * the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * more details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include <linux/module.h>
24#include <linux/platform_device.h>
25#include <linux/pwm.h>
26#include <linux/i2c/twl.h>
27#include <linux/slab.h>
28
29/*
30 * This driver handles the PWM driven LED terminals of TWL4030 and TWL6030.
31 * To generate the signal on TWL4030:
32 * - LEDA uses PWMA
33 * - LEDB uses PWMB
34 * TWL6030 has one LED pin with dedicated LEDPWM
35 */
36
37#define TWL4030_LED_MAX 0x7f
38#define TWL6030_LED_MAX 0xff
39
40/* Registers, bits and macro for TWL4030 */
41#define TWL4030_LEDEN_REG 0x00
42#define TWL4030_PWMA_REG 0x01
43
44#define TWL4030_LEDXON (1 << 0)
45#define TWL4030_LEDXPWM (1 << 4)
46#define TWL4030_LED_PINS (TWL4030_LEDXON | TWL4030_LEDXPWM)
47#define TWL4030_LED_TOGGLE(led, x) ((x) << (led))
48
49/* Register, bits and macro for TWL6030 */
50#define TWL6030_LED_PWM_CTRL1 0xf4
51#define TWL6030_LED_PWM_CTRL2 0xf5
52
53#define TWL6040_LED_MODE_HW 0x00
54#define TWL6040_LED_MODE_ON 0x01
55#define TWL6040_LED_MODE_OFF 0x02
56#define TWL6040_LED_MODE_MASK 0x03
57
58struct twl_pwmled_chip {
59 struct pwm_chip chip;
60 struct mutex mutex;
61};
62
63static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip)
64{
65 return container_of(chip, struct twl_pwmled_chip, chip);
66}
67
68static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
69 int duty_ns, int period_ns)
70{
71 int duty_cycle = DIV_ROUND_UP(duty_ns * TWL4030_LED_MAX, period_ns) + 1;
72 u8 pwm_config[2] = { 1, 0 };
73 int base, ret;
74
75 /*
76 * To configure the duty period:
77 * On-cycle is set to 1 (the minimum allowed value)
78 * The off time of 0 is not configurable, so the mapping is:
79 * 0 -> off cycle = 2,
80 * 1 -> off cycle = 2,
81 * 2 -> off cycle = 3,
82 * 126 - > off cycle 127,
83 * 127 - > off cycle 1
84 * When on cycle == off cycle the PWM will be always on
85 */
86 if (duty_cycle == 1)
87 duty_cycle = 2;
88 else if (duty_cycle > TWL4030_LED_MAX)
89 duty_cycle = 1;
90
91 base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
92
93 pwm_config[1] = duty_cycle;
94
95 ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);
96 if (ret < 0)
97 dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
98
99 return ret;
100}
101
102static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
103{
104 struct twl_pwmled_chip *twl = to_twl(chip);
105 int ret;
106 u8 val;
107
108 mutex_lock(&twl->mutex);
109 ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
110 if (ret < 0) {
111 dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label);
112 goto out;
113 }
114
115 val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
116
117 ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
118 if (ret < 0)
119 dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
120
121out:
122 mutex_unlock(&twl->mutex);
123 return ret;
124}
125
126static void twl4030_pwmled_disable(struct pwm_chip *chip,
127 struct pwm_device *pwm)
128{
129 struct twl_pwmled_chip *twl = to_twl(chip);
130 int ret;
131 u8 val;
132
133 mutex_lock(&twl->mutex);
134 ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
135 if (ret < 0) {
136 dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label);
137 goto out;
138 }
139
140 val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
141
142 ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
143 if (ret < 0)
144 dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
145
146out:
147 mutex_unlock(&twl->mutex);
148}
149
150static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
151 int duty_ns, int period_ns)
152{
153 int duty_cycle = (duty_ns * TWL6030_LED_MAX) / period_ns;
154 u8 on_time;
155 int ret;
156
157 on_time = duty_cycle & 0xff;
158
159 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, on_time,
160 TWL6030_LED_PWM_CTRL1);
161 if (ret < 0)
162 dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
163
164 return ret;
165}
166
167static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
168{
169 struct twl_pwmled_chip *twl = to_twl(chip);
170 int ret;
171 u8 val;
172
173 mutex_lock(&twl->mutex);
174 ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
175 if (ret < 0) {
176 dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
177 pwm->label);
178 goto out;
179 }
180
181 val &= ~TWL6040_LED_MODE_MASK;
182 val |= TWL6040_LED_MODE_ON;
183
184 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
185 if (ret < 0)
186 dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
187
188out:
189 mutex_unlock(&twl->mutex);
190 return ret;
191}
192
193static void twl6030_pwmled_disable(struct pwm_chip *chip,
194 struct pwm_device *pwm)
195{
196 struct twl_pwmled_chip *twl = to_twl(chip);
197 int ret;
198 u8 val;
199
200 mutex_lock(&twl->mutex);
201 ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
202 if (ret < 0) {
203 dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
204 pwm->label);
205 goto out;
206 }
207
208 val &= ~TWL6040_LED_MODE_MASK;
209 val |= TWL6040_LED_MODE_OFF;
210
211 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
212 if (ret < 0)
213 dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
214
215out:
216 mutex_unlock(&twl->mutex);
217}
218
219static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
220{
221 struct twl_pwmled_chip *twl = to_twl(chip);
222 int ret;
223 u8 val;
224
225 mutex_lock(&twl->mutex);
226 ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
227 if (ret < 0) {
228 dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
229 pwm->label);
230 goto out;
231 }
232
233 val &= ~TWL6040_LED_MODE_MASK;
234 val |= TWL6040_LED_MODE_OFF;
235
236 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
237 if (ret < 0)
238 dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
239
240out:
241 mutex_unlock(&twl->mutex);
242 return ret;
243}
244
245static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
246{
247 struct twl_pwmled_chip *twl = to_twl(chip);
248 int ret;
249 u8 val;
250
251 mutex_lock(&twl->mutex);
252 ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
253 if (ret < 0) {
254 dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
255 pwm->label);
256 goto out;
257 }
258
259 val &= ~TWL6040_LED_MODE_MASK;
260 val |= TWL6040_LED_MODE_HW;
261
262 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
263 if (ret < 0)
264 dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
265
266out:
267 mutex_unlock(&twl->mutex);
268}
269
270static const struct pwm_ops twl4030_pwmled_ops = {
271 .enable = twl4030_pwmled_enable,
272 .disable = twl4030_pwmled_disable,
273 .config = twl4030_pwmled_config,
274};
275
276static const struct pwm_ops twl6030_pwmled_ops = {
277 .enable = twl6030_pwmled_enable,
278 .disable = twl6030_pwmled_disable,
279 .config = twl6030_pwmled_config,
280 .request = twl6030_pwmled_request,
281 .free = twl6030_pwmled_free,
282};
283
284static int twl_pwmled_probe(struct platform_device *pdev)
285{
286 struct twl_pwmled_chip *twl;
287 int ret;
288
289 twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
290 if (!twl)
291 return -ENOMEM;
292
293 if (twl_class_is_4030()) {
294 twl->chip.ops = &twl4030_pwmled_ops;
295 twl->chip.npwm = 2;
296 } else {
297 twl->chip.ops = &twl6030_pwmled_ops;
298 twl->chip.npwm = 1;
299 }
300
301 twl->chip.dev = &pdev->dev;
302 twl->chip.base = -1;
303
304 mutex_init(&twl->mutex);
305
306 ret = pwmchip_add(&twl->chip);
307 if (ret < 0)
308 return ret;
309
310 platform_set_drvdata(pdev, twl);
311
312 return 0;
313}
314
315static int twl_pwmled_remove(struct platform_device *pdev)
316{
317 struct twl_pwmled_chip *twl = platform_get_drvdata(pdev);
318
319 return pwmchip_remove(&twl->chip);
320}
321
322#ifdef CONFIG_OF
323static struct of_device_id twl_pwmled_of_match[] = {
324 { .compatible = "ti,twl4030-pwmled" },
325 { .compatible = "ti,twl6030-pwmled" },
326 { },
327};
328MODULE_DEVICE_TABLE(of, twl_pwmled_of_match);
329#endif
330
331static struct platform_driver twl_pwmled_driver = {
332 .driver = {
333 .name = "twl-pwmled",
334 .of_match_table = of_match_ptr(twl_pwmled_of_match),
335 },
336 .probe = twl_pwmled_probe,
337 .remove = twl_pwmled_remove,
338};
339module_platform_driver(twl_pwmled_driver);
340
341MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
342MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs");
343MODULE_ALIAS("platform:twl-pwmled");
344MODULE_LICENSE("GPL");