Compare commits
11 commits
main
...
main-lnp-l
Author | SHA1 | Date | |
---|---|---|---|
|
afa51c37b3 | ||
f589e28d3e | |||
ca62408dc5 | |||
|
cc1deb72bf | ||
e29b9e2723 | |||
|
0d285558c3 | ||
5095cabcad | |||
a124cb33a8 | |||
c780a8e857 | |||
b2e9dfa0c3 | |||
98c5360b2b |
2 changed files with 483 additions and 14 deletions
|
@ -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
|
|
@ -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");
|
Loading…
Add table
Reference in a new issue