]>
Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
aa765647 PU |
2 | /* |
3 | * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver | |
4 | * | |
5 | * Copyright (C) 2012 Texas Instruments | |
6 | * Author: Peter Ujfalusi <[email protected]> | |
7 | * | |
8 | * This driver is a complete rewrite of the former pwm-twl6030.c authorded by: | |
9 | * Hemanth V <[email protected]> | |
ea95b299 UKK |
10 | * |
11 | * Reference manual for the twl6030 is available at: | |
12 | * https://www.ti.com/lit/ds/symlink/twl6030.pdf | |
13 | * | |
14 | * Limitations: | |
15 | * - The twl6030 hardware only supports two period lengths (128 clock ticks and | |
16 | * 64 clock ticks), the driver only uses 128 ticks | |
17 | * - The hardware doesn't support ON = 0, so the active part of a period doesn't | |
18 | * start at its beginning. | |
19 | * - The hardware could support inverted polarity (with a similar limitation as | |
20 | * for normal: the last clock tick is always inactive). | |
21 | * - The hardware emits a constant low output when disabled. | |
22 | * - A request for .duty_cycle = 0 results in an output wave with one active | |
23 | * clock tick per period. This should better use the disabled state. | |
24 | * - The driver only implements setting the relative duty cycle. | |
25 | * - The driver doesn't implement .get_state(). | |
aa765647 PU |
26 | */ |
27 | ||
28 | #include <linux/module.h> | |
e852340d | 29 | #include <linux/of.h> |
aa765647 PU |
30 | #include <linux/platform_device.h> |
31 | #include <linux/pwm.h> | |
a2054256 | 32 | #include <linux/mfd/twl.h> |
aa765647 PU |
33 | #include <linux/slab.h> |
34 | ||
35 | /* | |
36 | * This driver handles the PWM driven LED terminals of TWL4030 and TWL6030. | |
37 | * To generate the signal on TWL4030: | |
38 | * - LEDA uses PWMA | |
39 | * - LEDB uses PWMB | |
40 | * TWL6030 has one LED pin with dedicated LEDPWM | |
41 | */ | |
42 | ||
43 | #define TWL4030_LED_MAX 0x7f | |
44 | #define TWL6030_LED_MAX 0xff | |
45 | ||
46 | /* Registers, bits and macro for TWL4030 */ | |
47 | #define TWL4030_LEDEN_REG 0x00 | |
48 | #define TWL4030_PWMA_REG 0x01 | |
49 | ||
50 | #define TWL4030_LEDXON (1 << 0) | |
51 | #define TWL4030_LEDXPWM (1 << 4) | |
52 | #define TWL4030_LED_PINS (TWL4030_LEDXON | TWL4030_LEDXPWM) | |
53 | #define TWL4030_LED_TOGGLE(led, x) ((x) << (led)) | |
54 | ||
55 | /* Register, bits and macro for TWL6030 */ | |
56 | #define TWL6030_LED_PWM_CTRL1 0xf4 | |
57 | #define TWL6030_LED_PWM_CTRL2 0xf5 | |
58 | ||
59 | #define TWL6040_LED_MODE_HW 0x00 | |
60 | #define TWL6040_LED_MODE_ON 0x01 | |
61 | #define TWL6040_LED_MODE_OFF 0x02 | |
62 | #define TWL6040_LED_MODE_MASK 0x03 | |
63 | ||
64 | struct twl_pwmled_chip { | |
65 | struct pwm_chip chip; | |
66 | struct mutex mutex; | |
67 | }; | |
68 | ||
69 | static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip) | |
70 | { | |
71 | return container_of(chip, struct twl_pwmled_chip, chip); | |
72 | } | |
73 | ||
74 | static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, | |
75 | int duty_ns, int period_ns) | |
76 | { | |
77 | int duty_cycle = DIV_ROUND_UP(duty_ns * TWL4030_LED_MAX, period_ns) + 1; | |
78 | u8 pwm_config[2] = { 1, 0 }; | |
79 | int base, ret; | |
80 | ||
81 | /* | |
82 | * To configure the duty period: | |
83 | * On-cycle is set to 1 (the minimum allowed value) | |
84 | * The off time of 0 is not configurable, so the mapping is: | |
85 | * 0 -> off cycle = 2, | |
86 | * 1 -> off cycle = 2, | |
87 | * 2 -> off cycle = 3, | |
88 | * 126 - > off cycle 127, | |
89 | * 127 - > off cycle 1 | |
90 | * When on cycle == off cycle the PWM will be always on | |
91 | */ | |
92 | if (duty_cycle == 1) | |
93 | duty_cycle = 2; | |
94 | else if (duty_cycle > TWL4030_LED_MAX) | |
95 | duty_cycle = 1; | |
96 | ||
97 | base = pwm->hwpwm * 2 + TWL4030_PWMA_REG; | |
98 | ||
99 | pwm_config[1] = duty_cycle; | |
100 | ||
101 | ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2); | |
102 | if (ret < 0) | |
103 | dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); | |
104 | ||
105 | return ret; | |
106 | } | |
107 | ||
108 | static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
109 | { | |
110 | struct twl_pwmled_chip *twl = to_twl(chip); | |
111 | int ret; | |
112 | u8 val; | |
113 | ||
114 | mutex_lock(&twl->mutex); | |
115 | ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); | |
116 | if (ret < 0) { | |
117 | dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label); | |
118 | goto out; | |
119 | } | |
120 | ||
121 | val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); | |
122 | ||
123 | ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); | |
124 | if (ret < 0) | |
125 | dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); | |
126 | ||
127 | out: | |
128 | mutex_unlock(&twl->mutex); | |
129 | return ret; | |
130 | } | |
131 | ||
132 | static void twl4030_pwmled_disable(struct pwm_chip *chip, | |
133 | struct pwm_device *pwm) | |
134 | { | |
135 | struct twl_pwmled_chip *twl = to_twl(chip); | |
136 | int ret; | |
137 | u8 val; | |
138 | ||
139 | mutex_lock(&twl->mutex); | |
140 | ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); | |
141 | if (ret < 0) { | |
142 | dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label); | |
143 | goto out; | |
144 | } | |
145 | ||
146 | val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); | |
147 | ||
148 | ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); | |
149 | if (ret < 0) | |
150 | dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); | |
151 | ||
152 | out: | |
153 | mutex_unlock(&twl->mutex); | |
154 | } | |
155 | ||
a1bbf823 UKK |
156 | static int twl4030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
157 | const struct pwm_state *state) | |
158 | { | |
159 | int ret; | |
160 | ||
161 | if (state->polarity != PWM_POLARITY_NORMAL) | |
162 | return -EINVAL; | |
163 | ||
164 | if (!state->enabled) { | |
165 | if (pwm->state.enabled) | |
166 | twl4030_pwmled_disable(chip, pwm); | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | /* | |
172 | * We cannot skip calling ->config even if state->period == | |
173 | * pwm->state.period && state->duty_cycle == pwm->state.duty_cycle | |
174 | * because we might have exited early in the last call to | |
175 | * pwm_apply_state because of !state->enabled and so the two values in | |
176 | * pwm->state might not be configured in hardware. | |
177 | */ | |
178 | ret = twl4030_pwmled_config(pwm->chip, pwm, | |
179 | state->duty_cycle, state->period); | |
180 | if (ret) | |
181 | return ret; | |
182 | ||
183 | if (!pwm->state.enabled) | |
184 | ret = twl4030_pwmled_enable(chip, pwm); | |
185 | ||
186 | return ret; | |
187 | } | |
188 | ||
189 | ||
190 | static const struct pwm_ops twl4030_pwmled_ops = { | |
191 | .apply = twl4030_pwmled_apply, | |
192 | .owner = THIS_MODULE, | |
193 | }; | |
194 | ||
aa765647 PU |
195 | static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, |
196 | int duty_ns, int period_ns) | |
197 | { | |
198 | int duty_cycle = (duty_ns * TWL6030_LED_MAX) / period_ns; | |
199 | u8 on_time; | |
200 | int ret; | |
201 | ||
202 | on_time = duty_cycle & 0xff; | |
203 | ||
204 | ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, on_time, | |
205 | TWL6030_LED_PWM_CTRL1); | |
206 | if (ret < 0) | |
207 | dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); | |
208 | ||
209 | return ret; | |
210 | } | |
211 | ||
212 | static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
213 | { | |
214 | struct twl_pwmled_chip *twl = to_twl(chip); | |
215 | int ret; | |
216 | u8 val; | |
217 | ||
218 | mutex_lock(&twl->mutex); | |
219 | ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); | |
220 | if (ret < 0) { | |
221 | dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", | |
222 | pwm->label); | |
223 | goto out; | |
224 | } | |
225 | ||
226 | val &= ~TWL6040_LED_MODE_MASK; | |
227 | val |= TWL6040_LED_MODE_ON; | |
228 | ||
229 | ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); | |
230 | if (ret < 0) | |
231 | dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); | |
232 | ||
233 | out: | |
234 | mutex_unlock(&twl->mutex); | |
235 | return ret; | |
236 | } | |
237 | ||
238 | static void twl6030_pwmled_disable(struct pwm_chip *chip, | |
239 | struct pwm_device *pwm) | |
240 | { | |
241 | struct twl_pwmled_chip *twl = to_twl(chip); | |
242 | int ret; | |
243 | u8 val; | |
244 | ||
245 | mutex_lock(&twl->mutex); | |
246 | ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); | |
247 | if (ret < 0) { | |
248 | dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", | |
249 | pwm->label); | |
250 | goto out; | |
251 | } | |
252 | ||
253 | val &= ~TWL6040_LED_MODE_MASK; | |
254 | val |= TWL6040_LED_MODE_OFF; | |
255 | ||
256 | ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); | |
257 | if (ret < 0) | |
258 | dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); | |
259 | ||
260 | out: | |
261 | mutex_unlock(&twl->mutex); | |
262 | } | |
263 | ||
a1bbf823 UKK |
264 | static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
265 | const struct pwm_state *state) | |
266 | { | |
267 | int err; | |
268 | ||
269 | if (state->polarity != pwm->state.polarity) | |
270 | return -EINVAL; | |
271 | ||
272 | if (!state->enabled) { | |
273 | if (pwm->state.enabled) | |
274 | twl6030_pwmled_disable(chip, pwm); | |
275 | ||
276 | return 0; | |
277 | } | |
278 | ||
279 | err = twl6030_pwmled_config(pwm->chip, pwm, | |
280 | state->duty_cycle, state->period); | |
281 | if (err) | |
282 | return err; | |
283 | ||
284 | if (!pwm->state.enabled) | |
285 | err = twl6030_pwmled_enable(chip, pwm); | |
286 | ||
287 | return err; | |
288 | } | |
289 | ||
aa765647 PU |
290 | static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm) |
291 | { | |
292 | struct twl_pwmled_chip *twl = to_twl(chip); | |
293 | int ret; | |
294 | u8 val; | |
295 | ||
296 | mutex_lock(&twl->mutex); | |
297 | ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); | |
298 | if (ret < 0) { | |
299 | dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", | |
300 | pwm->label); | |
301 | goto out; | |
302 | } | |
303 | ||
304 | val &= ~TWL6040_LED_MODE_MASK; | |
305 | val |= TWL6040_LED_MODE_OFF; | |
306 | ||
307 | ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); | |
308 | if (ret < 0) | |
309 | dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label); | |
310 | ||
311 | out: | |
312 | mutex_unlock(&twl->mutex); | |
313 | return ret; | |
314 | } | |
315 | ||
316 | static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm) | |
317 | { | |
318 | struct twl_pwmled_chip *twl = to_twl(chip); | |
319 | int ret; | |
320 | u8 val; | |
321 | ||
322 | mutex_lock(&twl->mutex); | |
323 | ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); | |
324 | if (ret < 0) { | |
325 | dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", | |
326 | pwm->label); | |
327 | goto out; | |
328 | } | |
329 | ||
330 | val &= ~TWL6040_LED_MODE_MASK; | |
331 | val |= TWL6040_LED_MODE_HW; | |
332 | ||
333 | ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); | |
334 | if (ret < 0) | |
335 | dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label); | |
336 | ||
337 | out: | |
338 | mutex_unlock(&twl->mutex); | |
339 | } | |
340 | ||
aa765647 | 341 | static const struct pwm_ops twl6030_pwmled_ops = { |
a1bbf823 | 342 | .apply = twl6030_pwmled_apply, |
aa765647 PU |
343 | .request = twl6030_pwmled_request, |
344 | .free = twl6030_pwmled_free, | |
7fa25314 | 345 | .owner = THIS_MODULE, |
aa765647 PU |
346 | }; |
347 | ||
348 | static int twl_pwmled_probe(struct platform_device *pdev) | |
349 | { | |
350 | struct twl_pwmled_chip *twl; | |
aa765647 PU |
351 | |
352 | twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); | |
353 | if (!twl) | |
354 | return -ENOMEM; | |
355 | ||
356 | if (twl_class_is_4030()) { | |
357 | twl->chip.ops = &twl4030_pwmled_ops; | |
358 | twl->chip.npwm = 2; | |
359 | } else { | |
360 | twl->chip.ops = &twl6030_pwmled_ops; | |
361 | twl->chip.npwm = 1; | |
362 | } | |
363 | ||
364 | twl->chip.dev = &pdev->dev; | |
aa765647 PU |
365 | |
366 | mutex_init(&twl->mutex); | |
367 | ||
c9bb1c9e | 368 | return devm_pwmchip_add(&pdev->dev, &twl->chip); |
aa765647 PU |
369 | } |
370 | ||
371 | #ifdef CONFIG_OF | |
f1a8870a | 372 | static const struct of_device_id twl_pwmled_of_match[] = { |
aa765647 PU |
373 | { .compatible = "ti,twl4030-pwmled" }, |
374 | { .compatible = "ti,twl6030-pwmled" }, | |
375 | { }, | |
376 | }; | |
377 | MODULE_DEVICE_TABLE(of, twl_pwmled_of_match); | |
378 | #endif | |
379 | ||
380 | static struct platform_driver twl_pwmled_driver = { | |
381 | .driver = { | |
382 | .name = "twl-pwmled", | |
383 | .of_match_table = of_match_ptr(twl_pwmled_of_match), | |
384 | }, | |
385 | .probe = twl_pwmled_probe, | |
aa765647 PU |
386 | }; |
387 | module_platform_driver(twl_pwmled_driver); | |
388 | ||
389 | MODULE_AUTHOR("Peter Ujfalusi <[email protected]>"); | |
390 | MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs"); | |
391 | MODULE_ALIAS("platform:twl-pwmled"); | |
392 | MODULE_LICENSE("GPL"); |