1 // SPDX-License-Identifier: GPL-2.0-only
3 * Simple USB RGB LED driver
5 * Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com>
6 * Based on drivers/hid/hid-thingm.c and
7 * drivers/usb/misc/usbled.c
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>
18 enum hidled_report_type
{
31 static unsigned const char riso_kagaku_tbl
[] = {
32 /* R+2G+4B -> riso kagaku color index */
38 [5] = 6, /* magenta */
43 #define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)]
63 #define DELCOM_GREEN_LED 0
64 #define DELCOM_RED_LED 1
65 #define DELCOM_BLUE_LED 2
70 struct hidled_config
{
71 enum hidled_type type
;
73 const char *short_name
;
74 enum led_brightness max_brightness
;
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
);
83 struct led_classdev cdev
;
84 struct hidled_rgb
*rgb
;
89 struct hidled_device
*ldev
;
90 struct hidled_led red
;
91 struct hidled_led green
;
92 struct hidled_led blue
;
96 struct hidled_device
{
97 const struct hidled_config
*config
;
98 struct hid_device
*hdev
;
99 struct hidled_rgb
*rgb
;
104 #define MAX_REPORT_SIZE 16
106 #define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev)
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");
113 static int hidled_send(struct hidled_device
*ldev
, __u8
*buf
)
117 mutex_lock(&ldev
->lock
);
120 * buffer provided to hid_hw_raw_request must not be on the stack
121 * and must not be part of a data structure
123 memcpy(ldev
->buf
, buf
, ldev
->config
->report_size
);
125 if (ldev
->config
->report_type
== RAW_REQUEST
)
126 ret
= hid_hw_raw_request(ldev
->hdev
, buf
[0], ldev
->buf
,
127 ldev
->config
->report_size
,
130 else if (ldev
->config
->report_type
== OUTPUT_REPORT
)
131 ret
= hid_hw_output_report(ldev
->hdev
, ldev
->buf
,
132 ldev
->config
->report_size
);
136 mutex_unlock(&ldev
->lock
);
141 return ret
== ldev
->config
->report_size
? 0 : -EMSGSIZE
;
144 /* reading data is supported for report type RAW_REQUEST only */
145 static int hidled_recv(struct hidled_device
*ldev
, __u8
*buf
)
149 if (ldev
->config
->report_type
!= RAW_REQUEST
)
152 mutex_lock(&ldev
->lock
);
154 memcpy(ldev
->buf
, buf
, ldev
->config
->report_size
);
156 ret
= hid_hw_raw_request(ldev
->hdev
, buf
[0], ldev
->buf
,
157 ldev
->config
->report_size
,
163 ret
= hid_hw_raw_request(ldev
->hdev
, buf
[0], ldev
->buf
,
164 ldev
->config
->report_size
,
168 memcpy(buf
, ldev
->buf
, ldev
->config
->report_size
);
170 mutex_unlock(&ldev
->lock
);
172 return ret
< 0 ? ret
: 0;
175 static u8
riso_kagaku_index(struct hidled_rgb
*rgb
)
177 enum led_brightness r
, g
, b
;
179 r
= rgb
->red
.cdev
.brightness
;
180 g
= rgb
->green
.cdev
.brightness
;
181 b
= rgb
->blue
.cdev
.brightness
;
183 if (riso_kagaku_switch_green_blue
)
184 return RISO_KAGAKU_IX(r
, b
, g
);
186 return RISO_KAGAKU_IX(r
, g
, b
);
189 static int riso_kagaku_write(struct led_classdev
*cdev
, enum led_brightness br
)
191 struct hidled_led
*led
= to_hidled_led(cdev
);
192 struct hidled_rgb
*rgb
= led
->rgb
;
193 __u8 buf
[MAX_REPORT_SIZE
] = {};
195 buf
[1] = riso_kagaku_index(rgb
);
197 return hidled_send(rgb
->ldev
, buf
);
200 static int dream_cheeky_write(struct led_classdev
*cdev
, enum led_brightness br
)
202 struct hidled_led
*led
= to_hidled_led(cdev
);
203 struct hidled_rgb
*rgb
= led
->rgb
;
204 __u8 buf
[MAX_REPORT_SIZE
] = {};
206 buf
[1] = rgb
->red
.cdev
.brightness
;
207 buf
[2] = rgb
->green
.cdev
.brightness
;
208 buf
[3] = rgb
->blue
.cdev
.brightness
;
212 return hidled_send(rgb
->ldev
, buf
);
215 static int dream_cheeky_init(struct hidled_device
*ldev
)
217 __u8 buf
[MAX_REPORT_SIZE
] = {};
219 /* Dream Cheeky magic */
226 return hidled_send(ldev
, buf
);
229 static int _thingm_write(struct led_classdev
*cdev
, enum led_brightness br
,
232 struct hidled_led
*led
= to_hidled_led(cdev
);
233 __u8 buf
[MAX_REPORT_SIZE
] = { 1, 'c' };
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
;
240 return hidled_send(led
->rgb
->ldev
, buf
);
243 static int thingm_write_v1(struct led_classdev
*cdev
, enum led_brightness br
)
245 return _thingm_write(cdev
, br
, 0);
248 static int thingm_write(struct led_classdev
*cdev
, enum led_brightness br
)
250 return _thingm_write(cdev
, br
, 1);
253 static const struct hidled_config hidled_config_thingm_v1
= {
254 .name
= "ThingM blink(1) v1",
255 .short_name
= "thingm",
256 .max_brightness
= 255,
259 .report_type
= RAW_REQUEST
,
260 .write
= thingm_write_v1
,
263 static int thingm_init(struct hidled_device
*ldev
)
265 __u8 buf
[MAX_REPORT_SIZE
] = { 1, 'v' };
268 ret
= hidled_recv(ldev
, buf
);
272 /* Check for firmware major version 1 */
274 ldev
->config
= &hidled_config_thingm_v1
;
279 static inline int delcom_get_lednum(const struct hidled_led
*led
)
281 if (led
== &led
->rgb
->red
)
282 return DELCOM_RED_LED
;
283 else if (led
== &led
->rgb
->green
)
284 return DELCOM_GREEN_LED
;
286 return DELCOM_BLUE_LED
;
289 static int delcom_enable_led(struct hidled_led
*led
)
291 union delcom_packet dp
= { .tx
.major_cmd
= 101, .tx
.minor_cmd
= 12 };
293 dp
.tx
.data_lsb
= 1 << delcom_get_lednum(led
);
296 return hidled_send(led
->rgb
->ldev
, dp
.data
);
299 static int delcom_set_pwm(struct hidled_led
*led
)
301 union delcom_packet dp
= { .tx
.major_cmd
= 101, .tx
.minor_cmd
= 34 };
303 dp
.tx
.data_lsb
= delcom_get_lednum(led
);
304 dp
.tx
.data_msb
= led
->cdev
.brightness
;
306 return hidled_send(led
->rgb
->ldev
, dp
.data
);
309 static int delcom_write(struct led_classdev
*cdev
, enum led_brightness br
)
311 struct hidled_led
*led
= to_hidled_led(cdev
);
316 * We can't do this in the init function already because the device
317 * is internally reset later.
319 ret
= delcom_enable_led(led
);
323 return delcom_set_pwm(led
);
326 static int delcom_init(struct hidled_device
*ldev
)
328 union delcom_packet dp
= { .rx
.cmd
= 104 };
331 ret
= hidled_recv(ldev
, dp
.data
);
335 * Several Delcom devices share the same USB VID/PID
336 * Check for family id 2 for Visual Signal Indicator
338 return le16_to_cpu(dp
.fw
.family_code
) == 2 ? 0 : -ENODEV
;
341 static int luxafor_write(struct led_classdev
*cdev
, enum led_brightness br
)
343 struct hidled_led
*led
= to_hidled_led(cdev
);
344 __u8 buf
[MAX_REPORT_SIZE
] = { [1] = 1 };
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
;
351 return hidled_send(led
->rgb
->ldev
, buf
);
354 static const struct hidled_config hidled_configs
[] = {
357 .name
= "Riso Kagaku Webmail Notifier",
358 .short_name
= "riso_kagaku",
362 .report_type
= OUTPUT_REPORT
,
363 .write
= riso_kagaku_write
,
366 .type
= DREAM_CHEEKY
,
367 .name
= "Dream Cheeky Webmail Notifier",
368 .short_name
= "dream_cheeky",
369 .max_brightness
= 31,
372 .report_type
= RAW_REQUEST
,
373 .init
= dream_cheeky_init
,
374 .write
= dream_cheeky_write
,
378 .name
= "ThingM blink(1)",
379 .short_name
= "thingm",
380 .max_brightness
= 255,
383 .report_type
= RAW_REQUEST
,
385 .write
= thingm_write
,
389 .name
= "Delcom Visual Signal Indicator G2",
390 .short_name
= "delcom",
391 .max_brightness
= 100,
394 .report_type
= RAW_REQUEST
,
396 .write
= delcom_write
,
400 .name
= "Greynut Luxafor",
401 .short_name
= "luxafor",
402 .max_brightness
= 255,
405 .report_type
= OUTPUT_REPORT
,
406 .write
= luxafor_write
,
410 static int hidled_init_led(struct hidled_led
*led
, const char *color_name
,
411 struct hidled_rgb
*rgb
, unsigned int minor
)
413 const struct hidled_config
*config
= rgb
->ldev
->config
;
415 if (config
->num_leds
> 1)
416 snprintf(led
->name
, sizeof(led
->name
), "%s%u:%s:led%u",
417 config
->short_name
, minor
, color_name
, rgb
->num
);
419 snprintf(led
->name
, sizeof(led
->name
), "%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
;
427 return devm_led_classdev_register(&rgb
->ldev
->hdev
->dev
, &led
->cdev
);
430 static int hidled_init_rgb(struct hidled_rgb
*rgb
, unsigned int minor
)
434 /* Register the red diode */
435 ret
= hidled_init_led(&rgb
->red
, "red", rgb
, minor
);
439 /* Register the green diode */
440 ret
= hidled_init_led(&rgb
->green
, "green", rgb
, minor
);
444 /* Register the blue diode */
445 return hidled_init_led(&rgb
->blue
, "blue", rgb
, minor
);
448 static int hidled_probe(struct hid_device
*hdev
, const struct hid_device_id
*id
)
450 struct hidled_device
*ldev
;
454 ldev
= devm_kzalloc(&hdev
->dev
, sizeof(*ldev
), GFP_KERNEL
);
458 ldev
->buf
= devm_kmalloc(&hdev
->dev
, MAX_REPORT_SIZE
, GFP_KERNEL
);
462 ret
= hid_parse(hdev
);
467 mutex_init(&ldev
->lock
);
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
];
476 if (ldev
->config
->init
) {
477 ret
= ldev
->config
->init(ldev
);
482 ldev
->rgb
= devm_kcalloc(&hdev
->dev
, ldev
->config
->num_leds
,
483 sizeof(struct hidled_rgb
), GFP_KERNEL
);
487 ret
= hid_hw_start(hdev
, HID_CONNECT_HIDRAW
);
491 minor
= ((struct hidraw
*) hdev
->hidraw
)->minor
;
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(&ldev
->rgb
[i
], minor
);
503 hid_info(hdev
, "%s initialized\n", ldev
->config
->name
);
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
},
523 MODULE_DEVICE_TABLE(hid
, hidled_table
);
525 static struct hid_driver hidled_driver
= {
527 .probe
= hidled_probe
,
528 .id_table
= hidled_table
,
531 module_hid_driver(hidled_driver
);
533 MODULE_LICENSE("GPL");
534 MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>");
535 MODULE_DESCRIPTION("Simple USB RGB LED driver");