1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * leds-bd2802.c - RGB LED Driver |
4 | * |
5 | * Copyright (C) 2009 Samsung Electronics |
6 | * Kim Kyuwon <q1.kim@samsung.com> |
7 | * |
8 | * Datasheet: http://www.rohm.com/products/databook/driver/pdf/bd2802gu-e.pdf |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/gpio/consumer.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/leds.h> |
16 | #include <linux/leds-bd2802.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/pm.h> |
19 | |
20 | #define LED_CTL(rgb2en, rgb1en) ((rgb2en) << 4 | ((rgb1en) << 0)) |
21 | |
22 | #define BD2802_LED_OFFSET 0xa |
23 | #define BD2802_COLOR_OFFSET 0x3 |
24 | |
25 | #define BD2802_REG_CLKSETUP 0x00 |
26 | #define BD2802_REG_CONTROL 0x01 |
27 | #define BD2802_REG_HOURSETUP 0x02 |
28 | #define BD2802_REG_CURRENT1SETUP 0x03 |
29 | #define BD2802_REG_CURRENT2SETUP 0x04 |
30 | #define BD2802_REG_WAVEPATTERN 0x05 |
31 | |
32 | #define BD2802_CURRENT_032 0x10 /* 3.2mA */ |
33 | #define BD2802_CURRENT_000 0x00 /* 0.0mA */ |
34 | |
35 | #define BD2802_PATTERN_FULL 0x07 |
36 | #define BD2802_PATTERN_HALF 0x03 |
37 | |
38 | enum led_ids { |
39 | LED1, |
40 | LED2, |
41 | LED_NUM, |
42 | }; |
43 | |
44 | enum led_colors { |
45 | RED, |
46 | GREEN, |
47 | BLUE, |
48 | }; |
49 | |
50 | enum led_bits { |
51 | BD2802_OFF, |
52 | BD2802_BLINK, |
53 | BD2802_ON, |
54 | }; |
55 | |
56 | /* |
57 | * State '0' : 'off' |
58 | * State '1' : 'blink' |
59 | * State '2' : 'on'. |
60 | */ |
61 | struct led_state { |
62 | unsigned r:2; |
63 | unsigned g:2; |
64 | unsigned b:2; |
65 | }; |
66 | |
67 | struct bd2802_led { |
68 | struct bd2802_led_platform_data *pdata; |
69 | struct i2c_client *client; |
70 | struct gpio_desc *reset; |
71 | struct rw_semaphore rwsem; |
72 | |
73 | struct led_state led[2]; |
74 | |
75 | /* |
76 | * Making led_classdev as array is not recommended, because array |
77 | * members prevent using 'container_of' macro. So repetitive works |
78 | * are needed. |
79 | */ |
80 | struct led_classdev cdev_led1r; |
81 | struct led_classdev cdev_led1g; |
82 | struct led_classdev cdev_led1b; |
83 | struct led_classdev cdev_led2r; |
84 | struct led_classdev cdev_led2g; |
85 | struct led_classdev cdev_led2b; |
86 | |
87 | /* |
88 | * Advanced Configuration Function(ADF) mode: |
89 | * In ADF mode, user can set registers of BD2802GU directly, |
90 | * therefore BD2802GU doesn't enter reset state. |
91 | */ |
92 | int adf_on; |
93 | |
94 | enum led_ids led_id; |
95 | enum led_colors color; |
96 | enum led_bits state; |
97 | |
98 | /* General attributes of RGB LEDs */ |
99 | int wave_pattern; |
100 | int rgb_current; |
101 | }; |
102 | |
103 | |
104 | /*--------------------------------------------------------------*/ |
105 | /* BD2802GU helper functions */ |
106 | /*--------------------------------------------------------------*/ |
107 | |
108 | static inline int bd2802_is_rgb_off(struct bd2802_led *led, enum led_ids id, |
109 | enum led_colors color) |
110 | { |
111 | switch (color) { |
112 | case RED: |
113 | return !led->led[id].r; |
114 | case GREEN: |
115 | return !led->led[id].g; |
116 | case BLUE: |
117 | return !led->led[id].b; |
118 | default: |
119 | dev_err(&led->client->dev, "%s: Invalid color\n" , __func__); |
120 | return -EINVAL; |
121 | } |
122 | } |
123 | |
124 | static inline int bd2802_is_led_off(struct bd2802_led *led, enum led_ids id) |
125 | { |
126 | if (led->led[id].r || led->led[id].g || led->led[id].b) |
127 | return 0; |
128 | |
129 | return 1; |
130 | } |
131 | |
132 | static inline int bd2802_is_all_off(struct bd2802_led *led) |
133 | { |
134 | int i; |
135 | |
136 | for (i = 0; i < LED_NUM; i++) |
137 | if (!bd2802_is_led_off(led, id: i)) |
138 | return 0; |
139 | |
140 | return 1; |
141 | } |
142 | |
143 | static inline u8 bd2802_get_base_offset(enum led_ids id, enum led_colors color) |
144 | { |
145 | return id * BD2802_LED_OFFSET + color * BD2802_COLOR_OFFSET; |
146 | } |
147 | |
148 | static inline u8 bd2802_get_reg_addr(enum led_ids id, enum led_colors color, |
149 | u8 reg_offset) |
150 | { |
151 | return reg_offset + bd2802_get_base_offset(id, color); |
152 | } |
153 | |
154 | |
155 | /*--------------------------------------------------------------*/ |
156 | /* BD2802GU core functions */ |
157 | /*--------------------------------------------------------------*/ |
158 | |
159 | static int bd2802_write_byte(struct i2c_client *client, u8 reg, u8 val) |
160 | { |
161 | int ret = i2c_smbus_write_byte_data(client, command: reg, value: val); |
162 | if (ret >= 0) |
163 | return 0; |
164 | |
165 | dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n" , |
166 | __func__, reg, val, ret); |
167 | |
168 | return ret; |
169 | } |
170 | |
171 | static void bd2802_update_state(struct bd2802_led *led, enum led_ids id, |
172 | enum led_colors color, enum led_bits led_bit) |
173 | { |
174 | int i; |
175 | u8 value; |
176 | |
177 | for (i = 0; i < LED_NUM; i++) { |
178 | if (i == id) { |
179 | switch (color) { |
180 | case RED: |
181 | led->led[i].r = led_bit; |
182 | break; |
183 | case GREEN: |
184 | led->led[i].g = led_bit; |
185 | break; |
186 | case BLUE: |
187 | led->led[i].b = led_bit; |
188 | break; |
189 | default: |
190 | dev_err(&led->client->dev, |
191 | "%s: Invalid color\n" , __func__); |
192 | return; |
193 | } |
194 | } |
195 | } |
196 | |
197 | if (led_bit == BD2802_BLINK || led_bit == BD2802_ON) |
198 | return; |
199 | |
200 | if (!bd2802_is_led_off(led, id)) |
201 | return; |
202 | |
203 | if (bd2802_is_all_off(led) && !led->adf_on) { |
204 | gpiod_set_value(desc: led->reset, value: 1); |
205 | return; |
206 | } |
207 | |
208 | /* |
209 | * In this case, other led is turned on, and current led is turned |
210 | * off. So set RGB LED Control register to stop the current RGB LED |
211 | */ |
212 | value = (id == LED1) ? LED_CTL(1, 0) : LED_CTL(0, 1); |
213 | bd2802_write_byte(client: led->client, BD2802_REG_CONTROL, val: value); |
214 | } |
215 | |
216 | static void bd2802_configure(struct bd2802_led *led) |
217 | { |
218 | struct bd2802_led_platform_data *pdata = led->pdata; |
219 | u8 reg; |
220 | |
221 | reg = bd2802_get_reg_addr(id: LED1, color: RED, BD2802_REG_HOURSETUP); |
222 | bd2802_write_byte(client: led->client, reg, val: pdata->rgb_time); |
223 | |
224 | reg = bd2802_get_reg_addr(id: LED2, color: RED, BD2802_REG_HOURSETUP); |
225 | bd2802_write_byte(client: led->client, reg, val: pdata->rgb_time); |
226 | } |
227 | |
228 | static void bd2802_reset_cancel(struct bd2802_led *led) |
229 | { |
230 | gpiod_set_value(desc: led->reset, value: 0); |
231 | udelay(100); |
232 | bd2802_configure(led); |
233 | } |
234 | |
235 | static void bd2802_enable(struct bd2802_led *led, enum led_ids id) |
236 | { |
237 | enum led_ids other_led = (id == LED1) ? LED2 : LED1; |
238 | u8 value, other_led_on; |
239 | |
240 | other_led_on = !bd2802_is_led_off(led, id: other_led); |
241 | if (id == LED1) |
242 | value = LED_CTL(other_led_on, 1); |
243 | else |
244 | value = LED_CTL(1 , other_led_on); |
245 | |
246 | bd2802_write_byte(client: led->client, BD2802_REG_CONTROL, val: value); |
247 | } |
248 | |
249 | static void bd2802_set_on(struct bd2802_led *led, enum led_ids id, |
250 | enum led_colors color) |
251 | { |
252 | u8 reg; |
253 | |
254 | if (bd2802_is_all_off(led) && !led->adf_on) |
255 | bd2802_reset_cancel(led); |
256 | |
257 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP); |
258 | bd2802_write_byte(client: led->client, reg, val: led->rgb_current); |
259 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP); |
260 | bd2802_write_byte(client: led->client, reg, BD2802_CURRENT_000); |
261 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_WAVEPATTERN); |
262 | bd2802_write_byte(client: led->client, reg, BD2802_PATTERN_FULL); |
263 | |
264 | bd2802_enable(led, id); |
265 | bd2802_update_state(led, id, color, led_bit: BD2802_ON); |
266 | } |
267 | |
268 | static void bd2802_set_blink(struct bd2802_led *led, enum led_ids id, |
269 | enum led_colors color) |
270 | { |
271 | u8 reg; |
272 | |
273 | if (bd2802_is_all_off(led) && !led->adf_on) |
274 | bd2802_reset_cancel(led); |
275 | |
276 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP); |
277 | bd2802_write_byte(client: led->client, reg, BD2802_CURRENT_000); |
278 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP); |
279 | bd2802_write_byte(client: led->client, reg, val: led->rgb_current); |
280 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_WAVEPATTERN); |
281 | bd2802_write_byte(client: led->client, reg, val: led->wave_pattern); |
282 | |
283 | bd2802_enable(led, id); |
284 | bd2802_update_state(led, id, color, led_bit: BD2802_BLINK); |
285 | } |
286 | |
287 | static void bd2802_turn_on(struct bd2802_led *led, enum led_ids id, |
288 | enum led_colors color, enum led_bits led_bit) |
289 | { |
290 | if (led_bit == BD2802_OFF) { |
291 | dev_err(&led->client->dev, |
292 | "Only 'blink' and 'on' are allowed\n" ); |
293 | return; |
294 | } |
295 | |
296 | if (led_bit == BD2802_BLINK) |
297 | bd2802_set_blink(led, id, color); |
298 | else |
299 | bd2802_set_on(led, id, color); |
300 | } |
301 | |
302 | static void bd2802_turn_off(struct bd2802_led *led, enum led_ids id, |
303 | enum led_colors color) |
304 | { |
305 | u8 reg; |
306 | |
307 | if (bd2802_is_rgb_off(led, id, color)) |
308 | return; |
309 | |
310 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP); |
311 | bd2802_write_byte(client: led->client, reg, BD2802_CURRENT_000); |
312 | reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP); |
313 | bd2802_write_byte(client: led->client, reg, BD2802_CURRENT_000); |
314 | |
315 | bd2802_update_state(led, id, color, led_bit: BD2802_OFF); |
316 | } |
317 | |
318 | #define BD2802_SET_REGISTER(reg_addr, reg_name) \ |
319 | static ssize_t bd2802_store_reg##reg_addr(struct device *dev, \ |
320 | struct device_attribute *attr, const char *buf, size_t count) \ |
321 | { \ |
322 | struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\ |
323 | unsigned long val; \ |
324 | int ret; \ |
325 | if (!count) \ |
326 | return -EINVAL; \ |
327 | ret = kstrtoul(buf, 16, &val); \ |
328 | if (ret) \ |
329 | return ret; \ |
330 | down_write(&led->rwsem); \ |
331 | bd2802_write_byte(led->client, reg_addr, (u8) val); \ |
332 | up_write(&led->rwsem); \ |
333 | return count; \ |
334 | } \ |
335 | static struct device_attribute bd2802_reg##reg_addr##_attr = { \ |
336 | .attr = {.name = reg_name, .mode = 0644}, \ |
337 | .store = bd2802_store_reg##reg_addr, \ |
338 | }; |
339 | |
340 | BD2802_SET_REGISTER(0x00, "0x00" ); |
341 | BD2802_SET_REGISTER(0x01, "0x01" ); |
342 | BD2802_SET_REGISTER(0x02, "0x02" ); |
343 | BD2802_SET_REGISTER(0x03, "0x03" ); |
344 | BD2802_SET_REGISTER(0x04, "0x04" ); |
345 | BD2802_SET_REGISTER(0x05, "0x05" ); |
346 | BD2802_SET_REGISTER(0x06, "0x06" ); |
347 | BD2802_SET_REGISTER(0x07, "0x07" ); |
348 | BD2802_SET_REGISTER(0x08, "0x08" ); |
349 | BD2802_SET_REGISTER(0x09, "0x09" ); |
350 | BD2802_SET_REGISTER(0x0a, "0x0a" ); |
351 | BD2802_SET_REGISTER(0x0b, "0x0b" ); |
352 | BD2802_SET_REGISTER(0x0c, "0x0c" ); |
353 | BD2802_SET_REGISTER(0x0d, "0x0d" ); |
354 | BD2802_SET_REGISTER(0x0e, "0x0e" ); |
355 | BD2802_SET_REGISTER(0x0f, "0x0f" ); |
356 | BD2802_SET_REGISTER(0x10, "0x10" ); |
357 | BD2802_SET_REGISTER(0x11, "0x11" ); |
358 | BD2802_SET_REGISTER(0x12, "0x12" ); |
359 | BD2802_SET_REGISTER(0x13, "0x13" ); |
360 | BD2802_SET_REGISTER(0x14, "0x14" ); |
361 | BD2802_SET_REGISTER(0x15, "0x15" ); |
362 | |
363 | static struct device_attribute *bd2802_addr_attributes[] = { |
364 | &bd2802_reg0x00_attr, |
365 | &bd2802_reg0x01_attr, |
366 | &bd2802_reg0x02_attr, |
367 | &bd2802_reg0x03_attr, |
368 | &bd2802_reg0x04_attr, |
369 | &bd2802_reg0x05_attr, |
370 | &bd2802_reg0x06_attr, |
371 | &bd2802_reg0x07_attr, |
372 | &bd2802_reg0x08_attr, |
373 | &bd2802_reg0x09_attr, |
374 | &bd2802_reg0x0a_attr, |
375 | &bd2802_reg0x0b_attr, |
376 | &bd2802_reg0x0c_attr, |
377 | &bd2802_reg0x0d_attr, |
378 | &bd2802_reg0x0e_attr, |
379 | &bd2802_reg0x0f_attr, |
380 | &bd2802_reg0x10_attr, |
381 | &bd2802_reg0x11_attr, |
382 | &bd2802_reg0x12_attr, |
383 | &bd2802_reg0x13_attr, |
384 | &bd2802_reg0x14_attr, |
385 | &bd2802_reg0x15_attr, |
386 | }; |
387 | |
388 | static void bd2802_enable_adv_conf(struct bd2802_led *led) |
389 | { |
390 | int i, ret; |
391 | |
392 | for (i = 0; i < ARRAY_SIZE(bd2802_addr_attributes); i++) { |
393 | ret = device_create_file(device: &led->client->dev, |
394 | entry: bd2802_addr_attributes[i]); |
395 | if (ret) { |
396 | dev_err(&led->client->dev, "failed: sysfs file %s\n" , |
397 | bd2802_addr_attributes[i]->attr.name); |
398 | goto failed_remove_files; |
399 | } |
400 | } |
401 | |
402 | if (bd2802_is_all_off(led)) |
403 | bd2802_reset_cancel(led); |
404 | |
405 | led->adf_on = 1; |
406 | |
407 | return; |
408 | |
409 | failed_remove_files: |
410 | for (i--; i >= 0; i--) |
411 | device_remove_file(dev: &led->client->dev, |
412 | attr: bd2802_addr_attributes[i]); |
413 | } |
414 | |
415 | static void bd2802_disable_adv_conf(struct bd2802_led *led) |
416 | { |
417 | int i; |
418 | |
419 | for (i = 0; i < ARRAY_SIZE(bd2802_addr_attributes); i++) |
420 | device_remove_file(dev: &led->client->dev, |
421 | attr: bd2802_addr_attributes[i]); |
422 | |
423 | if (bd2802_is_all_off(led)) |
424 | gpiod_set_value(desc: led->reset, value: 1); |
425 | |
426 | led->adf_on = 0; |
427 | } |
428 | |
429 | static ssize_t bd2802_show_adv_conf(struct device *dev, |
430 | struct device_attribute *attr, char *buf) |
431 | { |
432 | struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev)); |
433 | ssize_t ret; |
434 | |
435 | down_read(sem: &led->rwsem); |
436 | if (led->adf_on) |
437 | ret = sprintf(buf, fmt: "on\n" ); |
438 | else |
439 | ret = sprintf(buf, fmt: "off\n" ); |
440 | up_read(sem: &led->rwsem); |
441 | |
442 | return ret; |
443 | } |
444 | |
445 | static ssize_t bd2802_store_adv_conf(struct device *dev, |
446 | struct device_attribute *attr, const char *buf, size_t count) |
447 | { |
448 | struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev)); |
449 | |
450 | if (!count) |
451 | return -EINVAL; |
452 | |
453 | down_write(sem: &led->rwsem); |
454 | if (!led->adf_on && !strncmp(buf, "on" , 2)) |
455 | bd2802_enable_adv_conf(led); |
456 | else if (led->adf_on && !strncmp(buf, "off" , 3)) |
457 | bd2802_disable_adv_conf(led); |
458 | up_write(sem: &led->rwsem); |
459 | |
460 | return count; |
461 | } |
462 | |
463 | static struct device_attribute bd2802_adv_conf_attr = { |
464 | .attr = { |
465 | .name = "advanced_configuration" , |
466 | .mode = 0644, |
467 | }, |
468 | .show = bd2802_show_adv_conf, |
469 | .store = bd2802_store_adv_conf, |
470 | }; |
471 | |
472 | #define BD2802_CONTROL_ATTR(attr_name, name_str) \ |
473 | static ssize_t bd2802_show_##attr_name(struct device *dev, \ |
474 | struct device_attribute *attr, char *buf) \ |
475 | { \ |
476 | struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\ |
477 | ssize_t ret; \ |
478 | down_read(&led->rwsem); \ |
479 | ret = sprintf(buf, "0x%02x\n", led->attr_name); \ |
480 | up_read(&led->rwsem); \ |
481 | return ret; \ |
482 | } \ |
483 | static ssize_t bd2802_store_##attr_name(struct device *dev, \ |
484 | struct device_attribute *attr, const char *buf, size_t count) \ |
485 | { \ |
486 | struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\ |
487 | unsigned long val; \ |
488 | int ret; \ |
489 | if (!count) \ |
490 | return -EINVAL; \ |
491 | ret = kstrtoul(buf, 16, &val); \ |
492 | if (ret) \ |
493 | return ret; \ |
494 | down_write(&led->rwsem); \ |
495 | led->attr_name = val; \ |
496 | up_write(&led->rwsem); \ |
497 | return count; \ |
498 | } \ |
499 | static struct device_attribute bd2802_##attr_name##_attr = { \ |
500 | .attr = { \ |
501 | .name = name_str, \ |
502 | .mode = 0644, \ |
503 | }, \ |
504 | .show = bd2802_show_##attr_name, \ |
505 | .store = bd2802_store_##attr_name, \ |
506 | }; |
507 | |
508 | BD2802_CONTROL_ATTR(wave_pattern, "wave_pattern" ); |
509 | BD2802_CONTROL_ATTR(rgb_current, "rgb_current" ); |
510 | |
511 | static struct device_attribute *bd2802_attributes[] = { |
512 | &bd2802_adv_conf_attr, |
513 | &bd2802_wave_pattern_attr, |
514 | &bd2802_rgb_current_attr, |
515 | }; |
516 | |
517 | #define BD2802_CONTROL_RGBS(name, id, clr) \ |
518 | static int bd2802_set_##name##_brightness(struct led_classdev *led_cdev,\ |
519 | enum led_brightness value) \ |
520 | { \ |
521 | struct bd2802_led *led = \ |
522 | container_of(led_cdev, struct bd2802_led, cdev_##name); \ |
523 | led->led_id = id; \ |
524 | led->color = clr; \ |
525 | if (value == LED_OFF) { \ |
526 | led->state = BD2802_OFF; \ |
527 | bd2802_turn_off(led, led->led_id, led->color); \ |
528 | } else { \ |
529 | led->state = BD2802_ON; \ |
530 | bd2802_turn_on(led, led->led_id, led->color, BD2802_ON);\ |
531 | } \ |
532 | return 0; \ |
533 | } \ |
534 | static int bd2802_set_##name##_blink(struct led_classdev *led_cdev, \ |
535 | unsigned long *delay_on, unsigned long *delay_off) \ |
536 | { \ |
537 | struct bd2802_led *led = \ |
538 | container_of(led_cdev, struct bd2802_led, cdev_##name); \ |
539 | if (*delay_on == 0 || *delay_off == 0) \ |
540 | return -EINVAL; \ |
541 | led->led_id = id; \ |
542 | led->color = clr; \ |
543 | led->state = BD2802_BLINK; \ |
544 | bd2802_turn_on(led, led->led_id, led->color, BD2802_BLINK); \ |
545 | return 0; \ |
546 | } |
547 | |
548 | BD2802_CONTROL_RGBS(led1r, LED1, RED); |
549 | BD2802_CONTROL_RGBS(led1g, LED1, GREEN); |
550 | BD2802_CONTROL_RGBS(led1b, LED1, BLUE); |
551 | BD2802_CONTROL_RGBS(led2r, LED2, RED); |
552 | BD2802_CONTROL_RGBS(led2g, LED2, GREEN); |
553 | BD2802_CONTROL_RGBS(led2b, LED2, BLUE); |
554 | |
555 | static int bd2802_register_led_classdev(struct bd2802_led *led) |
556 | { |
557 | int ret; |
558 | |
559 | led->cdev_led1r.name = "led1_R" ; |
560 | led->cdev_led1r.brightness = LED_OFF; |
561 | led->cdev_led1r.brightness_set_blocking = bd2802_set_led1r_brightness; |
562 | led->cdev_led1r.blink_set = bd2802_set_led1r_blink; |
563 | |
564 | ret = led_classdev_register(parent: &led->client->dev, led_cdev: &led->cdev_led1r); |
565 | if (ret < 0) { |
566 | dev_err(&led->client->dev, "couldn't register LED %s\n" , |
567 | led->cdev_led1r.name); |
568 | goto failed_unregister_led1_R; |
569 | } |
570 | |
571 | led->cdev_led1g.name = "led1_G" ; |
572 | led->cdev_led1g.brightness = LED_OFF; |
573 | led->cdev_led1g.brightness_set_blocking = bd2802_set_led1g_brightness; |
574 | led->cdev_led1g.blink_set = bd2802_set_led1g_blink; |
575 | |
576 | ret = led_classdev_register(parent: &led->client->dev, led_cdev: &led->cdev_led1g); |
577 | if (ret < 0) { |
578 | dev_err(&led->client->dev, "couldn't register LED %s\n" , |
579 | led->cdev_led1g.name); |
580 | goto failed_unregister_led1_G; |
581 | } |
582 | |
583 | led->cdev_led1b.name = "led1_B" ; |
584 | led->cdev_led1b.brightness = LED_OFF; |
585 | led->cdev_led1b.brightness_set_blocking = bd2802_set_led1b_brightness; |
586 | led->cdev_led1b.blink_set = bd2802_set_led1b_blink; |
587 | |
588 | ret = led_classdev_register(parent: &led->client->dev, led_cdev: &led->cdev_led1b); |
589 | if (ret < 0) { |
590 | dev_err(&led->client->dev, "couldn't register LED %s\n" , |
591 | led->cdev_led1b.name); |
592 | goto failed_unregister_led1_B; |
593 | } |
594 | |
595 | led->cdev_led2r.name = "led2_R" ; |
596 | led->cdev_led2r.brightness = LED_OFF; |
597 | led->cdev_led2r.brightness_set_blocking = bd2802_set_led2r_brightness; |
598 | led->cdev_led2r.blink_set = bd2802_set_led2r_blink; |
599 | |
600 | ret = led_classdev_register(parent: &led->client->dev, led_cdev: &led->cdev_led2r); |
601 | if (ret < 0) { |
602 | dev_err(&led->client->dev, "couldn't register LED %s\n" , |
603 | led->cdev_led2r.name); |
604 | goto failed_unregister_led2_R; |
605 | } |
606 | |
607 | led->cdev_led2g.name = "led2_G" ; |
608 | led->cdev_led2g.brightness = LED_OFF; |
609 | led->cdev_led2g.brightness_set_blocking = bd2802_set_led2g_brightness; |
610 | led->cdev_led2g.blink_set = bd2802_set_led2g_blink; |
611 | |
612 | ret = led_classdev_register(parent: &led->client->dev, led_cdev: &led->cdev_led2g); |
613 | if (ret < 0) { |
614 | dev_err(&led->client->dev, "couldn't register LED %s\n" , |
615 | led->cdev_led2g.name); |
616 | goto failed_unregister_led2_G; |
617 | } |
618 | |
619 | led->cdev_led2b.name = "led2_B" ; |
620 | led->cdev_led2b.brightness = LED_OFF; |
621 | led->cdev_led2b.brightness_set_blocking = bd2802_set_led2b_brightness; |
622 | led->cdev_led2b.blink_set = bd2802_set_led2b_blink; |
623 | led->cdev_led2b.flags |= LED_CORE_SUSPENDRESUME; |
624 | |
625 | ret = led_classdev_register(parent: &led->client->dev, led_cdev: &led->cdev_led2b); |
626 | if (ret < 0) { |
627 | dev_err(&led->client->dev, "couldn't register LED %s\n" , |
628 | led->cdev_led2b.name); |
629 | goto failed_unregister_led2_B; |
630 | } |
631 | |
632 | return 0; |
633 | |
634 | failed_unregister_led2_B: |
635 | led_classdev_unregister(led_cdev: &led->cdev_led2g); |
636 | failed_unregister_led2_G: |
637 | led_classdev_unregister(led_cdev: &led->cdev_led2r); |
638 | failed_unregister_led2_R: |
639 | led_classdev_unregister(led_cdev: &led->cdev_led1b); |
640 | failed_unregister_led1_B: |
641 | led_classdev_unregister(led_cdev: &led->cdev_led1g); |
642 | failed_unregister_led1_G: |
643 | led_classdev_unregister(led_cdev: &led->cdev_led1r); |
644 | failed_unregister_led1_R: |
645 | |
646 | return ret; |
647 | } |
648 | |
649 | static void bd2802_unregister_led_classdev(struct bd2802_led *led) |
650 | { |
651 | led_classdev_unregister(led_cdev: &led->cdev_led2b); |
652 | led_classdev_unregister(led_cdev: &led->cdev_led2g); |
653 | led_classdev_unregister(led_cdev: &led->cdev_led2r); |
654 | led_classdev_unregister(led_cdev: &led->cdev_led1b); |
655 | led_classdev_unregister(led_cdev: &led->cdev_led1g); |
656 | led_classdev_unregister(led_cdev: &led->cdev_led1r); |
657 | } |
658 | |
659 | static int bd2802_probe(struct i2c_client *client) |
660 | { |
661 | struct bd2802_led *led; |
662 | int ret, i; |
663 | |
664 | led = devm_kzalloc(dev: &client->dev, size: sizeof(struct bd2802_led), GFP_KERNEL); |
665 | if (!led) |
666 | return -ENOMEM; |
667 | |
668 | led->client = client; |
669 | i2c_set_clientdata(client, data: led); |
670 | |
671 | /* |
672 | * Configure RESET GPIO (L: RESET, H: RESET cancel) |
673 | * |
674 | * We request the reset GPIO as OUT_LOW which means de-asserted, |
675 | * board files specifying this GPIO line in a machine descriptor |
676 | * table should take care to specify GPIO_ACTIVE_LOW for this line. |
677 | */ |
678 | led->reset = devm_gpiod_get(dev: &client->dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
679 | if (IS_ERR(ptr: led->reset)) |
680 | return PTR_ERR(ptr: led->reset); |
681 | |
682 | /* Tacss = min 0.1ms */ |
683 | udelay(100); |
684 | |
685 | /* Detect BD2802GU */ |
686 | ret = bd2802_write_byte(client, BD2802_REG_CLKSETUP, val: 0x00); |
687 | if (ret < 0) { |
688 | dev_err(&client->dev, "failed to detect device\n" ); |
689 | return ret; |
690 | } else |
691 | dev_info(&client->dev, "return 0x%02x\n" , ret); |
692 | |
693 | /* To save the power, reset BD2802 after detecting */ |
694 | gpiod_set_value(desc: led->reset, value: 1); |
695 | |
696 | /* Default attributes */ |
697 | led->wave_pattern = BD2802_PATTERN_HALF; |
698 | led->rgb_current = BD2802_CURRENT_032; |
699 | |
700 | init_rwsem(&led->rwsem); |
701 | |
702 | for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++) { |
703 | ret = device_create_file(device: &led->client->dev, |
704 | entry: bd2802_attributes[i]); |
705 | if (ret) { |
706 | dev_err(&led->client->dev, "failed: sysfs file %s\n" , |
707 | bd2802_attributes[i]->attr.name); |
708 | goto failed_unregister_dev_file; |
709 | } |
710 | } |
711 | |
712 | ret = bd2802_register_led_classdev(led); |
713 | if (ret < 0) |
714 | goto failed_unregister_dev_file; |
715 | |
716 | return 0; |
717 | |
718 | failed_unregister_dev_file: |
719 | for (i--; i >= 0; i--) |
720 | device_remove_file(dev: &led->client->dev, attr: bd2802_attributes[i]); |
721 | return ret; |
722 | } |
723 | |
724 | static void bd2802_remove(struct i2c_client *client) |
725 | { |
726 | struct bd2802_led *led = i2c_get_clientdata(client); |
727 | int i; |
728 | |
729 | gpiod_set_value(desc: led->reset, value: 1); |
730 | bd2802_unregister_led_classdev(led); |
731 | if (led->adf_on) |
732 | bd2802_disable_adv_conf(led); |
733 | for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++) |
734 | device_remove_file(dev: &led->client->dev, attr: bd2802_attributes[i]); |
735 | } |
736 | |
737 | #ifdef CONFIG_PM_SLEEP |
738 | static void bd2802_restore_state(struct bd2802_led *led) |
739 | { |
740 | int i; |
741 | |
742 | for (i = 0; i < LED_NUM; i++) { |
743 | if (led->led[i].r) |
744 | bd2802_turn_on(led, id: i, color: RED, led_bit: led->led[i].r); |
745 | if (led->led[i].g) |
746 | bd2802_turn_on(led, id: i, color: GREEN, led_bit: led->led[i].g); |
747 | if (led->led[i].b) |
748 | bd2802_turn_on(led, id: i, color: BLUE, led_bit: led->led[i].b); |
749 | } |
750 | } |
751 | |
752 | static int bd2802_suspend(struct device *dev) |
753 | { |
754 | struct i2c_client *client = to_i2c_client(dev); |
755 | struct bd2802_led *led = i2c_get_clientdata(client); |
756 | |
757 | gpiod_set_value(desc: led->reset, value: 1); |
758 | |
759 | return 0; |
760 | } |
761 | |
762 | static int bd2802_resume(struct device *dev) |
763 | { |
764 | struct i2c_client *client = to_i2c_client(dev); |
765 | struct bd2802_led *led = i2c_get_clientdata(client); |
766 | |
767 | if (!bd2802_is_all_off(led) || led->adf_on) { |
768 | bd2802_reset_cancel(led); |
769 | bd2802_restore_state(led); |
770 | } |
771 | |
772 | return 0; |
773 | } |
774 | #endif |
775 | |
776 | static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume); |
777 | |
778 | static const struct i2c_device_id bd2802_id[] = { |
779 | { "BD2802" , 0 }, |
780 | { } |
781 | }; |
782 | MODULE_DEVICE_TABLE(i2c, bd2802_id); |
783 | |
784 | static struct i2c_driver bd2802_i2c_driver = { |
785 | .driver = { |
786 | .name = "BD2802" , |
787 | .pm = &bd2802_pm, |
788 | }, |
789 | .probe = bd2802_probe, |
790 | .remove = bd2802_remove, |
791 | .id_table = bd2802_id, |
792 | }; |
793 | |
794 | module_i2c_driver(bd2802_i2c_driver); |
795 | |
796 | MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>" ); |
797 | MODULE_DESCRIPTION("BD2802 LED driver" ); |
798 | MODULE_LICENSE("GPL v2" ); |
799 | |