1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * LP5521/LP5523/LP55231/LP5562 Common Driver |
4 | * |
5 | * Copyright 2012 Texas Instruments |
6 | * |
7 | * Author: Milo(Woogyom) Kim <milo.kim@ti.com> |
8 | * |
9 | * Derived from leds-lp5521.c, leds-lp5523.c |
10 | */ |
11 | |
12 | #include <linux/clk.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/firmware.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/leds.h> |
17 | #include <linux/module.h> |
18 | #include <linux/platform_data/leds-lp55xx.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/gpio/consumer.h> |
21 | #include <dt-bindings/leds/leds-lp55xx.h> |
22 | |
23 | #include "leds-lp55xx-common.h" |
24 | |
25 | /* External clock rate */ |
26 | #define LP55XX_CLK_32K 32768 |
27 | |
28 | static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev) |
29 | { |
30 | return container_of(cdev, struct lp55xx_led, cdev); |
31 | } |
32 | |
33 | static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev) |
34 | { |
35 | return cdev_to_lp55xx_led(cdev: dev_get_drvdata(dev)); |
36 | } |
37 | |
38 | static struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev) |
39 | { |
40 | return container_of(mc_cdev, struct lp55xx_led, mc_cdev); |
41 | } |
42 | |
43 | static void lp55xx_reset_device(struct lp55xx_chip *chip) |
44 | { |
45 | struct lp55xx_device_config *cfg = chip->cfg; |
46 | u8 addr = cfg->reset.addr; |
47 | u8 val = cfg->reset.val; |
48 | |
49 | /* no error checking here because no ACK from the device after reset */ |
50 | lp55xx_write(chip, reg: addr, val); |
51 | } |
52 | |
53 | static int lp55xx_detect_device(struct lp55xx_chip *chip) |
54 | { |
55 | struct lp55xx_device_config *cfg = chip->cfg; |
56 | u8 addr = cfg->enable.addr; |
57 | u8 val = cfg->enable.val; |
58 | int ret; |
59 | |
60 | ret = lp55xx_write(chip, reg: addr, val); |
61 | if (ret) |
62 | return ret; |
63 | |
64 | usleep_range(min: 1000, max: 2000); |
65 | |
66 | ret = lp55xx_read(chip, reg: addr, val: &val); |
67 | if (ret) |
68 | return ret; |
69 | |
70 | if (val != cfg->enable.val) |
71 | return -ENODEV; |
72 | |
73 | return 0; |
74 | } |
75 | |
76 | static int lp55xx_post_init_device(struct lp55xx_chip *chip) |
77 | { |
78 | struct lp55xx_device_config *cfg = chip->cfg; |
79 | |
80 | if (!cfg->post_init_device) |
81 | return 0; |
82 | |
83 | return cfg->post_init_device(chip); |
84 | } |
85 | |
86 | static ssize_t led_current_show(struct device *dev, |
87 | struct device_attribute *attr, |
88 | char *buf) |
89 | { |
90 | struct lp55xx_led *led = dev_to_lp55xx_led(dev); |
91 | |
92 | return sysfs_emit(buf, fmt: "%d\n" , led->led_current); |
93 | } |
94 | |
95 | static ssize_t led_current_store(struct device *dev, |
96 | struct device_attribute *attr, |
97 | const char *buf, size_t len) |
98 | { |
99 | struct lp55xx_led *led = dev_to_lp55xx_led(dev); |
100 | struct lp55xx_chip *chip = led->chip; |
101 | unsigned long curr; |
102 | |
103 | if (kstrtoul(s: buf, base: 0, res: &curr)) |
104 | return -EINVAL; |
105 | |
106 | if (curr > led->max_current) |
107 | return -EINVAL; |
108 | |
109 | if (!chip->cfg->set_led_current) |
110 | return len; |
111 | |
112 | mutex_lock(&chip->lock); |
113 | chip->cfg->set_led_current(led, (u8)curr); |
114 | mutex_unlock(lock: &chip->lock); |
115 | |
116 | return len; |
117 | } |
118 | |
119 | static ssize_t max_current_show(struct device *dev, |
120 | struct device_attribute *attr, |
121 | char *buf) |
122 | { |
123 | struct lp55xx_led *led = dev_to_lp55xx_led(dev); |
124 | |
125 | return sysfs_emit(buf, fmt: "%d\n" , led->max_current); |
126 | } |
127 | |
128 | static DEVICE_ATTR_RW(led_current); |
129 | static DEVICE_ATTR_RO(max_current); |
130 | |
131 | static struct attribute *lp55xx_led_attrs[] = { |
132 | &dev_attr_led_current.attr, |
133 | &dev_attr_max_current.attr, |
134 | NULL, |
135 | }; |
136 | ATTRIBUTE_GROUPS(lp55xx_led); |
137 | |
138 | static int lp55xx_set_mc_brightness(struct led_classdev *cdev, |
139 | enum led_brightness brightness) |
140 | { |
141 | struct led_classdev_mc *mc_dev = lcdev_to_mccdev(led_cdev: cdev); |
142 | struct lp55xx_led *led = mcled_cdev_to_led(mc_cdev: mc_dev); |
143 | struct lp55xx_device_config *cfg = led->chip->cfg; |
144 | |
145 | led_mc_calc_color_components(mcled_cdev: &led->mc_cdev, brightness); |
146 | return cfg->multicolor_brightness_fn(led); |
147 | |
148 | } |
149 | |
150 | static int lp55xx_set_brightness(struct led_classdev *cdev, |
151 | enum led_brightness brightness) |
152 | { |
153 | struct lp55xx_led *led = cdev_to_lp55xx_led(cdev); |
154 | struct lp55xx_device_config *cfg = led->chip->cfg; |
155 | |
156 | led->brightness = (u8)brightness; |
157 | return cfg->brightness_fn(led); |
158 | } |
159 | |
160 | static int lp55xx_init_led(struct lp55xx_led *led, |
161 | struct lp55xx_chip *chip, int chan) |
162 | { |
163 | struct lp55xx_platform_data *pdata = chip->pdata; |
164 | struct lp55xx_device_config *cfg = chip->cfg; |
165 | struct device *dev = &chip->cl->dev; |
166 | int max_channel = cfg->max_channel; |
167 | struct mc_subled *mc_led_info; |
168 | struct led_classdev *led_cdev; |
169 | char name[32]; |
170 | int i; |
171 | int ret; |
172 | |
173 | if (chan >= max_channel) { |
174 | dev_err(dev, "invalid channel: %d / %d\n" , chan, max_channel); |
175 | return -EINVAL; |
176 | } |
177 | |
178 | if (pdata->led_config[chan].led_current == 0) |
179 | return 0; |
180 | |
181 | if (pdata->led_config[chan].name) { |
182 | led->cdev.name = pdata->led_config[chan].name; |
183 | } else { |
184 | snprintf(buf: name, size: sizeof(name), fmt: "%s:channel%d" , |
185 | pdata->label ? : chip->cl->name, chan); |
186 | led->cdev.name = name; |
187 | } |
188 | |
189 | if (pdata->led_config[chan].num_colors > 1) { |
190 | mc_led_info = devm_kcalloc(dev, |
191 | n: pdata->led_config[chan].num_colors, |
192 | size: sizeof(*mc_led_info), GFP_KERNEL); |
193 | if (!mc_led_info) |
194 | return -ENOMEM; |
195 | |
196 | led_cdev = &led->mc_cdev.led_cdev; |
197 | led_cdev->name = led->cdev.name; |
198 | led_cdev->brightness_set_blocking = lp55xx_set_mc_brightness; |
199 | led->mc_cdev.num_colors = pdata->led_config[chan].num_colors; |
200 | for (i = 0; i < led->mc_cdev.num_colors; i++) { |
201 | mc_led_info[i].color_index = |
202 | pdata->led_config[chan].color_id[i]; |
203 | mc_led_info[i].channel = |
204 | pdata->led_config[chan].output_num[i]; |
205 | } |
206 | |
207 | led->mc_cdev.subled_info = mc_led_info; |
208 | } else { |
209 | led->cdev.brightness_set_blocking = lp55xx_set_brightness; |
210 | } |
211 | |
212 | led->cdev.groups = lp55xx_led_groups; |
213 | led->cdev.default_trigger = pdata->led_config[chan].default_trigger; |
214 | led->led_current = pdata->led_config[chan].led_current; |
215 | led->max_current = pdata->led_config[chan].max_current; |
216 | led->chan_nr = pdata->led_config[chan].chan_nr; |
217 | |
218 | if (led->chan_nr >= max_channel) { |
219 | dev_err(dev, "Use channel numbers between 0 and %d\n" , |
220 | max_channel - 1); |
221 | return -EINVAL; |
222 | } |
223 | |
224 | if (pdata->led_config[chan].num_colors > 1) |
225 | ret = devm_led_classdev_multicolor_register(parent: dev, mcled_cdev: &led->mc_cdev); |
226 | else |
227 | ret = devm_led_classdev_register(parent: dev, led_cdev: &led->cdev); |
228 | |
229 | if (ret) { |
230 | dev_err(dev, "led register err: %d\n" , ret); |
231 | return ret; |
232 | } |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static void lp55xx_firmware_loaded(const struct firmware *fw, void *context) |
238 | { |
239 | struct lp55xx_chip *chip = context; |
240 | struct device *dev = &chip->cl->dev; |
241 | enum lp55xx_engine_index idx = chip->engine_idx; |
242 | |
243 | if (!fw) { |
244 | dev_err(dev, "firmware request failed\n" ); |
245 | return; |
246 | } |
247 | |
248 | /* handling firmware data is chip dependent */ |
249 | mutex_lock(&chip->lock); |
250 | |
251 | chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD; |
252 | chip->fw = fw; |
253 | if (chip->cfg->firmware_cb) |
254 | chip->cfg->firmware_cb(chip); |
255 | |
256 | mutex_unlock(lock: &chip->lock); |
257 | |
258 | /* firmware should be released for other channel use */ |
259 | release_firmware(fw: chip->fw); |
260 | chip->fw = NULL; |
261 | } |
262 | |
263 | static int lp55xx_request_firmware(struct lp55xx_chip *chip) |
264 | { |
265 | const char *name = chip->cl->name; |
266 | struct device *dev = &chip->cl->dev; |
267 | |
268 | return request_firmware_nowait(THIS_MODULE, uevent: false, name, device: dev, |
269 | GFP_KERNEL, context: chip, cont: lp55xx_firmware_loaded); |
270 | } |
271 | |
272 | static ssize_t select_engine_show(struct device *dev, |
273 | struct device_attribute *attr, |
274 | char *buf) |
275 | { |
276 | struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); |
277 | struct lp55xx_chip *chip = led->chip; |
278 | |
279 | return sprintf(buf, fmt: "%d\n" , chip->engine_idx); |
280 | } |
281 | |
282 | static ssize_t select_engine_store(struct device *dev, |
283 | struct device_attribute *attr, |
284 | const char *buf, size_t len) |
285 | { |
286 | struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); |
287 | struct lp55xx_chip *chip = led->chip; |
288 | unsigned long val; |
289 | int ret; |
290 | |
291 | if (kstrtoul(s: buf, base: 0, res: &val)) |
292 | return -EINVAL; |
293 | |
294 | /* select the engine to be run */ |
295 | |
296 | switch (val) { |
297 | case LP55XX_ENGINE_1: |
298 | case LP55XX_ENGINE_2: |
299 | case LP55XX_ENGINE_3: |
300 | mutex_lock(&chip->lock); |
301 | chip->engine_idx = val; |
302 | ret = lp55xx_request_firmware(chip); |
303 | mutex_unlock(lock: &chip->lock); |
304 | break; |
305 | default: |
306 | dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n" , val); |
307 | return -EINVAL; |
308 | } |
309 | |
310 | if (ret) { |
311 | dev_err(dev, "request firmware err: %d\n" , ret); |
312 | return ret; |
313 | } |
314 | |
315 | return len; |
316 | } |
317 | |
318 | static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start) |
319 | { |
320 | if (chip->cfg->run_engine) |
321 | chip->cfg->run_engine(chip, start); |
322 | } |
323 | |
324 | static ssize_t run_engine_store(struct device *dev, |
325 | struct device_attribute *attr, |
326 | const char *buf, size_t len) |
327 | { |
328 | struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); |
329 | struct lp55xx_chip *chip = led->chip; |
330 | unsigned long val; |
331 | |
332 | if (kstrtoul(s: buf, base: 0, res: &val)) |
333 | return -EINVAL; |
334 | |
335 | /* run or stop the selected engine */ |
336 | |
337 | if (val <= 0) { |
338 | lp55xx_run_engine(chip, start: false); |
339 | return len; |
340 | } |
341 | |
342 | mutex_lock(&chip->lock); |
343 | lp55xx_run_engine(chip, start: true); |
344 | mutex_unlock(lock: &chip->lock); |
345 | |
346 | return len; |
347 | } |
348 | |
349 | static DEVICE_ATTR_RW(select_engine); |
350 | static DEVICE_ATTR_WO(run_engine); |
351 | |
352 | static struct attribute *lp55xx_engine_attributes[] = { |
353 | &dev_attr_select_engine.attr, |
354 | &dev_attr_run_engine.attr, |
355 | NULL, |
356 | }; |
357 | |
358 | static const struct attribute_group lp55xx_engine_attr_group = { |
359 | .attrs = lp55xx_engine_attributes, |
360 | }; |
361 | |
362 | int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val) |
363 | { |
364 | return i2c_smbus_write_byte_data(client: chip->cl, command: reg, value: val); |
365 | } |
366 | EXPORT_SYMBOL_GPL(lp55xx_write); |
367 | |
368 | int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val) |
369 | { |
370 | s32 ret; |
371 | |
372 | ret = i2c_smbus_read_byte_data(client: chip->cl, command: reg); |
373 | if (ret < 0) |
374 | return ret; |
375 | |
376 | *val = ret; |
377 | return 0; |
378 | } |
379 | EXPORT_SYMBOL_GPL(lp55xx_read); |
380 | |
381 | int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val) |
382 | { |
383 | int ret; |
384 | u8 tmp; |
385 | |
386 | ret = lp55xx_read(chip, reg, &tmp); |
387 | if (ret) |
388 | return ret; |
389 | |
390 | tmp &= ~mask; |
391 | tmp |= val & mask; |
392 | |
393 | return lp55xx_write(chip, reg, tmp); |
394 | } |
395 | EXPORT_SYMBOL_GPL(lp55xx_update_bits); |
396 | |
397 | bool lp55xx_is_extclk_used(struct lp55xx_chip *chip) |
398 | { |
399 | struct clk *clk; |
400 | int err; |
401 | |
402 | clk = devm_clk_get(dev: &chip->cl->dev, id: "32k_clk" ); |
403 | if (IS_ERR(ptr: clk)) |
404 | goto use_internal_clk; |
405 | |
406 | err = clk_prepare_enable(clk); |
407 | if (err) |
408 | goto use_internal_clk; |
409 | |
410 | if (clk_get_rate(clk) != LP55XX_CLK_32K) { |
411 | clk_disable_unprepare(clk); |
412 | goto use_internal_clk; |
413 | } |
414 | |
415 | dev_info(&chip->cl->dev, "%dHz external clock used\n" , LP55XX_CLK_32K); |
416 | |
417 | chip->clk = clk; |
418 | return true; |
419 | |
420 | use_internal_clk: |
421 | dev_info(&chip->cl->dev, "internal clock used\n" ); |
422 | return false; |
423 | } |
424 | EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used); |
425 | |
426 | int lp55xx_init_device(struct lp55xx_chip *chip) |
427 | { |
428 | struct lp55xx_platform_data *pdata; |
429 | struct lp55xx_device_config *cfg; |
430 | struct device *dev = &chip->cl->dev; |
431 | int ret = 0; |
432 | |
433 | WARN_ON(!chip); |
434 | |
435 | pdata = chip->pdata; |
436 | cfg = chip->cfg; |
437 | |
438 | if (!pdata || !cfg) |
439 | return -EINVAL; |
440 | |
441 | if (pdata->enable_gpiod) { |
442 | gpiod_direction_output(desc: pdata->enable_gpiod, value: 0); |
443 | |
444 | gpiod_set_consumer_name(desc: pdata->enable_gpiod, name: "LP55xx enable" ); |
445 | gpiod_set_value_cansleep(desc: pdata->enable_gpiod, value: 0); |
446 | usleep_range(min: 1000, max: 2000); /* Keep enable down at least 1ms */ |
447 | gpiod_set_value_cansleep(desc: pdata->enable_gpiod, value: 1); |
448 | usleep_range(min: 1000, max: 2000); /* 500us abs min. */ |
449 | } |
450 | |
451 | lp55xx_reset_device(chip); |
452 | |
453 | /* |
454 | * Exact value is not available. 10 - 20ms |
455 | * appears to be enough for reset. |
456 | */ |
457 | usleep_range(min: 10000, max: 20000); |
458 | |
459 | ret = lp55xx_detect_device(chip); |
460 | if (ret) { |
461 | dev_err(dev, "device detection err: %d\n" , ret); |
462 | goto err; |
463 | } |
464 | |
465 | /* chip specific initialization */ |
466 | ret = lp55xx_post_init_device(chip); |
467 | if (ret) { |
468 | dev_err(dev, "post init device err: %d\n" , ret); |
469 | goto err_post_init; |
470 | } |
471 | |
472 | return 0; |
473 | |
474 | err_post_init: |
475 | lp55xx_deinit_device(chip); |
476 | err: |
477 | return ret; |
478 | } |
479 | EXPORT_SYMBOL_GPL(lp55xx_init_device); |
480 | |
481 | void lp55xx_deinit_device(struct lp55xx_chip *chip) |
482 | { |
483 | struct lp55xx_platform_data *pdata = chip->pdata; |
484 | |
485 | if (chip->clk) |
486 | clk_disable_unprepare(clk: chip->clk); |
487 | |
488 | if (pdata->enable_gpiod) |
489 | gpiod_set_value(desc: pdata->enable_gpiod, value: 0); |
490 | } |
491 | EXPORT_SYMBOL_GPL(lp55xx_deinit_device); |
492 | |
493 | int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) |
494 | { |
495 | struct lp55xx_platform_data *pdata = chip->pdata; |
496 | struct lp55xx_device_config *cfg = chip->cfg; |
497 | int num_channels = pdata->num_channels; |
498 | struct lp55xx_led *each; |
499 | u8 led_current; |
500 | int ret; |
501 | int i; |
502 | |
503 | if (!cfg->brightness_fn) { |
504 | dev_err(&chip->cl->dev, "empty brightness configuration\n" ); |
505 | return -EINVAL; |
506 | } |
507 | |
508 | for (i = 0; i < num_channels; i++) { |
509 | |
510 | /* do not initialize channels that are not connected */ |
511 | if (pdata->led_config[i].led_current == 0) |
512 | continue; |
513 | |
514 | led_current = pdata->led_config[i].led_current; |
515 | each = led + i; |
516 | ret = lp55xx_init_led(led: each, chip, chan: i); |
517 | if (ret) |
518 | goto err_init_led; |
519 | |
520 | chip->num_leds++; |
521 | each->chip = chip; |
522 | |
523 | /* setting led current at each channel */ |
524 | if (cfg->set_led_current) |
525 | cfg->set_led_current(each, led_current); |
526 | } |
527 | |
528 | return 0; |
529 | |
530 | err_init_led: |
531 | return ret; |
532 | } |
533 | EXPORT_SYMBOL_GPL(lp55xx_register_leds); |
534 | |
535 | int lp55xx_register_sysfs(struct lp55xx_chip *chip) |
536 | { |
537 | struct device *dev = &chip->cl->dev; |
538 | struct lp55xx_device_config *cfg = chip->cfg; |
539 | int ret; |
540 | |
541 | if (!cfg->run_engine || !cfg->firmware_cb) |
542 | goto dev_specific_attrs; |
543 | |
544 | ret = sysfs_create_group(kobj: &dev->kobj, grp: &lp55xx_engine_attr_group); |
545 | if (ret) |
546 | return ret; |
547 | |
548 | dev_specific_attrs: |
549 | return cfg->dev_attr_group ? |
550 | sysfs_create_group(kobj: &dev->kobj, grp: cfg->dev_attr_group) : 0; |
551 | } |
552 | EXPORT_SYMBOL_GPL(lp55xx_register_sysfs); |
553 | |
554 | void lp55xx_unregister_sysfs(struct lp55xx_chip *chip) |
555 | { |
556 | struct device *dev = &chip->cl->dev; |
557 | struct lp55xx_device_config *cfg = chip->cfg; |
558 | |
559 | if (cfg->dev_attr_group) |
560 | sysfs_remove_group(kobj: &dev->kobj, grp: cfg->dev_attr_group); |
561 | |
562 | sysfs_remove_group(kobj: &dev->kobj, grp: &lp55xx_engine_attr_group); |
563 | } |
564 | EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs); |
565 | |
566 | static int lp55xx_parse_common_child(struct device_node *np, |
567 | struct lp55xx_led_config *cfg, |
568 | int led_number, int *chan_nr) |
569 | { |
570 | int ret; |
571 | |
572 | of_property_read_string(np, propname: "chan-name" , |
573 | out_string: &cfg[led_number].name); |
574 | of_property_read_u8(np, propname: "led-cur" , |
575 | out_value: &cfg[led_number].led_current); |
576 | of_property_read_u8(np, propname: "max-cur" , |
577 | out_value: &cfg[led_number].max_current); |
578 | |
579 | ret = of_property_read_u32(np, propname: "reg" , out_value: chan_nr); |
580 | if (ret) |
581 | return ret; |
582 | |
583 | if (*chan_nr < 0 || *chan_nr > cfg->max_channel) |
584 | return -EINVAL; |
585 | |
586 | return 0; |
587 | } |
588 | |
589 | static int lp55xx_parse_multi_led_child(struct device_node *child, |
590 | struct lp55xx_led_config *cfg, |
591 | int child_number, int color_number) |
592 | { |
593 | int chan_nr, color_id, ret; |
594 | |
595 | ret = lp55xx_parse_common_child(np: child, cfg, led_number: child_number, chan_nr: &chan_nr); |
596 | if (ret) |
597 | return ret; |
598 | |
599 | ret = of_property_read_u32(np: child, propname: "color" , out_value: &color_id); |
600 | if (ret) |
601 | return ret; |
602 | |
603 | cfg[child_number].color_id[color_number] = color_id; |
604 | cfg[child_number].output_num[color_number] = chan_nr; |
605 | |
606 | return 0; |
607 | } |
608 | |
609 | static int lp55xx_parse_multi_led(struct device_node *np, |
610 | struct lp55xx_led_config *cfg, |
611 | int child_number) |
612 | { |
613 | struct device_node *child; |
614 | int num_colors = 0, ret; |
615 | |
616 | for_each_available_child_of_node(np, child) { |
617 | ret = lp55xx_parse_multi_led_child(child, cfg, child_number, |
618 | color_number: num_colors); |
619 | if (ret) { |
620 | of_node_put(node: child); |
621 | return ret; |
622 | } |
623 | num_colors++; |
624 | } |
625 | |
626 | cfg[child_number].num_colors = num_colors; |
627 | |
628 | return 0; |
629 | } |
630 | |
631 | static int lp55xx_parse_logical_led(struct device_node *np, |
632 | struct lp55xx_led_config *cfg, |
633 | int child_number) |
634 | { |
635 | int led_color, ret; |
636 | int chan_nr = 0; |
637 | |
638 | cfg[child_number].default_trigger = |
639 | of_get_property(node: np, name: "linux,default-trigger" , NULL); |
640 | |
641 | ret = of_property_read_u32(np, propname: "color" , out_value: &led_color); |
642 | if (ret) |
643 | return ret; |
644 | |
645 | if (led_color == LED_COLOR_ID_RGB) |
646 | return lp55xx_parse_multi_led(np, cfg, child_number); |
647 | |
648 | ret = lp55xx_parse_common_child(np, cfg, led_number: child_number, chan_nr: &chan_nr); |
649 | if (ret < 0) |
650 | return ret; |
651 | |
652 | cfg[child_number].chan_nr = chan_nr; |
653 | |
654 | return ret; |
655 | } |
656 | |
657 | struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev, |
658 | struct device_node *np, |
659 | struct lp55xx_chip *chip) |
660 | { |
661 | struct device_node *child; |
662 | struct lp55xx_platform_data *pdata; |
663 | struct lp55xx_led_config *cfg; |
664 | int num_channels; |
665 | int i = 0; |
666 | int ret; |
667 | |
668 | pdata = devm_kzalloc(dev, size: sizeof(*pdata), GFP_KERNEL); |
669 | if (!pdata) |
670 | return ERR_PTR(error: -ENOMEM); |
671 | |
672 | num_channels = of_get_available_child_count(np); |
673 | if (num_channels == 0) { |
674 | dev_err(dev, "no LED channels\n" ); |
675 | return ERR_PTR(error: -EINVAL); |
676 | } |
677 | |
678 | cfg = devm_kcalloc(dev, n: num_channels, size: sizeof(*cfg), GFP_KERNEL); |
679 | if (!cfg) |
680 | return ERR_PTR(error: -ENOMEM); |
681 | |
682 | pdata->led_config = &cfg[0]; |
683 | pdata->num_channels = num_channels; |
684 | cfg->max_channel = chip->cfg->max_channel; |
685 | |
686 | for_each_available_child_of_node(np, child) { |
687 | ret = lp55xx_parse_logical_led(np: child, cfg, child_number: i); |
688 | if (ret) { |
689 | of_node_put(node: child); |
690 | return ERR_PTR(error: -EINVAL); |
691 | } |
692 | i++; |
693 | } |
694 | |
695 | if (of_property_read_u32(np, propname: "ti,charge-pump-mode" , out_value: &pdata->charge_pump_mode)) |
696 | pdata->charge_pump_mode = LP55XX_CP_AUTO; |
697 | |
698 | if (pdata->charge_pump_mode > LP55XX_CP_AUTO) { |
699 | dev_err(dev, "invalid charge pump mode %d\n" , pdata->charge_pump_mode); |
700 | return ERR_PTR(error: -EINVAL); |
701 | } |
702 | |
703 | of_property_read_string(np, propname: "label" , out_string: &pdata->label); |
704 | of_property_read_u8(np, propname: "clock-mode" , out_value: &pdata->clock_mode); |
705 | |
706 | pdata->enable_gpiod = devm_gpiod_get_optional(dev, con_id: "enable" , |
707 | flags: GPIOD_ASIS); |
708 | if (IS_ERR(ptr: pdata->enable_gpiod)) |
709 | return ERR_CAST(ptr: pdata->enable_gpiod); |
710 | |
711 | /* LP8501 specific */ |
712 | of_property_read_u8(np, propname: "pwr-sel" , out_value: (u8 *)&pdata->pwr_sel); |
713 | |
714 | return pdata; |
715 | } |
716 | EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata); |
717 | |
718 | MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>" ); |
719 | MODULE_DESCRIPTION("LP55xx Common Driver" ); |
720 | MODULE_LICENSE("GPL" ); |
721 | |