From patchwork Sat Aug 29 14:03:52 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Cl=C3=A9ment_VUCHENER?= X-Patchwork-Id: 7095181 Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 7654FBEEC1 for ; Sat, 29 Aug 2015 14:04:07 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 4779F206B8 for ; Sat, 29 Aug 2015 14:04:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2048E206B1 for ; Sat, 29 Aug 2015 14:04:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752633AbbH2OD6 (ORCPT ); Sat, 29 Aug 2015 10:03:58 -0400 Received: from mail-wi0-f173.google.com ([209.85.212.173]:36883 "EHLO mail-wi0-f173.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752135AbbH2OD4 (ORCPT ); Sat, 29 Aug 2015 10:03:56 -0400 Received: by wicfv10 with SMTP id fv10so34700490wic.0; Sat, 29 Aug 2015 07:03:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:subject:to:references:cc:message-id:date:user-agent :mime-version:in-reply-to:content-type:content-transfer-encoding; bh=XVf3/XnTI4KRcYH4LN2UksDFasukENTMtrPwTHYa+KU=; b=dfRt09++JsXmw8kJ6KESpNfIZr4IfI6+ZOKbHq5c1oKB0ZMSCoRaWXyLhrfUBFg/si h/MzNl2lduR8nMAjtSoKW3ZVkxTMPQOJ1Po62zq0/qo7LWe0YYHa3/J5YtwiECxf3lGd xpMdRQiN7Qfm7hcNzTpDWhiQ8m18vtRJa2TTlLgSIxsYdWMZJKZdp1pKw1yxvYQOcKG3 ZWHyFHs0JsWo8AECQjichAz9tQ8z4uRwNRmSNCvoZrgg/EjLI6T+VJRomWuKpHI33TDJ mHedYURk33w05m6orDnHdVaecRX+OwbMZEbHEbE5o3OcAYmzfHJZtBTGO7M75oIMEdub gLOw== X-Received: by 10.194.92.143 with SMTP id cm15mr18123324wjb.17.1440857034291; Sat, 29 Aug 2015 07:03:54 -0700 (PDT) Received: from [192.168.1.13] (ARennes-658-1-63-232.w2-14.abo.wanadoo.fr. [2.14.166.232]) by smtp.googlemail.com with ESMTPSA id lk16sm8733324wic.6.2015.08.29.07.03.53 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 29 Aug 2015 07:03:53 -0700 (PDT) From: =?UTF-8?Q?Cl=c3=a9ment_Vuchener?= Subject: [PATCH 1/1] Corsair Vengeance K90 driver To: jkosina@suse.com References: Cc: linux-api@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org Message-ID: <55E1BBC8.9010303@gmail.com> Date: Sat, 29 Aug 2015 16:03:52 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.1.0 MIME-Version: 1.0 In-Reply-To: Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-7.8 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP --- Documentation/ABI/testing/sysfs-class-k90_profile | 55 ++ .../ABI/testing/sysfs-driver-hid-corsair-k90 | 15 + drivers/hid/Kconfig | 10 + drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-corsair-k90.c | 690 +++++++++++++++++++++ drivers/hid/hid-ids.h | 3 + 7 files changed, 775 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-k90_profile create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-corsair-k90 create mode 100644 drivers/hid/hid-corsair-k90.c --- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/Documentation/ABI/testing/sysfs-class-k90_profile b/Documentation/ABI/testing/sysfs-class-k90_profile new file mode 100644 index 0000000..3275c16 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-k90_profile @@ -0,0 +1,55 @@ +What: /sys/class/k90_profile//profile_number +Date: August 2015 +KernelVersion: 4.2 +Contact: Clement Vuchener +Description: Get the number of the profile. + +What: /sys/class/k90_profile//bindings +Date: August 2015 +KernelVersion: 4.2 +Contact: Clement Vuchener +Description: Write bindings to the keyboard on-board profile. + The data structure is: + - number of bindings in this structure (1 byte) + - size of this data structure (2 bytes big endian) + - size of the macro data written to + /sys/class/k90_profile//data (2 bytes big endian) + - array of bindings referencing the data from + /sys/class/k90_profile//data, each containing: + * 0x10 for a key usage, 0x20 for a macro (1 byte) + * offset of the key usage/macro data (2 bytes big endian) + * length of the key usage/macro data (2 bytes big endian) + +What: /sys/class/k90_profile//keys +Date: August 2015 +KernelVersion: 4.2 +Contact: Clement Vuchener +Description: Write the list of the keys used by the bindings and how + the macros are repeated. + The data struct is: + - number of keys in this structure (1 byte) + - array of keys and repeat mode: + * key usage (G1 to G16 are 0xD0 to 0xDF, G17 and + G18 are 0xE8 and 0xE9) (1 byte) + * repeat mode (1 byte): + 1: play when pressed + 2: repeat while key is pressed + 3: repeat until the key is pressed again + +What: /sys/class/k90_profile//data +Date: August 2015 +KernelVersion: 4.2 +Contact: Clement Vuchener +Description: Write the key usage and macros used by + /sys/class/k90_profile//bindings + Macro items are: + - Key event: + * item type: 0x84 (1 byte) + * HID usage (1 byte) + * new state: 0 released, 1 pressed (1 byte) + - Delay + * item type: 0x87 (1 byte) + * delay in milliseconds (2 bytes big endian) + - Macro end + * item type: 0x86 (1 byte) + * repeat count (2 bytes big endian) diff --git a/Documentation/ABI/testing/sysfs-driver-hid-corsair-k90 b/Documentation/ABI/testing/sysfs-driver-hid-corsair-k90 new file mode 100644 index 0000000..16d50ae --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-corsair-k90 @@ -0,0 +1,15 @@ +What: /sys/bus/drivers/k90//macro_mode +Date: August 2015 +KernelVersion: 4.2 +Contact: Clement Vuchener +Description: Get/set the current playback mode. "SW" for software mode + where G-keys triggers their regular key codes. "HW" for + hardware playback mode where the G-keys play their macro + from the on-board memory. + + +What: /sys/bus/drivers/k90//current_profile +Date: August 2015 +KernelVersion: 4.2 +Contact: Clement Vuchener +Description: Get/set the current selected profile. Values are from 1 to 3. diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index cc4c664..db6d8a5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -171,6 +171,16 @@ config HID_CHICONY ---help--- Support for Chicony Tactical pad. +config HID_CORSAIR + tristate "Corsair devices" + depends on HID + ---help--- + Support for Corsair devices that are not fully compliant with the + HID standard. + + Supported devices: + - Vengeance K90 + config HID_PRODIKEYS tristate "Prodikeys PC-MIDI Keyboard support" depends on HID && SND diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 2f8a41d..e94a0a5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_HID_BELKIN) += hid-belkin.o obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o obj-$(CONFIG_HID_CHERRY) += hid-cherry.o obj-$(CONFIG_HID_CHICONY) += hid-chicony.o +obj-$(CONFIG_HID_CORSAIR) += hid-corsair-k90.o obj-$(CONFIG_HID_CP2112) += hid-cp2112.o obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o obj-$(CONFIG_HID_DRAGONRISE) += hid-dr.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index e6fce23..f0d9125 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1807,6 +1807,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90) }, { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) }, diff --git a/drivers/hid/hid-corsair-k90.c b/drivers/hid/hid-corsair-k90.c new file mode 100644 index 0000000..67c1095 --- /dev/null +++ b/drivers/hid/hid-corsair-k90.c @@ -0,0 +1,690 @@ +/* + * HID driver for Corsair Vengeance K90 Keyboard + * Copyright (c) 2015 Clement Vuchener + */ + +/* + * 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; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +struct k90_led { + struct led_classdev cdev; + int brightness; + struct work_struct work; +}; + +struct k90_profile { + struct device *dev; + int profile; +}; + +struct k90_drvdata { + int current_profile; + int macro_mode; + int meta_locked; + struct k90_led backlight; + struct k90_led record_led; + struct k90_profile profile[3]; +}; + +#define K90_GKEY_COUNT 18 + +static int k90_usage_to_gkey(unsigned int usage) +{ + /* G1 (0xd0) to G16 (0xdf) */ + if (usage >= 0xd0 && usage <= 0xdf) + return usage - 0xd0 + 1; + /* G17 (0xe8) to G18 (0xe9) */ + if (usage >= 0xe8 && usage <= 0xe9) + return usage - 0xe8 + 17; + return 0; +} + +static unsigned short k90_gkey_map[K90_GKEY_COUNT] = { + BTN_TRIGGER_HAPPY1, + BTN_TRIGGER_HAPPY2, + BTN_TRIGGER_HAPPY3, + BTN_TRIGGER_HAPPY4, + BTN_TRIGGER_HAPPY5, + BTN_TRIGGER_HAPPY6, + BTN_TRIGGER_HAPPY7, + BTN_TRIGGER_HAPPY8, + BTN_TRIGGER_HAPPY9, + BTN_TRIGGER_HAPPY10, + BTN_TRIGGER_HAPPY11, + BTN_TRIGGER_HAPPY12, + BTN_TRIGGER_HAPPY13, + BTN_TRIGGER_HAPPY14, + BTN_TRIGGER_HAPPY15, + BTN_TRIGGER_HAPPY16, + BTN_TRIGGER_HAPPY17, + BTN_TRIGGER_HAPPY18, +}; + +module_param_array_named(gkey_codes, k90_gkey_map, ushort, NULL, S_IRUGO); + +#define K90_USAGE_SPECIAL_MIN 0xf0 +#define K90_USAGE_SPECIAL_MAX 0xff + +#define K90_USAGE_MACRO_RECORD_START 0xf6 +#define K90_USAGE_MACRO_RECORD_STOP 0xf7 + +#define K90_USAGE_PROFILE 0xf1 +#define K90_USAGE_M1 0xf1 +#define K90_USAGE_M2 0xf2 +#define K90_USAGE_M3 0xf3 +#define K90_USAGE_PROFILE_MAX 0xf3 + +#define K90_USAGE_META_OFF 0xf4 +#define K90_USAGE_META_ON 0xf5 + +#define K90_USAGE_LIGHT 0xfa +#define K90_USAGE_LIGHT_OFF 0xfa +#define K90_USAGE_LIGHT_DIM 0xfb +#define K90_USAGE_LIGHT_MEDIUM 0xfc +#define K90_USAGE_LIGHT_BRIGHT 0xfd +#define K90_USAGE_LIGHT_MAX 0xfd + +/* USB control protocol */ + +#define K90_REQUEST_BRIGHTNESS 49 +#define K90_REQUEST_MACRO_MODE 2 +#define K90_REQUEST_STATUS 4 +#define K90_REQUEST_GET_MODE 5 +#define K90_REQUEST_PROFILE 20 + +#define K90_REQUEST_PROFILE_BINDINGS 16 +#define K90_REQUEST_PROFILE_KEYS 22 +#define K90_REQUEST_PROFILE_DATA 18 + +#define K90_BINDINGS_MAX_LENGTH 128 +#define K90_KEYS_MAX_LENGTH 64 +/* K90_DATA_MAX_LENGTH may be higher but that is the maximum I tested */ +#define K90_DATA_MAX_LENGTH 4096 + +#define K90_MACRO_MODE_SW 0x0030 +#define K90_MACRO_MODE_HW 0x0001 + +#define K90_MACRO_LED_ON 0x0020 +#define K90_MACRO_LED_OFF 0x0040 + +/* + * LED class devices + */ + +#define K90_BACKLIGHT_LED_SUFFIX ":blue:backlight" +#define K90_RECORD_LED_SUFFIX ":red:record" + +static enum led_brightness k90_brightness_get(struct led_classdev *led_cdev) +{ + struct k90_led *led = container_of(led_cdev, struct k90_led, cdev); + + return led->brightness; +} + +static void k90_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct k90_led *led = container_of(led_cdev, struct k90_led, cdev); + + led->brightness = brightness; + schedule_work(&led->work); +} + +static void k90_backlight_work(struct work_struct *work) +{ + int ret; + struct k90_led *led = container_of(work, struct k90_led, work); + struct device *dev = led->cdev.dev->parent; + struct usb_interface *usbif = to_usb_interface(dev->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_BRIGHTNESS, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, led->brightness, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (ret != 0) + dev_warn(dev, "Failed to set backlight brightness (error: %d).\n", + ret); +} + +static void k90_record_led_work(struct work_struct *work) +{ + int ret; + struct k90_led *led = container_of(work, struct k90_led, work); + struct device *dev = led->cdev.dev->parent; + struct usb_interface *usbif = to_usb_interface(dev->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + int value; + + if (led->brightness > 0) + value = K90_MACRO_LED_ON; + else + value = K90_MACRO_LED_OFF; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_MACRO_MODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret != 0) + dev_warn(dev, "Failed to set record LED state (error: %d).\n", + ret); +} + +/* + * Keyboard attributes + */ + +static ssize_t k90_show_macro_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k90_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", + (drvdata->macro_mode ? "HW" : "SW")); +} + +static ssize_t k90_store_macro_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct usb_interface *usbif = to_usb_interface(dev->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + struct k90_drvdata *drvdata = dev_get_drvdata(dev); + __u16 value; + + if (strncmp(buf, "SW", 2) == 0) + value = K90_MACRO_MODE_SW; + else if (strncmp(buf, "HW", 2) == 0) + value = K90_MACRO_MODE_HW; + else + return -EINVAL; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_MACRO_MODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret != 0) + return ret; + + drvdata->macro_mode = (value == K90_MACRO_MODE_HW); + + return count; +} + +static ssize_t k90_show_current_profile(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct k90_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->current_profile); +} + +static ssize_t k90_store_current_profile(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct usb_interface *usbif = to_usb_interface(dev->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + struct k90_drvdata *drvdata = dev_get_drvdata(dev); + int profile; + + if (kstrtoint(buf, 10, &profile)) + return -EINVAL; + if (profile < 1 || profile > 3) + return -EINVAL; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_PROFILE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, profile, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret != 0) + return ret; + + drvdata->current_profile = profile; + + return count; +} + +static DEVICE_ATTR(macro_mode, 0644, k90_show_macro_mode, k90_store_macro_mode); +static DEVICE_ATTR(current_profile, 0644, k90_show_current_profile, + k90_store_current_profile); + +static struct attribute *k90_attrs[] = { + &dev_attr_macro_mode.attr, + &dev_attr_current_profile.attr, + NULL +}; + +static const struct attribute_group k90_attr_group = { + .attrs = k90_attrs, +}; + +/* + * Profile device class and attributes + */ + +static struct class *k90_profile_class; + +static ssize_t k90_profile_show_profile_number(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct k90_profile *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->profile); +} + +static ssize_t k90_profile_write_bindings(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + int ret; + struct device *pdev = container_of(kobj, struct device, kobj); + struct k90_profile *data = dev_get_drvdata(pdev); + struct usb_interface *usbif = to_usb_interface(pdev->parent->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + + if (count > K90_BINDINGS_MAX_LENGTH) + return -EMSGSIZE; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_PROFILE_BINDINGS, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, 0, data->profile, buf, + count, USB_CTRL_SET_TIMEOUT); + if (ret != 0) + return ret; + + return count; +} + +static ssize_t k90_profile_write_keys(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + int ret; + struct device *pdev = container_of(kobj, struct device, kobj); + struct k90_profile *data = dev_get_drvdata(pdev); + struct usb_interface *usbif = to_usb_interface(pdev->parent->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + + if (count > K90_KEYS_MAX_LENGTH) + return -EMSGSIZE; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_PROFILE_KEYS, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, 0, data->profile, buf, + count, USB_CTRL_SET_TIMEOUT); + if (ret != 0) + return ret; + + return count; +} + +static ssize_t k90_profile_write_data(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + int ret; + struct device *pdev = container_of(kobj, struct device, kobj); + struct k90_profile *data = dev_get_drvdata(pdev); + struct usb_interface *usbif = to_usb_interface(pdev->parent->parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + + if (count > K90_DATA_MAX_LENGTH) + return -EMSGSIZE; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + K90_REQUEST_PROFILE_DATA, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, 0, data->profile, buf, + count, USB_CTRL_SET_TIMEOUT); + if (ret != 0) + return ret; + + return count; +} + +static DEVICE_ATTR(profile_number, 0444, k90_profile_show_profile_number, NULL); +static BIN_ATTR(bindings, 0200, NULL, k90_profile_write_bindings, 0); +static BIN_ATTR(keys, 0200, NULL, k90_profile_write_keys, 0); +static BIN_ATTR(data, 0200, NULL, k90_profile_write_data, 0); + +static struct attribute *k90_profile_attrs[] = { + &dev_attr_profile_number.attr, + NULL +}; + +static struct bin_attribute *k90_profile_bin_attrs[] = { + &bin_attr_bindings, + &bin_attr_keys, + &bin_attr_data, + NULL +}; + +static const struct attribute_group k90_profile_attr_group = { + .attrs = k90_profile_attrs, + .bin_attrs = k90_profile_bin_attrs, +}; + +static const struct attribute_group *k90_profile_attr_groups[] = { + &k90_profile_attr_group, + NULL +}; + +/* + * Driver functions + */ + +static int k90_init_special_functions(struct hid_device *dev) +{ + int ret, i; + struct usb_interface *usbif = to_usb_interface(dev->dev.parent); + struct usb_device *usbdev = interface_to_usbdev(usbif); + char data[8]; + struct k90_drvdata *drvdata = + kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL); + size_t name_sz; + char *name; + struct k90_led *led; + + if (!drvdata) { + ret = -ENOMEM; + goto fail_drvdata; + } + hid_set_drvdata(dev, drvdata); + + /* Get current status */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + K90_REQUEST_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, 0, 0, data, 8, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + hid_warn(dev, "Failed to get K90 initial state (error %d).\n", + ret); + drvdata->backlight.brightness = 0; + drvdata->current_profile = 1; + } else { + drvdata->backlight.brightness = data[4]; + drvdata->current_profile = data[7]; + } + /* Get current mode */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + K90_REQUEST_GET_MODE, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, 0, 0, data, 2, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) + hid_warn(dev, "Failed to get K90 initial mode (error %d).\n", + ret); + else { + switch (data[0]) { + case K90_MACRO_MODE_HW: + drvdata->macro_mode = 1; + break; + case K90_MACRO_MODE_SW: + drvdata->macro_mode = 0; + break; + default: + hid_warn(dev, "K90 in unknown mode: %02x.\n", + data[0]); + } + } + + /* Init LED device for backlight */ + name_sz = + strlen(dev_name(&dev->dev)) + sizeof(K90_BACKLIGHT_LED_SUFFIX); + name = devm_kzalloc(&dev->dev, name_sz, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto fail_backlight; + } + snprintf(name, name_sz, "%s" K90_BACKLIGHT_LED_SUFFIX, + dev_name(&dev->dev)); + led = &drvdata->backlight; + led->cdev.name = name; + led->cdev.max_brightness = 3; + led->cdev.brightness_set = k90_brightness_set; + led->cdev.brightness_get = k90_brightness_get; + INIT_WORK(&led->work, k90_backlight_work); + ret = led_classdev_register(&dev->dev, &led->cdev); + if (ret != 0) + goto fail_backlight; + + /* Init LED device for record LED */ + name_sz = strlen(dev_name(&dev->dev)) + sizeof(K90_RECORD_LED_SUFFIX); + name = devm_kzalloc(&dev->dev, name_sz, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto fail_record_led; + } + snprintf(name, name_sz, "%s" K90_RECORD_LED_SUFFIX, + dev_name(&dev->dev)); + led = &drvdata->record_led; + led->cdev.name = name; + led->cdev.max_brightness = 1; + led->cdev.brightness_set = k90_brightness_set; + led->cdev.brightness_get = k90_brightness_get; + INIT_WORK(&led->work, k90_record_led_work); + ret = led_classdev_register(&dev->dev, &led->cdev); + if (ret != 0) + goto fail_record_led; + + /* Create profile devices */ + for (i = 0; i < 3; ++i) { + drvdata->profile[i].profile = i + 1; + drvdata->profile[i].dev = + device_create_with_groups(k90_profile_class, &dev->dev, 0, + &drvdata->profile[i], + k90_profile_attr_groups, + "%s:profile%d", + dev_name(&dev->dev), i + 1); + if (IS_ERR(drvdata->profile[i].dev)) { + ret = PTR_ERR(drvdata->profile[i].dev); + for (i = i - 1; i >= 0; --i) + device_unregister(drvdata->profile[i].dev); + goto fail_profile; + } + } + + /* Init attributes */ + ret = sysfs_create_group(&dev->dev.kobj, &k90_attr_group); + if (ret != 0) + goto fail_sysfs; + + return 0; + +fail_sysfs: + for (i = 0; i < 3; ++i) + device_unregister(drvdata->profile[i].dev); +fail_profile: + led_classdev_unregister(&drvdata->record_led.cdev); + flush_work(&drvdata->record_led.work); +fail_record_led: + led_classdev_unregister(&drvdata->backlight.cdev); + flush_work(&drvdata->backlight.work); +fail_backlight: + kfree(drvdata); +fail_drvdata: + hid_set_drvdata(dev, NULL); + return ret; +} + +static void k90_cleanup_special_functions(struct hid_device *dev) +{ + int i; + struct k90_drvdata *drvdata = hid_get_drvdata(dev); + + if (drvdata) { + sysfs_remove_group(&dev->dev.kobj, &k90_attr_group); + for (i = 0; i < 3; ++i) + device_unregister(drvdata->profile[i].dev); + led_classdev_unregister(&drvdata->record_led.cdev); + led_classdev_unregister(&drvdata->backlight.cdev); + flush_work(&drvdata->record_led.work); + flush_work(&drvdata->backlight.work); + kfree(drvdata); + } +} + +static int k90_probe(struct hid_device *dev, const struct hid_device_id *id) +{ + int ret; + struct usb_interface *usbif = to_usb_interface(dev->dev.parent); + + ret = hid_parse(dev); + if (ret != 0) { + hid_err(dev, "parse failed\n"); + return ret; + } + ret = hid_hw_start(dev, HID_CONNECT_DEFAULT); + if (ret != 0) { + hid_err(dev, "hw start failed\n"); + return ret; + } + + if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) { + ret = k90_init_special_functions(dev); + if (ret != 0) + hid_warn(dev, "Failed to initialize K90 special functions.\n"); + } else + hid_set_drvdata(dev, NULL); + + return 0; +} + +static void k90_remove(struct hid_device *dev) +{ + struct usb_interface *usbif = to_usb_interface(dev->dev.parent); + + if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) + k90_cleanup_special_functions(dev); + + hid_hw_stop(dev); +} + +static int k90_event(struct hid_device *dev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct k90_drvdata *drvdata = hid_get_drvdata(dev); + + if (!drvdata) + return 0; + + switch (usage->hid & HID_USAGE) { + case K90_USAGE_MACRO_RECORD_START: + drvdata->record_led.brightness = 1; + break; + case K90_USAGE_MACRO_RECORD_STOP: + drvdata->record_led.brightness = 0; + break; + case K90_USAGE_M1: + case K90_USAGE_M2: + case K90_USAGE_M3: + drvdata->current_profile = + (usage->hid & HID_USAGE) - K90_USAGE_PROFILE + 1; + break; + case K90_USAGE_META_OFF: + drvdata->meta_locked = 0; + break; + case K90_USAGE_META_ON: + drvdata->meta_locked = 1; + break; + case K90_USAGE_LIGHT_OFF: + case K90_USAGE_LIGHT_DIM: + case K90_USAGE_LIGHT_MEDIUM: + case K90_USAGE_LIGHT_BRIGHT: + drvdata->backlight.brightness = (usage->hid & HID_USAGE) - + K90_USAGE_LIGHT; + break; + default: + break; + } + + return 0; +} + +static int k90_input_mapping(struct hid_device *dev, struct hid_input *input, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + int gkey; + + gkey = k90_usage_to_gkey(usage->hid & HID_USAGE); + if (gkey != 0) { + hid_map_usage_clear(input, usage, bit, max, EV_KEY, + k90_gkey_map[gkey - 1]); + return 1; + } + if ((usage->hid & HID_USAGE) >= K90_USAGE_SPECIAL_MIN && + (usage->hid & HID_USAGE) <= K90_USAGE_SPECIAL_MAX) + return -1; + + return 0; +} + +static const struct hid_device_id k90_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, k90_devices); + +static struct hid_driver k90_driver = { + .name = "k90", + .id_table = k90_devices, + .probe = k90_probe, + .event = k90_event, + .remove = k90_remove, + .input_mapping = k90_input_mapping, +}; + +static int __init k90_init(void) +{ + int ret; + + k90_profile_class = class_create(THIS_MODULE, "k90_profile"); + if (IS_ERR(k90_profile_class)) + return PTR_ERR(k90_profile_class); + + ret = hid_register_driver(&k90_driver); + if (ret != 0) { + class_destroy(k90_profile_class); + return ret; + } + + return 0; +} + +static void k90_exit(void) +{ + hid_unregister_driver(&k90_driver); + class_destroy(k90_profile_class); +} + +module_init(k90_init); +module_exit(k90_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clement Vuchener"); +MODULE_DESCRIPTION("HID driver for Corsair Vengeance K90 Keyboard"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b3b225b..f23b9ac 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -246,6 +246,9 @@ #define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500 #define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff +#define USB_VENDOR_ID_CORSAIR 0x1b1c +#define USB_DEVICE_ID_CORSAIR_K90 0x1b02 + #define USB_VENDOR_ID_CREATIVELABS 0x041e #define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801 -- 2.4.3