1 // SPDX-License-Identifier: GPL-2.0-or-later
4 * (c) Jan-Simon Möller (dl9pf@gmx.de)
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>
19 /* Addresses to scan - BlinkM is on 0x09 by default*/
20 static const unsigned short normal_i2c
[] = { 0x09, I2C_CLIENT_END
};
22 static int blinkm_transfer_hw(struct i2c_client
*client
, int cmd
);
23 static int blinkm_test_run(struct i2c_client
*client
);
26 struct i2c_client
*i2c_client
;
27 struct led_classdev led_cdev
;
31 #define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev)
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 */
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 */
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 */
72 /* mapping command names to cmd chars - see datasheet */
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
92 * as extracted out of the datasheet:
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)
101 static const struct {
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},
128 static ssize_t
show_color_common(struct device
*dev
, char *buf
, int color
)
130 struct i2c_client
*client
;
131 struct blinkm_data
*data
;
134 client
= to_i2c_client(dev
);
135 data
= i2c_get_clientdata(client
);
137 ret
= blinkm_transfer_hw(client
, BLM_GET_CUR_RGB
);
142 return scnprintf(buf
, PAGE_SIZE
, "%02X\n", data
->red
);
144 return scnprintf(buf
, PAGE_SIZE
, "%02X\n", data
->green
);
146 return scnprintf(buf
, PAGE_SIZE
, "%02X\n", data
->blue
);
153 static int store_color_common(struct device
*dev
, const char *buf
, int color
)
155 struct i2c_client
*client
;
156 struct blinkm_data
*data
;
160 client
= to_i2c_client(dev
);
161 data
= i2c_get_clientdata(client
);
163 ret
= kstrtou8(buf
, 10, &value
);
165 dev_err(dev
, "BlinkM: value too large!\n");
171 data
->next_red
= value
;
174 data
->next_green
= value
;
177 data
->next_blue
= value
;
183 dev_dbg(dev
, "next_red = %d, next_green = %d, next_blue = %d\n",
184 data
->next_red
, data
->next_green
, data
->next_blue
);
187 ret
= blinkm_transfer_hw(client
, BLM_GO_RGB
);
189 dev_err(dev
, "BlinkM: can't set RGB\n");
195 static ssize_t
show_red(struct device
*dev
, struct device_attribute
*attr
,
198 return show_color_common(dev
, buf
, RED
);
201 static ssize_t
store_red(struct device
*dev
, struct device_attribute
*attr
,
202 const char *buf
, size_t count
)
206 ret
= store_color_common(dev
, buf
, RED
);
212 static DEVICE_ATTR(red
, S_IRUGO
| S_IWUSR
, show_red
, store_red
);
214 static ssize_t
show_green(struct device
*dev
, struct device_attribute
*attr
,
217 return show_color_common(dev
, buf
, GREEN
);
220 static ssize_t
store_green(struct device
*dev
, struct device_attribute
*attr
,
221 const char *buf
, size_t count
)
226 ret
= store_color_common(dev
, buf
, GREEN
);
232 static DEVICE_ATTR(green
, S_IRUGO
| S_IWUSR
, show_green
, store_green
);
234 static ssize_t
show_blue(struct device
*dev
, struct device_attribute
*attr
,
237 return show_color_common(dev
, buf
, BLUE
);
240 static ssize_t
store_blue(struct device
*dev
, struct device_attribute
*attr
,
241 const char *buf
, size_t count
)
245 ret
= store_color_common(dev
, buf
, BLUE
);
251 static DEVICE_ATTR(blue
, S_IRUGO
| S_IWUSR
, show_blue
, store_blue
);
253 static ssize_t
show_test(struct device
*dev
, struct device_attribute
*attr
,
256 return scnprintf(buf
, PAGE_SIZE
,
257 "#Write into test to start test sequence!#\n");
260 static ssize_t
store_test(struct device
*dev
, struct device_attribute
*attr
,
261 const char *buf
, size_t count
)
264 struct i2c_client
*client
;
266 client
= to_i2c_client(dev
);
269 ret
= blinkm_test_run(client
);
276 static DEVICE_ATTR(test
, S_IRUGO
| S_IWUSR
, show_test
, store_test
);
278 /* TODO: HSB, fade, timeadj, script ... */
280 static struct attribute
*blinkm_attrs
[] = {
282 &dev_attr_green
.attr
,
288 static const struct attribute_group blinkm_group
= {
290 .attrs
= blinkm_attrs
,
293 static int blinkm_write(struct i2c_client
*client
, int cmd
, u8
*arg
)
297 int arglen
= blinkm_cmds
[cmd
].nr_args
;
298 /* write out cmd to blinkm - always / default step */
299 result
= i2c_smbus_write_byte(client
, blinkm_cmds
[cmd
].cmdbyte
);
302 /* no args to write out */
306 for (i
= 0; i
< arglen
; i
++) {
307 /* repeat for arglen */
308 result
= i2c_smbus_write_byte(client
, arg
[i
]);
315 static int blinkm_read(struct i2c_client
*client
, int cmd
, u8
*arg
)
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
);
331 static int blinkm_transfer_hw(struct i2c_client
*client
, int cmd
)
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)
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.
344 /* args / return are in private data struct */
345 struct blinkm_data
*data
= i2c_get_clientdata(client
);
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)
352 /* switch cmd - usually write before reads */
354 case BLM_FADE_RAND_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
, data
->args
);
361 data
->red
= data
->args
[0];
362 data
->green
= data
->args
[1];
363 data
->blue
= data
->args
[2];
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
, data
->args
);
371 data
->hue
= data
->next_hue
;
372 data
->saturation
= data
->next_saturation
;
373 data
->brightness
= data
->next_brightness
;
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
, data
->args
);
381 case BLM_STOP_SCRIPT
:
382 blinkm_write(client
, cmd
, NULL
);
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
, data
->args
);
390 data
->red
= data
->args
[0];
391 data
->green
= data
->args
[1];
392 data
->blue
= data
->args
[2];
395 data
->args
[0] = data
->i2c_addr
;
396 blinkm_write(client
, cmd
, NULL
);
397 blinkm_read(client
, cmd
, data
->args
);
398 data
->i2c_addr
= data
->args
[0];
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
:
407 case BLM_SET_STARTUP_PARAM
:
408 dev_err(&client
->dev
,
409 "BlinkM: cmd %d not implemented yet.\n", cmd
);
412 dev_err(&client
->dev
, "BlinkM: unknown command %d\n", cmd
);
413 mutex_unlock(&data
->update_lock
);
415 } /* end switch(cmd) */
417 /* transfers done, unlock */
418 mutex_unlock(&data
->update_lock
);
422 static int blinkm_led_common_set(struct led_classdev
*led_cdev
,
423 enum led_brightness value
, int color
)
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(led
->i2c_client
);
431 /* bail out if there's no change */
432 if (data
->next_red
== (u8
) value
)
434 data
->next_red
= (u8
) value
;
437 /* bail out if there's no change */
438 if (data
->next_green
== (u8
) value
)
440 data
->next_green
= (u8
) value
;
443 /* bail out if there's no change */
444 if (data
->next_blue
== (u8
) value
)
446 data
->next_blue
= (u8
) value
;
450 dev_err(&led
->i2c_client
->dev
, "BlinkM: unknown color.\n");
454 blinkm_transfer_hw(led
->i2c_client
, BLM_GO_RGB
);
455 dev_dbg(&led
->i2c_client
->dev
,
456 "# DONE # next_red = %d, next_green = %d,"
458 data
->next_red
, data
->next_green
,
463 static int blinkm_led_red_set(struct led_classdev
*led_cdev
,
464 enum led_brightness value
)
466 return blinkm_led_common_set(led_cdev
, value
, RED
);
469 static int blinkm_led_green_set(struct led_classdev
*led_cdev
,
470 enum led_brightness value
)
472 return blinkm_led_common_set(led_cdev
, value
, GREEN
);
475 static int blinkm_led_blue_set(struct led_classdev
*led_cdev
,
476 enum led_brightness value
)
478 return blinkm_led_common_set(led_cdev
, value
, BLUE
);
481 static void blinkm_init_hw(struct i2c_client
*client
)
484 ret
= blinkm_transfer_hw(client
, BLM_STOP_SCRIPT
);
485 ret
= blinkm_transfer_hw(client
, BLM_GO_RGB
);
488 static int blinkm_test_run(struct i2c_client
*client
)
491 struct blinkm_data
*data
= i2c_get_clientdata(client
);
493 data
->next_red
= 0x01;
494 data
->next_green
= 0x05;
495 data
->next_blue
= 0x10;
496 ret
= blinkm_transfer_hw(client
, BLM_GO_RGB
);
501 data
->next_red
= 0x25;
502 data
->next_green
= 0x10;
503 data
->next_blue
= 0x31;
504 ret
= blinkm_transfer_hw(client
, BLM_FADE_RGB
);
509 data
->next_hue
= 0x50;
510 data
->next_saturation
= 0x10;
511 data
->next_brightness
= 0x20;
512 ret
= blinkm_transfer_hw(client
, BLM_FADE_HSB
);
520 /* Return 0 if detection is successful, -ENODEV otherwise */
521 static int blinkm_detect(struct i2c_client
*client
, struct i2c_board_info
*info
)
523 struct i2c_adapter
*adapter
= client
->adapter
;
528 if (!i2c_check_functionality(adapter
, I2C_FUNC_SMBUS_BYTE_DATA
529 | I2C_FUNC_SMBUS_WORD_DATA
530 | I2C_FUNC_SMBUS_WRITE_BYTE
))
533 /* Now, we do the remaining detection. Simple for now. */
534 /* We might need more guards to protect other i2c slaves */
536 /* make sure the blinkM is balanced (read/writes) */
538 ret
= blinkm_write(client
, BLM_GET_ADDR
, NULL
);
541 usleep_range(5000, 10000);
542 ret
= blinkm_read(client
, BLM_GET_ADDR
, tmpargs
);
545 usleep_range(5000, 10000);
546 if (tmpargs
[0] == 0x09)
551 /* Step 1: Read BlinkM address back - cmd_char 'a' */
552 ret
= blinkm_write(client
, BLM_GET_ADDR
, NULL
);
555 usleep_range(20000, 30000); /* allow a small delay */
556 ret
= blinkm_read(client
, BLM_GET_ADDR
, tmpargs
);
560 if (tmpargs
[0] != 0x09) {
561 dev_err(&client
->dev
, "enodev DEV ADDR = 0x%02X\n", tmpargs
[0]);
565 strlcpy(info
->type
, "blinkm", I2C_NAME_SIZE
);
569 static int blinkm_probe(struct i2c_client
*client
,
570 const struct i2c_device_id
*id
)
572 struct blinkm_data
*data
;
573 struct blinkm_led
*led
[3];
575 char blinkm_led_name
[28];
577 data
= devm_kzalloc(&client
->dev
,
578 sizeof(struct blinkm_data
), GFP_KERNEL
);
584 data
->i2c_addr
= 0x08;
585 /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */
587 /* firmware version - use fake until we read real value
588 * (currently broken - BlinkM confused!) */
589 data
->script_id
= 0x01;
590 data
->i2c_client
= client
;
592 i2c_set_clientdata(client
, data
);
593 mutex_init(&data
->update_lock
);
595 /* Register sysfs hooks */
596 err
= sysfs_create_group(&client
->dev
.kobj
, &blinkm_group
);
598 dev_err(&client
->dev
, "couldn't register sysfs group\n");
602 for (i
= 0; i
< 3; i
++) {
603 /* RED = 0, GREEN = 1, BLUE = 2 */
604 led
[i
] = &data
->blinkm_leds
[i
];
605 led
[i
]->i2c_client
= client
;
607 led
[i
]->led_cdev
.max_brightness
= 255;
608 led
[i
]->led_cdev
.flags
= LED_CORE_SUSPENDRESUME
;
611 snprintf(blinkm_led_name
, sizeof(blinkm_led_name
),
615 led
[i
]->led_cdev
.name
= blinkm_led_name
;
616 led
[i
]->led_cdev
.brightness_set_blocking
=
618 err
= led_classdev_register(&client
->dev
,
621 dev_err(&client
->dev
,
622 "couldn't register LED %s\n",
623 led
[i
]->led_cdev
.name
);
628 snprintf(blinkm_led_name
, sizeof(blinkm_led_name
),
629 "blinkm-%d-%d-green",
632 led
[i
]->led_cdev
.name
= blinkm_led_name
;
633 led
[i
]->led_cdev
.brightness_set_blocking
=
634 blinkm_led_green_set
;
635 err
= led_classdev_register(&client
->dev
,
638 dev_err(&client
->dev
,
639 "couldn't register LED %s\n",
640 led
[i
]->led_cdev
.name
);
645 snprintf(blinkm_led_name
, sizeof(blinkm_led_name
),
649 led
[i
]->led_cdev
.name
= blinkm_led_name
;
650 led
[i
]->led_cdev
.brightness_set_blocking
=
652 err
= led_classdev_register(&client
->dev
,
655 dev_err(&client
->dev
,
656 "couldn't register LED %s\n",
657 led
[i
]->led_cdev
.name
);
664 /* Initialize the blinkm */
665 blinkm_init_hw(client
);
670 led_classdev_unregister(&led
[GREEN
]->led_cdev
);
673 led_classdev_unregister(&led
[RED
]->led_cdev
);
676 sysfs_remove_group(&client
->dev
.kobj
, &blinkm_group
);
681 static int blinkm_remove(struct i2c_client
*client
)
683 struct blinkm_data
*data
= i2c_get_clientdata(client
);
687 /* make sure no workqueue entries are pending */
688 for (i
= 0; i
< 3; i
++)
689 led_classdev_unregister(&data
->blinkm_leds
[i
].led_cdev
);
692 data
->next_red
= 0x00;
693 data
->next_green
= 0x00;
694 data
->next_blue
= 0x00;
695 ret
= blinkm_transfer_hw(client
, BLM_FADE_RGB
);
697 dev_err(&client
->dev
, "Failure in blinkm_remove ignored. Continuing.\n");
700 data
->next_hue
= 0x00;
701 data
->next_saturation
= 0x00;
702 data
->next_brightness
= 0x00;
703 ret
= blinkm_transfer_hw(client
, BLM_FADE_HSB
);
705 dev_err(&client
->dev
, "Failure in blinkm_remove ignored. Continuing.\n");
707 /* red fade to off */
708 data
->next_red
= 0xff;
709 ret
= blinkm_transfer_hw(client
, BLM_GO_RGB
);
711 dev_err(&client
->dev
, "Failure in blinkm_remove ignored. Continuing.\n");
714 data
->next_red
= 0x00;
715 ret
= blinkm_transfer_hw(client
, BLM_FADE_RGB
);
717 dev_err(&client
->dev
, "Failure in blinkm_remove ignored. Continuing.\n");
719 sysfs_remove_group(&client
->dev
.kobj
, &blinkm_group
);
723 static const struct i2c_device_id blinkm_id
[] = {
728 MODULE_DEVICE_TABLE(i2c
, blinkm_id
);
730 /* This is the driver that will be inserted */
731 static struct i2c_driver blinkm_driver
= {
732 .class = I2C_CLASS_HWMON
,
736 .probe
= blinkm_probe
,
737 .remove
= blinkm_remove
,
738 .id_table
= blinkm_id
,
739 .detect
= blinkm_detect
,
740 .address_list
= normal_i2c
,
743 module_i2c_driver(blinkm_driver
);
745 MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>");
746 MODULE_DESCRIPTION("BlinkM RGB LED driver");
747 MODULE_LICENSE("GPL");