diff options
-rw-r--r-- | Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt | 17 | ||||
-rw-r--r-- | drivers/pwm/Kconfig | 9 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-twl.c | 359 |
4 files changed, 386 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt b/Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt new file mode 100644 index 000000000000..2943ee5fce00 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/ti,twl-pwm.txt | |||
@@ -0,0 +1,17 @@ | |||
1 | Texas Instruments TWL series PWM drivers | ||
2 | |||
3 | Supported PWMs: | ||
4 | On TWL4030 series: PWM1 and PWM2 | ||
5 | On TWL6030 series: PWM0 and PWM1 | ||
6 | |||
7 | Required properties: | ||
8 | - compatible: "ti,twl4030-pwm" or "ti,twl6030-pwm" | ||
9 | - #pwm-cells: should be 2. The first cell specifies the per-chip index | ||
10 | of the PWM to use and the second cell is the period in nanoseconds. | ||
11 | |||
12 | Example: | ||
13 | |||
14 | twl_pwm: pwm { | ||
15 | compatible = "ti,twl6030-pwm"; | ||
16 | #pwm-cells = <2>; | ||
17 | }; | ||
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 | ||
176 | config 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 | |||
176 | config PWM_VT8500 | 185 | config 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 | |||
14 | obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o | 14 | obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o |
15 | obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o | 15 | obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o |
16 | obj-$(CONFIG_PWM_TWL6030) += pwm-twl6030.o | 16 | obj-$(CONFIG_PWM_TWL6030) += pwm-twl6030.o |
17 | obj-$(CONFIG_PWM_TWL) += pwm-twl.o | ||
17 | obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o | 18 | 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 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 | |||
58 | struct twl_pwm_chip { | ||
59 | struct pwm_chip chip; | ||
60 | struct mutex mutex; | ||
61 | u8 twl6030_toggle3; | ||
62 | u8 twl4030_pwm_mux; | ||
63 | }; | ||
64 | |||
65 | static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip) | ||
66 | { | ||
67 | return container_of(chip, struct twl_pwm_chip, chip); | ||
68 | } | ||
69 | |||
70 | static 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 | |||
104 | static 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 | |||
129 | out: | ||
130 | mutex_unlock(&twl->mutex); | ||
131 | return ret; | ||
132 | } | ||
133 | |||
134 | static 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 | |||
159 | out: | ||
160 | mutex_unlock(&twl->mutex); | ||
161 | } | ||
162 | |||
163 | static 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 | |||
196 | out: | ||
197 | mutex_unlock(&twl->mutex); | ||
198 | return ret; | ||
199 | } | ||
200 | |||
201 | static 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 | |||
228 | out: | ||
229 | mutex_unlock(&twl->mutex); | ||
230 | } | ||
231 | |||
232 | static 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; | ||
251 | out: | ||
252 | mutex_unlock(&twl->mutex); | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | static 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; | ||
283 | out: | ||
284 | mutex_unlock(&twl->mutex); | ||
285 | } | ||
286 | |||
287 | static 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 | |||
295 | static const struct pwm_ops twl6030_pwm_ops = { | ||
296 | .config = twl_pwm_config, | ||
297 | .enable = twl6030_pwm_enable, | ||
298 | .disable = twl6030_pwm_disable, | ||
299 | }; | ||
300 | |||
301 | static 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 | |||
330 | static 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 | ||
338 | static struct of_device_id twl_pwm_of_match[] = { | ||
339 | { .compatible = "ti,twl4030-pwm" }, | ||
340 | { .compatible = "ti,twl6030-pwm" }, | ||
341 | { }, | ||
342 | }; | ||
343 | MODULE_DEVICE_TABLE(of, twl_pwm_of_match); | ||
344 | #endif | ||
345 | |||
346 | static 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 | }; | ||
354 | module_platform_driver(twl_pwm_driver); | ||
355 | |||
356 | MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); | ||
357 | MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030"); | ||
358 | MODULE_ALIAS("platform:twl-pwm"); | ||
359 | MODULE_LICENSE("GPL"); | ||