Compare commits

...
Sign in to create a new pull request.

11 commits

2 changed files with 483 additions and 14 deletions

View file

@ -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
- 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

View file

@ -1,40 +1,505 @@
#include <linux/init.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/led-class-multicolor.h>
#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 <florian.richer@protonmail.com>");
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");