xref: /linux/drivers/hid/hid-led.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1a10e763bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
26c7ad07eSHeiner Kallweit /*
36c7ad07eSHeiner Kallweit  * Simple USB RGB LED driver
46c7ad07eSHeiner Kallweit  *
56c7ad07eSHeiner Kallweit  * Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com>
66c7ad07eSHeiner Kallweit  * Based on drivers/hid/hid-thingm.c and
76c7ad07eSHeiner Kallweit  * drivers/usb/misc/usbled.c
86c7ad07eSHeiner Kallweit  */
96c7ad07eSHeiner Kallweit 
106c7ad07eSHeiner Kallweit #include <linux/hid.h>
116c7ad07eSHeiner Kallweit #include <linux/hidraw.h>
126c7ad07eSHeiner Kallweit #include <linux/leds.h>
136c7ad07eSHeiner Kallweit #include <linux/module.h>
146c7ad07eSHeiner Kallweit #include <linux/mutex.h>
156c7ad07eSHeiner Kallweit 
166c7ad07eSHeiner Kallweit #include "hid-ids.h"
176c7ad07eSHeiner Kallweit 
186c7ad07eSHeiner Kallweit enum hidled_report_type {
196c7ad07eSHeiner Kallweit 	RAW_REQUEST,
206c7ad07eSHeiner Kallweit 	OUTPUT_REPORT
216c7ad07eSHeiner Kallweit };
226c7ad07eSHeiner Kallweit 
236c7ad07eSHeiner Kallweit enum hidled_type {
246c7ad07eSHeiner Kallweit 	RISO_KAGAKU,
256c7ad07eSHeiner Kallweit 	DREAM_CHEEKY,
26007414e8SHeiner Kallweit 	THINGM,
27de908650SHeiner Kallweit 	DELCOM,
289d1e048cSHeiner Kallweit 	LUXAFOR,
296c7ad07eSHeiner Kallweit };
306c7ad07eSHeiner Kallweit 
316c7ad07eSHeiner Kallweit static unsigned const char riso_kagaku_tbl[] = {
326c7ad07eSHeiner Kallweit /* R+2G+4B -> riso kagaku color index */
336c7ad07eSHeiner Kallweit 	[0] = 0, /* black   */
346c7ad07eSHeiner Kallweit 	[1] = 2, /* red     */
356c7ad07eSHeiner Kallweit 	[2] = 1, /* green   */
366c7ad07eSHeiner Kallweit 	[3] = 5, /* yellow  */
376c7ad07eSHeiner Kallweit 	[4] = 3, /* blue    */
386c7ad07eSHeiner Kallweit 	[5] = 6, /* magenta */
396c7ad07eSHeiner Kallweit 	[6] = 4, /* cyan    */
406c7ad07eSHeiner Kallweit 	[7] = 7  /* white   */
416c7ad07eSHeiner Kallweit };
426c7ad07eSHeiner Kallweit 
436c7ad07eSHeiner Kallweit #define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)]
446c7ad07eSHeiner Kallweit 
45de908650SHeiner Kallweit union delcom_packet {
46de908650SHeiner Kallweit 	__u8 data[8];
47de908650SHeiner Kallweit 	struct {
48de908650SHeiner Kallweit 		__u8 major_cmd;
49de908650SHeiner Kallweit 		__u8 minor_cmd;
50de908650SHeiner Kallweit 		__u8 data_lsb;
51de908650SHeiner Kallweit 		__u8 data_msb;
52de908650SHeiner Kallweit 	} tx;
53de908650SHeiner Kallweit 	struct {
54de908650SHeiner Kallweit 		__u8 cmd;
55de908650SHeiner Kallweit 	} rx;
56de908650SHeiner Kallweit 	struct {
57de908650SHeiner Kallweit 		__le16 family_code;
58de908650SHeiner Kallweit 		__le16 security_code;
59de908650SHeiner Kallweit 		__u8 fw_version;
60de908650SHeiner Kallweit 	} fw;
61de908650SHeiner Kallweit };
62de908650SHeiner Kallweit 
63de908650SHeiner Kallweit #define DELCOM_GREEN_LED	0
64de908650SHeiner Kallweit #define DELCOM_RED_LED		1
65de908650SHeiner Kallweit #define DELCOM_BLUE_LED		2
66de908650SHeiner Kallweit 
676c7ad07eSHeiner Kallweit struct hidled_device;
685bc83936SHeiner Kallweit struct hidled_rgb;
696c7ad07eSHeiner Kallweit 
706c7ad07eSHeiner Kallweit struct hidled_config {
716c7ad07eSHeiner Kallweit 	enum hidled_type	type;
726c7ad07eSHeiner Kallweit 	const char		*name;
736c7ad07eSHeiner Kallweit 	const char		*short_name;
746c7ad07eSHeiner Kallweit 	enum led_brightness	max_brightness;
755bc83936SHeiner Kallweit 	int			num_leds;
766c7ad07eSHeiner Kallweit 	size_t			report_size;
776c7ad07eSHeiner Kallweit 	enum hidled_report_type	report_type;
786c7ad07eSHeiner Kallweit 	int (*init)(struct hidled_device *ldev);
796c7ad07eSHeiner Kallweit 	int (*write)(struct led_classdev *cdev, enum led_brightness br);
806c7ad07eSHeiner Kallweit };
816c7ad07eSHeiner Kallweit 
826c7ad07eSHeiner Kallweit struct hidled_led {
836c7ad07eSHeiner Kallweit 	struct led_classdev	cdev;
845bc83936SHeiner Kallweit 	struct hidled_rgb	*rgb;
856c7ad07eSHeiner Kallweit 	char			name[32];
866c7ad07eSHeiner Kallweit };
876c7ad07eSHeiner Kallweit 
885bc83936SHeiner Kallweit struct hidled_rgb {
895bc83936SHeiner Kallweit 	struct hidled_device	*ldev;
905bc83936SHeiner Kallweit 	struct hidled_led	red;
915bc83936SHeiner Kallweit 	struct hidled_led	green;
925bc83936SHeiner Kallweit 	struct hidled_led	blue;
935bc83936SHeiner Kallweit 	u8			num;
945bc83936SHeiner Kallweit };
955bc83936SHeiner Kallweit 
966c7ad07eSHeiner Kallweit struct hidled_device {
976c7ad07eSHeiner Kallweit 	const struct hidled_config *config;
986c7ad07eSHeiner Kallweit 	struct hid_device       *hdev;
995bc83936SHeiner Kallweit 	struct hidled_rgb	*rgb;
1003d1355b3SHeiner Kallweit 	u8			*buf;
1016c7ad07eSHeiner Kallweit 	struct mutex		lock;
1026c7ad07eSHeiner Kallweit };
1036c7ad07eSHeiner Kallweit 
1046c7ad07eSHeiner Kallweit #define MAX_REPORT_SIZE		16
1056c7ad07eSHeiner Kallweit 
1066c7ad07eSHeiner Kallweit #define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev)
1076c7ad07eSHeiner Kallweit 
1086c7ad07eSHeiner Kallweit static bool riso_kagaku_switch_green_blue;
1096c7ad07eSHeiner Kallweit module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR);
1106c7ad07eSHeiner Kallweit MODULE_PARM_DESC(riso_kagaku_switch_green_blue,
1116c7ad07eSHeiner Kallweit 	"switch green and blue RGB component for Riso Kagaku devices");
1126c7ad07eSHeiner Kallweit 
hidled_send(struct hidled_device * ldev,__u8 * buf)1136c7ad07eSHeiner Kallweit static int hidled_send(struct hidled_device *ldev, __u8 *buf)
1146c7ad07eSHeiner Kallweit {
1156c7ad07eSHeiner Kallweit 	int ret;
1166c7ad07eSHeiner Kallweit 
1176c7ad07eSHeiner Kallweit 	mutex_lock(&ldev->lock);
1186c7ad07eSHeiner Kallweit 
1193d1355b3SHeiner Kallweit 	/*
1203d1355b3SHeiner Kallweit 	 * buffer provided to hid_hw_raw_request must not be on the stack
1213d1355b3SHeiner Kallweit 	 * and must not be part of a data structure
1223d1355b3SHeiner Kallweit 	 */
1233d1355b3SHeiner Kallweit 	memcpy(ldev->buf, buf, ldev->config->report_size);
1243d1355b3SHeiner Kallweit 
1256c7ad07eSHeiner Kallweit 	if (ldev->config->report_type == RAW_REQUEST)
1263d1355b3SHeiner Kallweit 		ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
1276c7ad07eSHeiner Kallweit 					 ldev->config->report_size,
1286c7ad07eSHeiner Kallweit 					 HID_FEATURE_REPORT,
1296c7ad07eSHeiner Kallweit 					 HID_REQ_SET_REPORT);
1306c7ad07eSHeiner Kallweit 	else if (ldev->config->report_type == OUTPUT_REPORT)
1313d1355b3SHeiner Kallweit 		ret = hid_hw_output_report(ldev->hdev, ldev->buf,
1326c7ad07eSHeiner Kallweit 					   ldev->config->report_size);
1336c7ad07eSHeiner Kallweit 	else
1346c7ad07eSHeiner Kallweit 		ret = -EINVAL;
1356c7ad07eSHeiner Kallweit 
1366c7ad07eSHeiner Kallweit 	mutex_unlock(&ldev->lock);
1376c7ad07eSHeiner Kallweit 
1386c7ad07eSHeiner Kallweit 	if (ret < 0)
1396c7ad07eSHeiner Kallweit 		return ret;
1406c7ad07eSHeiner Kallweit 
1416c7ad07eSHeiner Kallweit 	return ret == ldev->config->report_size ? 0 : -EMSGSIZE;
1426c7ad07eSHeiner Kallweit }
1436c7ad07eSHeiner Kallweit 
14443745730SHeiner Kallweit /* reading data is supported for report type RAW_REQUEST only */
hidled_recv(struct hidled_device * ldev,__u8 * buf)14543745730SHeiner Kallweit static int hidled_recv(struct hidled_device *ldev, __u8 *buf)
14643745730SHeiner Kallweit {
14743745730SHeiner Kallweit 	int ret;
14843745730SHeiner Kallweit 
14943745730SHeiner Kallweit 	if (ldev->config->report_type != RAW_REQUEST)
15043745730SHeiner Kallweit 		return -EINVAL;
15143745730SHeiner Kallweit 
15243745730SHeiner Kallweit 	mutex_lock(&ldev->lock);
15343745730SHeiner Kallweit 
1543d1355b3SHeiner Kallweit 	memcpy(ldev->buf, buf, ldev->config->report_size);
1553d1355b3SHeiner Kallweit 
1563d1355b3SHeiner Kallweit 	ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
15743745730SHeiner Kallweit 				 ldev->config->report_size,
15843745730SHeiner Kallweit 				 HID_FEATURE_REPORT,
15943745730SHeiner Kallweit 				 HID_REQ_SET_REPORT);
16043745730SHeiner Kallweit 	if (ret < 0)
16143745730SHeiner Kallweit 		goto err;
16243745730SHeiner Kallweit 
1633d1355b3SHeiner Kallweit 	ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
16443745730SHeiner Kallweit 				 ldev->config->report_size,
16543745730SHeiner Kallweit 				 HID_FEATURE_REPORT,
16643745730SHeiner Kallweit 				 HID_REQ_GET_REPORT);
1673d1355b3SHeiner Kallweit 
1683d1355b3SHeiner Kallweit 	memcpy(buf, ldev->buf, ldev->config->report_size);
16943745730SHeiner Kallweit err:
17043745730SHeiner Kallweit 	mutex_unlock(&ldev->lock);
17143745730SHeiner Kallweit 
17243745730SHeiner Kallweit 	return ret < 0 ? ret : 0;
17343745730SHeiner Kallweit }
17443745730SHeiner Kallweit 
riso_kagaku_index(struct hidled_rgb * rgb)1755bc83936SHeiner Kallweit static u8 riso_kagaku_index(struct hidled_rgb *rgb)
1766c7ad07eSHeiner Kallweit {
1776c7ad07eSHeiner Kallweit 	enum led_brightness r, g, b;
1786c7ad07eSHeiner Kallweit 
1795bc83936SHeiner Kallweit 	r = rgb->red.cdev.brightness;
1805bc83936SHeiner Kallweit 	g = rgb->green.cdev.brightness;
1815bc83936SHeiner Kallweit 	b = rgb->blue.cdev.brightness;
1826c7ad07eSHeiner Kallweit 
1836c7ad07eSHeiner Kallweit 	if (riso_kagaku_switch_green_blue)
1846c7ad07eSHeiner Kallweit 		return RISO_KAGAKU_IX(r, b, g);
1856c7ad07eSHeiner Kallweit 	else
1866c7ad07eSHeiner Kallweit 		return RISO_KAGAKU_IX(r, g, b);
1876c7ad07eSHeiner Kallweit }
1886c7ad07eSHeiner Kallweit 
riso_kagaku_write(struct led_classdev * cdev,enum led_brightness br)1896c7ad07eSHeiner Kallweit static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br)
1906c7ad07eSHeiner Kallweit {
1916c7ad07eSHeiner Kallweit 	struct hidled_led *led = to_hidled_led(cdev);
1925bc83936SHeiner Kallweit 	struct hidled_rgb *rgb = led->rgb;
1936c7ad07eSHeiner Kallweit 	__u8 buf[MAX_REPORT_SIZE] = {};
1946c7ad07eSHeiner Kallweit 
1955bc83936SHeiner Kallweit 	buf[1] = riso_kagaku_index(rgb);
1966c7ad07eSHeiner Kallweit 
1975bc83936SHeiner Kallweit 	return hidled_send(rgb->ldev, buf);
1986c7ad07eSHeiner Kallweit }
1996c7ad07eSHeiner Kallweit 
dream_cheeky_write(struct led_classdev * cdev,enum led_brightness br)2006c7ad07eSHeiner Kallweit static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br)
2016c7ad07eSHeiner Kallweit {
2026c7ad07eSHeiner Kallweit 	struct hidled_led *led = to_hidled_led(cdev);
2035bc83936SHeiner Kallweit 	struct hidled_rgb *rgb = led->rgb;
2046c7ad07eSHeiner Kallweit 	__u8 buf[MAX_REPORT_SIZE] = {};
2056c7ad07eSHeiner Kallweit 
2065bc83936SHeiner Kallweit 	buf[1] = rgb->red.cdev.brightness;
2075bc83936SHeiner Kallweit 	buf[2] = rgb->green.cdev.brightness;
2085bc83936SHeiner Kallweit 	buf[3] = rgb->blue.cdev.brightness;
2096c7ad07eSHeiner Kallweit 	buf[7] = 0x1a;
2106c7ad07eSHeiner Kallweit 	buf[8] = 0x05;
2116c7ad07eSHeiner Kallweit 
2125bc83936SHeiner Kallweit 	return hidled_send(rgb->ldev, buf);
2136c7ad07eSHeiner Kallweit }
2146c7ad07eSHeiner Kallweit 
dream_cheeky_init(struct hidled_device * ldev)2156c7ad07eSHeiner Kallweit static int dream_cheeky_init(struct hidled_device *ldev)
2166c7ad07eSHeiner Kallweit {
2176c7ad07eSHeiner Kallweit 	__u8 buf[MAX_REPORT_SIZE] = {};
2186c7ad07eSHeiner Kallweit 
2196c7ad07eSHeiner Kallweit 	/* Dream Cheeky magic */
2206c7ad07eSHeiner Kallweit 	buf[1] = 0x1f;
2216c7ad07eSHeiner Kallweit 	buf[2] = 0x02;
2226c7ad07eSHeiner Kallweit 	buf[4] = 0x5f;
2236c7ad07eSHeiner Kallweit 	buf[7] = 0x1a;
2246c7ad07eSHeiner Kallweit 	buf[8] = 0x03;
2256c7ad07eSHeiner Kallweit 
2266c7ad07eSHeiner Kallweit 	return hidled_send(ldev, buf);
2276c7ad07eSHeiner Kallweit }
2286c7ad07eSHeiner Kallweit 
_thingm_write(struct led_classdev * cdev,enum led_brightness br,u8 offset)229007414e8SHeiner Kallweit static int _thingm_write(struct led_classdev *cdev, enum led_brightness br,
230007414e8SHeiner Kallweit 			 u8 offset)
231007414e8SHeiner Kallweit {
232007414e8SHeiner Kallweit 	struct hidled_led *led = to_hidled_led(cdev);
23334d9810bSHeiner Kallweit 	__u8 buf[MAX_REPORT_SIZE] = { 1, 'c' };
234007414e8SHeiner Kallweit 
235007414e8SHeiner Kallweit 	buf[2] = led->rgb->red.cdev.brightness;
236007414e8SHeiner Kallweit 	buf[3] = led->rgb->green.cdev.brightness;
237007414e8SHeiner Kallweit 	buf[4] = led->rgb->blue.cdev.brightness;
238007414e8SHeiner Kallweit 	buf[7] = led->rgb->num + offset;
239007414e8SHeiner Kallweit 
240007414e8SHeiner Kallweit 	return hidled_send(led->rgb->ldev, buf);
241007414e8SHeiner Kallweit }
242007414e8SHeiner Kallweit 
thingm_write_v1(struct led_classdev * cdev,enum led_brightness br)243007414e8SHeiner Kallweit static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br)
244007414e8SHeiner Kallweit {
245007414e8SHeiner Kallweit 	return _thingm_write(cdev, br, 0);
246007414e8SHeiner Kallweit }
247007414e8SHeiner Kallweit 
thingm_write(struct led_classdev * cdev,enum led_brightness br)248007414e8SHeiner Kallweit static int thingm_write(struct led_classdev *cdev, enum led_brightness br)
249007414e8SHeiner Kallweit {
250007414e8SHeiner Kallweit 	return _thingm_write(cdev, br, 1);
251007414e8SHeiner Kallweit }
252007414e8SHeiner Kallweit 
253007414e8SHeiner Kallweit static const struct hidled_config hidled_config_thingm_v1 = {
254007414e8SHeiner Kallweit 	.name = "ThingM blink(1) v1",
255007414e8SHeiner Kallweit 	.short_name = "thingm",
256007414e8SHeiner Kallweit 	.max_brightness = 255,
257007414e8SHeiner Kallweit 	.num_leds = 1,
258007414e8SHeiner Kallweit 	.report_size = 9,
259007414e8SHeiner Kallweit 	.report_type = RAW_REQUEST,
260007414e8SHeiner Kallweit 	.write = thingm_write_v1,
261007414e8SHeiner Kallweit };
262007414e8SHeiner Kallweit 
thingm_init(struct hidled_device * ldev)263007414e8SHeiner Kallweit static int thingm_init(struct hidled_device *ldev)
264007414e8SHeiner Kallweit {
26534d9810bSHeiner Kallweit 	__u8 buf[MAX_REPORT_SIZE] = { 1, 'v' };
266007414e8SHeiner Kallweit 	int ret;
267007414e8SHeiner Kallweit 
268007414e8SHeiner Kallweit 	ret = hidled_recv(ldev, buf);
269007414e8SHeiner Kallweit 	if (ret)
270007414e8SHeiner Kallweit 		return ret;
271007414e8SHeiner Kallweit 
272007414e8SHeiner Kallweit 	/* Check for firmware major version 1 */
273007414e8SHeiner Kallweit 	if (buf[3] == '1')
274007414e8SHeiner Kallweit 		ldev->config = &hidled_config_thingm_v1;
275007414e8SHeiner Kallweit 
276007414e8SHeiner Kallweit 	return 0;
277007414e8SHeiner Kallweit }
278007414e8SHeiner Kallweit 
delcom_get_lednum(const struct hidled_led * led)279de908650SHeiner Kallweit static inline int delcom_get_lednum(const struct hidled_led *led)
280de908650SHeiner Kallweit {
281de908650SHeiner Kallweit 	if (led == &led->rgb->red)
282de908650SHeiner Kallweit 		return DELCOM_RED_LED;
283de908650SHeiner Kallweit 	else if (led == &led->rgb->green)
284de908650SHeiner Kallweit 		return DELCOM_GREEN_LED;
285de908650SHeiner Kallweit 	else
286de908650SHeiner Kallweit 		return DELCOM_BLUE_LED;
287de908650SHeiner Kallweit }
288de908650SHeiner Kallweit 
delcom_enable_led(struct hidled_led * led)289de908650SHeiner Kallweit static int delcom_enable_led(struct hidled_led *led)
290de908650SHeiner Kallweit {
291de908650SHeiner Kallweit 	union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 12 };
292de908650SHeiner Kallweit 
293de908650SHeiner Kallweit 	dp.tx.data_lsb = 1 << delcom_get_lednum(led);
294de908650SHeiner Kallweit 	dp.tx.data_msb = 0;
295de908650SHeiner Kallweit 
296de908650SHeiner Kallweit 	return hidled_send(led->rgb->ldev, dp.data);
297de908650SHeiner Kallweit }
298de908650SHeiner Kallweit 
delcom_set_pwm(struct hidled_led * led)299de908650SHeiner Kallweit static int delcom_set_pwm(struct hidled_led *led)
300de908650SHeiner Kallweit {
301de908650SHeiner Kallweit 	union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 34 };
302de908650SHeiner Kallweit 
303de908650SHeiner Kallweit 	dp.tx.data_lsb = delcom_get_lednum(led);
304de908650SHeiner Kallweit 	dp.tx.data_msb = led->cdev.brightness;
305de908650SHeiner Kallweit 
306de908650SHeiner Kallweit 	return hidled_send(led->rgb->ldev, dp.data);
307de908650SHeiner Kallweit }
308de908650SHeiner Kallweit 
delcom_write(struct led_classdev * cdev,enum led_brightness br)309de908650SHeiner Kallweit static int delcom_write(struct led_classdev *cdev, enum led_brightness br)
310de908650SHeiner Kallweit {
311de908650SHeiner Kallweit 	struct hidled_led *led = to_hidled_led(cdev);
312de908650SHeiner Kallweit 	int ret;
313de908650SHeiner Kallweit 
314de908650SHeiner Kallweit 	/*
315de908650SHeiner Kallweit 	 * enable LED
316de908650SHeiner Kallweit 	 * We can't do this in the init function already because the device
317de908650SHeiner Kallweit 	 * is internally reset later.
318de908650SHeiner Kallweit 	 */
319de908650SHeiner Kallweit 	ret = delcom_enable_led(led);
320de908650SHeiner Kallweit 	if (ret)
321de908650SHeiner Kallweit 		return ret;
322de908650SHeiner Kallweit 
323de908650SHeiner Kallweit 	return delcom_set_pwm(led);
324de908650SHeiner Kallweit }
325de908650SHeiner Kallweit 
delcom_init(struct hidled_device * ldev)326de908650SHeiner Kallweit static int delcom_init(struct hidled_device *ldev)
327de908650SHeiner Kallweit {
328de908650SHeiner Kallweit 	union delcom_packet dp = { .rx.cmd = 104 };
329de908650SHeiner Kallweit 	int ret;
330de908650SHeiner Kallweit 
331de908650SHeiner Kallweit 	ret = hidled_recv(ldev, dp.data);
332de908650SHeiner Kallweit 	if (ret)
333de908650SHeiner Kallweit 		return ret;
334de908650SHeiner Kallweit 	/*
335de908650SHeiner Kallweit 	 * Several Delcom devices share the same USB VID/PID
336de908650SHeiner Kallweit 	 * Check for family id 2 for Visual Signal Indicator
337de908650SHeiner Kallweit 	 */
338f4c109b6SHeiner Kallweit 	return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV;
339de908650SHeiner Kallweit }
340de908650SHeiner Kallweit 
luxafor_write(struct led_classdev * cdev,enum led_brightness br)3419d1e048cSHeiner Kallweit static int luxafor_write(struct led_classdev *cdev, enum led_brightness br)
3429d1e048cSHeiner Kallweit {
3439d1e048cSHeiner Kallweit 	struct hidled_led *led = to_hidled_led(cdev);
3449d1e048cSHeiner Kallweit 	__u8 buf[MAX_REPORT_SIZE] = { [1] = 1 };
3459d1e048cSHeiner Kallweit 
3469d1e048cSHeiner Kallweit 	buf[2] = led->rgb->num + 1;
3479d1e048cSHeiner Kallweit 	buf[3] = led->rgb->red.cdev.brightness;
3489d1e048cSHeiner Kallweit 	buf[4] = led->rgb->green.cdev.brightness;
3499d1e048cSHeiner Kallweit 	buf[5] = led->rgb->blue.cdev.brightness;
3509d1e048cSHeiner Kallweit 
3519d1e048cSHeiner Kallweit 	return hidled_send(led->rgb->ldev, buf);
3529d1e048cSHeiner Kallweit }
3539d1e048cSHeiner Kallweit 
3546c7ad07eSHeiner Kallweit static const struct hidled_config hidled_configs[] = {
3556c7ad07eSHeiner Kallweit 	{
3566c7ad07eSHeiner Kallweit 		.type = RISO_KAGAKU,
3576c7ad07eSHeiner Kallweit 		.name = "Riso Kagaku Webmail Notifier",
3586c7ad07eSHeiner Kallweit 		.short_name = "riso_kagaku",
3596c7ad07eSHeiner Kallweit 		.max_brightness = 1,
3605bc83936SHeiner Kallweit 		.num_leds = 1,
3616c7ad07eSHeiner Kallweit 		.report_size = 6,
3626c7ad07eSHeiner Kallweit 		.report_type = OUTPUT_REPORT,
3636c7ad07eSHeiner Kallweit 		.write = riso_kagaku_write,
3646c7ad07eSHeiner Kallweit 	},
3656c7ad07eSHeiner Kallweit 	{
3666c7ad07eSHeiner Kallweit 		.type = DREAM_CHEEKY,
3676c7ad07eSHeiner Kallweit 		.name = "Dream Cheeky Webmail Notifier",
3686c7ad07eSHeiner Kallweit 		.short_name = "dream_cheeky",
369*116c3f4aSJonathan Teh 		.max_brightness = 63,
3705bc83936SHeiner Kallweit 		.num_leds = 1,
3716c7ad07eSHeiner Kallweit 		.report_size = 9,
3726c7ad07eSHeiner Kallweit 		.report_type = RAW_REQUEST,
3736c7ad07eSHeiner Kallweit 		.init = dream_cheeky_init,
3746c7ad07eSHeiner Kallweit 		.write = dream_cheeky_write,
3756c7ad07eSHeiner Kallweit 	},
376007414e8SHeiner Kallweit 	{
377007414e8SHeiner Kallweit 		.type = THINGM,
378007414e8SHeiner Kallweit 		.name = "ThingM blink(1)",
379007414e8SHeiner Kallweit 		.short_name = "thingm",
380007414e8SHeiner Kallweit 		.max_brightness = 255,
381007414e8SHeiner Kallweit 		.num_leds = 2,
382007414e8SHeiner Kallweit 		.report_size = 9,
383007414e8SHeiner Kallweit 		.report_type = RAW_REQUEST,
384007414e8SHeiner Kallweit 		.init = thingm_init,
385007414e8SHeiner Kallweit 		.write = thingm_write,
386007414e8SHeiner Kallweit 	},
387de908650SHeiner Kallweit 	{
388de908650SHeiner Kallweit 		.type = DELCOM,
389de908650SHeiner Kallweit 		.name = "Delcom Visual Signal Indicator G2",
390de908650SHeiner Kallweit 		.short_name = "delcom",
391de908650SHeiner Kallweit 		.max_brightness = 100,
392de908650SHeiner Kallweit 		.num_leds = 1,
393de908650SHeiner Kallweit 		.report_size = 8,
394de908650SHeiner Kallweit 		.report_type = RAW_REQUEST,
395de908650SHeiner Kallweit 		.init = delcom_init,
396de908650SHeiner Kallweit 		.write = delcom_write,
397de908650SHeiner Kallweit 	},
3989d1e048cSHeiner Kallweit 	{
3999d1e048cSHeiner Kallweit 		.type = LUXAFOR,
4009d1e048cSHeiner Kallweit 		.name = "Greynut Luxafor",
4019d1e048cSHeiner Kallweit 		.short_name = "luxafor",
4029d1e048cSHeiner Kallweit 		.max_brightness = 255,
4039d1e048cSHeiner Kallweit 		.num_leds = 6,
4049d1e048cSHeiner Kallweit 		.report_size = 9,
4059d1e048cSHeiner Kallweit 		.report_type = OUTPUT_REPORT,
4069d1e048cSHeiner Kallweit 		.write = luxafor_write,
4079d1e048cSHeiner Kallweit 	},
4086c7ad07eSHeiner Kallweit };
4096c7ad07eSHeiner Kallweit 
hidled_init_led(struct hidled_led * led,const char * color_name,struct hidled_rgb * rgb,unsigned int minor)4106c7ad07eSHeiner Kallweit static int hidled_init_led(struct hidled_led *led, const char *color_name,
4115bc83936SHeiner Kallweit 			   struct hidled_rgb *rgb, unsigned int minor)
4126c7ad07eSHeiner Kallweit {
4135bc83936SHeiner Kallweit 	const struct hidled_config *config = rgb->ldev->config;
4146c7ad07eSHeiner Kallweit 
4155bc83936SHeiner Kallweit 	if (config->num_leds > 1)
4165bc83936SHeiner Kallweit 		snprintf(led->name, sizeof(led->name), "%s%u:%s:led%u",
4175bc83936SHeiner Kallweit 			 config->short_name, minor, color_name, rgb->num);
4185bc83936SHeiner Kallweit 	else
4195bc83936SHeiner Kallweit 		snprintf(led->name, sizeof(led->name), "%s%u:%s",
4205bc83936SHeiner Kallweit 			 config->short_name, minor, color_name);
4215bc83936SHeiner Kallweit 	led->cdev.name = led->name;
4225bc83936SHeiner Kallweit 	led->cdev.max_brightness = config->max_brightness;
4235bc83936SHeiner Kallweit 	led->cdev.brightness_set_blocking = config->write;
4245bc83936SHeiner Kallweit 	led->cdev.flags = LED_HW_PLUGGABLE;
4255bc83936SHeiner Kallweit 	led->rgb = rgb;
4265bc83936SHeiner Kallweit 
4275bc83936SHeiner Kallweit 	return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev);
4286c7ad07eSHeiner Kallweit }
4296c7ad07eSHeiner Kallweit 
hidled_init_rgb(struct hidled_rgb * rgb,unsigned int minor)4305bc83936SHeiner Kallweit static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor)
4316c7ad07eSHeiner Kallweit {
4326c7ad07eSHeiner Kallweit 	int ret;
4336c7ad07eSHeiner Kallweit 
4346c7ad07eSHeiner Kallweit 	/* Register the red diode */
4355bc83936SHeiner Kallweit 	ret = hidled_init_led(&rgb->red, "red", rgb, minor);
4366c7ad07eSHeiner Kallweit 	if (ret)
4376c7ad07eSHeiner Kallweit 		return ret;
4386c7ad07eSHeiner Kallweit 
4396c7ad07eSHeiner Kallweit 	/* Register the green diode */
4405bc83936SHeiner Kallweit 	ret = hidled_init_led(&rgb->green, "green", rgb, minor);
4416c7ad07eSHeiner Kallweit 	if (ret)
4426c7ad07eSHeiner Kallweit 		return ret;
4436c7ad07eSHeiner Kallweit 
4446c7ad07eSHeiner Kallweit 	/* Register the blue diode */
4455bc83936SHeiner Kallweit 	return hidled_init_led(&rgb->blue, "blue", rgb, minor);
4466c7ad07eSHeiner Kallweit }
4476c7ad07eSHeiner Kallweit 
hidled_probe(struct hid_device * hdev,const struct hid_device_id * id)4486c7ad07eSHeiner Kallweit static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id)
4496c7ad07eSHeiner Kallweit {
4506c7ad07eSHeiner Kallweit 	struct hidled_device *ldev;
4516c7ad07eSHeiner Kallweit 	unsigned int minor;
4526c7ad07eSHeiner Kallweit 	int ret, i;
4536c7ad07eSHeiner Kallweit 
4546c7ad07eSHeiner Kallweit 	ldev = devm_kzalloc(&hdev->dev, sizeof(*ldev), GFP_KERNEL);
4556c7ad07eSHeiner Kallweit 	if (!ldev)
4566c7ad07eSHeiner Kallweit 		return -ENOMEM;
4576c7ad07eSHeiner Kallweit 
4583d1355b3SHeiner Kallweit 	ldev->buf = devm_kmalloc(&hdev->dev, MAX_REPORT_SIZE, GFP_KERNEL);
4593d1355b3SHeiner Kallweit 	if (!ldev->buf)
4603d1355b3SHeiner Kallweit 		return -ENOMEM;
4613d1355b3SHeiner Kallweit 
4626c7ad07eSHeiner Kallweit 	ret = hid_parse(hdev);
4636c7ad07eSHeiner Kallweit 	if (ret)
4646c7ad07eSHeiner Kallweit 		return ret;
4656c7ad07eSHeiner Kallweit 
4666c7ad07eSHeiner Kallweit 	ldev->hdev = hdev;
4676c7ad07eSHeiner Kallweit 	mutex_init(&ldev->lock);
4686c7ad07eSHeiner Kallweit 
4696c7ad07eSHeiner Kallweit 	for (i = 0; !ldev->config && i < ARRAY_SIZE(hidled_configs); i++)
4706c7ad07eSHeiner Kallweit 		if (hidled_configs[i].type == id->driver_data)
4716c7ad07eSHeiner Kallweit 			ldev->config = &hidled_configs[i];
4726c7ad07eSHeiner Kallweit 
4736c7ad07eSHeiner Kallweit 	if (!ldev->config)
4746c7ad07eSHeiner Kallweit 		return -EINVAL;
4756c7ad07eSHeiner Kallweit 
4766c7ad07eSHeiner Kallweit 	if (ldev->config->init) {
4776c7ad07eSHeiner Kallweit 		ret = ldev->config->init(ldev);
4786c7ad07eSHeiner Kallweit 		if (ret)
4796c7ad07eSHeiner Kallweit 			return ret;
4806c7ad07eSHeiner Kallweit 	}
4816c7ad07eSHeiner Kallweit 
4825bc83936SHeiner Kallweit 	ldev->rgb = devm_kcalloc(&hdev->dev, ldev->config->num_leds,
4835bc83936SHeiner Kallweit 				 sizeof(struct hidled_rgb), GFP_KERNEL);
4845bc83936SHeiner Kallweit 	if (!ldev->rgb)
4855bc83936SHeiner Kallweit 		return -ENOMEM;
4865bc83936SHeiner Kallweit 
4876c7ad07eSHeiner Kallweit 	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
4886c7ad07eSHeiner Kallweit 	if (ret)
4896c7ad07eSHeiner Kallweit 		return ret;
4906c7ad07eSHeiner Kallweit 
4916c7ad07eSHeiner Kallweit 	minor = ((struct hidraw *) hdev->hidraw)->minor;
4926c7ad07eSHeiner Kallweit 
4935bc83936SHeiner Kallweit 	for (i = 0; i < ldev->config->num_leds; i++) {
4945bc83936SHeiner Kallweit 		ldev->rgb[i].ldev = ldev;
4955bc83936SHeiner Kallweit 		ldev->rgb[i].num = i;
4965bc83936SHeiner Kallweit 		ret = hidled_init_rgb(&ldev->rgb[i], minor);
4976c7ad07eSHeiner Kallweit 		if (ret) {
4986c7ad07eSHeiner Kallweit 			hid_hw_stop(hdev);
4996c7ad07eSHeiner Kallweit 			return ret;
5006c7ad07eSHeiner Kallweit 		}
5015bc83936SHeiner Kallweit 	}
5026c7ad07eSHeiner Kallweit 
5036c7ad07eSHeiner Kallweit 	hid_info(hdev, "%s initialized\n", ldev->config->name);
5046c7ad07eSHeiner Kallweit 
5056c7ad07eSHeiner Kallweit 	return 0;
5066c7ad07eSHeiner Kallweit }
5076c7ad07eSHeiner Kallweit 
5086c7ad07eSHeiner Kallweit static const struct hid_device_id hidled_table[] = {
5096c7ad07eSHeiner Kallweit 	{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU,
5106c7ad07eSHeiner Kallweit 	  USB_DEVICE_ID_RI_KA_WEBMAIL), .driver_data = RISO_KAGAKU },
5116c7ad07eSHeiner Kallweit 	{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
5126c7ad07eSHeiner Kallweit 	  USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY },
5136c7ad07eSHeiner Kallweit 	{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
5146c7ad07eSHeiner Kallweit 	  USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY },
515007414e8SHeiner Kallweit 	{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM,
516007414e8SHeiner Kallweit 	  USB_DEVICE_ID_BLINK1), .driver_data = THINGM },
517de908650SHeiner Kallweit 	{ HID_USB_DEVICE(USB_VENDOR_ID_DELCOM,
518de908650SHeiner Kallweit 	  USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM },
5199d1e048cSHeiner Kallweit 	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP,
5209d1e048cSHeiner Kallweit 	  USB_DEVICE_ID_LUXAFOR), .driver_data = LUXAFOR },
5216c7ad07eSHeiner Kallweit 	{ }
5226c7ad07eSHeiner Kallweit };
5236c7ad07eSHeiner Kallweit MODULE_DEVICE_TABLE(hid, hidled_table);
5246c7ad07eSHeiner Kallweit 
5256c7ad07eSHeiner Kallweit static struct hid_driver hidled_driver = {
5266c7ad07eSHeiner Kallweit 	.name = "hid-led",
5276c7ad07eSHeiner Kallweit 	.probe = hidled_probe,
5286c7ad07eSHeiner Kallweit 	.id_table = hidled_table,
5296c7ad07eSHeiner Kallweit };
5306c7ad07eSHeiner Kallweit 
5316c7ad07eSHeiner Kallweit module_hid_driver(hidled_driver);
5326c7ad07eSHeiner Kallweit 
5336c7ad07eSHeiner Kallweit MODULE_LICENSE("GPL");
5346c7ad07eSHeiner Kallweit MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>");
5356c7ad07eSHeiner Kallweit MODULE_DESCRIPTION("Simple USB RGB LED driver");
536