Commit d41c2a7011dffc60571eab8dc4e2a297ef106f44
Committed by
Jiri Kosina
1 parent
8e8da023f5
Exists in
master
and in
38 other branches
HID: roccat: Add support for Isku keyboard
This patch adds support for Roccat Isku keyboard. Userland tools can be found at http://sourceforge.net/projects/roccat Signed-off-by: Stefan Achatz <erazor_de@users.sourceforge.net> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Showing 7 changed files with 779 additions and 0 deletions Side-by-side Diff
Documentation/ABI/testing/sysfs-driver-hid-roccat-isku
1 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/actual_profile | |
2 | +Date: June 2011 | |
3 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
4 | +Description: The integer value of this attribute ranges from 0-4. | |
5 | + When read, this attribute returns the number of the actual | |
6 | + profile. This value is persistent, so its equivalent to the | |
7 | + profile that's active when the device is powered on next time. | |
8 | + When written, this file sets the number of the startup profile | |
9 | + and the device activates this profile immediately. | |
10 | +Users: http://roccat.sourceforge.net | |
11 | + | |
12 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/info | |
13 | +Date: June 2011 | |
14 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
15 | +Description: When read, this file returns general data like firmware version. | |
16 | + The data is 6 bytes long. | |
17 | + This file is readonly. | |
18 | +Users: http://roccat.sourceforge.net | |
19 | + | |
20 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/key_mask | |
21 | +Date: June 2011 | |
22 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
23 | +Description: When written, this file lets one deactivate certain keys like | |
24 | + windows and application keys, to prevent accidental presses. | |
25 | + Profile number for which this settings occur is included in | |
26 | + written data. The data has to be 6 bytes long. | |
27 | + Before reading this file, control has to be written to select | |
28 | + which profile to read. | |
29 | +Users: http://roccat.sourceforge.net | |
30 | + | |
31 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/keys_capslock | |
32 | +Date: June 2011 | |
33 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
34 | +Description: When written, this file lets one set the function of the | |
35 | + capslock key for a specific profile. Profile number is included | |
36 | + in written data. The data has to be 6 bytes long. | |
37 | + Before reading this file, control has to be written to select | |
38 | + which profile to read. | |
39 | +Users: http://roccat.sourceforge.net | |
40 | + | |
41 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/keys_easyzone | |
42 | +Date: June 2011 | |
43 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
44 | +Description: When written, this file lets one set the function of the | |
45 | + easyzone keys for a specific profile. Profile number is included | |
46 | + in written data. The data has to be 65 bytes long. | |
47 | + Before reading this file, control has to be written to select | |
48 | + which profile to read. | |
49 | +Users: http://roccat.sourceforge.net | |
50 | + | |
51 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/keys_function | |
52 | +Date: June 2011 | |
53 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
54 | +Description: When written, this file lets one set the function of the | |
55 | + function keys for a specific profile. Profile number is included | |
56 | + in written data. The data has to be 41 bytes long. | |
57 | + Before reading this file, control has to be written to select | |
58 | + which profile to read. | |
59 | +Users: http://roccat.sourceforge.net | |
60 | + | |
61 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/keys_macro | |
62 | +Date: June 2011 | |
63 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
64 | +Description: When written, this file lets one set the function of the macro | |
65 | + keys for a specific profile. Profile number is included in | |
66 | + written data. The data has to be 35 bytes long. | |
67 | + Before reading this file, control has to be written to select | |
68 | + which profile to read. | |
69 | +Users: http://roccat.sourceforge.net | |
70 | + | |
71 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/keys_media | |
72 | +Date: June 2011 | |
73 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
74 | +Description: When written, this file lets one set the function of the media | |
75 | + keys for a specific profile. Profile number is included in | |
76 | + written data. The data has to be 29 bytes long. | |
77 | + Before reading this file, control has to be written to select | |
78 | + which profile to read. | |
79 | +Users: http://roccat.sourceforge.net | |
80 | + | |
81 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/keys_thumbster | |
82 | +Date: June 2011 | |
83 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
84 | +Description: When written, this file lets one set the function of the | |
85 | + thumbster keys for a specific profile. Profile number is included | |
86 | + in written data. The data has to be 23 bytes long. | |
87 | + Before reading this file, control has to be written to select | |
88 | + which profile to read. | |
89 | +Users: http://roccat.sourceforge.net | |
90 | + | |
91 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/last_set | |
92 | +Date: June 2011 | |
93 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
94 | +Description: When written, this file lets one set the time in secs since | |
95 | + epoch in which the last configuration took place. | |
96 | + The data has to be 20 bytes long. | |
97 | +Users: http://roccat.sourceforge.net | |
98 | + | |
99 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/light | |
100 | +Date: June 2011 | |
101 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
102 | +Description: When written, this file lets one set the backlight intensity for | |
103 | + a specific profile. Profile number is included in written data. | |
104 | + The data has to be 10 bytes long. | |
105 | + Before reading this file, control has to be written to select | |
106 | + which profile to read. | |
107 | +Users: http://roccat.sourceforge.net | |
108 | + | |
109 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/macro | |
110 | +Date: June 2011 | |
111 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
112 | +Description: When written, this file lets one store macros with max 500 | |
113 | + keystrokes for a specific button for a specific profile. | |
114 | + Button and profile numbers are included in written data. | |
115 | + The data has to be 2083 bytes long. | |
116 | + Before reading this file, control has to be written to select | |
117 | + which profile and key to read. | |
118 | +Users: http://roccat.sourceforge.net | |
119 | + | |
120 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/control | |
121 | +Date: June 2011 | |
122 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
123 | +Description: When written, this file lets one select which data from which | |
124 | + profile will be read next. The data has to be 3 bytes long. | |
125 | + This file is writeonly. | |
126 | +Users: http://roccat.sourceforge.net | |
127 | + | |
128 | +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/isku/roccatisku<minor>/talk | |
129 | +Date: June 2011 | |
130 | +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> | |
131 | +Description: When written, this file lets one trigger easyshift functionality | |
132 | + from the host. | |
133 | + The data has to be 16 bytes long. | |
134 | + This file is writeonly. | |
135 | +Users: http://roccat.sourceforge.net |
drivers/hid/Kconfig
... | ... | @@ -492,6 +492,13 @@ |
492 | 492 | ---help--- |
493 | 493 | Support for Roccat Arvo keyboard. |
494 | 494 | |
495 | +config HID_ROCCAT_ISKU | |
496 | + tristate "Roccat Isku keyboard support" | |
497 | + depends on USB_HID | |
498 | + depends on HID_ROCCAT | |
499 | + ---help--- | |
500 | + Support for Roccat Isku keyboard. | |
501 | + | |
495 | 502 | config HID_ROCCAT_KONE |
496 | 503 | tristate "Roccat Kone Mouse support" |
497 | 504 | depends on USB_HID |
drivers/hid/Makefile
... | ... | @@ -59,6 +59,7 @@ |
59 | 59 | obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o |
60 | 60 | obj-$(CONFIG_HID_ROCCAT_COMMON) += hid-roccat-common.o |
61 | 61 | obj-$(CONFIG_HID_ROCCAT_ARVO) += hid-roccat-arvo.o |
62 | +obj-$(CONFIG_HID_ROCCAT_ISKU) += hid-roccat-isku.o | |
62 | 63 | obj-$(CONFIG_HID_ROCCAT_KONE) += hid-roccat-kone.o |
63 | 64 | obj-$(CONFIG_HID_ROCCAT_KONEPLUS) += hid-roccat-koneplus.o |
64 | 65 | obj-$(CONFIG_HID_ROCCAT_KOVAPLUS) += hid-roccat-kovaplus.o |
drivers/hid/hid-core.c
... | ... | @@ -1503,6 +1503,7 @@ |
1503 | 1503 | { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, |
1504 | 1504 | { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) }, |
1505 | 1505 | { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) }, |
1506 | + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) }, | |
1506 | 1507 | { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPLUS) }, |
1507 | 1508 | { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, |
1508 | 1509 | { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) }, |
drivers/hid/hid-ids.h
... | ... | @@ -586,6 +586,7 @@ |
586 | 586 | |
587 | 587 | #define USB_VENDOR_ID_ROCCAT 0x1e7d |
588 | 588 | #define USB_DEVICE_ID_ROCCAT_ARVO 0x30d4 |
589 | +#define USB_DEVICE_ID_ROCCAT_ISKU 0x319c | |
589 | 590 | #define USB_DEVICE_ID_ROCCAT_KONE 0x2ced |
590 | 591 | #define USB_DEVICE_ID_ROCCAT_KONEPLUS 0x2d51 |
591 | 592 | #define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50 |
drivers/hid/hid-roccat-isku.c
1 | +/* | |
2 | + * Roccat Isku driver for Linux | |
3 | + * | |
4 | + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> | |
5 | + */ | |
6 | + | |
7 | +/* | |
8 | + * This program is free software; you can redistribute it and/or modify it | |
9 | + * under the terms of the GNU General Public License as published by the Free | |
10 | + * Software Foundation; either version 2 of the License, or (at your option) | |
11 | + * any later version. | |
12 | + */ | |
13 | + | |
14 | +/* | |
15 | + * Roccat Isku is a gamer keyboard with macro keys that can be configured in | |
16 | + * 5 profiles. | |
17 | + */ | |
18 | + | |
19 | +#include <linux/device.h> | |
20 | +#include <linux/input.h> | |
21 | +#include <linux/hid.h> | |
22 | +#include <linux/module.h> | |
23 | +#include <linux/slab.h> | |
24 | +#include <linux/hid-roccat.h> | |
25 | +#include "hid-ids.h" | |
26 | +#include "hid-roccat-common.h" | |
27 | +#include "hid-roccat-isku.h" | |
28 | + | |
29 | +static struct class *isku_class; | |
30 | + | |
31 | +static void isku_profile_activated(struct isku_device *isku, uint new_profile) | |
32 | +{ | |
33 | + isku->actual_profile = new_profile; | |
34 | +} | |
35 | + | |
36 | +static int isku_receive(struct usb_device *usb_dev, uint command, | |
37 | + void *buf, uint size) | |
38 | +{ | |
39 | + return roccat_common_receive(usb_dev, command, buf, size); | |
40 | +} | |
41 | + | |
42 | +static int isku_receive_control_status(struct usb_device *usb_dev) | |
43 | +{ | |
44 | + int retval; | |
45 | + struct isku_control control; | |
46 | + | |
47 | + do { | |
48 | + msleep(50); | |
49 | + retval = isku_receive(usb_dev, ISKU_COMMAND_CONTROL, | |
50 | + &control, sizeof(struct isku_control)); | |
51 | + | |
52 | + if (retval) | |
53 | + return retval; | |
54 | + | |
55 | + switch (control.value) { | |
56 | + case ISKU_CONTROL_VALUE_STATUS_OK: | |
57 | + return 0; | |
58 | + case ISKU_CONTROL_VALUE_STATUS_WAIT: | |
59 | + continue; | |
60 | + case ISKU_CONTROL_VALUE_STATUS_INVALID: | |
61 | + /* seems to be critical - replug necessary */ | |
62 | + case ISKU_CONTROL_VALUE_STATUS_OVERLOAD: | |
63 | + return -EINVAL; | |
64 | + default: | |
65 | + hid_err(usb_dev, "isku_receive_control_status: " | |
66 | + "unknown response value 0x%x\n", | |
67 | + control.value); | |
68 | + return -EINVAL; | |
69 | + } | |
70 | + | |
71 | + } while (1); | |
72 | +} | |
73 | + | |
74 | +static int isku_send(struct usb_device *usb_dev, uint command, | |
75 | + void const *buf, uint size) | |
76 | +{ | |
77 | + int retval; | |
78 | + | |
79 | + retval = roccat_common_send(usb_dev, command, buf, size); | |
80 | + if (retval) | |
81 | + return retval; | |
82 | + | |
83 | + return isku_receive_control_status(usb_dev); | |
84 | +} | |
85 | + | |
86 | +static int isku_get_actual_profile(struct usb_device *usb_dev) | |
87 | +{ | |
88 | + struct isku_actual_profile buf; | |
89 | + int retval; | |
90 | + | |
91 | + retval = isku_receive(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE, | |
92 | + &buf, sizeof(struct isku_actual_profile)); | |
93 | + return retval ? retval : buf.actual_profile; | |
94 | +} | |
95 | + | |
96 | +static int isku_set_actual_profile(struct usb_device *usb_dev, int new_profile) | |
97 | +{ | |
98 | + struct isku_actual_profile buf; | |
99 | + | |
100 | + buf.command = ISKU_COMMAND_ACTUAL_PROFILE; | |
101 | + buf.size = sizeof(struct isku_actual_profile); | |
102 | + buf.actual_profile = new_profile; | |
103 | + return isku_send(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE, &buf, | |
104 | + sizeof(struct isku_actual_profile)); | |
105 | +} | |
106 | + | |
107 | +static ssize_t isku_sysfs_show_actual_profile(struct device *dev, | |
108 | + struct device_attribute *attr, char *buf) | |
109 | +{ | |
110 | + struct isku_device *isku = | |
111 | + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); | |
112 | + return snprintf(buf, PAGE_SIZE, "%d\n", isku->actual_profile); | |
113 | +} | |
114 | + | |
115 | +static ssize_t isku_sysfs_set_actual_profile(struct device *dev, | |
116 | + struct device_attribute *attr, char const *buf, size_t size) | |
117 | +{ | |
118 | + struct isku_device *isku; | |
119 | + struct usb_device *usb_dev; | |
120 | + unsigned long profile; | |
121 | + int retval; | |
122 | + struct isku_roccat_report roccat_report; | |
123 | + | |
124 | + dev = dev->parent->parent; | |
125 | + isku = hid_get_drvdata(dev_get_drvdata(dev)); | |
126 | + usb_dev = interface_to_usbdev(to_usb_interface(dev)); | |
127 | + | |
128 | + retval = strict_strtoul(buf, 10, &profile); | |
129 | + if (retval) | |
130 | + return retval; | |
131 | + | |
132 | + if (profile > 4) | |
133 | + return -EINVAL; | |
134 | + | |
135 | + mutex_lock(&isku->isku_lock); | |
136 | + | |
137 | + retval = isku_set_actual_profile(usb_dev, profile); | |
138 | + if (retval) { | |
139 | + mutex_unlock(&isku->isku_lock); | |
140 | + return retval; | |
141 | + } | |
142 | + | |
143 | + isku_profile_activated(isku, profile); | |
144 | + | |
145 | + roccat_report.event = ISKU_REPORT_BUTTON_EVENT_PROFILE; | |
146 | + roccat_report.data1 = profile + 1; | |
147 | + roccat_report.data2 = 0; | |
148 | + roccat_report.profile = profile + 1; | |
149 | + roccat_report_event(isku->chrdev_minor, (uint8_t const *)&roccat_report); | |
150 | + | |
151 | + mutex_unlock(&isku->isku_lock); | |
152 | + | |
153 | + return size; | |
154 | +} | |
155 | + | |
156 | +static struct device_attribute isku_attributes[] = { | |
157 | + __ATTR(actual_profile, 0660, | |
158 | + isku_sysfs_show_actual_profile, | |
159 | + isku_sysfs_set_actual_profile), | |
160 | + __ATTR_NULL | |
161 | +}; | |
162 | + | |
163 | +static ssize_t isku_sysfs_read(struct file *fp, struct kobject *kobj, | |
164 | + char *buf, loff_t off, size_t count, | |
165 | + size_t real_size, uint command) | |
166 | +{ | |
167 | + struct device *dev = | |
168 | + container_of(kobj, struct device, kobj)->parent->parent; | |
169 | + struct isku_device *isku = hid_get_drvdata(dev_get_drvdata(dev)); | |
170 | + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); | |
171 | + int retval; | |
172 | + | |
173 | + if (off >= real_size) | |
174 | + return 0; | |
175 | + | |
176 | + if (off != 0 || count != real_size) | |
177 | + return -EINVAL; | |
178 | + | |
179 | + mutex_lock(&isku->isku_lock); | |
180 | + retval = isku_receive(usb_dev, command, buf, real_size); | |
181 | + mutex_unlock(&isku->isku_lock); | |
182 | + | |
183 | + return retval ? retval : real_size; | |
184 | +} | |
185 | + | |
186 | +static ssize_t isku_sysfs_write(struct file *fp, struct kobject *kobj, | |
187 | + void const *buf, loff_t off, size_t count, | |
188 | + size_t real_size, uint command) | |
189 | +{ | |
190 | + struct device *dev = | |
191 | + container_of(kobj, struct device, kobj)->parent->parent; | |
192 | + struct isku_device *isku = hid_get_drvdata(dev_get_drvdata(dev)); | |
193 | + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); | |
194 | + int retval; | |
195 | + | |
196 | + if (off != 0 || count != real_size) | |
197 | + return -EINVAL; | |
198 | + | |
199 | + mutex_lock(&isku->isku_lock); | |
200 | + retval = isku_send(usb_dev, command, (void *)buf, real_size); | |
201 | + mutex_unlock(&isku->isku_lock); | |
202 | + | |
203 | + return retval ? retval : real_size; | |
204 | +} | |
205 | + | |
206 | +#define ISKU_SYSFS_W(thingy, THINGY) \ | |
207 | +static ssize_t isku_sysfs_write_ ## thingy(struct file *fp, struct kobject *kobj, \ | |
208 | + struct bin_attribute *attr, char *buf, \ | |
209 | + loff_t off, size_t count) \ | |
210 | +{ \ | |
211 | + return isku_sysfs_write(fp, kobj, buf, off, count, \ | |
212 | + sizeof(struct isku_ ## thingy), ISKU_COMMAND_ ## THINGY); \ | |
213 | +} | |
214 | + | |
215 | +#define ISKU_SYSFS_R(thingy, THINGY) \ | |
216 | +static ssize_t isku_sysfs_read_ ## thingy(struct file *fp, struct kobject *kobj, \ | |
217 | + struct bin_attribute *attr, char *buf, \ | |
218 | + loff_t off, size_t count) \ | |
219 | +{ \ | |
220 | + return isku_sysfs_read(fp, kobj, buf, off, count, \ | |
221 | + sizeof(struct isku_ ## thingy), ISKU_COMMAND_ ## THINGY); \ | |
222 | +} | |
223 | + | |
224 | +#define ISKU_SYSFS_RW(thingy, THINGY) \ | |
225 | +ISKU_SYSFS_R(thingy, THINGY) \ | |
226 | +ISKU_SYSFS_W(thingy, THINGY) | |
227 | + | |
228 | +#define ISKU_BIN_ATTR_RW(thingy) \ | |
229 | +{ \ | |
230 | + .attr = { .name = #thingy, .mode = 0660 }, \ | |
231 | + .size = sizeof(struct isku_ ## thingy), \ | |
232 | + .read = isku_sysfs_read_ ## thingy, \ | |
233 | + .write = isku_sysfs_write_ ## thingy \ | |
234 | +} | |
235 | + | |
236 | +#define ISKU_BIN_ATTR_R(thingy) \ | |
237 | +{ \ | |
238 | + .attr = { .name = #thingy, .mode = 0440 }, \ | |
239 | + .size = sizeof(struct isku_ ## thingy), \ | |
240 | + .read = isku_sysfs_read_ ## thingy, \ | |
241 | +} | |
242 | + | |
243 | +#define ISKU_BIN_ATTR_W(thingy) \ | |
244 | +{ \ | |
245 | + .attr = { .name = #thingy, .mode = 0220 }, \ | |
246 | + .size = sizeof(struct isku_ ## thingy), \ | |
247 | + .write = isku_sysfs_write_ ## thingy \ | |
248 | +} | |
249 | + | |
250 | +ISKU_SYSFS_RW(macro, MACRO) | |
251 | +ISKU_SYSFS_RW(keys_function, KEYS_FUNCTION) | |
252 | +ISKU_SYSFS_RW(keys_easyzone, KEYS_EASYZONE) | |
253 | +ISKU_SYSFS_RW(keys_media, KEYS_MEDIA) | |
254 | +ISKU_SYSFS_RW(keys_thumbster, KEYS_THUMBSTER) | |
255 | +ISKU_SYSFS_RW(keys_macro, KEYS_MACRO) | |
256 | +ISKU_SYSFS_RW(keys_capslock, KEYS_CAPSLOCK) | |
257 | +ISKU_SYSFS_RW(light, LIGHT) | |
258 | +ISKU_SYSFS_RW(key_mask, KEY_MASK) | |
259 | +ISKU_SYSFS_RW(last_set, LAST_SET) | |
260 | +ISKU_SYSFS_W(talk, TALK) | |
261 | +ISKU_SYSFS_R(info, INFO) | |
262 | +ISKU_SYSFS_W(control, CONTROL) | |
263 | + | |
264 | +static struct bin_attribute isku_bin_attributes[] = { | |
265 | + ISKU_BIN_ATTR_RW(macro), | |
266 | + ISKU_BIN_ATTR_RW(keys_function), | |
267 | + ISKU_BIN_ATTR_RW(keys_easyzone), | |
268 | + ISKU_BIN_ATTR_RW(keys_media), | |
269 | + ISKU_BIN_ATTR_RW(keys_thumbster), | |
270 | + ISKU_BIN_ATTR_RW(keys_macro), | |
271 | + ISKU_BIN_ATTR_RW(keys_capslock), | |
272 | + ISKU_BIN_ATTR_RW(light), | |
273 | + ISKU_BIN_ATTR_RW(key_mask), | |
274 | + ISKU_BIN_ATTR_RW(last_set), | |
275 | + ISKU_BIN_ATTR_W(talk), | |
276 | + ISKU_BIN_ATTR_R(info), | |
277 | + ISKU_BIN_ATTR_W(control), | |
278 | + __ATTR_NULL | |
279 | +}; | |
280 | + | |
281 | +static int isku_init_isku_device_struct(struct usb_device *usb_dev, | |
282 | + struct isku_device *isku) | |
283 | +{ | |
284 | + int retval; | |
285 | + | |
286 | + mutex_init(&isku->isku_lock); | |
287 | + | |
288 | + retval = isku_get_actual_profile(usb_dev); | |
289 | + if (retval < 0) | |
290 | + return retval; | |
291 | + isku_profile_activated(isku, retval); | |
292 | + | |
293 | + return 0; | |
294 | +} | |
295 | + | |
296 | +static int isku_init_specials(struct hid_device *hdev) | |
297 | +{ | |
298 | + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); | |
299 | + struct usb_device *usb_dev = interface_to_usbdev(intf); | |
300 | + struct isku_device *isku; | |
301 | + int retval; | |
302 | + | |
303 | + if (intf->cur_altsetting->desc.bInterfaceProtocol | |
304 | + != ISKU_USB_INTERFACE_PROTOCOL) { | |
305 | + hid_set_drvdata(hdev, NULL); | |
306 | + return 0; | |
307 | + } | |
308 | + | |
309 | + isku = kzalloc(sizeof(*isku), GFP_KERNEL); | |
310 | + if (!isku) { | |
311 | + hid_err(hdev, "can't alloc device descriptor\n"); | |
312 | + return -ENOMEM; | |
313 | + } | |
314 | + hid_set_drvdata(hdev, isku); | |
315 | + | |
316 | + retval = isku_init_isku_device_struct(usb_dev, isku); | |
317 | + if (retval) { | |
318 | + hid_err(hdev, "couldn't init struct isku_device\n"); | |
319 | + goto exit_free; | |
320 | + } | |
321 | + | |
322 | + retval = roccat_connect(isku_class, hdev, | |
323 | + sizeof(struct isku_roccat_report)); | |
324 | + if (retval < 0) { | |
325 | + hid_err(hdev, "couldn't init char dev\n"); | |
326 | + } else { | |
327 | + isku->chrdev_minor = retval; | |
328 | + isku->roccat_claimed = 1; | |
329 | + } | |
330 | + | |
331 | + return 0; | |
332 | +exit_free: | |
333 | + kfree(isku); | |
334 | + return retval; | |
335 | +} | |
336 | + | |
337 | +static void isku_remove_specials(struct hid_device *hdev) | |
338 | +{ | |
339 | + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); | |
340 | + struct isku_device *isku; | |
341 | + | |
342 | + if (intf->cur_altsetting->desc.bInterfaceProtocol | |
343 | + != ISKU_USB_INTERFACE_PROTOCOL) | |
344 | + return; | |
345 | + | |
346 | + isku = hid_get_drvdata(hdev); | |
347 | + if (isku->roccat_claimed) | |
348 | + roccat_disconnect(isku->chrdev_minor); | |
349 | + kfree(isku); | |
350 | +} | |
351 | + | |
352 | +static int isku_probe(struct hid_device *hdev, | |
353 | + const struct hid_device_id *id) | |
354 | +{ | |
355 | + int retval; | |
356 | + | |
357 | + retval = hid_parse(hdev); | |
358 | + if (retval) { | |
359 | + hid_err(hdev, "parse failed\n"); | |
360 | + goto exit; | |
361 | + } | |
362 | + | |
363 | + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | |
364 | + if (retval) { | |
365 | + hid_err(hdev, "hw start failed\n"); | |
366 | + goto exit; | |
367 | + } | |
368 | + | |
369 | + retval = isku_init_specials(hdev); | |
370 | + if (retval) { | |
371 | + hid_err(hdev, "couldn't install keyboard\n"); | |
372 | + goto exit_stop; | |
373 | + } | |
374 | + | |
375 | + return 0; | |
376 | + | |
377 | +exit_stop: | |
378 | + hid_hw_stop(hdev); | |
379 | +exit: | |
380 | + return retval; | |
381 | +} | |
382 | + | |
383 | +static void isku_remove(struct hid_device *hdev) | |
384 | +{ | |
385 | + isku_remove_specials(hdev); | |
386 | + hid_hw_stop(hdev); | |
387 | +} | |
388 | + | |
389 | +static void isku_keep_values_up_to_date(struct isku_device *isku, | |
390 | + u8 const *data) | |
391 | +{ | |
392 | + struct isku_report_button const *button_report; | |
393 | + | |
394 | + switch (data[0]) { | |
395 | + case ISKU_REPORT_NUMBER_BUTTON: | |
396 | + button_report = (struct isku_report_button const *)data; | |
397 | + switch (button_report->event) { | |
398 | + case ISKU_REPORT_BUTTON_EVENT_PROFILE: | |
399 | + isku_profile_activated(isku, button_report->data1 - 1); | |
400 | + break; | |
401 | + } | |
402 | + break; | |
403 | + } | |
404 | +} | |
405 | + | |
406 | +static void isku_report_to_chrdev(struct isku_device const *isku, | |
407 | + u8 const *data) | |
408 | +{ | |
409 | + struct isku_roccat_report roccat_report; | |
410 | + struct isku_report_button const *button_report; | |
411 | + | |
412 | + if (data[0] != ISKU_REPORT_NUMBER_BUTTON) | |
413 | + return; | |
414 | + | |
415 | + button_report = (struct isku_report_button const *)data; | |
416 | + | |
417 | + roccat_report.event = button_report->event; | |
418 | + roccat_report.data1 = button_report->data1; | |
419 | + roccat_report.data2 = button_report->data2; | |
420 | + roccat_report.profile = isku->actual_profile + 1; | |
421 | + roccat_report_event(isku->chrdev_minor, | |
422 | + (uint8_t const *)&roccat_report); | |
423 | +} | |
424 | + | |
425 | +static int isku_raw_event(struct hid_device *hdev, | |
426 | + struct hid_report *report, u8 *data, int size) | |
427 | +{ | |
428 | + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); | |
429 | + struct isku_device *isku = hid_get_drvdata(hdev); | |
430 | + | |
431 | + if (intf->cur_altsetting->desc.bInterfaceProtocol | |
432 | + != ISKU_USB_INTERFACE_PROTOCOL) | |
433 | + return 0; | |
434 | + | |
435 | + if (isku == NULL) | |
436 | + return 0; | |
437 | + | |
438 | + isku_keep_values_up_to_date(isku, data); | |
439 | + | |
440 | + if (isku->roccat_claimed) | |
441 | + isku_report_to_chrdev(isku, data); | |
442 | + | |
443 | + return 0; | |
444 | +} | |
445 | + | |
446 | +static const struct hid_device_id isku_devices[] = { | |
447 | + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) }, | |
448 | + { } | |
449 | +}; | |
450 | + | |
451 | +MODULE_DEVICE_TABLE(hid, isku_devices); | |
452 | + | |
453 | +static struct hid_driver isku_driver = { | |
454 | + .name = "isku", | |
455 | + .id_table = isku_devices, | |
456 | + .probe = isku_probe, | |
457 | + .remove = isku_remove, | |
458 | + .raw_event = isku_raw_event | |
459 | +}; | |
460 | + | |
461 | +static int __init isku_init(void) | |
462 | +{ | |
463 | + int retval; | |
464 | + isku_class = class_create(THIS_MODULE, "isku"); | |
465 | + if (IS_ERR(isku_class)) | |
466 | + return PTR_ERR(isku_class); | |
467 | + isku_class->dev_attrs = isku_attributes; | |
468 | + isku_class->dev_bin_attrs = isku_bin_attributes; | |
469 | + | |
470 | + retval = hid_register_driver(&isku_driver); | |
471 | + if (retval) | |
472 | + class_destroy(isku_class); | |
473 | + return retval; | |
474 | +} | |
475 | + | |
476 | +static void __exit isku_exit(void) | |
477 | +{ | |
478 | + hid_unregister_driver(&isku_driver); | |
479 | + class_destroy(isku_class); | |
480 | +} | |
481 | + | |
482 | +module_init(isku_init); | |
483 | +module_exit(isku_exit); | |
484 | + | |
485 | +MODULE_AUTHOR("Stefan Achatz"); | |
486 | +MODULE_DESCRIPTION("USB Roccat Isku driver"); | |
487 | +MODULE_LICENSE("GPL v2"); |
drivers/hid/hid-roccat-isku.h
1 | +#ifndef __HID_ROCCAT_ISKU_H | |
2 | +#define __HID_ROCCAT_ISKU_H | |
3 | + | |
4 | +/* | |
5 | + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> | |
6 | + */ | |
7 | + | |
8 | +/* | |
9 | + * This program is free software; you can redistribute it and/or modify it | |
10 | + * under the terms of the GNU General Public License as published by the Free | |
11 | + * Software Foundation; either version 2 of the License, or (at your option) | |
12 | + * any later version. | |
13 | + */ | |
14 | + | |
15 | +#include <linux/types.h> | |
16 | + | |
17 | +enum { | |
18 | + ISKU_PROFILE_NUM = 5, | |
19 | + ISKU_USB_INTERFACE_PROTOCOL = 0, | |
20 | +}; | |
21 | + | |
22 | +struct isku_control { | |
23 | + uint8_t command; /* ISKU_COMMAND_CONTROL */ | |
24 | + uint8_t value; | |
25 | + uint8_t request; | |
26 | +} __packed; | |
27 | + | |
28 | +enum isku_control_values { | |
29 | + ISKU_CONTROL_VALUE_STATUS_OVERLOAD = 0, | |
30 | + ISKU_CONTROL_VALUE_STATUS_OK = 1, | |
31 | + ISKU_CONTROL_VALUE_STATUS_INVALID = 2, | |
32 | + ISKU_CONTROL_VALUE_STATUS_WAIT = 3, | |
33 | +}; | |
34 | + | |
35 | +struct isku_actual_profile { | |
36 | + uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */ | |
37 | + uint8_t size; /* always 3 */ | |
38 | + uint8_t actual_profile; | |
39 | +} __packed; | |
40 | + | |
41 | +struct isku_key_mask { | |
42 | + uint8_t command; /* ISKU_COMMAND_KEY_MASK */ | |
43 | + uint8_t size; /* 6 */ | |
44 | + uint8_t profile_number; /* 0-4 */ | |
45 | + uint8_t mask; | |
46 | + uint16_t checksum; | |
47 | +} __packed; | |
48 | + | |
49 | +struct isku_keys_function { | |
50 | + uint8_t data[0x29]; | |
51 | +} __packed; | |
52 | + | |
53 | +struct isku_keys_easyzone { | |
54 | + uint8_t data[0x41]; | |
55 | +} __packed; | |
56 | + | |
57 | +struct isku_keys_media { | |
58 | + uint8_t data[0x1d]; | |
59 | +} __packed; | |
60 | + | |
61 | +struct isku_keys_thumbster { | |
62 | + uint8_t data[0x17]; | |
63 | +} __packed; | |
64 | + | |
65 | +struct isku_keys_macro { | |
66 | + uint8_t data[0x23]; | |
67 | +} __packed; | |
68 | + | |
69 | +struct isku_keys_capslock { | |
70 | + uint8_t data[0x6]; | |
71 | +} __packed; | |
72 | + | |
73 | +struct isku_macro { | |
74 | + uint8_t data[0x823]; | |
75 | +} __packed; | |
76 | + | |
77 | +struct isku_light { | |
78 | + uint8_t data[0xa]; | |
79 | +} __packed; | |
80 | + | |
81 | +struct isku_info { | |
82 | + uint8_t data[2]; | |
83 | + uint8_t firmware_version; | |
84 | + uint8_t unknown[3]; | |
85 | +} __packed; | |
86 | + | |
87 | +struct isku_talk { | |
88 | + uint8_t data[0x10]; | |
89 | +} __packed; | |
90 | + | |
91 | +struct isku_last_set { | |
92 | + uint8_t data[0x14]; | |
93 | +} __packed; | |
94 | + | |
95 | +enum isku_commands { | |
96 | + ISKU_COMMAND_CONTROL = 0x4, | |
97 | + ISKU_COMMAND_ACTUAL_PROFILE = 0x5, | |
98 | + ISKU_COMMAND_KEY_MASK = 0x7, | |
99 | + ISKU_COMMAND_KEYS_FUNCTION = 0x8, | |
100 | + ISKU_COMMAND_KEYS_EASYZONE = 0x9, | |
101 | + ISKU_COMMAND_KEYS_MEDIA = 0xa, | |
102 | + ISKU_COMMAND_KEYS_THUMBSTER = 0xb, | |
103 | + ISKU_COMMAND_KEYS_MACRO = 0xd, | |
104 | + ISKU_COMMAND_MACRO = 0xe, | |
105 | + ISKU_COMMAND_INFO = 0xf, | |
106 | + ISKU_COMMAND_LIGHT = 0x10, | |
107 | + ISKU_COMMAND_KEYS_CAPSLOCK = 0x13, | |
108 | + ISKU_COMMAND_LAST_SET = 0x14, | |
109 | + ISKU_COMMAND_15 = 0x15, | |
110 | + ISKU_COMMAND_TALK = 0x16, | |
111 | + ISKU_COMMAND_FIRMWARE_WRITE = 0x1b, | |
112 | + ISKU_COMMAND_FIRMWARE_WRITE_CONTROL = 0x1c, | |
113 | +}; | |
114 | + | |
115 | +struct isku_report_button { | |
116 | + uint8_t number; /* ISKU_REPORT_NUMBER_BUTTON */ | |
117 | + uint8_t zero; | |
118 | + uint8_t event; | |
119 | + uint8_t data1; | |
120 | + uint8_t data2; | |
121 | +}; | |
122 | + | |
123 | +enum isku_report_numbers { | |
124 | + ISKU_REPORT_NUMBER_BUTTON = 3, | |
125 | +}; | |
126 | + | |
127 | +enum isku_report_button_events { | |
128 | + ISKU_REPORT_BUTTON_EVENT_PROFILE = 0x2, | |
129 | +}; | |
130 | + | |
131 | +struct isku_roccat_report { | |
132 | + uint8_t event; | |
133 | + uint8_t data1; | |
134 | + uint8_t data2; | |
135 | + uint8_t profile; | |
136 | +} __packed; | |
137 | + | |
138 | +struct isku_device { | |
139 | + int roccat_claimed; | |
140 | + int chrdev_minor; | |
141 | + | |
142 | + struct mutex isku_lock; | |
143 | + | |
144 | + int actual_profile; | |
145 | +}; | |
146 | + | |
147 | +#endif |