aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@ti.com>2012-11-27 05:09:57 -0500
committerThierry Reding <thierry.reding@avionic-design.de>2012-11-28 09:16:19 -0500
commit3744c26386971db1c7ecf5aa5900882deae60492 (patch)
treefffddf445235da513125e4d32d769b8f98961eea /drivers/pwm
parent98ccf49daf3bd626196814e38ec8d2460e0d4517 (diff)
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 is going to be restored. 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.c359
3 files changed, 369 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 3dcb76d86a97..e1792d5e3861 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -173,6 +173,15 @@ config PWM_TWL6030
173 To compile this driver as a module, choose M here: the module 173 To compile this driver as a module, choose M here: the module
174 will be called pwm-twl6030. 174 will be called pwm-twl6030.
175 175
176config PWM_TWL
177 tristate "TWL4030/6030 PWM support"
178 depends on TWL4030_CORE
179 help
180 Generic PWM framework driver for TWL4030/6030.
181
182 To compile this driver as a module, choose M here: the module
183 will be called pwm-twl.
184
176config PWM_VT8500 185config PWM_VT8500
177 tristate "vt8500 pwm support" 186 tristate "vt8500 pwm support"
178 depends on ARCH_VT8500 187 depends on ARCH_VT8500
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 4fb39f8b6ba9..3b7efa7a40aa 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -14,4 +14,5 @@ obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
14obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o 14obj-$(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_VT8500) += pwm-vt8500.o 18obj-$(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 000000000000..e65db95d5e59
--- /dev/null
+++ b/drivers/pwm/pwm-twl.c
@@ -0,0 +1,359 @@
1/*
2 * Driver for TWL4030/6030 Generic Pulse Width Modulator
3 *
4 * Copyright (C) 2012 Texas Instruments
5 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published by
9 * the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <linux/module.h>
21#include <linux/platform_device.h>
22#include <linux/pwm.h>
23#include <linux/i2c/twl.h>
24#include <linux/slab.h>
25
26/*
27 * This driver handles the PWMs of TWL4030 and TWL6030.
28 * The TRM names for the PWMs on TWL4030 are: PWM0, PWM1
29 * TWL6030 also have two PWMs named in the TRM as PWM1, PWM2
30 */
31
32#define TWL_PWM_MAX 0x7f
33
34/* Registers, bits and macro for TWL4030 */
35#define TWL4030_GPBR1_REG 0x0c
36#define TWL4030_PMBR1_REG 0x0d
37
38/* GPBR1 register bits */
39#define TWL4030_PWMXCLK_ENABLE (1 << 0)
40#define TWL4030_PWMX_ENABLE (1 << 2)
41#define TWL4030_PWMX_BITS (TWL4030_PWMX_ENABLE | TWL4030_PWMXCLK_ENABLE)
42#define TWL4030_PWM_TOGGLE(pwm, x) ((x) << (pwm))
43
44/* PMBR1 register bits */
45#define TWL4030_GPIO6_PWM0_MUTE_MASK (0x03 << 2)
46#define TWL4030_GPIO6_PWM0_MUTE_PWM0 (0x01 << 2)
47#define TWL4030_GPIO7_VIBRASYNC_PWM1_MASK (0x03 << 4)
48#define TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1 (0x03 << 4)
49
50/* Register, bits and macro for TWL6030 */
51#define TWL6030_TOGGLE3_REG 0x92
52
53#define TWL6030_PWMXR (1 << 0)
54#define TWL6030_PWMXS (1 << 1)
55#define TWL6030_PWMXEN (1 << 2)
56#define TWL6030_PWM_TOGGLE(pwm, x) ((x) << (pwm * 3))
57
58struct twl_pwm_chip {
59 struct pwm_chip chip;
60 struct mutex mutex;
61 u8 twl6030_toggle3;
62 u8 twl4030_pwm_mux;
63};
64
65static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip)
66{
67 return container_of(chip, struct twl_pwm_chip, chip);
68}
69
70static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
71 int duty_ns, int period_ns)
72{
73 int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1;
74 u8 pwm_config[2] = { 1, 0 };
75 int base, ret;
76
77 /*
78 * To configure the duty period:
79 * On-cycle is set to 1 (the minimum allowed value)
80 * The off time of 0 is not configurable, so the mapping is:
81 * 0 -> off cycle = 2,
82 * 1 -> off cycle = 2,
83 * 2 -> off cycle = 3,
84 * 126 - > off cycle 127,
85 * 127 - > off cycle 1
86 * When on cycle == off cycle the PWM will be always on
87 */
88 if (duty_cycle == 1)
89 duty_cycle = 2;
90 else if (duty_cycle > TWL_PWM_MAX)
91 duty_cycle = 1;
92
93 base = pwm->hwpwm * 3;
94
95 pwm_config[1] = duty_cycle;
96
97 ret = twl_i2c_write(TWL_MODULE_PWM, pwm_config, base, 2);
98 if (ret < 0)
99 dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
100
101 return ret;
102}
103
104static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
105{
106 struct twl_pwm_chip *twl = to_twl(chip);
107 int ret;
108 u8 val;
109
110 mutex_lock(&twl->mutex);
111 ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
112 if (ret < 0) {
113 dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
114 goto out;
115 }
116
117 val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
118
119 ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
120 if (ret < 0)
121 dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
122
123 val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
124
125 ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
126 if (ret < 0)
127 dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
128
129out:
130 mutex_unlock(&twl->mutex);
131 return ret;
132}
133
134static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
135{
136 struct twl_pwm_chip *twl = to_twl(chip);
137 int ret;
138 u8 val;
139
140 mutex_lock(&twl->mutex);
141 ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
142 if (ret < 0) {
143 dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
144 goto out;
145 }
146
147 val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
148
149 ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
150 if (ret < 0)
151 dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
152
153 val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
154
155 ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
156 if (ret < 0)
157 dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
158
159out:
160 mutex_unlock(&twl->mutex);
161}
162
163static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
164{
165 struct twl_pwm_chip *twl = to_twl(chip);
166 int ret;
167 u8 val, mask, bits;
168
169 if (pwm->hwpwm == 1) {
170 mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
171 bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
172 } else {
173 mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
174 bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
175 }
176
177 mutex_lock(&twl->mutex);
178 ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
179 if (ret < 0) {
180 dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
181 goto out;
182 }
183
184 /* Save the current MUX configuration for the PWM */
185 twl->twl4030_pwm_mux &= ~mask;
186 twl->twl4030_pwm_mux |= (val & mask);
187
188 /* Select PWM functionality */
189 val &= ~mask;
190 val |= bits;
191
192 ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
193 if (ret < 0)
194 dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
195
196out:
197 mutex_unlock(&twl->mutex);
198 return ret;
199}
200
201static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
202{
203 struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
204 chip);
205 int ret;
206 u8 val, mask;
207
208 if (pwm->hwpwm == 1)
209 mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
210 else
211 mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
212
213 mutex_lock(&twl->mutex);
214 ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
215 if (ret < 0) {
216 dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
217 goto out;
218 }
219
220 /* Restore the MUX configuration for the PWM */
221 val &= ~mask;
222 val |= (twl->twl4030_pwm_mux & mask);
223
224 ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
225 if (ret < 0)
226 dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
227
228out:
229 mutex_unlock(&twl->mutex);
230}
231
232static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
233{
234 struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
235 chip);
236 int ret;
237 u8 val;
238
239 mutex_lock(&twl->mutex);
240 val = twl->twl6030_toggle3;
241 val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
242 val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
243
244 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
245 if (ret < 0) {
246 dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
247 goto out;
248 }
249
250 twl->twl6030_toggle3 = val;
251out:
252 mutex_unlock(&twl->mutex);
253 return 0;
254}
255
256static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
257{
258 struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
259 chip);
260 int ret;
261 u8 val;
262
263 mutex_lock(&twl->mutex);
264 val = twl->twl6030_toggle3;
265 val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
266 val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
267
268 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
269 if (ret < 0) {
270 dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label);
271 goto out;
272 }
273
274 val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
275
276 ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
277 if (ret < 0) {
278 dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
279 goto out;
280 }
281
282 twl->twl6030_toggle3 = val;
283out:
284 mutex_unlock(&twl->mutex);
285}
286
287static const struct pwm_ops twl4030_pwm_ops = {
288 .config = twl_pwm_config,
289 .enable = twl4030_pwm_enable,
290 .disable = twl4030_pwm_disable,
291 .request = twl4030_pwm_request,
292 .free = twl4030_pwm_free,
293};
294
295static const struct pwm_ops twl6030_pwm_ops = {
296 .config = twl_pwm_config,
297 .enable = twl6030_pwm_enable,
298 .disable = twl6030_pwm_disable,
299};
300
301static int twl_pwm_probe(struct platform_device *pdev)
302{
303 struct twl_pwm_chip *twl;
304 int ret;
305
306 twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
307 if (!twl)
308 return -ENOMEM;
309
310 if (twl_class_is_4030())
311 twl->chip.ops = &twl4030_pwm_ops;
312 else
313 twl->chip.ops = &twl6030_pwm_ops;
314
315 twl->chip.dev = &pdev->dev;
316 twl->chip.base = -1;
317 twl->chip.npwm = 2;
318
319 mutex_init(&twl->mutex);
320
321 ret = pwmchip_add(&twl->chip);
322 if (ret < 0)
323 return ret;
324
325 platform_set_drvdata(pdev, twl);
326
327 return 0;
328}
329
330static int twl_pwm_remove(struct platform_device *pdev)
331{
332 struct twl_pwm_chip *twl = platform_get_drvdata(pdev);
333
334 return pwmchip_remove(&twl->chip);
335}
336
337#ifdef CONFIG_OF
338static struct of_device_id twl_pwm_of_match[] = {
339 { .compatible = "ti,twl4030-pwm" },
340 { .compatible = "ti,twl6030-pwm" },
341 { },
342};
343MODULE_DEVICE_TABLE(of, twl_pwm_of_match);
344#endif
345
346static struct platform_driver twl_pwm_driver = {
347 .driver = {
348 .name = "twl-pwm",
349 .of_match_table = of_match_ptr(twl_pwm_of_match),
350 },
351 .probe = twl_pwm_probe,
352 .remove = twl_pwm_remove,
353};
354module_platform_driver(twl_pwm_driver);
355
356MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
357MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030");
358MODULE_ALIAS("platform:twl-pwm");
359MODULE_LICENSE("GPL");