123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- /*
- * Simple USB RGB LED driver
- *
- * Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com>
- * Based on drivers/hid/hid-thingm.c and
- * drivers/usb/misc/usbled.c
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, version 2.
- */
- #include <linux/hid.h>
- #include <linux/hidraw.h>
- #include <linux/leds.h>
- #include <linux/module.h>
- #include <linux/mutex.h>
- #include "hid-ids.h"
- enum hidled_report_type {
- RAW_REQUEST,
- OUTPUT_REPORT
- };
- enum hidled_type {
- RISO_KAGAKU,
- DREAM_CHEEKY,
- THINGM,
- DELCOM,
- LUXAFOR,
- };
- static unsigned const char riso_kagaku_tbl[] = {
- /* R+2G+4B -> riso kagaku color index */
- [0] = 0, /* black */
- [1] = 2, /* red */
- [2] = 1, /* green */
- [3] = 5, /* yellow */
- [4] = 3, /* blue */
- [5] = 6, /* magenta */
- [6] = 4, /* cyan */
- [7] = 7 /* white */
- };
- #define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)]
- union delcom_packet {
- __u8 data[8];
- struct {
- __u8 major_cmd;
- __u8 minor_cmd;
- __u8 data_lsb;
- __u8 data_msb;
- } tx;
- struct {
- __u8 cmd;
- } rx;
- struct {
- __le16 family_code;
- __le16 security_code;
- __u8 fw_version;
- } fw;
- };
- #define DELCOM_GREEN_LED 0
- #define DELCOM_RED_LED 1
- #define DELCOM_BLUE_LED 2
- struct hidled_device;
- struct hidled_rgb;
- struct hidled_config {
- enum hidled_type type;
- const char *name;
- const char *short_name;
- enum led_brightness max_brightness;
- int num_leds;
- size_t report_size;
- enum hidled_report_type report_type;
- int (*init)(struct hidled_device *ldev);
- int (*write)(struct led_classdev *cdev, enum led_brightness br);
- };
- struct hidled_led {
- struct led_classdev cdev;
- struct hidled_rgb *rgb;
- char name[32];
- };
- struct hidled_rgb {
- struct hidled_device *ldev;
- struct hidled_led red;
- struct hidled_led green;
- struct hidled_led blue;
- u8 num;
- };
- struct hidled_device {
- const struct hidled_config *config;
- struct hid_device *hdev;
- struct hidled_rgb *rgb;
- u8 *buf;
- struct mutex lock;
- };
- #define MAX_REPORT_SIZE 16
- #define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev)
- static bool riso_kagaku_switch_green_blue;
- module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR);
- MODULE_PARM_DESC(riso_kagaku_switch_green_blue,
- "switch green and blue RGB component for Riso Kagaku devices");
- static int hidled_send(struct hidled_device *ldev, __u8 *buf)
- {
- int ret;
- mutex_lock(&ldev->lock);
- /*
- * buffer provided to hid_hw_raw_request must not be on the stack
- * and must not be part of a data structure
- */
- memcpy(ldev->buf, buf, ldev->config->report_size);
- if (ldev->config->report_type == RAW_REQUEST)
- ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
- ldev->config->report_size,
- HID_FEATURE_REPORT,
- HID_REQ_SET_REPORT);
- else if (ldev->config->report_type == OUTPUT_REPORT)
- ret = hid_hw_output_report(ldev->hdev, ldev->buf,
- ldev->config->report_size);
- else
- ret = -EINVAL;
- mutex_unlock(&ldev->lock);
- if (ret < 0)
- return ret;
- return ret == ldev->config->report_size ? 0 : -EMSGSIZE;
- }
- /* reading data is supported for report type RAW_REQUEST only */
- static int hidled_recv(struct hidled_device *ldev, __u8 *buf)
- {
- int ret;
- if (ldev->config->report_type != RAW_REQUEST)
- return -EINVAL;
- mutex_lock(&ldev->lock);
- memcpy(ldev->buf, buf, ldev->config->report_size);
- ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
- ldev->config->report_size,
- HID_FEATURE_REPORT,
- HID_REQ_SET_REPORT);
- if (ret < 0)
- goto err;
- ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
- ldev->config->report_size,
- HID_FEATURE_REPORT,
- HID_REQ_GET_REPORT);
- memcpy(buf, ldev->buf, ldev->config->report_size);
- err:
- mutex_unlock(&ldev->lock);
- return ret < 0 ? ret : 0;
- }
- static u8 riso_kagaku_index(struct hidled_rgb *rgb)
- {
- enum led_brightness r, g, b;
- r = rgb->red.cdev.brightness;
- g = rgb->green.cdev.brightness;
- b = rgb->blue.cdev.brightness;
- if (riso_kagaku_switch_green_blue)
- return RISO_KAGAKU_IX(r, b, g);
- else
- return RISO_KAGAKU_IX(r, g, b);
- }
- static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br)
- {
- struct hidled_led *led = to_hidled_led(cdev);
- struct hidled_rgb *rgb = led->rgb;
- __u8 buf[MAX_REPORT_SIZE] = {};
- buf[1] = riso_kagaku_index(rgb);
- return hidled_send(rgb->ldev, buf);
- }
- static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br)
- {
- struct hidled_led *led = to_hidled_led(cdev);
- struct hidled_rgb *rgb = led->rgb;
- __u8 buf[MAX_REPORT_SIZE] = {};
- buf[1] = rgb->red.cdev.brightness;
- buf[2] = rgb->green.cdev.brightness;
- buf[3] = rgb->blue.cdev.brightness;
- buf[7] = 0x1a;
- buf[8] = 0x05;
- return hidled_send(rgb->ldev, buf);
- }
- static int dream_cheeky_init(struct hidled_device *ldev)
- {
- __u8 buf[MAX_REPORT_SIZE] = {};
- /* Dream Cheeky magic */
- buf[1] = 0x1f;
- buf[2] = 0x02;
- buf[4] = 0x5f;
- buf[7] = 0x1a;
- buf[8] = 0x03;
- return hidled_send(ldev, buf);
- }
- static int _thingm_write(struct led_classdev *cdev, enum led_brightness br,
- u8 offset)
- {
- struct hidled_led *led = to_hidled_led(cdev);
- __u8 buf[MAX_REPORT_SIZE] = { 1, 'c' };
- buf[2] = led->rgb->red.cdev.brightness;
- buf[3] = led->rgb->green.cdev.brightness;
- buf[4] = led->rgb->blue.cdev.brightness;
- buf[7] = led->rgb->num + offset;
- return hidled_send(led->rgb->ldev, buf);
- }
- static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br)
- {
- return _thingm_write(cdev, br, 0);
- }
- static int thingm_write(struct led_classdev *cdev, enum led_brightness br)
- {
- return _thingm_write(cdev, br, 1);
- }
- static const struct hidled_config hidled_config_thingm_v1 = {
- .name = "ThingM blink(1) v1",
- .short_name = "thingm",
- .max_brightness = 255,
- .num_leds = 1,
- .report_size = 9,
- .report_type = RAW_REQUEST,
- .write = thingm_write_v1,
- };
- static int thingm_init(struct hidled_device *ldev)
- {
- __u8 buf[MAX_REPORT_SIZE] = { 1, 'v' };
- int ret;
- ret = hidled_recv(ldev, buf);
- if (ret)
- return ret;
- /* Check for firmware major version 1 */
- if (buf[3] == '1')
- ldev->config = &hidled_config_thingm_v1;
- return 0;
- }
- static inline int delcom_get_lednum(const struct hidled_led *led)
- {
- if (led == &led->rgb->red)
- return DELCOM_RED_LED;
- else if (led == &led->rgb->green)
- return DELCOM_GREEN_LED;
- else
- return DELCOM_BLUE_LED;
- }
- static int delcom_enable_led(struct hidled_led *led)
- {
- union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 12 };
- dp.tx.data_lsb = 1 << delcom_get_lednum(led);
- dp.tx.data_msb = 0;
- return hidled_send(led->rgb->ldev, dp.data);
- }
- static int delcom_set_pwm(struct hidled_led *led)
- {
- union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 34 };
- dp.tx.data_lsb = delcom_get_lednum(led);
- dp.tx.data_msb = led->cdev.brightness;
- return hidled_send(led->rgb->ldev, dp.data);
- }
- static int delcom_write(struct led_classdev *cdev, enum led_brightness br)
- {
- struct hidled_led *led = to_hidled_led(cdev);
- int ret;
- /*
- * enable LED
- * We can't do this in the init function already because the device
- * is internally reset later.
- */
- ret = delcom_enable_led(led);
- if (ret)
- return ret;
- return delcom_set_pwm(led);
- }
- static int delcom_init(struct hidled_device *ldev)
- {
- union delcom_packet dp = { .rx.cmd = 104 };
- int ret;
- ret = hidled_recv(ldev, dp.data);
- if (ret)
- return ret;
- /*
- * Several Delcom devices share the same USB VID/PID
- * Check for family id 2 for Visual Signal Indicator
- */
- return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV;
- }
- static int luxafor_write(struct led_classdev *cdev, enum led_brightness br)
- {
- struct hidled_led *led = to_hidled_led(cdev);
- __u8 buf[MAX_REPORT_SIZE] = { [1] = 1 };
- buf[2] = led->rgb->num + 1;
- buf[3] = led->rgb->red.cdev.brightness;
- buf[4] = led->rgb->green.cdev.brightness;
- buf[5] = led->rgb->blue.cdev.brightness;
- return hidled_send(led->rgb->ldev, buf);
- }
- static const struct hidled_config hidled_configs[] = {
- {
- .type = RISO_KAGAKU,
- .name = "Riso Kagaku Webmail Notifier",
- .short_name = "riso_kagaku",
- .max_brightness = 1,
- .num_leds = 1,
- .report_size = 6,
- .report_type = OUTPUT_REPORT,
- .write = riso_kagaku_write,
- },
- {
- .type = DREAM_CHEEKY,
- .name = "Dream Cheeky Webmail Notifier",
- .short_name = "dream_cheeky",
- .max_brightness = 31,
- .num_leds = 1,
- .report_size = 9,
- .report_type = RAW_REQUEST,
- .init = dream_cheeky_init,
- .write = dream_cheeky_write,
- },
- {
- .type = THINGM,
- .name = "ThingM blink(1)",
- .short_name = "thingm",
- .max_brightness = 255,
- .num_leds = 2,
- .report_size = 9,
- .report_type = RAW_REQUEST,
- .init = thingm_init,
- .write = thingm_write,
- },
- {
- .type = DELCOM,
- .name = "Delcom Visual Signal Indicator G2",
- .short_name = "delcom",
- .max_brightness = 100,
- .num_leds = 1,
- .report_size = 8,
- .report_type = RAW_REQUEST,
- .init = delcom_init,
- .write = delcom_write,
- },
- {
- .type = LUXAFOR,
- .name = "Greynut Luxafor",
- .short_name = "luxafor",
- .max_brightness = 255,
- .num_leds = 6,
- .report_size = 9,
- .report_type = OUTPUT_REPORT,
- .write = luxafor_write,
- },
- };
- static int hidled_init_led(struct hidled_led *led, const char *color_name,
- struct hidled_rgb *rgb, unsigned int minor)
- {
- const struct hidled_config *config = rgb->ldev->config;
- if (config->num_leds > 1)
- snprintf(led->name, sizeof(led->name), "%s%u:%s:led%u",
- config->short_name, minor, color_name, rgb->num);
- else
- snprintf(led->name, sizeof(led->name), "%s%u:%s",
- config->short_name, minor, color_name);
- led->cdev.name = led->name;
- led->cdev.max_brightness = config->max_brightness;
- led->cdev.brightness_set_blocking = config->write;
- led->cdev.flags = LED_HW_PLUGGABLE;
- led->rgb = rgb;
- return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev);
- }
- static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor)
- {
- int ret;
- /* Register the red diode */
- ret = hidled_init_led(&rgb->red, "red", rgb, minor);
- if (ret)
- return ret;
- /* Register the green diode */
- ret = hidled_init_led(&rgb->green, "green", rgb, minor);
- if (ret)
- return ret;
- /* Register the blue diode */
- return hidled_init_led(&rgb->blue, "blue", rgb, minor);
- }
- static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id)
- {
- struct hidled_device *ldev;
- unsigned int minor;
- int ret, i;
- ldev = devm_kzalloc(&hdev->dev, sizeof(*ldev), GFP_KERNEL);
- if (!ldev)
- return -ENOMEM;
- ldev->buf = devm_kmalloc(&hdev->dev, MAX_REPORT_SIZE, GFP_KERNEL);
- if (!ldev->buf)
- return -ENOMEM;
- ret = hid_parse(hdev);
- if (ret)
- return ret;
- ldev->hdev = hdev;
- mutex_init(&ldev->lock);
- for (i = 0; !ldev->config && i < ARRAY_SIZE(hidled_configs); i++)
- if (hidled_configs[i].type == id->driver_data)
- ldev->config = &hidled_configs[i];
- if (!ldev->config)
- return -EINVAL;
- if (ldev->config->init) {
- ret = ldev->config->init(ldev);
- if (ret)
- return ret;
- }
- ldev->rgb = devm_kcalloc(&hdev->dev, ldev->config->num_leds,
- sizeof(struct hidled_rgb), GFP_KERNEL);
- if (!ldev->rgb)
- return -ENOMEM;
- ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
- if (ret)
- return ret;
- minor = ((struct hidraw *) hdev->hidraw)->minor;
- for (i = 0; i < ldev->config->num_leds; i++) {
- ldev->rgb[i].ldev = ldev;
- ldev->rgb[i].num = i;
- ret = hidled_init_rgb(&ldev->rgb[i], minor);
- if (ret) {
- hid_hw_stop(hdev);
- return ret;
- }
- }
- hid_info(hdev, "%s initialized\n", ldev->config->name);
- return 0;
- }
- static const struct hid_device_id hidled_table[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU,
- USB_DEVICE_ID_RI_KA_WEBMAIL), .driver_data = RISO_KAGAKU },
- { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
- USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY },
- { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
- USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY },
- { HID_USB_DEVICE(USB_VENDOR_ID_THINGM,
- USB_DEVICE_ID_BLINK1), .driver_data = THINGM },
- { HID_USB_DEVICE(USB_VENDOR_ID_DELCOM,
- USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM },
- { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP,
- USB_DEVICE_ID_LUXAFOR), .driver_data = LUXAFOR },
- { }
- };
- MODULE_DEVICE_TABLE(hid, hidled_table);
- static struct hid_driver hidled_driver = {
- .name = "hid-led",
- .probe = hidled_probe,
- .id_table = hidled_table,
- };
- module_hid_driver(hidled_driver);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>");
- MODULE_DESCRIPTION("Simple USB RGB LED driver");
|