diff --git a/10_lightning_node_pro_led/README.md b/10_lightning_node_pro_led/README.md index dca7723..1ba3d39 100644 --- a/10_lightning_node_pro_led/README.md +++ b/10_lightning_node_pro_led/README.md @@ -16,4 +16,8 @@ If is attached to driver, it appear here. ## Usefull links -- https://github.com/torvalds/linux/blob/master/drivers/hid/hid-led.c \ No newline at end of file +- https://github.com/torvalds/linux/blob/master/drivers/hid/hid-led.c +- https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c +- https://gitlab.com/CalcProgrammer1/OpenRGB/-/blob/master/Controllers/CorsairLightingNodeController/CorsairLightingNodeController.cpp +- https://github.com/benburkhart1/lighting-node-pro +- https://github.com/Legion2/CorsairLightingProtocol \ No newline at end of file diff --git a/10_lightning_node_pro_led/lightning_node_pro_led.c b/10_lightning_node_pro_led/lightning_node_pro_led.c index 2f9ba8f..cc38eee 100644 --- a/10_lightning_node_pro_led/lightning_node_pro_led.c +++ b/10_lightning_node_pro_led/lightning_node_pro_led.c @@ -1,40 +1,505 @@ #include #include #include +#include #define USB_VENDOR_ID_CORSAIR 0x1b1c #define USB_DEVICE_ID_LIGHTNING_NODE_PRO 0x0c0b -static int lightning_node_pro_led_probe(struct hid_device *hdev, - const struct hid_device_id *id) +#define NUMBER_OF_LEDS_PER_LL120_FAN 16 +#define LIGHTNODE_PRO_MAX_FAN 6 + +#define MSG_SIZE 64 + +static short int number_of_fan = 1; +module_param(number_of_fan, short, 0000); +MODULE_PARM_DESC(number_of_fan, + "Number of LL120 FAN connected to the lightning node pro"); + +enum LNP_LED_TYPE { + LNP_LED_LL120, +}; + +enum { + LNP_PACKET_ID_FIRMWARE = + 0x02, /* Get firmware version */ + LNP_PACKET_ID_DIRECT = 0x32, /* Direct mode LED update packet */ + LNP_PACKET_ID_COMMIT = 0x33, /* Commit changes packet */ + LNP_PACKET_ID_BEGIN = 0x34, /* Begin effect packet */ + LNP_PACKET_ID_EFFECT_CONFIG = + 0x35, /* Effect mode configuration packet */ + LNP_PACKET_ID_TEMPERATURE = + 0x36, /* Update temperature value packet */ + LNP_PACKET_ID_RESET = 0x37, /* Reset channel packet */ + LNP_PACKET_ID_PORT_STATE = + 0x38, /* Set port state packet */ + LNP_PACKET_ID_BRIGHTNESS = + 0x39, /* Set brightness packet */ + LNP_PACKET_ID_LED_COUNT = + 0x3A, /* Set LED count packet */ + LNP_PACKET_ID_PROTOCOL = + 0x3B, /* Set protocol packet */ +}; + +enum { + LNP_DIRECT_CHANNEL_RED = + 0x00, /* Red channel for direct update */ + LNP_DIRECT_CHANNEL_GREEN = + 0x01, /* Green channel for direct update */ + LNP_DIRECT_CHANNEL_BLUE = + 0x02, /* Blue channel for direct update */ +}; + +enum { + LNP_PORT_STATE_HARDWARE = + 0x01, /* Effect hardware control of channel */ + LNP_PORT_STATE_SOFTWARE = + 0x02, /* Direct software control of channel */ +}; + +enum { + LNP_LED_TYPE_LED_STRIP = + 0x0A, /* Corsair LED Strip Type */ + LNP_LED_TYPE_HD_FAN = 0x0C, /* Corsair HD-series Fan Type */ + LNP_LED_TYPE_SP_FAN = 0x01, /* Corsair SP-series Fan Type */ + LNP_LED_TYPE_ML_FAN = 0x02, /* Corsair ML-series Fan Type */ +}; + +enum { + LNP_CHANNEL_1 = 0x00, /* Channel 1 */ + LNP_CHANNEL_2 = 0x01, /* Channel 2 */ + LNP_NUM_CHANNELS = 0x02, /* Number of channels */ +}; + +enum { + LNP_SPEED_FAST = 0x00, /* Fast speed */ + LNP_SPEED_MEDIUM = 0x01, /* Medium speed */ + LNP_SPEED_SLOW = 0x02, /* Slow speed */ +}; + +enum { + LNP_MODE_RAINBOW_WAVE = 0x00, /* Rainbow Wave mode */ + LNP_MODE_COLOR_SHIFT = 0x01, /* Color Shift mode */ + LNP_MODE_COLOR_PULSE = 0x02, /* Color Pulse mode */ + LNP_MODE_COLOR_WAVE = 0x03, /* Color Wave mode */ + LNP_MODE_STATIC = 0x04, /* Static mode */ + LNP_MODE_TEMPERATURE = 0x05, /* Temperature mode */ + LNP_MODE_VISOR = 0x06, /* Visor mode */ + LNP_MODE_MARQUEE = 0x07, /* Marquee mode */ + LNP_MODE_BLINK = 0x08, /* Blink mode */ + LNP_MODE_SEQUENTIAL = 0x09, /* Sequential mode */ + LNP_MODE_RAINBOW = 0x0A, /* Rainbow mode */ +}; + +struct lnp_rgb_led { + struct led_classdev_mc cdev; + int led_index; + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +struct lnp_fan { + int fan_index; + struct lnp_rgb_led *rgb_leds_data; +}; + +struct lnp_device { + struct hid_device *hdev; + spinlock_t lock; + + const char *dev_name; + + enum LNP_LED_TYPE type; + int fans_count; + int rgb_leds_per_fan_count; + void *fans_data; + bool update_fans_leds; + + struct work_struct output_worker; + bool output_worker_initialized; +}; + +static int lnp_send_reports(struct hid_device *hdev, u8 **pkts, u8 pkts_count) { - pr_info("Détection USB pour Lightning Node Pro\n"); + int i, ret; + + for (i = 0; i < pkts_count; i++) { + u8 *pkt = *(pkts + i); + + ret = hid_hw_output_report(hdev, pkt, MSG_SIZE); + if (ret < 0) + return ret; + } return 0; } -static void lightning_node_pro_led_remove(struct hid_device *dev) +static int lnp_send_fans_leds_report(struct lnp_device *led_dev) { - pr_info("Retrait USB pour Lightning Node Pro\n"); + int ret; + + u8 *packets[7]; + u8 pktcolors[6][MSG_SIZE]; + + // INDEX 1 = r, 2 = g, 3 = b, 4 = s, 5 = t, 6 = u + // s, t, u are R, G, B extended for fans 3-6 + for (u8 i = 1; i <= 6; i++) { + // Prepare the packet in pktcolors[i] + u8 *pktcolor = pktcolors[i]; + + // Initialize the first 4 bytes + pktcolor[0] = 0x32; + pktcolor[1] = 0x00; + // For Fan [1-3]: 0x32, 0x00, 0x00, 0x32 + // For Fan [4-6]: 0x32, 0x00, 0x32, 0x2e + pktcolor[2] = (i > 2) ? 0x32 : 0x00; + pktcolor[3] = (i > 2) ? 0x2e : 0x32; + + // For red color the fifth Bytes must be equals to 0x00 + // For green color the fifth Bytes must be equals to 0x01 + // For blue color the fifth Bytes must be equals to 0x02 + if (i == 1 || i == 4) { + pktcolor[4] = LNP_DIRECT_CHANNEL_RED; + } + if (i == 2 || i == 5) { + pktcolor[4] = LNP_DIRECT_CHANNEL_GREEN; + } + if (i == 3 || i == 6) { + pktcolor[4] = LNP_DIRECT_CHANNEL_BLUE; + } + + packets[i - 1] = pktcolor; + } + + u8 pktend[MSG_SIZE] = { 0x33, 0xff }; + packets[6] = &pktend[0]; + + ret = lnp_send_reports(led_dev->hdev, &packets[0], + 7); // TODO: Check if 2d array usage is correct + if (ret < 0) + return ret; + + return 0; } -static struct hid_device_id lightning_node_pro_led_table[] = { +static int lnp_send_init_report(struct hid_device *hdev) +{ + int ret; + + u8 pkt1[MSG_SIZE] = { LNP_PACKET_ID_RESET }; + // 0x35 - Init + u8 pkt2[MSG_SIZE] = { 0x35, 0x00, 0x00, number_of_fan << 4, + 0x00, 0x01, 0x01 }; + u8 pkt3[MSG_SIZE] = { 0x3b, 0x00, 0x01 }; + u8 pkt4[MSG_SIZE] = { 0x38, 0x00, 0x02 }; + u8 pkt5[MSG_SIZE] = { 0x34 }; + u8 pkt6[MSG_SIZE] = { 0x37, 0x01 }; + u8 pkt7[MSG_SIZE] = { 0x34, 0x01 }; + u8 pkt8[MSG_SIZE] = { 0x38, 0x01, 0x01 }; + u8 pkt9[MSG_SIZE] = { 0x33, 0xff }; + + u8 *pkts[9] = { &pkt1[0], &pkt2[0], &pkt3[0], &pkt4[0], &pkt5[0], + &pkt6[0], &pkt7[0], &pkt8[0], &pkt9[0] }; + + ret = lnp_send_reports(hdev, &pkts[0], 9); + if (ret < 0) + return ret; + + return 0; +} + +static inline void lnp_schedule_work(struct lnp_device *lnp_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&lnp_dev->lock, flags); + if (lnp_dev->output_worker_initialized) + schedule_work(&lnp_dev->output_worker); + spin_unlock_irqrestore(&lnp_dev->lock, flags); +} + +static void lnp_set_led_colors(struct lnp_device *lnp_dev, + struct lnp_rgb_led *lnp_rgb_led, uint8_t red, + uint8_t green, uint8_t blue) +{ + unsigned long flags; + + spin_lock_irqsave(&lnp_dev->lock, flags); + lnp_dev->update_fans_leds = true; + lnp_rgb_led->red = red; + lnp_rgb_led->green = green; + lnp_rgb_led->blue = blue; + spin_unlock_irqrestore(&lnp_dev->lock, flags); +} + +static inline int lnp_extract_fan_and_led_index(struct led_classdev *cdev, + int *fan_index, int *led_index) +{ + int ret; + + char *substr = strstr(cdev->name, "fan-"); + if (!substr) + return -EINVAL; + + ret = sscanf(substr, "fan-%d-led-%d", fan_index, led_index); + if (ret != 2) + return -EINVAL; + + return 0; +} + +static int lnp_rgb_let_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct hid_device *hdev = to_hid_device(cdev->dev->parent); + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct lnp_device *lnp_dev = hid_get_drvdata(hdev); + + int ret, fan_index, led_index; + + ret = lnp_extract_fan_and_led_index(cdev, &fan_index, &led_index); + if (ret) { + hid_warn(hdev, "Failed to get fan index and led_index for %s", + lnp_dev->dev_name); + return ret; + } + + struct lnp_fan *lnp_fan = lnp_dev->fans_data + fan_index; + struct lnp_rgb_led *lnp_rgb_led = lnp_fan->rgb_leds_data + led_index; + uint8_t red, green, blue; + + led_mc_calc_color_components(mc_cdev, brightness); + + red = mc_cdev->subled_info[0].brightness; + green = mc_cdev->subled_info[1].brightness; + blue = mc_cdev->subled_info[2].brightness; + + lnp_set_led_colors(lnp_dev, lnp_rgb_led, red, green, blue); + + return 0; +} + +static inline int lnp_register_rgb_led(struct lnp_device *lnp_dev, + struct lnp_fan *fan_data, + struct lnp_rgb_led *rgb_led_data) +{ + struct hid_device *hdev = lnp_dev->hdev; + struct mc_subled *mc_leds_info; + struct led_classdev *led_cdev; + int ret; + + mc_leds_info = devm_kmalloc_array(&hdev->dev, 3, sizeof(*mc_leds_info), + GFP_KERNEL); + + if (!mc_leds_info) { + hid_err(lnp_dev->hdev, + "Failed to allocate multicolor leds info for LED %d on FAN %d\n", + rgb_led_data->led_index, fan_data->fan_index); + return -ENOMEM; + } + + mc_leds_info[0].color_index = LED_COLOR_ID_RED; + mc_leds_info[1].color_index = LED_COLOR_ID_GREEN; + mc_leds_info[2].color_index = LED_COLOR_ID_BLUE; + + rgb_led_data->cdev.subled_info = mc_leds_info; + rgb_led_data->cdev.num_colors = 3; + + led_cdev = &rgb_led_data->cdev.led_cdev; + led_cdev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "%s:rgb:fan-%d-led-%d", + lnp_dev->dev_name, fan_data->fan_index, + rgb_led_data->led_index); + + if (!led_cdev->name) { + hid_err(lnp_dev->hdev, + "Failed to allocate name for LED %d on FAN %d\n", + rgb_led_data->led_index, fan_data->fan_index); + return -ENOMEM; + } + + led_cdev->brightness = 0; + led_cdev->max_brightness = 255; + led_cdev->brightness_set_blocking = lnp_rgb_let_brightness_set; + + ret = devm_led_classdev_multicolor_register(&hdev->dev, + &rgb_led_data->cdev); + if (ret < 0) { + hid_err(hdev, + "Cannot register multicolor LED device for LED %d on FAN %d\n", + rgb_led_data->led_index, fan_data->fan_index); + return ret; + } + + return 0; +} + +static inline int lnp_register_fan(struct lnp_device *lnp_dev, + struct lnp_fan *fan_data) +{ + int ret, led_index; + + fan_data->rgb_leds_data = devm_kmalloc_array( + &lnp_dev->hdev->dev, lnp_dev->rgb_leds_per_fan_count, + sizeof(struct lnp_rgb_led), GFP_KERNEL | __GFP_ZERO); + + if (!fan_data->rgb_leds_data) { + hid_err(lnp_dev->hdev, + "Failed to allocate rgb leds data for FAN %d\n", + fan_data->fan_index); + return -ENOMEM; + } + + for (led_index = 0; led_index < lnp_dev->rgb_leds_per_fan_count; + led_index++) { + struct lnp_rgb_led *rgb_led_data = + fan_data->rgb_leds_data + led_index; + rgb_led_data->led_index = led_index; + + ret = lnp_register_rgb_led(lnp_dev, fan_data, rgb_led_data); + if (ret) + return ret; + } + + return 0; +} + +static inline int lnp_register_fans(struct lnp_device *lnp_dev) +{ + int ret, fan_index; + struct lnp_fan *fans_data = lnp_dev->fans_data; + + for (fan_index = 0; fan_index < lnp_dev->fans_count; fan_index++) { + struct lnp_fan *fan_data = fans_data + fan_index; + fan_data->fan_index = fan_index; + + ret = lnp_register_fan(lnp_dev, fan_data); + if (ret) + return ret; + } + + return 0; +} + +static void lnp_output_worker(struct work_struct *work) +{ + struct lnp_device *lnp_dev = + container_of(work, struct lnp_device, output_worker); + unsigned long flags; + + spin_lock_irqsave(&lnp_dev->lock, flags); + + spin_unlock_irqrestore(&lnp_dev->lock, flags); + + // hid_hw_output_report(lnp_dev->hdev, data, len); +} + +static inline int lnp_init(struct hid_device *hdev) +{ + int ret; + struct lnp_device *lnp_dev; + + lnp_dev = devm_kzalloc(&hdev->dev, sizeof(*lnp_dev), GFP_KERNEL); + if (!lnp_dev) { + hid_err(hdev, "Failed to allocate lnp device data\n"); + return -ENOMEM; + } + + lnp_dev->hdev = hdev; + lnp_dev->dev_name = dev_name(&hdev->dev); + lnp_dev->type = LNP_LED_LL120; // Only support LL120 for now + lnp_dev->fans_count = number_of_fan; + spin_lock_init(&lnp_dev->lock); + INIT_WORK(&lnp_dev->output_worker, lnp_output_worker); + lnp_dev->output_worker_initialized = true; + + if (lnp_dev->type == LNP_LED_LL120) { + lnp_dev->rgb_leds_per_fan_count = NUMBER_OF_LEDS_PER_LL120_FAN; + } else { + hid_err(lnp_dev->hdev, "Invalid fan type %d\n", lnp_dev->type); + return -ENOSYS; + } + + lnp_dev->fans_data = devm_kmalloc_array(&lnp_dev->hdev->dev, + lnp_dev->fans_count, + sizeof(struct lnp_fan), + GFP_KERNEL | __GFP_ZERO); + + if (!lnp_dev->fans_data) { + hid_err(hdev, "Failed to allocate fans data\n"); + return -ENOMEM; + } + + ret = lnp_register_fans(lnp_dev); + if (ret) { + hid_err(lnp_dev->hdev, "Failed to register device\n"); + return ret; + } + + hid_set_drvdata(hdev, lnp_dev); + + return 0; +} + +static int lnp_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + goto err_stop; + } + + ret = lnp_init(hdev); + if (ret) + goto err_close; + + return 0; + +err_close: + hid_hw_close(hdev); +err_stop: + hid_hw_stop(hdev); + return ret; +} + +static void lnp_remove(struct hid_device *hdev) +{ + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static struct hid_device_id lnp_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_LIGHTNING_NODE_PRO) }, {} /* Entrée de terminaison */ }; -MODULE_DEVICE_TABLE(hid, lightning_node_pro_led_table); +MODULE_DEVICE_TABLE(hid, lnp_table); -static struct hid_driver lightning_node_pro_led_driver = { +static struct hid_driver lnp_driver = { .name = "lightning-node-pro", - .id_table = lightning_node_pro_led_table, - .probe = lightning_node_pro_led_probe, - .remove = lightning_node_pro_led_remove, + .id_table = lnp_table, + .probe = lnp_probe, + .remove = lnp_remove, }; -module_hid_driver(lightning_node_pro_led_driver); +module_hid_driver(lnp_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Florian RICHER "); -MODULE_DESCRIPTION("Un module noyau pour utiliser le Lightning Node Pro LED"); +MODULE_DESCRIPTION("Un module noyau pour utiliser le Lightning Node Pro"); MODULE_VERSION("1.0"); \ No newline at end of file