1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * leds-blinkm.c |
4 | * (c) Jan-Simon Möller (dl9pf@gmx.de) |
5 | */ |
6 | |
7 | #include <linux/module.h> |
8 | #include <linux/slab.h> |
9 | #include <linux/jiffies.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/err.h> |
12 | #include <linux/mutex.h> |
13 | #include <linux/sysfs.h> |
14 | #include <linux/printk.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/leds.h> |
17 | #include <linux/delay.h> |
18 | |
19 | /* Addresses to scan - BlinkM is on 0x09 by default*/ |
20 | static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; |
21 | |
22 | static int blinkm_transfer_hw(struct i2c_client *client, int cmd); |
23 | static int blinkm_test_run(struct i2c_client *client); |
24 | |
25 | struct blinkm_led { |
26 | struct i2c_client *i2c_client; |
27 | struct led_classdev led_cdev; |
28 | int id; |
29 | }; |
30 | |
31 | #define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev) |
32 | |
33 | struct blinkm_data { |
34 | struct i2c_client *i2c_client; |
35 | struct mutex update_lock; |
36 | /* used for led class interface */ |
37 | struct blinkm_led blinkm_leds[3]; |
38 | /* used for "blinkm" sysfs interface */ |
39 | u8 red; /* color red */ |
40 | u8 green; /* color green */ |
41 | u8 blue; /* color blue */ |
42 | /* next values to use for transfer */ |
43 | u8 next_red; /* color red */ |
44 | u8 next_green; /* color green */ |
45 | u8 next_blue; /* color blue */ |
46 | /* internal use */ |
47 | u8 args[7]; /* set of args for transmission */ |
48 | u8 i2c_addr; /* i2c addr */ |
49 | u8 fw_ver; /* firmware version */ |
50 | /* used, but not from userspace */ |
51 | u8 hue; /* HSB hue */ |
52 | u8 saturation; /* HSB saturation */ |
53 | u8 brightness; /* HSB brightness */ |
54 | u8 next_hue; /* HSB hue */ |
55 | u8 next_saturation; /* HSB saturation */ |
56 | u8 next_brightness; /* HSB brightness */ |
57 | /* currently unused / todo */ |
58 | u8 fade_speed; /* fade speed 1 - 255 */ |
59 | s8 time_adjust; /* time adjust -128 - 127 */ |
60 | u8 fade:1; /* fade on = 1, off = 0 */ |
61 | u8 rand:1; /* rand fade mode on = 1 */ |
62 | u8 script_id; /* script ID */ |
63 | u8 script_repeats; /* repeats of script */ |
64 | u8 script_startline; /* line to start */ |
65 | }; |
66 | |
67 | /* Colors */ |
68 | #define RED 0 |
69 | #define GREEN 1 |
70 | #define BLUE 2 |
71 | |
72 | /* mapping command names to cmd chars - see datasheet */ |
73 | #define BLM_GO_RGB 0 |
74 | #define BLM_FADE_RGB 1 |
75 | #define BLM_FADE_HSB 2 |
76 | #define BLM_FADE_RAND_RGB 3 |
77 | #define BLM_FADE_RAND_HSB 4 |
78 | #define BLM_PLAY_SCRIPT 5 |
79 | #define BLM_STOP_SCRIPT 6 |
80 | #define BLM_SET_FADE_SPEED 7 |
81 | #define BLM_SET_TIME_ADJ 8 |
82 | #define BLM_GET_CUR_RGB 9 |
83 | #define BLM_WRITE_SCRIPT_LINE 10 |
84 | #define BLM_READ_SCRIPT_LINE 11 |
85 | #define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */ |
86 | #define BLM_SET_ADDR 13 |
87 | #define BLM_GET_ADDR 14 |
88 | #define BLM_GET_FW_VER 15 |
89 | #define BLM_SET_STARTUP_PARAM 16 |
90 | |
91 | /* BlinkM Commands |
92 | * as extracted out of the datasheet: |
93 | * |
94 | * cmdchar = command (ascii) |
95 | * cmdbyte = command in hex |
96 | * nr_args = number of arguments (to send) |
97 | * nr_ret = number of return values (to read) |
98 | * dir = direction (0 = read, 1 = write, 2 = both) |
99 | * |
100 | */ |
101 | static const struct { |
102 | char cmdchar; |
103 | u8 cmdbyte; |
104 | u8 nr_args; |
105 | u8 nr_ret; |
106 | u8 dir:2; |
107 | } blinkm_cmds[17] = { |
108 | /* cmdchar, cmdbyte, nr_args, nr_ret, dir */ |
109 | { 'n', 0x6e, 3, 0, 1}, |
110 | { 'c', 0x63, 3, 0, 1}, |
111 | { 'h', 0x68, 3, 0, 1}, |
112 | { 'C', 0x43, 3, 0, 1}, |
113 | { 'H', 0x48, 3, 0, 1}, |
114 | { 'p', 0x70, 3, 0, 1}, |
115 | { 'o', 0x6f, 0, 0, 1}, |
116 | { 'f', 0x66, 1, 0, 1}, |
117 | { 't', 0x74, 1, 0, 1}, |
118 | { 'g', 0x67, 0, 3, 0}, |
119 | { 'W', 0x57, 7, 0, 1}, |
120 | { 'R', 0x52, 2, 5, 2}, |
121 | { 'L', 0x4c, 3, 0, 1}, |
122 | { 'A', 0x41, 4, 0, 1}, |
123 | { 'a', 0x61, 0, 1, 0}, |
124 | { 'Z', 0x5a, 0, 1, 0}, |
125 | { 'B', 0x42, 5, 0, 1}, |
126 | }; |
127 | |
128 | static ssize_t show_color_common(struct device *dev, char *buf, int color) |
129 | { |
130 | struct i2c_client *client; |
131 | struct blinkm_data *data; |
132 | int ret; |
133 | |
134 | client = to_i2c_client(dev); |
135 | data = i2c_get_clientdata(client); |
136 | |
137 | ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB); |
138 | if (ret < 0) |
139 | return ret; |
140 | switch (color) { |
141 | case RED: |
142 | return sysfs_emit(buf, fmt: "%02X\n" , data->red); |
143 | case GREEN: |
144 | return sysfs_emit(buf, fmt: "%02X\n" , data->green); |
145 | case BLUE: |
146 | return sysfs_emit(buf, fmt: "%02X\n" , data->blue); |
147 | default: |
148 | return -EINVAL; |
149 | } |
150 | return -EINVAL; |
151 | } |
152 | |
153 | static int store_color_common(struct device *dev, const char *buf, int color) |
154 | { |
155 | struct i2c_client *client; |
156 | struct blinkm_data *data; |
157 | int ret; |
158 | u8 value; |
159 | |
160 | client = to_i2c_client(dev); |
161 | data = i2c_get_clientdata(client); |
162 | |
163 | ret = kstrtou8(s: buf, base: 10, res: &value); |
164 | if (ret < 0) { |
165 | dev_err(dev, "BlinkM: value too large!\n" ); |
166 | return ret; |
167 | } |
168 | |
169 | switch (color) { |
170 | case RED: |
171 | data->next_red = value; |
172 | break; |
173 | case GREEN: |
174 | data->next_green = value; |
175 | break; |
176 | case BLUE: |
177 | data->next_blue = value; |
178 | break; |
179 | default: |
180 | return -EINVAL; |
181 | } |
182 | |
183 | dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n" , |
184 | data->next_red, data->next_green, data->next_blue); |
185 | |
186 | /* if mode ... */ |
187 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); |
188 | if (ret < 0) { |
189 | dev_err(dev, "BlinkM: can't set RGB\n" ); |
190 | return ret; |
191 | } |
192 | return 0; |
193 | } |
194 | |
195 | static ssize_t red_show(struct device *dev, struct device_attribute *attr, |
196 | char *buf) |
197 | { |
198 | return show_color_common(dev, buf, RED); |
199 | } |
200 | |
201 | static ssize_t red_store(struct device *dev, struct device_attribute *attr, |
202 | const char *buf, size_t count) |
203 | { |
204 | int ret; |
205 | |
206 | ret = store_color_common(dev, buf, RED); |
207 | if (ret < 0) |
208 | return ret; |
209 | return count; |
210 | } |
211 | |
212 | static DEVICE_ATTR_RW(red); |
213 | |
214 | static ssize_t green_show(struct device *dev, struct device_attribute *attr, |
215 | char *buf) |
216 | { |
217 | return show_color_common(dev, buf, GREEN); |
218 | } |
219 | |
220 | static ssize_t green_store(struct device *dev, struct device_attribute *attr, |
221 | const char *buf, size_t count) |
222 | { |
223 | |
224 | int ret; |
225 | |
226 | ret = store_color_common(dev, buf, GREEN); |
227 | if (ret < 0) |
228 | return ret; |
229 | return count; |
230 | } |
231 | |
232 | static DEVICE_ATTR_RW(green); |
233 | |
234 | static ssize_t blue_show(struct device *dev, struct device_attribute *attr, |
235 | char *buf) |
236 | { |
237 | return show_color_common(dev, buf, BLUE); |
238 | } |
239 | |
240 | static ssize_t blue_store(struct device *dev, struct device_attribute *attr, |
241 | const char *buf, size_t count) |
242 | { |
243 | int ret; |
244 | |
245 | ret = store_color_common(dev, buf, BLUE); |
246 | if (ret < 0) |
247 | return ret; |
248 | return count; |
249 | } |
250 | |
251 | static DEVICE_ATTR_RW(blue); |
252 | |
253 | static ssize_t test_show(struct device *dev, struct device_attribute *attr, |
254 | char *buf) |
255 | { |
256 | return sysfs_emit(buf, |
257 | fmt: "#Write into test to start test sequence!#\n" ); |
258 | } |
259 | |
260 | static ssize_t test_store(struct device *dev, struct device_attribute *attr, |
261 | const char *buf, size_t count) |
262 | { |
263 | |
264 | struct i2c_client *client; |
265 | int ret; |
266 | client = to_i2c_client(dev); |
267 | |
268 | /*test */ |
269 | ret = blinkm_test_run(client); |
270 | if (ret < 0) |
271 | return ret; |
272 | |
273 | return count; |
274 | } |
275 | |
276 | static DEVICE_ATTR_RW(test); |
277 | |
278 | /* TODO: HSB, fade, timeadj, script ... */ |
279 | |
280 | static struct attribute *blinkm_attrs[] = { |
281 | &dev_attr_red.attr, |
282 | &dev_attr_green.attr, |
283 | &dev_attr_blue.attr, |
284 | &dev_attr_test.attr, |
285 | NULL, |
286 | }; |
287 | |
288 | static const struct attribute_group blinkm_group = { |
289 | .name = "blinkm" , |
290 | .attrs = blinkm_attrs, |
291 | }; |
292 | |
293 | static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg) |
294 | { |
295 | int result; |
296 | int i; |
297 | int arglen = blinkm_cmds[cmd].nr_args; |
298 | /* write out cmd to blinkm - always / default step */ |
299 | result = i2c_smbus_write_byte(client, value: blinkm_cmds[cmd].cmdbyte); |
300 | if (result < 0) |
301 | return result; |
302 | /* no args to write out */ |
303 | if (arglen == 0) |
304 | return 0; |
305 | |
306 | for (i = 0; i < arglen; i++) { |
307 | /* repeat for arglen */ |
308 | result = i2c_smbus_write_byte(client, value: arg[i]); |
309 | if (result < 0) |
310 | return result; |
311 | } |
312 | return 0; |
313 | } |
314 | |
315 | static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg) |
316 | { |
317 | int result; |
318 | int i; |
319 | int retlen = blinkm_cmds[cmd].nr_ret; |
320 | for (i = 0; i < retlen; i++) { |
321 | /* repeat for retlen */ |
322 | result = i2c_smbus_read_byte(client); |
323 | if (result < 0) |
324 | return result; |
325 | arg[i] = result; |
326 | } |
327 | |
328 | return 0; |
329 | } |
330 | |
331 | static int blinkm_transfer_hw(struct i2c_client *client, int cmd) |
332 | { |
333 | /* the protocol is simple but non-standard: |
334 | * e.g. cmd 'g' (= 0x67) for "get device address" |
335 | * - which defaults to 0x09 - would be the sequence: |
336 | * a) write 0x67 to the device (byte write) |
337 | * b) read the value (0x09) back right after (byte read) |
338 | * |
339 | * Watch out for "unfinished" sequences (i.e. not enough reads |
340 | * or writes after a command. It will make the blinkM misbehave. |
341 | * Sequence is key here. |
342 | */ |
343 | |
344 | /* args / return are in private data struct */ |
345 | struct blinkm_data *data = i2c_get_clientdata(client); |
346 | |
347 | /* We start hardware transfers which are not to be |
348 | * mixed with other commands. Aquire a lock now. */ |
349 | if (mutex_lock_interruptible(&data->update_lock) < 0) |
350 | return -EAGAIN; |
351 | |
352 | /* switch cmd - usually write before reads */ |
353 | switch (cmd) { |
354 | case BLM_FADE_RAND_RGB: |
355 | case BLM_GO_RGB: |
356 | case BLM_FADE_RGB: |
357 | data->args[0] = data->next_red; |
358 | data->args[1] = data->next_green; |
359 | data->args[2] = data->next_blue; |
360 | blinkm_write(client, cmd, arg: data->args); |
361 | data->red = data->args[0]; |
362 | data->green = data->args[1]; |
363 | data->blue = data->args[2]; |
364 | break; |
365 | case BLM_FADE_HSB: |
366 | case BLM_FADE_RAND_HSB: |
367 | data->args[0] = data->next_hue; |
368 | data->args[1] = data->next_saturation; |
369 | data->args[2] = data->next_brightness; |
370 | blinkm_write(client, cmd, arg: data->args); |
371 | data->hue = data->next_hue; |
372 | data->saturation = data->next_saturation; |
373 | data->brightness = data->next_brightness; |
374 | break; |
375 | case BLM_PLAY_SCRIPT: |
376 | data->args[0] = data->script_id; |
377 | data->args[1] = data->script_repeats; |
378 | data->args[2] = data->script_startline; |
379 | blinkm_write(client, cmd, arg: data->args); |
380 | break; |
381 | case BLM_STOP_SCRIPT: |
382 | blinkm_write(client, cmd, NULL); |
383 | break; |
384 | case BLM_GET_CUR_RGB: |
385 | data->args[0] = data->red; |
386 | data->args[1] = data->green; |
387 | data->args[2] = data->blue; |
388 | blinkm_write(client, cmd, NULL); |
389 | blinkm_read(client, cmd, arg: data->args); |
390 | data->red = data->args[0]; |
391 | data->green = data->args[1]; |
392 | data->blue = data->args[2]; |
393 | break; |
394 | case BLM_GET_ADDR: |
395 | data->args[0] = data->i2c_addr; |
396 | blinkm_write(client, cmd, NULL); |
397 | blinkm_read(client, cmd, arg: data->args); |
398 | data->i2c_addr = data->args[0]; |
399 | break; |
400 | case BLM_SET_TIME_ADJ: |
401 | case BLM_SET_FADE_SPEED: |
402 | case BLM_READ_SCRIPT_LINE: |
403 | case BLM_WRITE_SCRIPT_LINE: |
404 | case BLM_SET_SCRIPT_LR: |
405 | case BLM_SET_ADDR: |
406 | case BLM_GET_FW_VER: |
407 | case BLM_SET_STARTUP_PARAM: |
408 | dev_err(&client->dev, |
409 | "BlinkM: cmd %d not implemented yet.\n" , cmd); |
410 | break; |
411 | default: |
412 | dev_err(&client->dev, "BlinkM: unknown command %d\n" , cmd); |
413 | mutex_unlock(lock: &data->update_lock); |
414 | return -EINVAL; |
415 | } /* end switch(cmd) */ |
416 | |
417 | /* transfers done, unlock */ |
418 | mutex_unlock(lock: &data->update_lock); |
419 | return 0; |
420 | } |
421 | |
422 | static int blinkm_led_common_set(struct led_classdev *led_cdev, |
423 | enum led_brightness value, int color) |
424 | { |
425 | /* led_brightness is 0, 127 or 255 - we just use it here as-is */ |
426 | struct blinkm_led *led = cdev_to_blmled(led_cdev); |
427 | struct blinkm_data *data = i2c_get_clientdata(client: led->i2c_client); |
428 | |
429 | switch (color) { |
430 | case RED: |
431 | /* bail out if there's no change */ |
432 | if (data->next_red == (u8) value) |
433 | return 0; |
434 | data->next_red = (u8) value; |
435 | break; |
436 | case GREEN: |
437 | /* bail out if there's no change */ |
438 | if (data->next_green == (u8) value) |
439 | return 0; |
440 | data->next_green = (u8) value; |
441 | break; |
442 | case BLUE: |
443 | /* bail out if there's no change */ |
444 | if (data->next_blue == (u8) value) |
445 | return 0; |
446 | data->next_blue = (u8) value; |
447 | break; |
448 | |
449 | default: |
450 | dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n" ); |
451 | return -EINVAL; |
452 | } |
453 | |
454 | blinkm_transfer_hw(client: led->i2c_client, BLM_GO_RGB); |
455 | dev_dbg(&led->i2c_client->dev, |
456 | "# DONE # next_red = %d, next_green = %d," |
457 | " next_blue = %d\n" , |
458 | data->next_red, data->next_green, |
459 | data->next_blue); |
460 | return 0; |
461 | } |
462 | |
463 | static int blinkm_led_red_set(struct led_classdev *led_cdev, |
464 | enum led_brightness value) |
465 | { |
466 | return blinkm_led_common_set(led_cdev, value, RED); |
467 | } |
468 | |
469 | static int blinkm_led_green_set(struct led_classdev *led_cdev, |
470 | enum led_brightness value) |
471 | { |
472 | return blinkm_led_common_set(led_cdev, value, GREEN); |
473 | } |
474 | |
475 | static int blinkm_led_blue_set(struct led_classdev *led_cdev, |
476 | enum led_brightness value) |
477 | { |
478 | return blinkm_led_common_set(led_cdev, value, BLUE); |
479 | } |
480 | |
481 | static void blinkm_init_hw(struct i2c_client *client) |
482 | { |
483 | blinkm_transfer_hw(client, BLM_STOP_SCRIPT); |
484 | blinkm_transfer_hw(client, BLM_GO_RGB); |
485 | } |
486 | |
487 | static int blinkm_test_run(struct i2c_client *client) |
488 | { |
489 | int ret; |
490 | struct blinkm_data *data = i2c_get_clientdata(client); |
491 | |
492 | data->next_red = 0x01; |
493 | data->next_green = 0x05; |
494 | data->next_blue = 0x10; |
495 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); |
496 | if (ret < 0) |
497 | return ret; |
498 | msleep(msecs: 2000); |
499 | |
500 | data->next_red = 0x25; |
501 | data->next_green = 0x10; |
502 | data->next_blue = 0x31; |
503 | ret = blinkm_transfer_hw(client, BLM_FADE_RGB); |
504 | if (ret < 0) |
505 | return ret; |
506 | msleep(msecs: 2000); |
507 | |
508 | data->next_hue = 0x50; |
509 | data->next_saturation = 0x10; |
510 | data->next_brightness = 0x20; |
511 | ret = blinkm_transfer_hw(client, BLM_FADE_HSB); |
512 | if (ret < 0) |
513 | return ret; |
514 | msleep(msecs: 2000); |
515 | |
516 | return 0; |
517 | } |
518 | |
519 | /* Return 0 if detection is successful, -ENODEV otherwise */ |
520 | static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) |
521 | { |
522 | struct i2c_adapter *adapter = client->adapter; |
523 | int ret; |
524 | int count = 99; |
525 | u8 tmpargs[7]; |
526 | |
527 | if (!i2c_check_functionality(adap: adapter, I2C_FUNC_SMBUS_BYTE_DATA |
528 | | I2C_FUNC_SMBUS_WORD_DATA |
529 | | I2C_FUNC_SMBUS_WRITE_BYTE)) |
530 | return -ENODEV; |
531 | |
532 | /* Now, we do the remaining detection. Simple for now. */ |
533 | /* We might need more guards to protect other i2c slaves */ |
534 | |
535 | /* make sure the blinkM is balanced (read/writes) */ |
536 | while (count > 0) { |
537 | ret = blinkm_write(client, BLM_GET_ADDR, NULL); |
538 | if (ret) |
539 | return ret; |
540 | usleep_range(min: 5000, max: 10000); |
541 | ret = blinkm_read(client, BLM_GET_ADDR, arg: tmpargs); |
542 | if (ret) |
543 | return ret; |
544 | usleep_range(min: 5000, max: 10000); |
545 | if (tmpargs[0] == 0x09) |
546 | count = 0; |
547 | count--; |
548 | } |
549 | |
550 | /* Step 1: Read BlinkM address back - cmd_char 'a' */ |
551 | ret = blinkm_write(client, BLM_GET_ADDR, NULL); |
552 | if (ret < 0) |
553 | return ret; |
554 | usleep_range(min: 20000, max: 30000); /* allow a small delay */ |
555 | ret = blinkm_read(client, BLM_GET_ADDR, arg: tmpargs); |
556 | if (ret < 0) |
557 | return ret; |
558 | |
559 | if (tmpargs[0] != 0x09) { |
560 | dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n" , tmpargs[0]); |
561 | return -ENODEV; |
562 | } |
563 | |
564 | strscpy(p: info->type, q: "blinkm" , I2C_NAME_SIZE); |
565 | return 0; |
566 | } |
567 | |
568 | static int blinkm_probe(struct i2c_client *client) |
569 | { |
570 | struct blinkm_data *data; |
571 | struct blinkm_led *led[3]; |
572 | int err, i; |
573 | char blinkm_led_name[28]; |
574 | |
575 | data = devm_kzalloc(dev: &client->dev, |
576 | size: sizeof(struct blinkm_data), GFP_KERNEL); |
577 | if (!data) { |
578 | err = -ENOMEM; |
579 | goto exit; |
580 | } |
581 | |
582 | data->i2c_addr = 0x08; |
583 | /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ |
584 | data->fw_ver = 0xfe; |
585 | /* firmware version - use fake until we read real value |
586 | * (currently broken - BlinkM confused!) */ |
587 | data->script_id = 0x01; |
588 | data->i2c_client = client; |
589 | |
590 | i2c_set_clientdata(client, data); |
591 | mutex_init(&data->update_lock); |
592 | |
593 | /* Register sysfs hooks */ |
594 | err = sysfs_create_group(kobj: &client->dev.kobj, grp: &blinkm_group); |
595 | if (err < 0) { |
596 | dev_err(&client->dev, "couldn't register sysfs group\n" ); |
597 | goto exit; |
598 | } |
599 | |
600 | for (i = 0; i < 3; i++) { |
601 | /* RED = 0, GREEN = 1, BLUE = 2 */ |
602 | led[i] = &data->blinkm_leds[i]; |
603 | led[i]->i2c_client = client; |
604 | led[i]->id = i; |
605 | led[i]->led_cdev.max_brightness = 255; |
606 | led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME; |
607 | switch (i) { |
608 | case RED: |
609 | snprintf(buf: blinkm_led_name, size: sizeof(blinkm_led_name), |
610 | fmt: "blinkm-%d-%d-red" , |
611 | client->adapter->nr, |
612 | client->addr); |
613 | led[i]->led_cdev.name = blinkm_led_name; |
614 | led[i]->led_cdev.brightness_set_blocking = |
615 | blinkm_led_red_set; |
616 | err = led_classdev_register(parent: &client->dev, |
617 | led_cdev: &led[i]->led_cdev); |
618 | if (err < 0) { |
619 | dev_err(&client->dev, |
620 | "couldn't register LED %s\n" , |
621 | led[i]->led_cdev.name); |
622 | goto failred; |
623 | } |
624 | break; |
625 | case GREEN: |
626 | snprintf(buf: blinkm_led_name, size: sizeof(blinkm_led_name), |
627 | fmt: "blinkm-%d-%d-green" , |
628 | client->adapter->nr, |
629 | client->addr); |
630 | led[i]->led_cdev.name = blinkm_led_name; |
631 | led[i]->led_cdev.brightness_set_blocking = |
632 | blinkm_led_green_set; |
633 | err = led_classdev_register(parent: &client->dev, |
634 | led_cdev: &led[i]->led_cdev); |
635 | if (err < 0) { |
636 | dev_err(&client->dev, |
637 | "couldn't register LED %s\n" , |
638 | led[i]->led_cdev.name); |
639 | goto failgreen; |
640 | } |
641 | break; |
642 | case BLUE: |
643 | snprintf(buf: blinkm_led_name, size: sizeof(blinkm_led_name), |
644 | fmt: "blinkm-%d-%d-blue" , |
645 | client->adapter->nr, |
646 | client->addr); |
647 | led[i]->led_cdev.name = blinkm_led_name; |
648 | led[i]->led_cdev.brightness_set_blocking = |
649 | blinkm_led_blue_set; |
650 | err = led_classdev_register(parent: &client->dev, |
651 | led_cdev: &led[i]->led_cdev); |
652 | if (err < 0) { |
653 | dev_err(&client->dev, |
654 | "couldn't register LED %s\n" , |
655 | led[i]->led_cdev.name); |
656 | goto failblue; |
657 | } |
658 | break; |
659 | } /* end switch */ |
660 | } /* end for */ |
661 | |
662 | /* Initialize the blinkm */ |
663 | blinkm_init_hw(client); |
664 | |
665 | return 0; |
666 | |
667 | failblue: |
668 | led_classdev_unregister(led_cdev: &led[GREEN]->led_cdev); |
669 | |
670 | failgreen: |
671 | led_classdev_unregister(led_cdev: &led[RED]->led_cdev); |
672 | |
673 | failred: |
674 | sysfs_remove_group(kobj: &client->dev.kobj, grp: &blinkm_group); |
675 | exit: |
676 | return err; |
677 | } |
678 | |
679 | static void blinkm_remove(struct i2c_client *client) |
680 | { |
681 | struct blinkm_data *data = i2c_get_clientdata(client); |
682 | int ret = 0; |
683 | int i; |
684 | |
685 | /* make sure no workqueue entries are pending */ |
686 | for (i = 0; i < 3; i++) |
687 | led_classdev_unregister(led_cdev: &data->blinkm_leds[i].led_cdev); |
688 | |
689 | /* reset rgb */ |
690 | data->next_red = 0x00; |
691 | data->next_green = 0x00; |
692 | data->next_blue = 0x00; |
693 | ret = blinkm_transfer_hw(client, BLM_FADE_RGB); |
694 | if (ret < 0) |
695 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n" ); |
696 | |
697 | /* reset hsb */ |
698 | data->next_hue = 0x00; |
699 | data->next_saturation = 0x00; |
700 | data->next_brightness = 0x00; |
701 | ret = blinkm_transfer_hw(client, BLM_FADE_HSB); |
702 | if (ret < 0) |
703 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n" ); |
704 | |
705 | /* red fade to off */ |
706 | data->next_red = 0xff; |
707 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); |
708 | if (ret < 0) |
709 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n" ); |
710 | |
711 | /* off */ |
712 | data->next_red = 0x00; |
713 | ret = blinkm_transfer_hw(client, BLM_FADE_RGB); |
714 | if (ret < 0) |
715 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n" ); |
716 | |
717 | sysfs_remove_group(kobj: &client->dev.kobj, grp: &blinkm_group); |
718 | } |
719 | |
720 | static const struct i2c_device_id blinkm_id[] = { |
721 | {"blinkm" , 0}, |
722 | {} |
723 | }; |
724 | |
725 | MODULE_DEVICE_TABLE(i2c, blinkm_id); |
726 | |
727 | /* This is the driver that will be inserted */ |
728 | static struct i2c_driver blinkm_driver = { |
729 | .class = I2C_CLASS_HWMON, |
730 | .driver = { |
731 | .name = "blinkm" , |
732 | }, |
733 | .probe = blinkm_probe, |
734 | .remove = blinkm_remove, |
735 | .id_table = blinkm_id, |
736 | .detect = blinkm_detect, |
737 | .address_list = normal_i2c, |
738 | }; |
739 | |
740 | module_i2c_driver(blinkm_driver); |
741 | |
742 | MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>" ); |
743 | MODULE_DESCRIPTION("BlinkM RGB LED driver" ); |
744 | MODULE_LICENSE("GPL" ); |
745 | |
746 | |