hid-led.c 12 KB

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