16f78193eSClément Vuchener /* 26f78193eSClément Vuchener * HID driver for Corsair devices 36f78193eSClément Vuchener * 46f78193eSClément Vuchener * Supported devices: 56f78193eSClément Vuchener * - Vengeance K90 Keyboard 66f78193eSClément Vuchener * 76f78193eSClément Vuchener * Copyright (c) 2015 Clement Vuchener 86f78193eSClément Vuchener */ 96f78193eSClément Vuchener 106f78193eSClément Vuchener /* 116f78193eSClément Vuchener * This program is free software; you can redistribute it and/or modify it 126f78193eSClément Vuchener * under the terms of the GNU General Public License as published by the Free 136f78193eSClément Vuchener * Software Foundation; either version 2 of the License, or (at your option) 146f78193eSClément Vuchener * any later version. 156f78193eSClément Vuchener */ 166f78193eSClément Vuchener 176f78193eSClément Vuchener #include <linux/hid.h> 186f78193eSClément Vuchener #include <linux/module.h> 196f78193eSClément Vuchener #include <linux/usb.h> 206f78193eSClément Vuchener #include <linux/leds.h> 216f78193eSClément Vuchener 226f78193eSClément Vuchener #include "hid-ids.h" 236f78193eSClément Vuchener 246f78193eSClément Vuchener #define CORSAIR_USE_K90_MACRO (1<<0) 256f78193eSClément Vuchener #define CORSAIR_USE_K90_BACKLIGHT (1<<1) 266f78193eSClément Vuchener 276f78193eSClément Vuchener struct k90_led { 286f78193eSClément Vuchener struct led_classdev cdev; 296f78193eSClément Vuchener int brightness; 306f78193eSClément Vuchener struct work_struct work; 31937804f3SJiri Kosina bool removed; 326f78193eSClément Vuchener }; 336f78193eSClément Vuchener 346f78193eSClément Vuchener struct k90_drvdata { 356f78193eSClément Vuchener struct k90_led record_led; 366f78193eSClément Vuchener }; 376f78193eSClément Vuchener 386f78193eSClément Vuchener struct corsair_drvdata { 396f78193eSClément Vuchener unsigned long quirks; 406f78193eSClément Vuchener struct k90_drvdata *k90; 416f78193eSClément Vuchener struct k90_led *backlight; 426f78193eSClément Vuchener }; 436f78193eSClément Vuchener 446f78193eSClément Vuchener #define K90_GKEY_COUNT 18 456f78193eSClément Vuchener 466f78193eSClément Vuchener static int corsair_usage_to_gkey(unsigned int usage) 476f78193eSClément Vuchener { 486f78193eSClément Vuchener /* G1 (0xd0) to G16 (0xdf) */ 496f78193eSClément Vuchener if (usage >= 0xd0 && usage <= 0xdf) 506f78193eSClément Vuchener return usage - 0xd0 + 1; 516f78193eSClément Vuchener /* G17 (0xe8) to G18 (0xe9) */ 526f78193eSClément Vuchener if (usage >= 0xe8 && usage <= 0xe9) 536f78193eSClément Vuchener return usage - 0xe8 + 17; 546f78193eSClément Vuchener return 0; 556f78193eSClément Vuchener } 566f78193eSClément Vuchener 576f78193eSClément Vuchener static unsigned short corsair_gkey_map[K90_GKEY_COUNT] = { 586f78193eSClément Vuchener BTN_TRIGGER_HAPPY1, 596f78193eSClément Vuchener BTN_TRIGGER_HAPPY2, 606f78193eSClément Vuchener BTN_TRIGGER_HAPPY3, 616f78193eSClément Vuchener BTN_TRIGGER_HAPPY4, 626f78193eSClément Vuchener BTN_TRIGGER_HAPPY5, 636f78193eSClément Vuchener BTN_TRIGGER_HAPPY6, 646f78193eSClément Vuchener BTN_TRIGGER_HAPPY7, 656f78193eSClément Vuchener BTN_TRIGGER_HAPPY8, 666f78193eSClément Vuchener BTN_TRIGGER_HAPPY9, 676f78193eSClément Vuchener BTN_TRIGGER_HAPPY10, 686f78193eSClément Vuchener BTN_TRIGGER_HAPPY11, 696f78193eSClément Vuchener BTN_TRIGGER_HAPPY12, 706f78193eSClément Vuchener BTN_TRIGGER_HAPPY13, 716f78193eSClément Vuchener BTN_TRIGGER_HAPPY14, 726f78193eSClément Vuchener BTN_TRIGGER_HAPPY15, 736f78193eSClément Vuchener BTN_TRIGGER_HAPPY16, 746f78193eSClément Vuchener BTN_TRIGGER_HAPPY17, 756f78193eSClément Vuchener BTN_TRIGGER_HAPPY18, 766f78193eSClément Vuchener }; 776f78193eSClément Vuchener 786f78193eSClément Vuchener module_param_array_named(gkey_codes, corsair_gkey_map, ushort, NULL, S_IRUGO); 796f78193eSClément Vuchener MODULE_PARM_DESC(gkey_codes, "Key codes for the G-keys"); 806f78193eSClément Vuchener 816f78193eSClément Vuchener static unsigned short corsair_record_keycodes[2] = { 826f78193eSClément Vuchener BTN_TRIGGER_HAPPY19, 836f78193eSClément Vuchener BTN_TRIGGER_HAPPY20 846f78193eSClément Vuchener }; 856f78193eSClément Vuchener 866f78193eSClément Vuchener module_param_array_named(recordkey_codes, corsair_record_keycodes, ushort, 876f78193eSClément Vuchener NULL, S_IRUGO); 886f78193eSClément Vuchener MODULE_PARM_DESC(recordkey_codes, "Key codes for the MR (start and stop record) button"); 896f78193eSClément Vuchener 906f78193eSClément Vuchener static unsigned short corsair_profile_keycodes[3] = { 916f78193eSClément Vuchener BTN_TRIGGER_HAPPY21, 926f78193eSClément Vuchener BTN_TRIGGER_HAPPY22, 936f78193eSClément Vuchener BTN_TRIGGER_HAPPY23 946f78193eSClément Vuchener }; 956f78193eSClément Vuchener 966f78193eSClément Vuchener module_param_array_named(profilekey_codes, corsair_profile_keycodes, ushort, 976f78193eSClément Vuchener NULL, S_IRUGO); 986f78193eSClément Vuchener MODULE_PARM_DESC(profilekey_codes, "Key codes for the profile buttons"); 996f78193eSClément Vuchener 1006f78193eSClément Vuchener #define CORSAIR_USAGE_SPECIAL_MIN 0xf0 1016f78193eSClément Vuchener #define CORSAIR_USAGE_SPECIAL_MAX 0xff 1026f78193eSClément Vuchener 1036f78193eSClément Vuchener #define CORSAIR_USAGE_MACRO_RECORD_START 0xf6 1046f78193eSClément Vuchener #define CORSAIR_USAGE_MACRO_RECORD_STOP 0xf7 1056f78193eSClément Vuchener 1066f78193eSClément Vuchener #define CORSAIR_USAGE_PROFILE 0xf1 1076f78193eSClément Vuchener #define CORSAIR_USAGE_M1 0xf1 1086f78193eSClément Vuchener #define CORSAIR_USAGE_M2 0xf2 1096f78193eSClément Vuchener #define CORSAIR_USAGE_M3 0xf3 1106f78193eSClément Vuchener #define CORSAIR_USAGE_PROFILE_MAX 0xf3 1116f78193eSClément Vuchener 1126f78193eSClément Vuchener #define CORSAIR_USAGE_META_OFF 0xf4 1136f78193eSClément Vuchener #define CORSAIR_USAGE_META_ON 0xf5 1146f78193eSClément Vuchener 1156f78193eSClément Vuchener #define CORSAIR_USAGE_LIGHT 0xfa 1166f78193eSClément Vuchener #define CORSAIR_USAGE_LIGHT_OFF 0xfa 1176f78193eSClément Vuchener #define CORSAIR_USAGE_LIGHT_DIM 0xfb 1186f78193eSClément Vuchener #define CORSAIR_USAGE_LIGHT_MEDIUM 0xfc 1196f78193eSClément Vuchener #define CORSAIR_USAGE_LIGHT_BRIGHT 0xfd 1206f78193eSClément Vuchener #define CORSAIR_USAGE_LIGHT_MAX 0xfd 1216f78193eSClément Vuchener 1226f78193eSClément Vuchener /* USB control protocol */ 1236f78193eSClément Vuchener 1246f78193eSClément Vuchener #define K90_REQUEST_BRIGHTNESS 49 1256f78193eSClément Vuchener #define K90_REQUEST_MACRO_MODE 2 1266f78193eSClément Vuchener #define K90_REQUEST_STATUS 4 1276f78193eSClément Vuchener #define K90_REQUEST_GET_MODE 5 1286f78193eSClément Vuchener #define K90_REQUEST_PROFILE 20 1296f78193eSClément Vuchener 1306f78193eSClément Vuchener #define K90_MACRO_MODE_SW 0x0030 1316f78193eSClément Vuchener #define K90_MACRO_MODE_HW 0x0001 1326f78193eSClément Vuchener 1336f78193eSClément Vuchener #define K90_MACRO_LED_ON 0x0020 1346f78193eSClément Vuchener #define K90_MACRO_LED_OFF 0x0040 1356f78193eSClément Vuchener 1366f78193eSClément Vuchener /* 1376f78193eSClément Vuchener * LED class devices 1386f78193eSClément Vuchener */ 1396f78193eSClément Vuchener 1406f78193eSClément Vuchener #define K90_BACKLIGHT_LED_SUFFIX "::backlight" 1416f78193eSClément Vuchener #define K90_RECORD_LED_SUFFIX "::record" 1426f78193eSClément Vuchener 1436f78193eSClément Vuchener static enum led_brightness k90_backlight_get(struct led_classdev *led_cdev) 1446f78193eSClément Vuchener { 1456f78193eSClément Vuchener int ret; 1466f78193eSClément Vuchener struct k90_led *led = container_of(led_cdev, struct k90_led, cdev); 1476f78193eSClément Vuchener struct device *dev = led->cdev.dev->parent; 1486f78193eSClément Vuchener struct usb_interface *usbif = to_usb_interface(dev->parent); 1496f78193eSClément Vuchener struct usb_device *usbdev = interface_to_usbdev(usbif); 1506f78193eSClément Vuchener int brightness; 1516d104af3SJohan Hovold char *data; 1526d104af3SJohan Hovold 1536d104af3SJohan Hovold data = kmalloc(8, GFP_KERNEL); 1546d104af3SJohan Hovold if (!data) 1556d104af3SJohan Hovold return -ENOMEM; 1566f78193eSClément Vuchener 1576f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 1586f78193eSClément Vuchener K90_REQUEST_STATUS, 1596f78193eSClément Vuchener USB_DIR_IN | USB_TYPE_VENDOR | 1606f78193eSClément Vuchener USB_RECIP_DEVICE, 0, 0, data, 8, 1616f78193eSClément Vuchener USB_CTRL_SET_TIMEOUT); 1626f78193eSClément Vuchener if (ret < 0) { 1636f78193eSClément Vuchener dev_warn(dev, "Failed to get K90 initial state (error %d).\n", 1646f78193eSClément Vuchener ret); 1656d104af3SJohan Hovold ret = -EIO; 1666d104af3SJohan Hovold goto out; 1676f78193eSClément Vuchener } 1686f78193eSClément Vuchener brightness = data[4]; 1696f78193eSClément Vuchener if (brightness < 0 || brightness > 3) { 1706f78193eSClément Vuchener dev_warn(dev, 1716f78193eSClément Vuchener "Read invalid backlight brightness: %02hhx.\n", 1726f78193eSClément Vuchener data[4]); 1736d104af3SJohan Hovold ret = -EIO; 1746d104af3SJohan Hovold goto out; 1756f78193eSClément Vuchener } 1766d104af3SJohan Hovold ret = brightness; 1776d104af3SJohan Hovold out: 1786d104af3SJohan Hovold kfree(data); 1796d104af3SJohan Hovold 1806d104af3SJohan Hovold return ret; 1816f78193eSClément Vuchener } 1826f78193eSClément Vuchener 1836f78193eSClément Vuchener static enum led_brightness k90_record_led_get(struct led_classdev *led_cdev) 1846f78193eSClément Vuchener { 1856f78193eSClément Vuchener struct k90_led *led = container_of(led_cdev, struct k90_led, cdev); 1866f78193eSClément Vuchener 1876f78193eSClément Vuchener return led->brightness; 1886f78193eSClément Vuchener } 1896f78193eSClément Vuchener 1906f78193eSClément Vuchener static void k90_brightness_set(struct led_classdev *led_cdev, 1916f78193eSClément Vuchener enum led_brightness brightness) 1926f78193eSClément Vuchener { 1936f78193eSClément Vuchener struct k90_led *led = container_of(led_cdev, struct k90_led, cdev); 1946f78193eSClément Vuchener 1956f78193eSClément Vuchener led->brightness = brightness; 1966f78193eSClément Vuchener schedule_work(&led->work); 1976f78193eSClément Vuchener } 1986f78193eSClément Vuchener 1996f78193eSClément Vuchener static void k90_backlight_work(struct work_struct *work) 2006f78193eSClément Vuchener { 2016f78193eSClément Vuchener int ret; 2026f78193eSClément Vuchener struct k90_led *led = container_of(work, struct k90_led, work); 2036f78193eSClément Vuchener struct device *dev; 2046f78193eSClément Vuchener struct usb_interface *usbif; 2056f78193eSClément Vuchener struct usb_device *usbdev; 2066f78193eSClément Vuchener 2076f78193eSClément Vuchener if (led->removed) 2086f78193eSClément Vuchener return; 2096f78193eSClément Vuchener 2106f78193eSClément Vuchener dev = led->cdev.dev->parent; 2116f78193eSClément Vuchener usbif = to_usb_interface(dev->parent); 2126f78193eSClément Vuchener usbdev = interface_to_usbdev(usbif); 2136f78193eSClément Vuchener 2146f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 2156f78193eSClément Vuchener K90_REQUEST_BRIGHTNESS, 2166f78193eSClément Vuchener USB_DIR_OUT | USB_TYPE_VENDOR | 2176f78193eSClément Vuchener USB_RECIP_DEVICE, led->brightness, 0, 2186f78193eSClément Vuchener NULL, 0, USB_CTRL_SET_TIMEOUT); 2196f78193eSClément Vuchener if (ret != 0) 2206f78193eSClément Vuchener dev_warn(dev, "Failed to set backlight brightness (error: %d).\n", 2216f78193eSClément Vuchener ret); 2226f78193eSClément Vuchener } 2236f78193eSClément Vuchener 2246f78193eSClément Vuchener static void k90_record_led_work(struct work_struct *work) 2256f78193eSClément Vuchener { 2266f78193eSClément Vuchener int ret; 2276f78193eSClément Vuchener struct k90_led *led = container_of(work, struct k90_led, work); 2286f78193eSClément Vuchener struct device *dev; 2296f78193eSClément Vuchener struct usb_interface *usbif; 2306f78193eSClément Vuchener struct usb_device *usbdev; 2316f78193eSClément Vuchener int value; 2326f78193eSClément Vuchener 2336f78193eSClément Vuchener if (led->removed) 2346f78193eSClément Vuchener return; 2356f78193eSClément Vuchener 2366f78193eSClément Vuchener dev = led->cdev.dev->parent; 2376f78193eSClément Vuchener usbif = to_usb_interface(dev->parent); 2386f78193eSClément Vuchener usbdev = interface_to_usbdev(usbif); 2396f78193eSClément Vuchener 2406f78193eSClément Vuchener if (led->brightness > 0) 2416f78193eSClément Vuchener value = K90_MACRO_LED_ON; 2426f78193eSClément Vuchener else 2436f78193eSClément Vuchener value = K90_MACRO_LED_OFF; 2446f78193eSClément Vuchener 2456f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 2466f78193eSClément Vuchener K90_REQUEST_MACRO_MODE, 2476f78193eSClément Vuchener USB_DIR_OUT | USB_TYPE_VENDOR | 2486f78193eSClément Vuchener USB_RECIP_DEVICE, value, 0, NULL, 0, 2496f78193eSClément Vuchener USB_CTRL_SET_TIMEOUT); 2506f78193eSClément Vuchener if (ret != 0) 2516f78193eSClément Vuchener dev_warn(dev, "Failed to set record LED state (error: %d).\n", 2526f78193eSClément Vuchener ret); 2536f78193eSClément Vuchener } 2546f78193eSClément Vuchener 2556f78193eSClément Vuchener /* 2566f78193eSClément Vuchener * Keyboard attributes 2576f78193eSClément Vuchener */ 2586f78193eSClément Vuchener 2596f78193eSClément Vuchener static ssize_t k90_show_macro_mode(struct device *dev, 2606f78193eSClément Vuchener struct device_attribute *attr, char *buf) 2616f78193eSClément Vuchener { 2626f78193eSClément Vuchener int ret; 2636f78193eSClément Vuchener struct usb_interface *usbif = to_usb_interface(dev->parent); 2646f78193eSClément Vuchener struct usb_device *usbdev = interface_to_usbdev(usbif); 2656f78193eSClément Vuchener const char *macro_mode; 2666d104af3SJohan Hovold char *data; 2676d104af3SJohan Hovold 2686d104af3SJohan Hovold data = kmalloc(2, GFP_KERNEL); 2696d104af3SJohan Hovold if (!data) 2706d104af3SJohan Hovold return -ENOMEM; 2716f78193eSClément Vuchener 2726f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 2736f78193eSClément Vuchener K90_REQUEST_GET_MODE, 2746f78193eSClément Vuchener USB_DIR_IN | USB_TYPE_VENDOR | 2756f78193eSClément Vuchener USB_RECIP_DEVICE, 0, 0, data, 2, 2766f78193eSClément Vuchener USB_CTRL_SET_TIMEOUT); 2776f78193eSClément Vuchener if (ret < 0) { 2786f78193eSClément Vuchener dev_warn(dev, "Failed to get K90 initial mode (error %d).\n", 2796f78193eSClément Vuchener ret); 2806d104af3SJohan Hovold ret = -EIO; 2816d104af3SJohan Hovold goto out; 2826f78193eSClément Vuchener } 2836f78193eSClément Vuchener 2846f78193eSClément Vuchener switch (data[0]) { 2856f78193eSClément Vuchener case K90_MACRO_MODE_HW: 2866f78193eSClément Vuchener macro_mode = "HW"; 2876f78193eSClément Vuchener break; 2886f78193eSClément Vuchener 2896f78193eSClément Vuchener case K90_MACRO_MODE_SW: 2906f78193eSClément Vuchener macro_mode = "SW"; 2916f78193eSClément Vuchener break; 2926f78193eSClément Vuchener default: 2936f78193eSClément Vuchener dev_warn(dev, "K90 in unknown mode: %02hhx.\n", 2946f78193eSClément Vuchener data[0]); 2956d104af3SJohan Hovold ret = -EIO; 2966d104af3SJohan Hovold goto out; 2976f78193eSClément Vuchener } 2986f78193eSClément Vuchener 2996d104af3SJohan Hovold ret = snprintf(buf, PAGE_SIZE, "%s\n", macro_mode); 3006d104af3SJohan Hovold out: 3016d104af3SJohan Hovold kfree(data); 3026d104af3SJohan Hovold 3036d104af3SJohan Hovold return ret; 3046f78193eSClément Vuchener } 3056f78193eSClément Vuchener 3066f78193eSClément Vuchener static ssize_t k90_store_macro_mode(struct device *dev, 3076f78193eSClément Vuchener struct device_attribute *attr, 3086f78193eSClément Vuchener const char *buf, size_t count) 3096f78193eSClément Vuchener { 3106f78193eSClément Vuchener int ret; 3116f78193eSClément Vuchener struct usb_interface *usbif = to_usb_interface(dev->parent); 3126f78193eSClément Vuchener struct usb_device *usbdev = interface_to_usbdev(usbif); 3136f78193eSClément Vuchener __u16 value; 3146f78193eSClément Vuchener 3156f78193eSClément Vuchener if (strncmp(buf, "SW", 2) == 0) 3166f78193eSClément Vuchener value = K90_MACRO_MODE_SW; 3176f78193eSClément Vuchener else if (strncmp(buf, "HW", 2) == 0) 3186f78193eSClément Vuchener value = K90_MACRO_MODE_HW; 3196f78193eSClément Vuchener else 3206f78193eSClément Vuchener return -EINVAL; 3216f78193eSClément Vuchener 3226f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 3236f78193eSClément Vuchener K90_REQUEST_MACRO_MODE, 3246f78193eSClément Vuchener USB_DIR_OUT | USB_TYPE_VENDOR | 3256f78193eSClément Vuchener USB_RECIP_DEVICE, value, 0, NULL, 0, 3266f78193eSClément Vuchener USB_CTRL_SET_TIMEOUT); 3276f78193eSClément Vuchener if (ret != 0) { 3286f78193eSClément Vuchener dev_warn(dev, "Failed to set macro mode.\n"); 3296f78193eSClément Vuchener return ret; 3306f78193eSClément Vuchener } 3316f78193eSClément Vuchener 3326f78193eSClément Vuchener return count; 3336f78193eSClément Vuchener } 3346f78193eSClément Vuchener 3356f78193eSClément Vuchener static ssize_t k90_show_current_profile(struct device *dev, 3366f78193eSClément Vuchener struct device_attribute *attr, 3376f78193eSClément Vuchener char *buf) 3386f78193eSClément Vuchener { 3396f78193eSClément Vuchener int ret; 3406f78193eSClément Vuchener struct usb_interface *usbif = to_usb_interface(dev->parent); 3416f78193eSClément Vuchener struct usb_device *usbdev = interface_to_usbdev(usbif); 3426f78193eSClément Vuchener int current_profile; 3436d104af3SJohan Hovold char *data; 3446d104af3SJohan Hovold 3456d104af3SJohan Hovold data = kmalloc(8, GFP_KERNEL); 3466d104af3SJohan Hovold if (!data) 3476d104af3SJohan Hovold return -ENOMEM; 3486f78193eSClément Vuchener 3496f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 3506f78193eSClément Vuchener K90_REQUEST_STATUS, 3516f78193eSClément Vuchener USB_DIR_IN | USB_TYPE_VENDOR | 3526f78193eSClément Vuchener USB_RECIP_DEVICE, 0, 0, data, 8, 3536f78193eSClément Vuchener USB_CTRL_SET_TIMEOUT); 3546f78193eSClément Vuchener if (ret < 0) { 3556f78193eSClément Vuchener dev_warn(dev, "Failed to get K90 initial state (error %d).\n", 3566f78193eSClément Vuchener ret); 3576d104af3SJohan Hovold ret = -EIO; 3586d104af3SJohan Hovold goto out; 3596f78193eSClément Vuchener } 3606f78193eSClément Vuchener current_profile = data[7]; 3616f78193eSClément Vuchener if (current_profile < 1 || current_profile > 3) { 3626f78193eSClément Vuchener dev_warn(dev, "Read invalid current profile: %02hhx.\n", 3636f78193eSClément Vuchener data[7]); 3646d104af3SJohan Hovold ret = -EIO; 3656d104af3SJohan Hovold goto out; 3666f78193eSClément Vuchener } 3676f78193eSClément Vuchener 3686d104af3SJohan Hovold ret = snprintf(buf, PAGE_SIZE, "%d\n", current_profile); 3696d104af3SJohan Hovold out: 3706d104af3SJohan Hovold kfree(data); 3716d104af3SJohan Hovold 3726d104af3SJohan Hovold return ret; 3736f78193eSClément Vuchener } 3746f78193eSClément Vuchener 3756f78193eSClément Vuchener static ssize_t k90_store_current_profile(struct device *dev, 3766f78193eSClément Vuchener struct device_attribute *attr, 3776f78193eSClément Vuchener const char *buf, size_t count) 3786f78193eSClément Vuchener { 3796f78193eSClément Vuchener int ret; 3806f78193eSClément Vuchener struct usb_interface *usbif = to_usb_interface(dev->parent); 3816f78193eSClément Vuchener struct usb_device *usbdev = interface_to_usbdev(usbif); 3826f78193eSClément Vuchener int profile; 3836f78193eSClément Vuchener 3846f78193eSClément Vuchener if (kstrtoint(buf, 10, &profile)) 3856f78193eSClément Vuchener return -EINVAL; 3866f78193eSClément Vuchener if (profile < 1 || profile > 3) 3876f78193eSClément Vuchener return -EINVAL; 3886f78193eSClément Vuchener 3896f78193eSClément Vuchener ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 3906f78193eSClément Vuchener K90_REQUEST_PROFILE, 3916f78193eSClément Vuchener USB_DIR_OUT | USB_TYPE_VENDOR | 3926f78193eSClément Vuchener USB_RECIP_DEVICE, profile, 0, NULL, 0, 3936f78193eSClément Vuchener USB_CTRL_SET_TIMEOUT); 3946f78193eSClément Vuchener if (ret != 0) { 3956f78193eSClément Vuchener dev_warn(dev, "Failed to change current profile (error %d).\n", 3966f78193eSClément Vuchener ret); 3976f78193eSClément Vuchener return ret; 3986f78193eSClément Vuchener } 3996f78193eSClément Vuchener 4006f78193eSClément Vuchener return count; 4016f78193eSClément Vuchener } 4026f78193eSClément Vuchener 4036f78193eSClément Vuchener static DEVICE_ATTR(macro_mode, 0644, k90_show_macro_mode, k90_store_macro_mode); 4046f78193eSClément Vuchener static DEVICE_ATTR(current_profile, 0644, k90_show_current_profile, 4056f78193eSClément Vuchener k90_store_current_profile); 4066f78193eSClément Vuchener 4076f78193eSClément Vuchener static struct attribute *k90_attrs[] = { 4086f78193eSClément Vuchener &dev_attr_macro_mode.attr, 4096f78193eSClément Vuchener &dev_attr_current_profile.attr, 4106f78193eSClément Vuchener NULL 4116f78193eSClément Vuchener }; 4126f78193eSClément Vuchener 4136f78193eSClément Vuchener static const struct attribute_group k90_attr_group = { 4146f78193eSClément Vuchener .attrs = k90_attrs, 4156f78193eSClément Vuchener }; 4166f78193eSClément Vuchener 4176f78193eSClément Vuchener /* 4186f78193eSClément Vuchener * Driver functions 4196f78193eSClément Vuchener */ 4206f78193eSClément Vuchener 4216f78193eSClément Vuchener static int k90_init_backlight(struct hid_device *dev) 4226f78193eSClément Vuchener { 4236f78193eSClément Vuchener int ret; 4246f78193eSClément Vuchener struct corsair_drvdata *drvdata = hid_get_drvdata(dev); 4256f78193eSClément Vuchener size_t name_sz; 4266f78193eSClément Vuchener char *name; 4276f78193eSClément Vuchener 4286f78193eSClément Vuchener drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL); 4296f78193eSClément Vuchener if (!drvdata->backlight) { 4306f78193eSClément Vuchener ret = -ENOMEM; 4316f78193eSClément Vuchener goto fail_backlight_alloc; 4326f78193eSClément Vuchener } 4336f78193eSClément Vuchener 4346f78193eSClément Vuchener name_sz = 4356f78193eSClément Vuchener strlen(dev_name(&dev->dev)) + sizeof(K90_BACKLIGHT_LED_SUFFIX); 4366f78193eSClément Vuchener name = kzalloc(name_sz, GFP_KERNEL); 4376f78193eSClément Vuchener if (!name) { 4386f78193eSClément Vuchener ret = -ENOMEM; 4396f78193eSClément Vuchener goto fail_name_alloc; 4406f78193eSClément Vuchener } 4416f78193eSClément Vuchener snprintf(name, name_sz, "%s" K90_BACKLIGHT_LED_SUFFIX, 4426f78193eSClément Vuchener dev_name(&dev->dev)); 443937804f3SJiri Kosina drvdata->backlight->removed = false; 4446f78193eSClément Vuchener drvdata->backlight->cdev.name = name; 4456f78193eSClément Vuchener drvdata->backlight->cdev.max_brightness = 3; 4466f78193eSClément Vuchener drvdata->backlight->cdev.brightness_set = k90_brightness_set; 4476f78193eSClément Vuchener drvdata->backlight->cdev.brightness_get = k90_backlight_get; 4486f78193eSClément Vuchener INIT_WORK(&drvdata->backlight->work, k90_backlight_work); 4496f78193eSClément Vuchener ret = led_classdev_register(&dev->dev, &drvdata->backlight->cdev); 4506f78193eSClément Vuchener if (ret != 0) 4516f78193eSClément Vuchener goto fail_register_cdev; 4526f78193eSClément Vuchener 4536f78193eSClément Vuchener return 0; 4546f78193eSClément Vuchener 4556f78193eSClément Vuchener fail_register_cdev: 4566f78193eSClément Vuchener kfree(drvdata->backlight->cdev.name); 4576f78193eSClément Vuchener fail_name_alloc: 4586f78193eSClément Vuchener kfree(drvdata->backlight); 4596f78193eSClément Vuchener drvdata->backlight = NULL; 4606f78193eSClément Vuchener fail_backlight_alloc: 4616f78193eSClément Vuchener return ret; 4626f78193eSClément Vuchener } 4636f78193eSClément Vuchener 4646f78193eSClément Vuchener static int k90_init_macro_functions(struct hid_device *dev) 4656f78193eSClément Vuchener { 4666f78193eSClément Vuchener int ret; 4676f78193eSClément Vuchener struct corsair_drvdata *drvdata = hid_get_drvdata(dev); 4686f78193eSClément Vuchener struct k90_drvdata *k90; 4696f78193eSClément Vuchener size_t name_sz; 4706f78193eSClément Vuchener char *name; 4716f78193eSClément Vuchener 4726f78193eSClément Vuchener k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL); 4736f78193eSClément Vuchener if (!k90) { 4746f78193eSClément Vuchener ret = -ENOMEM; 4756f78193eSClément Vuchener goto fail_drvdata; 4766f78193eSClément Vuchener } 4776f78193eSClément Vuchener drvdata->k90 = k90; 4786f78193eSClément Vuchener 4796f78193eSClément Vuchener /* Init LED device for record LED */ 4806f78193eSClément Vuchener name_sz = strlen(dev_name(&dev->dev)) + sizeof(K90_RECORD_LED_SUFFIX); 4816f78193eSClément Vuchener name = kzalloc(name_sz, GFP_KERNEL); 4826f78193eSClément Vuchener if (!name) { 4836f78193eSClément Vuchener ret = -ENOMEM; 4846f78193eSClément Vuchener goto fail_record_led_alloc; 4856f78193eSClément Vuchener } 4866f78193eSClément Vuchener snprintf(name, name_sz, "%s" K90_RECORD_LED_SUFFIX, 4876f78193eSClément Vuchener dev_name(&dev->dev)); 488937804f3SJiri Kosina k90->record_led.removed = false; 4896f78193eSClément Vuchener k90->record_led.cdev.name = name; 4906f78193eSClément Vuchener k90->record_led.cdev.max_brightness = 1; 4916f78193eSClément Vuchener k90->record_led.cdev.brightness_set = k90_brightness_set; 4926f78193eSClément Vuchener k90->record_led.cdev.brightness_get = k90_record_led_get; 4936f78193eSClément Vuchener INIT_WORK(&k90->record_led.work, k90_record_led_work); 4946f78193eSClément Vuchener k90->record_led.brightness = 0; 4956f78193eSClément Vuchener ret = led_classdev_register(&dev->dev, &k90->record_led.cdev); 4966f78193eSClément Vuchener if (ret != 0) 4976f78193eSClément Vuchener goto fail_record_led; 4986f78193eSClément Vuchener 4996f78193eSClément Vuchener /* Init attributes */ 5006f78193eSClément Vuchener ret = sysfs_create_group(&dev->dev.kobj, &k90_attr_group); 5016f78193eSClément Vuchener if (ret != 0) 5026f78193eSClément Vuchener goto fail_sysfs; 5036f78193eSClément Vuchener 5046f78193eSClément Vuchener return 0; 5056f78193eSClément Vuchener 5066f78193eSClément Vuchener fail_sysfs: 507937804f3SJiri Kosina k90->record_led.removed = true; 5086f78193eSClément Vuchener led_classdev_unregister(&k90->record_led.cdev); 5096f78193eSClément Vuchener cancel_work_sync(&k90->record_led.work); 5106f78193eSClément Vuchener fail_record_led: 5116f78193eSClément Vuchener kfree(k90->record_led.cdev.name); 5126f78193eSClément Vuchener fail_record_led_alloc: 5136f78193eSClément Vuchener kfree(k90); 5146f78193eSClément Vuchener fail_drvdata: 5156f78193eSClément Vuchener drvdata->k90 = NULL; 5166f78193eSClément Vuchener return ret; 5176f78193eSClément Vuchener } 5186f78193eSClément Vuchener 5196f78193eSClément Vuchener static void k90_cleanup_backlight(struct hid_device *dev) 5206f78193eSClément Vuchener { 5216f78193eSClément Vuchener struct corsair_drvdata *drvdata = hid_get_drvdata(dev); 5226f78193eSClément Vuchener 5236f78193eSClément Vuchener if (drvdata->backlight) { 524937804f3SJiri Kosina drvdata->backlight->removed = true; 5256f78193eSClément Vuchener led_classdev_unregister(&drvdata->backlight->cdev); 5266f78193eSClément Vuchener cancel_work_sync(&drvdata->backlight->work); 5276f78193eSClément Vuchener kfree(drvdata->backlight->cdev.name); 5286f78193eSClément Vuchener kfree(drvdata->backlight); 5296f78193eSClément Vuchener } 5306f78193eSClément Vuchener } 5316f78193eSClément Vuchener 5326f78193eSClément Vuchener static void k90_cleanup_macro_functions(struct hid_device *dev) 5336f78193eSClément Vuchener { 5346f78193eSClément Vuchener struct corsair_drvdata *drvdata = hid_get_drvdata(dev); 5356f78193eSClément Vuchener struct k90_drvdata *k90 = drvdata->k90; 5366f78193eSClément Vuchener 5376f78193eSClément Vuchener if (k90) { 5386f78193eSClément Vuchener sysfs_remove_group(&dev->dev.kobj, &k90_attr_group); 5396f78193eSClément Vuchener 540937804f3SJiri Kosina k90->record_led.removed = true; 5416f78193eSClément Vuchener led_classdev_unregister(&k90->record_led.cdev); 5426f78193eSClément Vuchener cancel_work_sync(&k90->record_led.work); 5436f78193eSClément Vuchener kfree(k90->record_led.cdev.name); 5446f78193eSClément Vuchener 5456f78193eSClément Vuchener kfree(k90); 5466f78193eSClément Vuchener } 5476f78193eSClément Vuchener } 5486f78193eSClément Vuchener 5496f78193eSClément Vuchener static int corsair_probe(struct hid_device *dev, const struct hid_device_id *id) 5506f78193eSClément Vuchener { 5516f78193eSClément Vuchener int ret; 5526f78193eSClément Vuchener unsigned long quirks = id->driver_data; 5536f78193eSClément Vuchener struct corsair_drvdata *drvdata; 5546f78193eSClément Vuchener struct usb_interface *usbif = to_usb_interface(dev->dev.parent); 5556f78193eSClément Vuchener 5566f78193eSClément Vuchener drvdata = devm_kzalloc(&dev->dev, sizeof(struct corsair_drvdata), 5576f78193eSClément Vuchener GFP_KERNEL); 5586f78193eSClément Vuchener if (drvdata == NULL) 5596f78193eSClément Vuchener return -ENOMEM; 5606f78193eSClément Vuchener drvdata->quirks = quirks; 5616f78193eSClément Vuchener hid_set_drvdata(dev, drvdata); 5626f78193eSClément Vuchener 5636f78193eSClément Vuchener ret = hid_parse(dev); 5646f78193eSClément Vuchener if (ret != 0) { 5656f78193eSClément Vuchener hid_err(dev, "parse failed\n"); 5666f78193eSClément Vuchener return ret; 5676f78193eSClément Vuchener } 5686f78193eSClément Vuchener ret = hid_hw_start(dev, HID_CONNECT_DEFAULT); 5696f78193eSClément Vuchener if (ret != 0) { 5706f78193eSClément Vuchener hid_err(dev, "hw start failed\n"); 5716f78193eSClément Vuchener return ret; 5726f78193eSClément Vuchener } 5736f78193eSClément Vuchener 5746f78193eSClément Vuchener if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) { 5756f78193eSClément Vuchener if (quirks & CORSAIR_USE_K90_MACRO) { 5766f78193eSClément Vuchener ret = k90_init_macro_functions(dev); 5776f78193eSClément Vuchener if (ret != 0) 5786f78193eSClément Vuchener hid_warn(dev, "Failed to initialize K90 macro functions.\n"); 5796f78193eSClément Vuchener } 5806f78193eSClément Vuchener if (quirks & CORSAIR_USE_K90_BACKLIGHT) { 5816f78193eSClément Vuchener ret = k90_init_backlight(dev); 5826f78193eSClément Vuchener if (ret != 0) 5836f78193eSClément Vuchener hid_warn(dev, "Failed to initialize K90 backlight.\n"); 5846f78193eSClément Vuchener } 5856f78193eSClément Vuchener } 5866f78193eSClément Vuchener 5876f78193eSClément Vuchener return 0; 5886f78193eSClément Vuchener } 5896f78193eSClément Vuchener 5906f78193eSClément Vuchener static void corsair_remove(struct hid_device *dev) 5916f78193eSClément Vuchener { 5926f78193eSClément Vuchener k90_cleanup_macro_functions(dev); 5936f78193eSClément Vuchener k90_cleanup_backlight(dev); 5946f78193eSClément Vuchener 5956f78193eSClément Vuchener hid_hw_stop(dev); 5966f78193eSClément Vuchener } 5976f78193eSClément Vuchener 5986f78193eSClément Vuchener static int corsair_event(struct hid_device *dev, struct hid_field *field, 5996f78193eSClément Vuchener struct hid_usage *usage, __s32 value) 6006f78193eSClément Vuchener { 6016f78193eSClément Vuchener struct corsair_drvdata *drvdata = hid_get_drvdata(dev); 6026f78193eSClément Vuchener 6036f78193eSClément Vuchener if (!drvdata->k90) 6046f78193eSClément Vuchener return 0; 6056f78193eSClément Vuchener 6066f78193eSClément Vuchener switch (usage->hid & HID_USAGE) { 6076f78193eSClément Vuchener case CORSAIR_USAGE_MACRO_RECORD_START: 6086f78193eSClément Vuchener drvdata->k90->record_led.brightness = 1; 6096f78193eSClément Vuchener break; 6106f78193eSClément Vuchener case CORSAIR_USAGE_MACRO_RECORD_STOP: 6116f78193eSClément Vuchener drvdata->k90->record_led.brightness = 0; 6126f78193eSClément Vuchener break; 6136f78193eSClément Vuchener default: 6146f78193eSClément Vuchener break; 6156f78193eSClément Vuchener } 6166f78193eSClément Vuchener 6176f78193eSClément Vuchener return 0; 6186f78193eSClément Vuchener } 6196f78193eSClément Vuchener 6206f78193eSClément Vuchener static int corsair_input_mapping(struct hid_device *dev, 6216f78193eSClément Vuchener struct hid_input *input, 6226f78193eSClément Vuchener struct hid_field *field, 6236f78193eSClément Vuchener struct hid_usage *usage, unsigned long **bit, 6246f78193eSClément Vuchener int *max) 6256f78193eSClément Vuchener { 6266f78193eSClément Vuchener int gkey; 6276f78193eSClément Vuchener 628e791f7b1SClément Vuchener if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD) 629e791f7b1SClément Vuchener return 0; 630e791f7b1SClément Vuchener 6316f78193eSClément Vuchener gkey = corsair_usage_to_gkey(usage->hid & HID_USAGE); 6326f78193eSClément Vuchener if (gkey != 0) { 6336f78193eSClément Vuchener hid_map_usage_clear(input, usage, bit, max, EV_KEY, 6346f78193eSClément Vuchener corsair_gkey_map[gkey - 1]); 6356f78193eSClément Vuchener return 1; 6366f78193eSClément Vuchener } 6376f78193eSClément Vuchener if ((usage->hid & HID_USAGE) >= CORSAIR_USAGE_SPECIAL_MIN && 6386f78193eSClément Vuchener (usage->hid & HID_USAGE) <= CORSAIR_USAGE_SPECIAL_MAX) { 6396f78193eSClément Vuchener switch (usage->hid & HID_USAGE) { 6406f78193eSClément Vuchener case CORSAIR_USAGE_MACRO_RECORD_START: 6416f78193eSClément Vuchener hid_map_usage_clear(input, usage, bit, max, EV_KEY, 6426f78193eSClément Vuchener corsair_record_keycodes[0]); 6436f78193eSClément Vuchener return 1; 6446f78193eSClément Vuchener 6456f78193eSClément Vuchener case CORSAIR_USAGE_MACRO_RECORD_STOP: 6466f78193eSClément Vuchener hid_map_usage_clear(input, usage, bit, max, EV_KEY, 6476f78193eSClément Vuchener corsair_record_keycodes[1]); 6486f78193eSClément Vuchener return 1; 6496f78193eSClément Vuchener 6506f78193eSClément Vuchener case CORSAIR_USAGE_M1: 6516f78193eSClément Vuchener hid_map_usage_clear(input, usage, bit, max, EV_KEY, 6526f78193eSClément Vuchener corsair_profile_keycodes[0]); 6536f78193eSClément Vuchener return 1; 6546f78193eSClément Vuchener 6556f78193eSClément Vuchener case CORSAIR_USAGE_M2: 6566f78193eSClément Vuchener hid_map_usage_clear(input, usage, bit, max, EV_KEY, 6576f78193eSClément Vuchener corsair_profile_keycodes[1]); 6586f78193eSClément Vuchener return 1; 6596f78193eSClément Vuchener 6606f78193eSClément Vuchener case CORSAIR_USAGE_M3: 6616f78193eSClément Vuchener hid_map_usage_clear(input, usage, bit, max, EV_KEY, 6626f78193eSClément Vuchener corsair_profile_keycodes[2]); 6636f78193eSClément Vuchener return 1; 6646f78193eSClément Vuchener 6656f78193eSClément Vuchener default: 6666f78193eSClément Vuchener return -1; 6676f78193eSClément Vuchener } 6686f78193eSClément Vuchener } 6696f78193eSClément Vuchener 6706f78193eSClément Vuchener return 0; 6716f78193eSClément Vuchener } 6726f78193eSClément Vuchener 6736f78193eSClément Vuchener static const struct hid_device_id corsair_devices[] = { 6746f78193eSClément Vuchener { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90), 6756f78193eSClément Vuchener .driver_data = CORSAIR_USE_K90_MACRO | 6766f78193eSClément Vuchener CORSAIR_USE_K90_BACKLIGHT }, 6776f78193eSClément Vuchener {} 6786f78193eSClément Vuchener }; 6796f78193eSClément Vuchener 6806f78193eSClément Vuchener MODULE_DEVICE_TABLE(hid, corsair_devices); 6816f78193eSClément Vuchener 6826f78193eSClément Vuchener static struct hid_driver corsair_driver = { 6836f78193eSClément Vuchener .name = "corsair", 6846f78193eSClément Vuchener .id_table = corsair_devices, 6856f78193eSClément Vuchener .probe = corsair_probe, 6866f78193eSClément Vuchener .event = corsair_event, 6876f78193eSClément Vuchener .remove = corsair_remove, 6886f78193eSClément Vuchener .input_mapping = corsair_input_mapping, 6896f78193eSClément Vuchener }; 6906f78193eSClément Vuchener 691e3fed748SAxel Lin module_hid_driver(corsair_driver); 6926f78193eSClément Vuchener 6936f78193eSClément Vuchener MODULE_LICENSE("GPL"); 6946f78193eSClément Vuchener MODULE_AUTHOR("Clement Vuchener"); 6956f78193eSClément Vuchener MODULE_DESCRIPTION("HID driver for Corsair devices"); 696