536 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Simple USB RGB LED driver4 *5 * Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com>6 * Based on drivers/hid/hid-thingm.c and7 * drivers/usb/misc/usbled.c8 */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 18enum hidled_report_type {19 RAW_REQUEST,20 OUTPUT_REPORT21};22 23enum hidled_type {24 RISO_KAGAKU,25 DREAM_CHEEKY,26 THINGM,27 DELCOM,28 LUXAFOR,29};30 31static 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 45union 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 064#define DELCOM_RED_LED 165#define DELCOM_BLUE_LED 266 67struct hidled_device;68struct hidled_rgb;69 70struct 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 82struct hidled_led {83 struct led_classdev cdev;84 struct hidled_rgb *rgb;85 char name[32];86};87 88struct 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 96struct 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 16105 106#define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev)107 108static bool riso_kagaku_switch_green_blue;109module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR);110MODULE_PARM_DESC(riso_kagaku_switch_green_blue,111 "switch green and blue RGB component for Riso Kagaku devices");112 113static 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 stack121 * and must not be part of a data structure122 */123 memcpy(ldev->buf, buf, ldev->config->report_size);124 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,128 HID_FEATURE_REPORT,129 HID_REQ_SET_REPORT);130 else if (ldev->config->report_type == OUTPUT_REPORT)131 ret = hid_hw_output_report(ldev->hdev, ldev->buf,132 ldev->config->report_size);133 else134 ret = -EINVAL;135 136 mutex_unlock(&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 */145static 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(ldev->hdev, buf[0], ldev->buf,157 ldev->config->report_size,158 HID_FEATURE_REPORT,159 HID_REQ_SET_REPORT);160 if (ret < 0)161 goto err;162 163 ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,164 ldev->config->report_size,165 HID_FEATURE_REPORT,166 HID_REQ_GET_REPORT);167 168 memcpy(buf, ldev->buf, ldev->config->report_size);169err:170 mutex_unlock(&ldev->lock);171 172 return ret < 0 ? ret : 0;173}174 175static 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 else186 return RISO_KAGAKU_IX(r, g, b);187}188 189static 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(rgb->ldev, buf);198}199 200static 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(rgb->ldev, buf);213}214 215static 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 229static 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(led->rgb->ldev, buf);241}242 243static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br)244{245 return _thingm_write(cdev, br, 0);246}247 248static int thingm_write(struct led_classdev *cdev, enum led_brightness br)249{250 return _thingm_write(cdev, br, 1);251}252 253static 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 263static 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 279static 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 else286 return DELCOM_BLUE_LED;287}288 289static 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(led->rgb->ldev, dp.data);297}298 299static 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(led->rgb->ldev, dp.data);307}308 309static 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 LED316 * We can't do this in the init function already because the device317 * 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 326static 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, dp.data);332 if (ret)333 return ret;334 /*335 * Several Delcom devices share the same USB VID/PID336 * Check for family id 2 for Visual Signal Indicator337 */338 return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV;339}340 341static 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(led->rgb->ldev, buf);352}353 354static 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 410static 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(led->name, sizeof(led->name), "%s%u:%s:led%u",417 config->short_name, minor, color_name, rgb->num);418 else419 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;425 led->rgb = rgb;426 427 return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev);428}429 430static 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(&rgb->red, "red", rgb, minor);436 if (ret)437 return ret;438 439 /* Register the green diode */440 ret = hidled_init_led(&rgb->green, "green", rgb, minor);441 if (ret)442 return ret;443 444 /* Register the blue diode */445 return hidled_init_led(&rgb->blue, "blue", rgb, minor);446}447 448static 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(&hdev->dev, sizeof(*ldev), GFP_KERNEL);455 if (!ldev)456 return -ENOMEM;457 458 ldev->buf = devm_kmalloc(&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(&hdev->dev, ldev->config->num_leds,483 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(&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 508static 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};523MODULE_DEVICE_TABLE(hid, hidled_table);524 525static struct hid_driver hidled_driver = {526 .name = "hid-led",527 .probe = hidled_probe,528 .id_table = hidled_table,529};530 531module_hid_driver(hidled_driver);532 533MODULE_LICENSE("GPL");534MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>");535MODULE_DESCRIPTION("Simple USB RGB LED driver");536