All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/1] Corsair Vengeance K90 driver
@ 2015-08-29 14:03   ` Clément Vuchener
  0 siblings, 0 replies; 5+ messages in thread
From: Clément Vuchener @ 2015-08-29 14:03 UTC (permalink / raw)
  To: jkosina; +Cc: linux-api, linux-kernel, linux-input

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

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>/profile_number
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener@gmail.com>
+Description:	Get the number of the profile.
+
+What:		/sys/class/k90_profile/<profile>/bindings
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener@gmail.com>
+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/<profile>/data (2 bytes big endian)
+		 - array of bindings referencing the data from
+		   /sys/class/k90_profile/<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/<profile>/keys
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener@gmail.com>
+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/<profile>/data
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener@gmail.com>
+Description:	Write the key usage and macros used by
+		/sys/class/k90_profile/<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/<dev>/macro_mode
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener@gmail.com>
+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/<dev>/current_profile
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener@gmail.com>
+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 <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/leds.h>
+
+#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


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 1/1] Corsair Vengeance K90 driver
@ 2015-08-29 14:03   ` Clément Vuchener
  0 siblings, 0 replies; 5+ messages in thread
From: Clément Vuchener @ 2015-08-29 14:03 UTC (permalink / raw)
  To: jkosina-IBi9RG/b67k
  Cc: linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-input-u79uwXL29TY76Z2rM5mHXA

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

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>/profile_number
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+Description:	Get the number of the profile.
+
+What:		/sys/class/k90_profile/<profile>/bindings
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+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/<profile>/data (2 bytes big endian)
+		 - array of bindings referencing the data from
+		   /sys/class/k90_profile/<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/<profile>/keys
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+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/<profile>/data
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+Description:	Write the key usage and macros used by
+		/sys/class/k90_profile/<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/<dev>/macro_mode
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+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/<dev>/current_profile
+Date:		August 2015
+KernelVersion:	4.2
+Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+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 <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/leds.h>
+
+#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

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/1] Corsair Vengeance K90 driver
  2015-08-29 14:03   ` Clément Vuchener
  (?)
@ 2015-09-04 13:27   ` Jiri Kosina
  2015-09-04 15:18       ` Clément Vuchener
  -1 siblings, 1 reply; 5+ messages in thread
From: Jiri Kosina @ 2015-09-04 13:27 UTC (permalink / raw)
  To: Clément Vuchener; +Cc: linux-api, linux-kernel, linux-input

On Sat, 29 Aug 2015, Clément Vuchener wrote:


This is missing changelog and Signed-off-by: line. I know that you have 
provided that in your cover letter, but cover letter never make it to the 
actual GIT commits.

So please fix that and resend the patch, thanks.

> ---
>  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
> 
> 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>/profile_number
> +Date:		August 2015
> +KernelVersion:	4.2
> +Contact:	Clement Vuchener <clement.vuchener@gmail.com>
> +Description:	Get the number of the profile.
> +
> +What:		/sys/class/k90_profile/<profile>/bindings
> +Date:		August 2015
> +KernelVersion:	4.2
> +Contact:	Clement Vuchener <clement.vuchener@gmail.com>
> +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/<profile>/data (2 bytes big endian)
> +		 - array of bindings referencing the data from
> +		   /sys/class/k90_profile/<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)
> +

This looks like violation of one-value-per-attribute rule for sysfs ABI. 
Could you please think about it once again with this aspect on mind?

[ ... snip ... ]
> 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) },

Your mail client is corrupting long lines, making tha patch application 
impossible. Please fix that for your following submissions.

> 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

Usually we try to be a little bit more generic, and name the driver 
according to the vendor (and fold all the vedor-specific stuff into the 
one driver).

So my suggestion would be to name the driver hid-corsair, keeping the 
possibility for adding more devices later open.

[ ... snip ... ]
> +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);

So you apparently also depend on USB ...

> +	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);

... and also on LED subsystem, but this dependency is not expressed in 
Kconfig.

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/1] Corsair Vengeance K90 driver
@ 2015-09-04 15:18       ` Clément Vuchener
  0 siblings, 0 replies; 5+ messages in thread
From: Clément Vuchener @ 2015-09-04 15:18 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-api, linux-kernel, linux-input


