hid-led.c (12389B)
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 18enum hidled_report_type { 19 RAW_REQUEST, 20 OUTPUT_REPORT 21}; 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 0 64#define DELCOM_RED_LED 1 65#define DELCOM_BLUE_LED 2 66 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 16 105 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 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(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 else 134 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 else 186 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 else 286 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 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 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/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 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 else 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; 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");