1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Simple USB RGB LED driver |
4 | * |
5 | * Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com> |
6 | * Based on drivers/hid/hid-thingm.c and |
7 | * drivers/usb/misc/usbled.c |
8 | */ |
9 | |
10 | #include <linux/hid.h> |
11 | #include <linux/hidraw.h> |
12 | #include <linux/leds.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mutex.h> |
15 | |
16 | #include "hid-ids.h" |
17 | |
18 | enum hidled_report_type { |
19 | RAW_REQUEST, |
20 | OUTPUT_REPORT |
21 | }; |
22 | |
23 | enum hidled_type { |
24 | RISO_KAGAKU, |
25 | DREAM_CHEEKY, |
26 | THINGM, |
27 | DELCOM, |
28 | LUXAFOR, |
29 | }; |
30 | |
31 | static unsigned const char riso_kagaku_tbl[] = { |
32 | /* R+2G+4B -> riso kagaku color index */ |
33 | [0] = 0, /* black */ |
34 | [1] = 2, /* red */ |
35 | [2] = 1, /* green */ |
36 | [3] = 5, /* yellow */ |
37 | [4] = 3, /* blue */ |
38 | [5] = 6, /* magenta */ |
39 | [6] = 4, /* cyan */ |
40 | [7] = 7 /* white */ |
41 | }; |
42 | |
43 | #define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)] |
44 | |
45 | union delcom_packet { |
46 | __u8 data[8]; |
47 | struct { |
48 | __u8 major_cmd; |
49 | __u8 minor_cmd; |
50 | __u8 data_lsb; |
51 | __u8 data_msb; |
52 | } tx; |
53 | struct { |
54 | __u8 cmd; |
55 | } rx; |
56 | struct { |
57 | __le16 family_code; |
58 | __le16 security_code; |
59 | __u8 fw_version; |
60 | } fw; |
61 | }; |
62 | |
63 | #define DELCOM_GREEN_LED 0 |
64 | #define DELCOM_RED_LED 1 |
65 | #define DELCOM_BLUE_LED 2 |
66 | |
67 | struct hidled_device; |
68 | struct hidled_rgb; |
69 | |
70 | struct hidled_config { |
71 | enum hidled_type type; |
72 | const char *name; |
73 | const char *short_name; |
74 | enum led_brightness max_brightness; |
75 | int num_leds; |
76 | size_t report_size; |
77 | enum hidled_report_type report_type; |
78 | int (*init)(struct hidled_device *ldev); |
79 | int (*write)(struct led_classdev *cdev, enum led_brightness br); |
80 | }; |
81 | |
82 | struct hidled_led { |
83 | struct led_classdev cdev; |
84 | struct hidled_rgb *rgb; |
85 | char name[32]; |
86 | }; |
87 | |
88 | struct hidled_rgb { |
89 | struct hidled_device *ldev; |
90 | struct hidled_led red; |
91 | struct hidled_led green; |
92 | struct hidled_led blue; |
93 | u8 num; |
94 | }; |
95 | |
96 | struct hidled_device { |
97 | const struct hidled_config *config; |
98 | struct hid_device *hdev; |
99 | struct hidled_rgb *rgb; |
100 | u8 *buf; |
101 | struct mutex lock; |
102 | }; |
103 | |
104 | #define MAX_REPORT_SIZE 16 |
105 | |
106 | #define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev) |
107 | |
108 | static bool riso_kagaku_switch_green_blue; |
109 | module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR); |
110 | MODULE_PARM_DESC(riso_kagaku_switch_green_blue, |
111 | "switch green and blue RGB component for Riso Kagaku devices" ); |
112 | |
113 | static int hidled_send(struct hidled_device *ldev, __u8 *buf) |
114 | { |
115 | int ret; |
116 | |
117 | mutex_lock(&ldev->lock); |
118 | |
119 | /* |
120 | * buffer provided to hid_hw_raw_request must not be on the stack |
121 | * and must not be part of a data structure |
122 | */ |
123 | memcpy(ldev->buf, buf, ldev->config->report_size); |
124 | |
125 | if (ldev->config->report_type == RAW_REQUEST) |
126 | ret = hid_hw_raw_request(hdev: ldev->hdev, reportnum: buf[0], buf: ldev->buf, |
127 | len: ldev->config->report_size, |
128 | rtype: HID_FEATURE_REPORT, |
129 | reqtype: HID_REQ_SET_REPORT); |
130 | else if (ldev->config->report_type == OUTPUT_REPORT) |
131 | ret = hid_hw_output_report(hdev: ldev->hdev, buf: ldev->buf, |
132 | len: ldev->config->report_size); |
133 | else |
134 | ret = -EINVAL; |
135 | |
136 | mutex_unlock(lock: &ldev->lock); |
137 | |
138 | if (ret < 0) |
139 | return ret; |
140 | |
141 | return ret == ldev->config->report_size ? 0 : -EMSGSIZE; |
142 | } |
143 | |
144 | /* reading data is supported for report type RAW_REQUEST only */ |
145 | static int hidled_recv(struct hidled_device *ldev, __u8 *buf) |
146 | { |
147 | int ret; |
148 | |
149 | if (ldev->config->report_type != RAW_REQUEST) |
150 | return -EINVAL; |
151 | |
152 | mutex_lock(&ldev->lock); |
153 | |
154 | memcpy(ldev->buf, buf, ldev->config->report_size); |
155 | |
156 | ret = hid_hw_raw_request(hdev: ldev->hdev, reportnum: buf[0], buf: ldev->buf, |
157 | len: ldev->config->report_size, |
158 | rtype: HID_FEATURE_REPORT, |
159 | reqtype: HID_REQ_SET_REPORT); |
160 | if (ret < 0) |
161 | goto err; |
162 | |
163 | ret = hid_hw_raw_request(hdev: ldev->hdev, reportnum: buf[0], buf: ldev->buf, |
164 | len: ldev->config->report_size, |
165 | rtype: HID_FEATURE_REPORT, |
166 | reqtype: HID_REQ_GET_REPORT); |
167 | |
168 | memcpy(buf, ldev->buf, ldev->config->report_size); |
169 | err: |
170 | mutex_unlock(lock: &ldev->lock); |
171 | |
172 | return ret < 0 ? ret : 0; |
173 | } |
174 | |
175 | static u8 riso_kagaku_index(struct hidled_rgb *rgb) |
176 | { |
177 | enum led_brightness r, g, b; |
178 | |
179 | r = rgb->red.cdev.brightness; |
180 | g = rgb->green.cdev.brightness; |
181 | b = rgb->blue.cdev.brightness; |
182 | |
183 | if (riso_kagaku_switch_green_blue) |
184 | return RISO_KAGAKU_IX(r, b, g); |
185 | else |
186 | return RISO_KAGAKU_IX(r, g, b); |
187 | } |
188 | |
189 | static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br) |
190 | { |
191 | struct hidled_led *led = to_hidled_led(cdev); |
192 | struct hidled_rgb *rgb = led->rgb; |
193 | __u8 buf[MAX_REPORT_SIZE] = {}; |
194 | |
195 | buf[1] = riso_kagaku_index(rgb); |
196 | |
197 | return hidled_send(ldev: rgb->ldev, buf); |
198 | } |
199 | |
200 | static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br) |
201 | { |
202 | struct hidled_led *led = to_hidled_led(cdev); |
203 | struct hidled_rgb *rgb = led->rgb; |
204 | __u8 buf[MAX_REPORT_SIZE] = {}; |
205 | |
206 | buf[1] = rgb->red.cdev.brightness; |
207 | buf[2] = rgb->green.cdev.brightness; |
208 | buf[3] = rgb->blue.cdev.brightness; |
209 | buf[7] = 0x1a; |
210 | buf[8] = 0x05; |
211 | |
212 | return hidled_send(ldev: rgb->ldev, buf); |
213 | } |
214 | |
215 | static int dream_cheeky_init(struct hidled_device *ldev) |
216 | { |
217 | __u8 buf[MAX_REPORT_SIZE] = {}; |
218 | |
219 | /* Dream Cheeky magic */ |
220 | buf[1] = 0x1f; |
221 | buf[2] = 0x02; |
222 | buf[4] = 0x5f; |
223 | buf[7] = 0x1a; |
224 | buf[8] = 0x03; |
225 | |
226 | return hidled_send(ldev, buf); |
227 | } |
228 | |
229 | static int _thingm_write(struct led_classdev *cdev, enum led_brightness br, |
230 | u8 offset) |
231 | { |
232 | struct hidled_led *led = to_hidled_led(cdev); |
233 | __u8 buf[MAX_REPORT_SIZE] = { 1, 'c' }; |
234 | |
235 | buf[2] = led->rgb->red.cdev.brightness; |
236 | buf[3] = led->rgb->green.cdev.brightness; |
237 | buf[4] = led->rgb->blue.cdev.brightness; |
238 | buf[7] = led->rgb->num + offset; |
239 | |
240 | return hidled_send(ldev: led->rgb->ldev, buf); |
241 | } |
242 | |
243 | static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br) |
244 | { |
245 | return _thingm_write(cdev, br, offset: 0); |
246 | } |
247 | |
248 | static int thingm_write(struct led_classdev *cdev, enum led_brightness br) |
249 | { |
250 | return _thingm_write(cdev, br, offset: 1); |
251 | } |
252 | |
253 | static const struct hidled_config hidled_config_thingm_v1 = { |
254 | .name = "ThingM blink(1) v1" , |
255 | .short_name = "thingm" , |
256 | .max_brightness = 255, |
257 | .num_leds = 1, |
258 | .report_size = 9, |
259 | .report_type = RAW_REQUEST, |
260 | .write = thingm_write_v1, |
261 | }; |
262 | |
263 | static int thingm_init(struct hidled_device *ldev) |
264 | { |
265 | __u8 buf[MAX_REPORT_SIZE] = { 1, 'v' }; |
266 | int ret; |
267 | |
268 | ret = hidled_recv(ldev, buf); |
269 | if (ret) |
270 | return ret; |
271 | |
272 | /* Check for firmware major version 1 */ |
273 | if (buf[3] == '1') |
274 | ldev->config = &hidled_config_thingm_v1; |
275 | |
276 | return 0; |
277 | } |
278 | |
279 | static inline int delcom_get_lednum(const struct hidled_led *led) |
280 | { |
281 | if (led == &led->rgb->red) |
282 | return DELCOM_RED_LED; |
283 | else if (led == &led->rgb->green) |
284 | return DELCOM_GREEN_LED; |
285 | else |
286 | return DELCOM_BLUE_LED; |
287 | } |
288 | |
289 | static int delcom_enable_led(struct hidled_led *led) |
290 | { |
291 | union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 12 }; |
292 | |
293 | dp.tx.data_lsb = 1 << delcom_get_lednum(led); |
294 | dp.tx.data_msb = 0; |
295 | |
296 | return hidled_send(ldev: led->rgb->ldev, buf: dp.data); |
297 | } |
298 | |
299 | static int delcom_set_pwm(struct hidled_led *led) |
300 | { |
301 | union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 34 }; |
302 | |
303 | dp.tx.data_lsb = delcom_get_lednum(led); |
304 | dp.tx.data_msb = led->cdev.brightness; |
305 | |
306 | return hidled_send(ldev: led->rgb->ldev, buf: dp.data); |
307 | } |
308 | |
309 | static int delcom_write(struct led_classdev *cdev, enum led_brightness br) |
310 | { |
311 | struct hidled_led *led = to_hidled_led(cdev); |
312 | int ret; |
313 | |
314 | /* |
315 | * enable LED |
316 | * We can't do this in the init function already because the device |
317 | * is internally reset later. |
318 | */ |
319 | ret = delcom_enable_led(led); |
320 | if (ret) |
321 | return ret; |
322 | |
323 | return delcom_set_pwm(led); |
324 | } |
325 | |
326 | static int delcom_init(struct hidled_device *ldev) |
327 | { |
328 | union delcom_packet dp = { .rx.cmd = 104 }; |
329 | int ret; |
330 | |
331 | ret = hidled_recv(ldev, buf: dp.data); |
332 | if (ret) |
333 | return ret; |
334 | /* |
335 | * Several Delcom devices share the same USB VID/PID |
336 | * Check for family id 2 for Visual Signal Indicator |
337 | */ |
338 | return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV; |
339 | } |
340 | |
341 | static int luxafor_write(struct led_classdev *cdev, enum led_brightness br) |
342 | { |
343 | struct hidled_led *led = to_hidled_led(cdev); |
344 | __u8 buf[MAX_REPORT_SIZE] = { [1] = 1 }; |
345 | |
346 | buf[2] = led->rgb->num + 1; |
347 | buf[3] = led->rgb->red.cdev.brightness; |
348 | buf[4] = led->rgb->green.cdev.brightness; |
349 | buf[5] = led->rgb->blue.cdev.brightness; |
350 | |
351 | return hidled_send(ldev: led->rgb->ldev, buf); |
352 | } |
353 | |
354 | static const struct hidled_config hidled_configs[] = { |
355 | { |
356 | .type = RISO_KAGAKU, |
357 | .name = "Riso Kagaku Webmail Notifier" , |
358 | .short_name = "riso_kagaku" , |
359 | .max_brightness = 1, |
360 | .num_leds = 1, |
361 | .report_size = 6, |
362 | .report_type = OUTPUT_REPORT, |
363 | .write = riso_kagaku_write, |
364 | }, |
365 | { |
366 | .type = DREAM_CHEEKY, |
367 | .name = "Dream Cheeky Webmail Notifier" , |
368 | .short_name = "dream_cheeky" , |
369 | .max_brightness = 63, |
370 | .num_leds = 1, |
371 | .report_size = 9, |
372 | .report_type = RAW_REQUEST, |
373 | .init = dream_cheeky_init, |
374 | .write = dream_cheeky_write, |
375 | }, |
376 | { |
377 | .type = THINGM, |
378 | .name = "ThingM blink(1)" , |
379 | .short_name = "thingm" , |
380 | .max_brightness = 255, |
381 | .num_leds = 2, |
382 | .report_size = 9, |
383 | .report_type = RAW_REQUEST, |
384 | .init = thingm_init, |
385 | .write = thingm_write, |
386 | }, |
387 | { |
388 | .type = DELCOM, |
389 | .name = "Delcom Visual Signal Indicator G2" , |
390 | .short_name = "delcom" , |
391 | .max_brightness = 100, |
392 | .num_leds = 1, |
393 | .report_size = 8, |
394 | .report_type = RAW_REQUEST, |
395 | .init = delcom_init, |
396 | .write = delcom_write, |
397 | }, |
398 | { |
399 | .type = LUXAFOR, |
400 | .name = "Greynut Luxafor" , |
401 | .short_name = "luxafor" , |
402 | .max_brightness = 255, |
403 | .num_leds = 6, |
404 | .report_size = 9, |
405 | .report_type = OUTPUT_REPORT, |
406 | .write = luxafor_write, |
407 | }, |
408 | }; |
409 | |
410 | static int hidled_init_led(struct hidled_led *led, const char *color_name, |
411 | struct hidled_rgb *rgb, unsigned int minor) |
412 | { |
413 | const struct hidled_config *config = rgb->ldev->config; |
414 | |
415 | if (config->num_leds > 1) |
416 | snprintf(buf: led->name, size: sizeof(led->name), fmt: "%s%u:%s:led%u" , |
417 | config->short_name, minor, color_name, rgb->num); |
418 | else |
419 | snprintf(buf: led->name, size: sizeof(led->name), fmt: "%s%u:%s" , |
420 | config->short_name, minor, color_name); |
421 | led->cdev.name = led->name; |
422 | led->cdev.max_brightness = config->max_brightness; |
423 | led->cdev.brightness_set_blocking = config->write; |
424 | led->cdev.flags = LED_HW_PLUGGABLE; |
425 | led->rgb = rgb; |
426 | |
427 | return devm_led_classdev_register(parent: &rgb->ldev->hdev->dev, led_cdev: &led->cdev); |
428 | } |
429 | |
430 | static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor) |
431 | { |
432 | int ret; |
433 | |
434 | /* Register the red diode */ |
435 | ret = hidled_init_led(led: &rgb->red, color_name: "red" , rgb, minor); |
436 | if (ret) |
437 | return ret; |
438 | |
439 | /* Register the green diode */ |
440 | ret = hidled_init_led(led: &rgb->green, color_name: "green" , rgb, minor); |
441 | if (ret) |
442 | return ret; |
443 | |
444 | /* Register the blue diode */ |
445 | return hidled_init_led(led: &rgb->blue, color_name: "blue" , rgb, minor); |
446 | } |
447 | |
448 | static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id) |
449 | { |
450 | struct hidled_device *ldev; |
451 | unsigned int minor; |
452 | int ret, i; |
453 | |
454 | ldev = devm_kzalloc(dev: &hdev->dev, size: sizeof(*ldev), GFP_KERNEL); |
455 | if (!ldev) |
456 | return -ENOMEM; |
457 | |
458 | ldev->buf = devm_kmalloc(dev: &hdev->dev, MAX_REPORT_SIZE, GFP_KERNEL); |
459 | if (!ldev->buf) |
460 | return -ENOMEM; |
461 | |
462 | ret = hid_parse(hdev); |
463 | if (ret) |
464 | return ret; |
465 | |
466 | ldev->hdev = hdev; |
467 | mutex_init(&ldev->lock); |
468 | |
469 | for (i = 0; !ldev->config && i < ARRAY_SIZE(hidled_configs); i++) |
470 | if (hidled_configs[i].type == id->driver_data) |
471 | ldev->config = &hidled_configs[i]; |
472 | |
473 | if (!ldev->config) |
474 | return -EINVAL; |
475 | |
476 | if (ldev->config->init) { |
477 | ret = ldev->config->init(ldev); |
478 | if (ret) |
479 | return ret; |
480 | } |
481 | |
482 | ldev->rgb = devm_kcalloc(dev: &hdev->dev, n: ldev->config->num_leds, |
483 | size: sizeof(struct hidled_rgb), GFP_KERNEL); |
484 | if (!ldev->rgb) |
485 | return -ENOMEM; |
486 | |
487 | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); |
488 | if (ret) |
489 | return ret; |
490 | |
491 | minor = ((struct hidraw *) hdev->hidraw)->minor; |
492 | |
493 | for (i = 0; i < ldev->config->num_leds; i++) { |
494 | ldev->rgb[i].ldev = ldev; |
495 | ldev->rgb[i].num = i; |
496 | ret = hidled_init_rgb(rgb: &ldev->rgb[i], minor); |
497 | if (ret) { |
498 | hid_hw_stop(hdev); |
499 | return ret; |
500 | } |
501 | } |
502 | |
503 | hid_info(hdev, "%s initialized\n" , ldev->config->name); |
504 | |
505 | return 0; |
506 | } |
507 | |
508 | static const struct hid_device_id hidled_table[] = { |
509 | { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, |
510 | USB_DEVICE_ID_RI_KA_WEBMAIL), .driver_data = RISO_KAGAKU }, |
511 | { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, |
512 | USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY }, |
513 | { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, |
514 | USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY }, |
515 | { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, |
516 | USB_DEVICE_ID_BLINK1), .driver_data = THINGM }, |
517 | { HID_USB_DEVICE(USB_VENDOR_ID_DELCOM, |
518 | USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM }, |
519 | { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, |
520 | USB_DEVICE_ID_LUXAFOR), .driver_data = LUXAFOR }, |
521 | { } |
522 | }; |
523 | MODULE_DEVICE_TABLE(hid, hidled_table); |
524 | |
525 | static struct hid_driver hidled_driver = { |
526 | .name = "hid-led" , |
527 | .probe = hidled_probe, |
528 | .id_table = hidled_table, |
529 | }; |
530 | |
531 | module_hid_driver(hidled_driver); |
532 | |
533 | MODULE_LICENSE("GPL" ); |
534 | MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>" ); |
535 | MODULE_DESCRIPTION("Simple USB RGB LED driver" ); |
536 | |