On 09/04/2015 03:27 PM, Jiri Kosina wrote:
> On Sat, 29 Aug 2015, Clément Vuchener wrote:
>
>
> This is missing changelog and Signed-off-by: line. I know that you have 
> provided that in your cover letter, but cover letter never make it to the 
> actual GIT commits.
>
> So please fix that and resend the patch, thanks.
>
>> ---
>>  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
>>
>> 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>/profile_number
>> +Date:		August 2015
>> +KernelVersion:	4.2
>> +Contact:	Clement Vuchener <clement.vuchener@gmail.com>
>> +Description:	Get the number of the profile.
>> +
>> +What:		/sys/class/k90_profile/<profile>/bindings
>> +Date:		August 2015
>> +KernelVersion:	4.2
>> +Contact:	Clement Vuchener <clement.vuchener@gmail.com>
>> +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/<profile>/data (2 bytes big endian)
>> +		 - array of bindings referencing the data from
>> +		   /sys/class/k90_profile/<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)
>> +
> This looks like violation of one-value-per-attribute rule for sysfs ABI. 
> Could you please think about it once again with this aspect on mind?
Per key attributes would be nice but I don't think I can do that. The profile must be written all at once and I don't know any way to read it from the hardware (the windows driver I studied does not do it). So writing one value only would erase all the others.
I think I will remove this part from the driver. The same thing can easily be done in user space through libusb and writing profile should be exceptional enough that it is not a problem to detach the driver while doing it. That part of the driver is not really useful with the current ABI.
>
> [ ... snip ... ]
>> 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) },
> Your mail client is corrupting long lines, making tha patch application 
> impossible. Please fix that for your following submissions.
>
>> 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
> Usually we try to be a little bit more generic, and name the driver 
> according to the vendor (and fold all the vedor-specific stuff into the 
> one driver).
>
> So my suggestion would be to name the driver hid-corsair, keeping the 
> possibility for adding more devices later open.
>
> [ ... snip ... ]
>> +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);
> So you apparently also depend on USB ...
>
>> +	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);
> ... and also on LED subsystem, but this dependency is not expressed in 
> Kconfig.
>
> Thanks,
>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/1] Corsair Vengeance K90 driver
@ 2015-09-04 15:18       ` Clément Vuchener
  0 siblings, 0 replies; 5+ messages in thread
From: Clément Vuchener @ 2015-09-04 15:18 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-input-u79uwXL29TY76Z2rM5mHXA


On 09/04/2015 03:27 PM, Jiri Kosina wrote:
> On Sat, 29 Aug 2015, Clément Vuchener wrote:
>
>
> This is missing changelog and Signed-off-by: line. I know that you have 
> provided that in your cover letter, but cover letter never make it to the 
> actual GIT commits.
>
> So please fix that and resend the patch, thanks.
>
>> ---
>>  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
>>
>> 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>/profile_number
>> +Date:		August 2015
>> +KernelVersion:	4.2
>> +Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> +Description:	Get the number of the profile.
>> +
>> +What:		/sys/class/k90_profile/<profile>/bindings
>> +Date:		August 2015
>> +KernelVersion:	4.2
>> +Contact:	Clement Vuchener <clement.vuchener-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> +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/<profile>/data (2 bytes big endian)
>> +		 - array of bindings referencing the data from
>> +		   /sys/class/k90_profile/<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)
>> +
> This looks like violation of one-value-per-attribute rule for sysfs ABI. 
> Could you please think about it once again with this aspect on mind?
Per key attributes would be nice but I don't think I can do that. The profile must be written all at once and I don't know any way to read it from the hardware (the windows driver I studied does not do it). So writing one value only would erase all the others.
I think I will remove this part from the driver. The same thing can easily be done in user space through libusb and writing profile should be exceptional enough that it is not a problem to detach the driver while doing it. That part of the driver is not really useful with the current ABI.
>
> [ ... snip ... ]
>> 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) },
> Your mail client is corrupting long lines, making tha patch application 
> impossible. Please fix that for your following submissions.
>
>> 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
> Usually we try to be a little bit more generic, and name the driver 
> according to the vendor (and fold all the vedor-specific stuff into the 
> one driver).
>
> So my suggestion would be to name the driver hid-corsair, keeping the 
> possibility for adding more devices later open.
>
> [ ... snip ... ]
>> +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);
> So you apparently also depend on USB ...
>
>> +	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);
> ... and also on LED subsystem, but this dependency is not expressed in 
> Kconfig.
>
> Thanks,
>

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2015-09-04 15:18 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <cover.1440853951.git.clement.vuchener@gmail.com>
2015-08-29 14:03 ` [PATCH 1/1] Corsair Vengeance K90 driver Clément Vuchener
2015-08-29 14:03   ` Clément Vuchener
2015-09-04 13:27   ` Jiri Kosina
2015-09-04 15:18     ` Clément Vuchener
2015-09-04 15:18       ` Clément Vuchener

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.