666 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Roccat Kova[+] driver for Linux4 *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 25static uint profile_numbers[5] = {0, 1, 2, 3, 4};26 27static uint kovaplus_convert_event_cpi(uint value)28{29 return (value == 7 ? 4 : (value == 4 ? 3 : value));30}31 32static 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 43static 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, ROCCAT_COMMON_COMMAND_CONTROL,59 &control, sizeof(struct roccat_common2_control));60 61 return retval;62}63 64static 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, number, request);68}69 70static 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 KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);77 if (retval)78 return retval;79 80 return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,81 buf, KOVAPLUS_SIZE_PROFILE_SETTINGS);82}83 84static 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 KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);91 if (retval)92 return retval;93 94 return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,95 buf, KOVAPLUS_SIZE_PROFILE_BUTTONS);96}97 98/* retval is 0-4 on success, < 0 on error */99static 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, KOVAPLUS_COMMAND_ACTUAL_PROFILE,105 &buf, sizeof(struct kovaplus_actual_profile));106 107 return retval ? retval : buf.actual_profile;108}109 110static 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 KOVAPLUS_COMMAND_ACTUAL_PROFILE,121 &buf, sizeof(struct kovaplus_actual_profile));122}123 124static 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(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, command, buf, real_size);141 mutex_unlock(&kovaplus->kovaplus_lock);142 143 if (retval)144 return retval;145 146 return real_size;147}148 149static 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(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, real_size);164 mutex_unlock(&kovaplus->kovaplus_lock);165 166 if (retval)167 return retval;168 169 return real_size;170}171 172#define KOVAPLUS_SYSFS_W(thingy, THINGY) \173static 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) \182static 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) \191KOVAPLUS_SYSFS_W(thingy, THINGY) \192KOVAPLUS_SYSFS_R(thingy, THINGY)193 194#define KOVAPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \195KOVAPLUS_SYSFS_RW(thingy, THINGY); \196static 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) \204KOVAPLUS_SYSFS_W(thingy, THINGY); \205static struct bin_attribute bin_attr_##thingy = { \206 .attr = { .name = #thingy, .mode = 0220 }, \207 .size = KOVAPLUS_SIZE_ ## THINGY, \208 .write = kovaplus_sysfs_write_ ## thingy \209}210KOVAPLUS_BIN_ATTRIBUTE_W(control, CONTROL);211KOVAPLUS_BIN_ATTRIBUTE_RW(info, INFO);212KOVAPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);213KOVAPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);214 215static 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, *(uint *)(attr->private),224 KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);225 if (retval)226 return retval;227 228 return kovaplus_sysfs_read(fp, kobj, buf, off, count,229 KOVAPLUS_SIZE_PROFILE_SETTINGS,230 KOVAPLUS_COMMAND_PROFILE_SETTINGS);231}232 233static 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, *(uint *)(attr->private),242 KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);243 if (retval)244 return retval;245 246 return kovaplus_sysfs_read(fp, kobj, buf, off, count,247 KOVAPLUS_SIZE_PROFILE_BUTTONS,248 KOVAPLUS_COMMAND_PROFILE_BUTTONS);249}250 251#define PROFILE_ATTR(number) \252static 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}; \258static 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};264PROFILE_ATTR(1);265PROFILE_ATTR(2);266PROFILE_ATTR(3);267PROFILE_ATTR(4);268PROFILE_ATTR(5);269 270static 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(dev_get_drvdata(dev->parent->parent));275 return sysfs_emit(buf, "%d\n", kovaplus->actual_profile);276}277 278static 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(dev_get_drvdata(dev));289 usb_dev = interface_to_usbdev(to_usb_interface(dev));290 291 retval = kstrtoul(buf, 10, &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, profile);300 if (retval) {301 mutex_unlock(&kovaplus->kovaplus_lock);302 return retval;303 }304 305 kovaplus_profile_activated(kovaplus, 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(kovaplus->chrdev_minor,313 (uint8_t const *)&roccat_report);314 315 mutex_unlock(&kovaplus->kovaplus_lock);316 317 return size;318}319static DEVICE_ATTR(actual_profile, 0660,320 kovaplus_sysfs_show_actual_profile,321 kovaplus_sysfs_set_actual_profile);322 323static 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(dev_get_drvdata(dev->parent->parent));328 return sysfs_emit(buf, "%d\n", kovaplus->actual_cpi);329}330static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL);331 332static 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(dev_get_drvdata(dev->parent->parent));337 return sysfs_emit(buf, "%d\n", kovaplus->actual_x_sensitivity);338}339static DEVICE_ATTR(actual_sensitivity_x, 0440,340 kovaplus_sysfs_show_actual_sensitivity_x, NULL);341 342static 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(dev_get_drvdata(dev->parent->parent));347 return sysfs_emit(buf, "%d\n", kovaplus->actual_y_sensitivity);348}349static DEVICE_ATTR(actual_sensitivity_y, 0440,350 kovaplus_sysfs_show_actual_sensitivity_y, NULL);351 352static 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(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, KOVAPLUS_COMMAND_INFO,365 &info, KOVAPLUS_SIZE_INFO);366 mutex_unlock(&kovaplus->kovaplus_lock);367 368 return sysfs_emit(buf, "%d\n", info.firmware_version);369}370static DEVICE_ATTR(firmware_version, 0440,371 kovaplus_sysfs_show_firmware_version, NULL);372 373static 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 382static 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 400static const struct attribute_group kovaplus_group = {401 .attrs = kovaplus_attrs,402 .bin_attrs = kovaplus_bin_attributes,403};404 405static const struct attribute_group *kovaplus_groups[] = {406 &kovaplus_group,407 NULL,408};409 410static const struct class kovaplus_class = {411 .name = "kovaplus",412 .dev_groups = kovaplus_groups,413};414 415static 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(wait);425 retval = kovaplus_get_profile_settings(usb_dev,426 &kovaplus->profile_settings[i], i);427 if (retval)428 return retval;429 430 msleep(wait);431 retval = kovaplus_get_profile_buttons(usb_dev,432 &kovaplus->profile_buttons[i], i);433 if (retval)434 return retval;435 }436 437 msleep(wait);438 retval = kovaplus_get_actual_profile(usb_dev);439 if (retval < 0)440 return retval;441 kovaplus_profile_activated(kovaplus, retval);442 443 return 0;444}445 446static 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.bInterfaceProtocol454 == USB_INTERFACE_PROTOCOL_MOUSE) {455 456 kovaplus = kzalloc(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, 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(&kovaplus_class, hdev,470 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;483exit_free:484 kfree(kovaplus);485 return retval;486}487 488static 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.bInterfaceProtocol494 == USB_INTERFACE_PROTOCOL_MOUSE) {495 kovaplus = hid_get_drvdata(hdev);496 if (kovaplus->roccat_claimed)497 roccat_disconnect(kovaplus->chrdev_minor);498 kfree(kovaplus);499 }500}501 502static 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 530exit_stop:531 hid_hw_stop(hdev);532exit:533 return retval;534}535 536static void kovaplus_remove(struct hid_device *hdev)537{538 kovaplus_remove_specials(hdev);539 hid_hw_stop(hdev);540}541 542static 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, button_report->data1 - 1);555 break;556 case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI:557 kovaplus->actual_cpi = kovaplus_convert_event_cpi(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 568static 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 else591 roccat_report.button = 0;592 593 if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI)594 roccat_report.data1 = kovaplus_convert_event_cpi(button_report->data1);595 else596 roccat_report.data1 = button_report->data1;597 598 roccat_report.data2 = button_report->data2;599 600 roccat_report_event(kovaplus->chrdev_minor,601 (uint8_t const *)&roccat_report);602}603 604static 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.bInterfaceProtocol611 != 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 625static const struct hid_device_id kovaplus_devices[] = {626 { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },627 { }628};629 630MODULE_DEVICE_TABLE(hid, kovaplus_devices);631 632static 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_event638};639 640static int __init kovaplus_init(void)641{642 int retval;643 644 retval = class_register(&kovaplus_class);645 if (retval)646 return retval;647 648 retval = hid_register_driver(&kovaplus_driver);649 if (retval)650 class_unregister(&kovaplus_class);651 return retval;652}653 654static void __exit kovaplus_exit(void)655{656 hid_unregister_driver(&kovaplus_driver);657 class_unregister(&kovaplus_class);658}659 660module_init(kovaplus_init);661module_exit(kovaplus_exit);662 663MODULE_AUTHOR("Stefan Achatz");664MODULE_DESCRIPTION("USB Roccat Kova[+] driver");665MODULE_LICENSE("GPL v2");666