1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Roccat Kova[+] driver for Linux |
4 | * |
5 | * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> |
6 | */ |
7 | |
8 | /* |
9 | */ |
10 | |
11 | /* |
12 | * Roccat Kova[+] is a bigger version of the Pyra with two more side buttons. |
13 | */ |
14 | |
15 | #include <linux/device.h> |
16 | #include <linux/input.h> |
17 | #include <linux/hid.h> |
18 | #include <linux/module.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/hid-roccat.h> |
21 | #include "hid-ids.h" |
22 | #include "hid-roccat-common.h" |
23 | #include "hid-roccat-kovaplus.h" |
24 | |
25 | static uint profile_numbers[5] = {0, 1, 2, 3, 4}; |
26 | |
27 | static uint kovaplus_convert_event_cpi(uint value) |
28 | { |
29 | return (value == 7 ? 4 : (value == 4 ? 3 : value)); |
30 | } |
31 | |
32 | static void kovaplus_profile_activated(struct kovaplus_device *kovaplus, |
33 | uint new_profile_index) |
34 | { |
35 | if (new_profile_index >= ARRAY_SIZE(kovaplus->profile_settings)) |
36 | return; |
37 | kovaplus->actual_profile = new_profile_index; |
38 | kovaplus->actual_cpi = kovaplus->profile_settings[new_profile_index].cpi_startup_level; |
39 | kovaplus->actual_x_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_x; |
40 | kovaplus->actual_y_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_y; |
41 | } |
42 | |
43 | static int kovaplus_send_control(struct usb_device *usb_dev, uint value, |
44 | enum kovaplus_control_requests request) |
45 | { |
46 | int retval; |
47 | struct roccat_common2_control control; |
48 | |
49 | if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || |
50 | request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && |
51 | value > 4) |
52 | return -EINVAL; |
53 | |
54 | control.command = ROCCAT_COMMON_COMMAND_CONTROL; |
55 | control.value = value; |
56 | control.request = request; |
57 | |
58 | retval = roccat_common2_send(usb_dev, report_id: ROCCAT_COMMON_COMMAND_CONTROL, |
59 | data: &control, size: sizeof(struct roccat_common2_control)); |
60 | |
61 | return retval; |
62 | } |
63 | |
64 | static int kovaplus_select_profile(struct usb_device *usb_dev, uint number, |
65 | enum kovaplus_control_requests request) |
66 | { |
67 | return kovaplus_send_control(usb_dev, value: number, request); |
68 | } |
69 | |
70 | static int kovaplus_get_profile_settings(struct usb_device *usb_dev, |
71 | struct kovaplus_profile_settings *buf, uint number) |
72 | { |
73 | int retval; |
74 | |
75 | retval = kovaplus_select_profile(usb_dev, number, |
76 | request: KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); |
77 | if (retval) |
78 | return retval; |
79 | |
80 | return roccat_common2_receive(usb_dev, report_id: KOVAPLUS_COMMAND_PROFILE_SETTINGS, |
81 | data: buf, size: KOVAPLUS_SIZE_PROFILE_SETTINGS); |
82 | } |
83 | |
84 | static int kovaplus_get_profile_buttons(struct usb_device *usb_dev, |
85 | struct kovaplus_profile_buttons *buf, int number) |
86 | { |
87 | int retval; |
88 | |
89 | retval = kovaplus_select_profile(usb_dev, number, |
90 | request: KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); |
91 | if (retval) |
92 | return retval; |
93 | |
94 | return roccat_common2_receive(usb_dev, report_id: KOVAPLUS_COMMAND_PROFILE_BUTTONS, |
95 | data: buf, size: KOVAPLUS_SIZE_PROFILE_BUTTONS); |
96 | } |
97 | |
98 | /* retval is 0-4 on success, < 0 on error */ |
99 | static int kovaplus_get_actual_profile(struct usb_device *usb_dev) |
100 | { |
101 | struct kovaplus_actual_profile buf; |
102 | int retval; |
103 | |
104 | retval = roccat_common2_receive(usb_dev, report_id: KOVAPLUS_COMMAND_ACTUAL_PROFILE, |
105 | data: &buf, size: sizeof(struct kovaplus_actual_profile)); |
106 | |
107 | return retval ? retval : buf.actual_profile; |
108 | } |
109 | |
110 | static int kovaplus_set_actual_profile(struct usb_device *usb_dev, |
111 | int new_profile) |
112 | { |
113 | struct kovaplus_actual_profile buf; |
114 | |
115 | buf.command = KOVAPLUS_COMMAND_ACTUAL_PROFILE; |
116 | buf.size = sizeof(struct kovaplus_actual_profile); |
117 | buf.actual_profile = new_profile; |
118 | |
119 | return roccat_common2_send_with_status(usb_dev, |
120 | command: KOVAPLUS_COMMAND_ACTUAL_PROFILE, |
121 | buf: &buf, size: sizeof(struct kovaplus_actual_profile)); |
122 | } |
123 | |
124 | static ssize_t kovaplus_sysfs_read(struct file *fp, struct kobject *kobj, |
125 | char *buf, loff_t off, size_t count, |
126 | size_t real_size, uint command) |
127 | { |
128 | struct device *dev = kobj_to_dev(kobj)->parent->parent; |
129 | struct kovaplus_device *kovaplus = hid_get_drvdata(hdev: dev_get_drvdata(dev)); |
130 | struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); |
131 | int retval; |
132 | |
133 | if (off >= real_size) |
134 | return 0; |
135 | |
136 | if (off != 0 || count != real_size) |
137 | return -EINVAL; |
138 | |
139 | mutex_lock(&kovaplus->kovaplus_lock); |
140 | retval = roccat_common2_receive(usb_dev, report_id: command, data: buf, size: real_size); |
141 | mutex_unlock(lock: &kovaplus->kovaplus_lock); |
142 | |
143 | if (retval) |
144 | return retval; |
145 | |
146 | return real_size; |
147 | } |
148 | |
149 | static ssize_t kovaplus_sysfs_write(struct file *fp, struct kobject *kobj, |
150 | void const *buf, loff_t off, size_t count, |
151 | size_t real_size, uint command) |
152 | { |
153 | struct device *dev = kobj_to_dev(kobj)->parent->parent; |
154 | struct kovaplus_device *kovaplus = hid_get_drvdata(hdev: dev_get_drvdata(dev)); |
155 | struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); |
156 | int retval; |
157 | |
158 | if (off != 0 || count != real_size) |
159 | return -EINVAL; |
160 | |
161 | mutex_lock(&kovaplus->kovaplus_lock); |
162 | retval = roccat_common2_send_with_status(usb_dev, command, |
163 | buf, size: real_size); |
164 | mutex_unlock(lock: &kovaplus->kovaplus_lock); |
165 | |
166 | if (retval) |
167 | return retval; |
168 | |
169 | return real_size; |
170 | } |
171 | |
172 | #define KOVAPLUS_SYSFS_W(thingy, THINGY) \ |
173 | static ssize_t kovaplus_sysfs_write_ ## thingy(struct file *fp, \ |
174 | struct kobject *kobj, struct bin_attribute *attr, char *buf, \ |
175 | loff_t off, size_t count) \ |
176 | { \ |
177 | return kovaplus_sysfs_write(fp, kobj, buf, off, count, \ |
178 | KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \ |
179 | } |
180 | |
181 | #define KOVAPLUS_SYSFS_R(thingy, THINGY) \ |
182 | static ssize_t kovaplus_sysfs_read_ ## thingy(struct file *fp, \ |
183 | struct kobject *kobj, struct bin_attribute *attr, char *buf, \ |
184 | loff_t off, size_t count) \ |
185 | { \ |
186 | return kovaplus_sysfs_read(fp, kobj, buf, off, count, \ |
187 | KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \ |
188 | } |
189 | |
190 | #define KOVAPLUS_SYSFS_RW(thingy, THINGY) \ |
191 | KOVAPLUS_SYSFS_W(thingy, THINGY) \ |
192 | KOVAPLUS_SYSFS_R(thingy, THINGY) |
193 | |
194 | #define KOVAPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \ |
195 | KOVAPLUS_SYSFS_RW(thingy, THINGY); \ |
196 | static struct bin_attribute bin_attr_##thingy = { \ |
197 | .attr = { .name = #thingy, .mode = 0660 }, \ |
198 | .size = KOVAPLUS_SIZE_ ## THINGY, \ |
199 | .read = kovaplus_sysfs_read_ ## thingy, \ |
200 | .write = kovaplus_sysfs_write_ ## thingy \ |
201 | } |
202 | |
203 | #define KOVAPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \ |
204 | KOVAPLUS_SYSFS_W(thingy, THINGY); \ |
205 | static struct bin_attribute bin_attr_##thingy = { \ |
206 | .attr = { .name = #thingy, .mode = 0220 }, \ |
207 | .size = KOVAPLUS_SIZE_ ## THINGY, \ |
208 | .write = kovaplus_sysfs_write_ ## thingy \ |
209 | } |
210 | KOVAPLUS_BIN_ATTRIBUTE_W(control, CONTROL); |
211 | KOVAPLUS_BIN_ATTRIBUTE_RW(info, INFO); |
212 | KOVAPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS); |
213 | KOVAPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS); |
214 | |
215 | static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp, |
216 | struct kobject *kobj, struct bin_attribute *attr, char *buf, |
217 | loff_t off, size_t count) |
218 | { |
219 | struct device *dev = kobj_to_dev(kobj)->parent->parent; |
220 | struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); |
221 | ssize_t retval; |
222 | |
223 | retval = kovaplus_select_profile(usb_dev, number: *(uint *)(attr->private), |
224 | request: KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); |
225 | if (retval) |
226 | return retval; |
227 | |
228 | return kovaplus_sysfs_read(fp, kobj, buf, off, count, |
229 | real_size: KOVAPLUS_SIZE_PROFILE_SETTINGS, |
230 | command: KOVAPLUS_COMMAND_PROFILE_SETTINGS); |
231 | } |
232 | |
233 | static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp, |
234 | struct kobject *kobj, struct bin_attribute *attr, char *buf, |
235 | loff_t off, size_t count) |
236 | { |
237 | struct device *dev = kobj_to_dev(kobj)->parent->parent; |
238 | struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); |
239 | ssize_t retval; |
240 | |
241 | retval = kovaplus_select_profile(usb_dev, number: *(uint *)(attr->private), |
242 | request: KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); |
243 | if (retval) |
244 | return retval; |
245 | |
246 | return kovaplus_sysfs_read(fp, kobj, buf, off, count, |
247 | real_size: KOVAPLUS_SIZE_PROFILE_BUTTONS, |
248 | command: KOVAPLUS_COMMAND_PROFILE_BUTTONS); |
249 | } |
250 | |
251 | #define PROFILE_ATTR(number) \ |
252 | static struct bin_attribute bin_attr_profile##number##_settings = { \ |
253 | .attr = { .name = "profile" #number "_settings", .mode = 0440 }, \ |
254 | .size = KOVAPLUS_SIZE_PROFILE_SETTINGS, \ |
255 | .read = kovaplus_sysfs_read_profilex_settings, \ |
256 | .private = &profile_numbers[number-1], \ |
257 | }; \ |
258 | static struct bin_attribute bin_attr_profile##number##_buttons = { \ |
259 | .attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \ |
260 | .size = KOVAPLUS_SIZE_PROFILE_BUTTONS, \ |
261 | .read = kovaplus_sysfs_read_profilex_buttons, \ |
262 | .private = &profile_numbers[number-1], \ |
263 | }; |
264 | PROFILE_ATTR(1); |
265 | PROFILE_ATTR(2); |
266 | PROFILE_ATTR(3); |
267 | PROFILE_ATTR(4); |
268 | PROFILE_ATTR(5); |
269 | |
270 | static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev, |
271 | struct device_attribute *attr, char *buf) |
272 | { |
273 | struct kovaplus_device *kovaplus = |
274 | hid_get_drvdata(hdev: dev_get_drvdata(dev: dev->parent->parent)); |
275 | return snprintf(buf, PAGE_SIZE, fmt: "%d\n" , kovaplus->actual_profile); |
276 | } |
277 | |
278 | static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev, |
279 | struct device_attribute *attr, char const *buf, size_t size) |
280 | { |
281 | struct kovaplus_device *kovaplus; |
282 | struct usb_device *usb_dev; |
283 | unsigned long profile; |
284 | int retval; |
285 | struct kovaplus_roccat_report roccat_report; |
286 | |
287 | dev = dev->parent->parent; |
288 | kovaplus = hid_get_drvdata(hdev: dev_get_drvdata(dev)); |
289 | usb_dev = interface_to_usbdev(to_usb_interface(dev)); |
290 | |
291 | retval = kstrtoul(s: buf, base: 10, res: &profile); |
292 | if (retval) |
293 | return retval; |
294 | |
295 | if (profile >= 5) |
296 | return -EINVAL; |
297 | |
298 | mutex_lock(&kovaplus->kovaplus_lock); |
299 | retval = kovaplus_set_actual_profile(usb_dev, new_profile: profile); |
300 | if (retval) { |
301 | mutex_unlock(lock: &kovaplus->kovaplus_lock); |
302 | return retval; |
303 | } |
304 | |
305 | kovaplus_profile_activated(kovaplus, new_profile_index: profile); |
306 | |
307 | roccat_report.type = KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1; |
308 | roccat_report.profile = profile + 1; |
309 | roccat_report.button = 0; |
310 | roccat_report.data1 = profile + 1; |
311 | roccat_report.data2 = 0; |
312 | roccat_report_event(minor: kovaplus->chrdev_minor, |
313 | data: (uint8_t const *)&roccat_report); |
314 | |
315 | mutex_unlock(lock: &kovaplus->kovaplus_lock); |
316 | |
317 | return size; |
318 | } |
319 | static DEVICE_ATTR(actual_profile, 0660, |
320 | kovaplus_sysfs_show_actual_profile, |
321 | kovaplus_sysfs_set_actual_profile); |
322 | |
323 | static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev, |
324 | struct device_attribute *attr, char *buf) |
325 | { |
326 | struct kovaplus_device *kovaplus = |
327 | hid_get_drvdata(hdev: dev_get_drvdata(dev: dev->parent->parent)); |
328 | return snprintf(buf, PAGE_SIZE, fmt: "%d\n" , kovaplus->actual_cpi); |
329 | } |
330 | static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL); |
331 | |
332 | static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev, |
333 | struct device_attribute *attr, char *buf) |
334 | { |
335 | struct kovaplus_device *kovaplus = |
336 | hid_get_drvdata(hdev: dev_get_drvdata(dev: dev->parent->parent)); |
337 | return snprintf(buf, PAGE_SIZE, fmt: "%d\n" , kovaplus->actual_x_sensitivity); |
338 | } |
339 | static DEVICE_ATTR(actual_sensitivity_x, 0440, |
340 | kovaplus_sysfs_show_actual_sensitivity_x, NULL); |
341 | |
342 | static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev, |
343 | struct device_attribute *attr, char *buf) |
344 | { |
345 | struct kovaplus_device *kovaplus = |
346 | hid_get_drvdata(hdev: dev_get_drvdata(dev: dev->parent->parent)); |
347 | return snprintf(buf, PAGE_SIZE, fmt: "%d\n" , kovaplus->actual_y_sensitivity); |
348 | } |
349 | static DEVICE_ATTR(actual_sensitivity_y, 0440, |
350 | kovaplus_sysfs_show_actual_sensitivity_y, NULL); |
351 | |
352 | static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev, |
353 | struct device_attribute *attr, char *buf) |
354 | { |
355 | struct kovaplus_device *kovaplus; |
356 | struct usb_device *usb_dev; |
357 | struct kovaplus_info info; |
358 | |
359 | dev = dev->parent->parent; |
360 | kovaplus = hid_get_drvdata(hdev: dev_get_drvdata(dev)); |
361 | usb_dev = interface_to_usbdev(to_usb_interface(dev)); |
362 | |
363 | mutex_lock(&kovaplus->kovaplus_lock); |
364 | roccat_common2_receive(usb_dev, report_id: KOVAPLUS_COMMAND_INFO, |
365 | data: &info, size: KOVAPLUS_SIZE_INFO); |
366 | mutex_unlock(lock: &kovaplus->kovaplus_lock); |
367 | |
368 | return snprintf(buf, PAGE_SIZE, fmt: "%d\n" , info.firmware_version); |
369 | } |
370 | static DEVICE_ATTR(firmware_version, 0440, |
371 | kovaplus_sysfs_show_firmware_version, NULL); |
372 | |
373 | static struct attribute *kovaplus_attrs[] = { |
374 | &dev_attr_actual_cpi.attr, |
375 | &dev_attr_firmware_version.attr, |
376 | &dev_attr_actual_profile.attr, |
377 | &dev_attr_actual_sensitivity_x.attr, |
378 | &dev_attr_actual_sensitivity_y.attr, |
379 | NULL, |
380 | }; |
381 | |
382 | static struct bin_attribute *kovaplus_bin_attributes[] = { |
383 | &bin_attr_control, |
384 | &bin_attr_info, |
385 | &bin_attr_profile_settings, |
386 | &bin_attr_profile_buttons, |
387 | &bin_attr_profile1_settings, |
388 | &bin_attr_profile2_settings, |
389 | &bin_attr_profile3_settings, |
390 | &bin_attr_profile4_settings, |
391 | &bin_attr_profile5_settings, |
392 | &bin_attr_profile1_buttons, |
393 | &bin_attr_profile2_buttons, |
394 | &bin_attr_profile3_buttons, |
395 | &bin_attr_profile4_buttons, |
396 | &bin_attr_profile5_buttons, |
397 | NULL, |
398 | }; |
399 | |
400 | static const struct attribute_group kovaplus_group = { |
401 | .attrs = kovaplus_attrs, |
402 | .bin_attrs = kovaplus_bin_attributes, |
403 | }; |
404 | |
405 | static const struct attribute_group *kovaplus_groups[] = { |
406 | &kovaplus_group, |
407 | NULL, |
408 | }; |
409 | |
410 | static const struct class kovaplus_class = { |
411 | .name = "kovaplus" , |
412 | .dev_groups = kovaplus_groups, |
413 | }; |
414 | |
415 | static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev, |
416 | struct kovaplus_device *kovaplus) |
417 | { |
418 | int retval, i; |
419 | static uint wait = 70; /* device will freeze with just 60 */ |
420 | |
421 | mutex_init(&kovaplus->kovaplus_lock); |
422 | |
423 | for (i = 0; i < 5; ++i) { |
424 | msleep(msecs: wait); |
425 | retval = kovaplus_get_profile_settings(usb_dev, |
426 | buf: &kovaplus->profile_settings[i], number: i); |
427 | if (retval) |
428 | return retval; |
429 | |
430 | msleep(msecs: wait); |
431 | retval = kovaplus_get_profile_buttons(usb_dev, |
432 | buf: &kovaplus->profile_buttons[i], number: i); |
433 | if (retval) |
434 | return retval; |
435 | } |
436 | |
437 | msleep(msecs: wait); |
438 | retval = kovaplus_get_actual_profile(usb_dev); |
439 | if (retval < 0) |
440 | return retval; |
441 | kovaplus_profile_activated(kovaplus, new_profile_index: retval); |
442 | |
443 | return 0; |
444 | } |
445 | |
446 | static int kovaplus_init_specials(struct hid_device *hdev) |
447 | { |
448 | struct usb_interface *intf = to_usb_interface(hdev->dev.parent); |
449 | struct usb_device *usb_dev = interface_to_usbdev(intf); |
450 | struct kovaplus_device *kovaplus; |
451 | int retval; |
452 | |
453 | if (intf->cur_altsetting->desc.bInterfaceProtocol |
454 | == USB_INTERFACE_PROTOCOL_MOUSE) { |
455 | |
456 | kovaplus = kzalloc(size: sizeof(*kovaplus), GFP_KERNEL); |
457 | if (!kovaplus) { |
458 | hid_err(hdev, "can't alloc device descriptor\n" ); |
459 | return -ENOMEM; |
460 | } |
461 | hid_set_drvdata(hdev, data: kovaplus); |
462 | |
463 | retval = kovaplus_init_kovaplus_device_struct(usb_dev, kovaplus); |
464 | if (retval) { |
465 | hid_err(hdev, "couldn't init struct kovaplus_device\n" ); |
466 | goto exit_free; |
467 | } |
468 | |
469 | retval = roccat_connect(klass: &kovaplus_class, hid: hdev, |
470 | report_size: sizeof(struct kovaplus_roccat_report)); |
471 | if (retval < 0) { |
472 | hid_err(hdev, "couldn't init char dev\n" ); |
473 | } else { |
474 | kovaplus->chrdev_minor = retval; |
475 | kovaplus->roccat_claimed = 1; |
476 | } |
477 | |
478 | } else { |
479 | hid_set_drvdata(hdev, NULL); |
480 | } |
481 | |
482 | return 0; |
483 | exit_free: |
484 | kfree(objp: kovaplus); |
485 | return retval; |
486 | } |
487 | |
488 | static void kovaplus_remove_specials(struct hid_device *hdev) |
489 | { |
490 | struct usb_interface *intf = to_usb_interface(hdev->dev.parent); |
491 | struct kovaplus_device *kovaplus; |
492 | |
493 | if (intf->cur_altsetting->desc.bInterfaceProtocol |
494 | == USB_INTERFACE_PROTOCOL_MOUSE) { |
495 | kovaplus = hid_get_drvdata(hdev); |
496 | if (kovaplus->roccat_claimed) |
497 | roccat_disconnect(minor: kovaplus->chrdev_minor); |
498 | kfree(objp: kovaplus); |
499 | } |
500 | } |
501 | |
502 | static int kovaplus_probe(struct hid_device *hdev, |
503 | const struct hid_device_id *id) |
504 | { |
505 | int retval; |
506 | |
507 | if (!hid_is_usb(hdev)) |
508 | return -EINVAL; |
509 | |
510 | retval = hid_parse(hdev); |
511 | if (retval) { |
512 | hid_err(hdev, "parse failed\n" ); |
513 | goto exit; |
514 | } |
515 | |
516 | retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
517 | if (retval) { |
518 | hid_err(hdev, "hw start failed\n" ); |
519 | goto exit; |
520 | } |
521 | |
522 | retval = kovaplus_init_specials(hdev); |
523 | if (retval) { |
524 | hid_err(hdev, "couldn't install mouse\n" ); |
525 | goto exit_stop; |
526 | } |
527 | |
528 | return 0; |
529 | |
530 | exit_stop: |
531 | hid_hw_stop(hdev); |
532 | exit: |
533 | return retval; |
534 | } |
535 | |
536 | static void kovaplus_remove(struct hid_device *hdev) |
537 | { |
538 | kovaplus_remove_specials(hdev); |
539 | hid_hw_stop(hdev); |
540 | } |
541 | |
542 | static void kovaplus_keep_values_up_to_date(struct kovaplus_device *kovaplus, |
543 | u8 const *data) |
544 | { |
545 | struct kovaplus_mouse_report_button const *button_report; |
546 | |
547 | if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON) |
548 | return; |
549 | |
550 | button_report = (struct kovaplus_mouse_report_button const *)data; |
551 | |
552 | switch (button_report->type) { |
553 | case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1: |
554 | kovaplus_profile_activated(kovaplus, new_profile_index: button_report->data1 - 1); |
555 | break; |
556 | case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI: |
557 | kovaplus->actual_cpi = kovaplus_convert_event_cpi(value: button_report->data1); |
558 | break; |
559 | case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY: |
560 | kovaplus->actual_x_sensitivity = button_report->data1; |
561 | kovaplus->actual_y_sensitivity = button_report->data2; |
562 | break; |
563 | default: |
564 | break; |
565 | } |
566 | } |
567 | |
568 | static void kovaplus_report_to_chrdev(struct kovaplus_device const *kovaplus, |
569 | u8 const *data) |
570 | { |
571 | struct kovaplus_roccat_report roccat_report; |
572 | struct kovaplus_mouse_report_button const *button_report; |
573 | |
574 | if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON) |
575 | return; |
576 | |
577 | button_report = (struct kovaplus_mouse_report_button const *)data; |
578 | |
579 | if (button_report->type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2) |
580 | return; |
581 | |
582 | roccat_report.type = button_report->type; |
583 | roccat_report.profile = kovaplus->actual_profile + 1; |
584 | |
585 | if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO || |
586 | roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT || |
587 | roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH || |
588 | roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER) |
589 | roccat_report.button = button_report->data1; |
590 | else |
591 | roccat_report.button = 0; |
592 | |
593 | if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI) |
594 | roccat_report.data1 = kovaplus_convert_event_cpi(value: button_report->data1); |
595 | else |
596 | roccat_report.data1 = button_report->data1; |
597 | |
598 | roccat_report.data2 = button_report->data2; |
599 | |
600 | roccat_report_event(minor: kovaplus->chrdev_minor, |
601 | data: (uint8_t const *)&roccat_report); |
602 | } |
603 | |
604 | static int kovaplus_raw_event(struct hid_device *hdev, |
605 | struct hid_report *report, u8 *data, int size) |
606 | { |
607 | struct usb_interface *intf = to_usb_interface(hdev->dev.parent); |
608 | struct kovaplus_device *kovaplus = hid_get_drvdata(hdev); |
609 | |
610 | if (intf->cur_altsetting->desc.bInterfaceProtocol |
611 | != USB_INTERFACE_PROTOCOL_MOUSE) |
612 | return 0; |
613 | |
614 | if (kovaplus == NULL) |
615 | return 0; |
616 | |
617 | kovaplus_keep_values_up_to_date(kovaplus, data); |
618 | |
619 | if (kovaplus->roccat_claimed) |
620 | kovaplus_report_to_chrdev(kovaplus, data); |
621 | |
622 | return 0; |
623 | } |
624 | |
625 | static const struct hid_device_id kovaplus_devices[] = { |
626 | { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, |
627 | { } |
628 | }; |
629 | |
630 | MODULE_DEVICE_TABLE(hid, kovaplus_devices); |
631 | |
632 | static struct hid_driver kovaplus_driver = { |
633 | .name = "kovaplus" , |
634 | .id_table = kovaplus_devices, |
635 | .probe = kovaplus_probe, |
636 | .remove = kovaplus_remove, |
637 | .raw_event = kovaplus_raw_event |
638 | }; |
639 | |
640 | static int __init kovaplus_init(void) |
641 | { |
642 | int retval; |
643 | |
644 | retval = class_register(class: &kovaplus_class); |
645 | if (retval) |
646 | return retval; |
647 | |
648 | retval = hid_register_driver(&kovaplus_driver); |
649 | if (retval) |
650 | class_unregister(class: &kovaplus_class); |
651 | return retval; |
652 | } |
653 | |
654 | static void __exit kovaplus_exit(void) |
655 | { |
656 | hid_unregister_driver(&kovaplus_driver); |
657 | class_unregister(class: &kovaplus_class); |
658 | } |
659 | |
660 | module_init(kovaplus_init); |
661 | module_exit(kovaplus_exit); |
662 | |
663 | MODULE_AUTHOR("Stefan Achatz" ); |
664 | MODULE_DESCRIPTION("USB Roccat Kova[+] driver" ); |
665 | MODULE_LICENSE("GPL v2" ); |
666 | |