Path: blob/master/drivers/hid/hid-roccat-kovaplus.c
15111 views
/*1* Roccat Kova[+] driver for Linux2*3* Copyright (c) 2011 Stefan Achatz <[email protected]>4*/56/*7* This program is free software; you can redistribute it and/or modify it8* under the terms of the GNU General Public License as published by the Free9* Software Foundation; either version 2 of the License, or (at your option)10* any later version.11*/1213/*14* Roccat Kova[+] is a bigger version of the Pyra with two more side buttons.15*/1617#include <linux/device.h>18#include <linux/input.h>19#include <linux/hid.h>20#include <linux/module.h>21#include <linux/slab.h>22#include <linux/hid-roccat.h>23#include "hid-ids.h"24#include "hid-roccat-common.h"25#include "hid-roccat-kovaplus.h"2627static uint profile_numbers[5] = {0, 1, 2, 3, 4};2829static struct class *kovaplus_class;3031static uint kovaplus_convert_event_cpi(uint value)32{33return (value == 7 ? 4 : (value == 4 ? 3 : value));34}3536static void kovaplus_profile_activated(struct kovaplus_device *kovaplus,37uint new_profile_index)38{39kovaplus->actual_profile = new_profile_index;40kovaplus->actual_cpi = kovaplus->profile_settings[new_profile_index].cpi_startup_level;41kovaplus->actual_x_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_x;42kovaplus->actual_y_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_y;43}4445static int kovaplus_send_control(struct usb_device *usb_dev, uint value,46enum kovaplus_control_requests request)47{48int retval;49struct kovaplus_control control;5051if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||52request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&53value > 4)54return -EINVAL;5556control.command = KOVAPLUS_COMMAND_CONTROL;57control.value = value;58control.request = request;5960retval = roccat_common_send(usb_dev, KOVAPLUS_USB_COMMAND_CONTROL,61&control, sizeof(struct kovaplus_control));6263return retval;64}6566static int kovaplus_receive_control_status(struct usb_device *usb_dev)67{68int retval;69struct kovaplus_control control;7071do {72retval = roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_CONTROL,73&control, sizeof(struct kovaplus_control));7475/* check if we get a completely wrong answer */76if (retval)77return retval;7879if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OK)80return 0;8182/* indicates that hardware needs some more time to complete action */83if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT) {84msleep(500); /* windows driver uses 1000 */85continue;86}8788/* seems to be critical - replug necessary */89if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD)90return -EINVAL;9192hid_err(usb_dev, "kovaplus_receive_control_status: "93"unknown response value 0x%x\n", control.value);94return -EINVAL;95} while (1);96}9798static int kovaplus_send(struct usb_device *usb_dev, uint command,99void const *buf, uint size)100{101int retval;102103retval = roccat_common_send(usb_dev, command, buf, size);104if (retval)105return retval;106107msleep(100);108109return kovaplus_receive_control_status(usb_dev);110}111112static int kovaplus_select_profile(struct usb_device *usb_dev, uint number,113enum kovaplus_control_requests request)114{115return kovaplus_send_control(usb_dev, number, request);116}117118static int kovaplus_get_info(struct usb_device *usb_dev,119struct kovaplus_info *buf)120{121return roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_INFO,122buf, sizeof(struct kovaplus_info));123}124125static int kovaplus_get_profile_settings(struct usb_device *usb_dev,126struct kovaplus_profile_settings *buf, uint number)127{128int retval;129130retval = kovaplus_select_profile(usb_dev, number,131KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);132if (retval)133return retval;134135return roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_SETTINGS,136buf, sizeof(struct kovaplus_profile_settings));137}138139static int kovaplus_set_profile_settings(struct usb_device *usb_dev,140struct kovaplus_profile_settings const *settings)141{142return kovaplus_send(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_SETTINGS,143settings, sizeof(struct kovaplus_profile_settings));144}145146static int kovaplus_get_profile_buttons(struct usb_device *usb_dev,147struct kovaplus_profile_buttons *buf, int number)148{149int retval;150151retval = kovaplus_select_profile(usb_dev, number,152KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);153if (retval)154return retval;155156return roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_BUTTONS,157buf, sizeof(struct kovaplus_profile_buttons));158}159160static int kovaplus_set_profile_buttons(struct usb_device *usb_dev,161struct kovaplus_profile_buttons const *buttons)162{163return kovaplus_send(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_BUTTONS,164buttons, sizeof(struct kovaplus_profile_buttons));165}166167/* retval is 0-4 on success, < 0 on error */168static int kovaplus_get_actual_profile(struct usb_device *usb_dev)169{170struct kovaplus_actual_profile buf;171int retval;172173retval = roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_ACTUAL_PROFILE,174&buf, sizeof(struct kovaplus_actual_profile));175176return retval ? retval : buf.actual_profile;177}178179static int kovaplus_set_actual_profile(struct usb_device *usb_dev,180int new_profile)181{182struct kovaplus_actual_profile buf;183184buf.command = KOVAPLUS_COMMAND_ACTUAL_PROFILE;185buf.size = sizeof(struct kovaplus_actual_profile);186buf.actual_profile = new_profile;187188return kovaplus_send(usb_dev, KOVAPLUS_USB_COMMAND_ACTUAL_PROFILE,189&buf, sizeof(struct kovaplus_actual_profile));190}191192static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp,193struct kobject *kobj, struct bin_attribute *attr, char *buf,194loff_t off, size_t count)195{196struct device *dev =197container_of(kobj, struct device, kobj)->parent->parent;198struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev));199200if (off >= sizeof(struct kovaplus_profile_settings))201return 0;202203if (off + count > sizeof(struct kovaplus_profile_settings))204count = sizeof(struct kovaplus_profile_settings) - off;205206mutex_lock(&kovaplus->kovaplus_lock);207memcpy(buf, ((char const *)&kovaplus->profile_settings[*(uint *)(attr->private)]) + off,208count);209mutex_unlock(&kovaplus->kovaplus_lock);210211return count;212}213214static ssize_t kovaplus_sysfs_write_profile_settings(struct file *fp,215struct kobject *kobj, struct bin_attribute *attr, char *buf,216loff_t off, size_t count)217{218struct device *dev =219container_of(kobj, struct device, kobj)->parent->parent;220struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev));221struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));222int retval = 0;223int difference;224int profile_index;225struct kovaplus_profile_settings *profile_settings;226227if (off != 0 || count != sizeof(struct kovaplus_profile_settings))228return -EINVAL;229230profile_index = ((struct kovaplus_profile_settings const *)buf)->profile_index;231profile_settings = &kovaplus->profile_settings[profile_index];232233mutex_lock(&kovaplus->kovaplus_lock);234difference = memcmp(buf, profile_settings,235sizeof(struct kovaplus_profile_settings));236if (difference) {237retval = kovaplus_set_profile_settings(usb_dev,238(struct kovaplus_profile_settings const *)buf);239if (!retval)240memcpy(profile_settings, buf,241sizeof(struct kovaplus_profile_settings));242}243mutex_unlock(&kovaplus->kovaplus_lock);244245if (retval)246return retval;247248return sizeof(struct kovaplus_profile_settings);249}250251static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp,252struct kobject *kobj, struct bin_attribute *attr, char *buf,253loff_t off, size_t count)254{255struct device *dev =256container_of(kobj, struct device, kobj)->parent->parent;257struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev));258259if (off >= sizeof(struct kovaplus_profile_buttons))260return 0;261262if (off + count > sizeof(struct kovaplus_profile_buttons))263count = sizeof(struct kovaplus_profile_buttons) - off;264265mutex_lock(&kovaplus->kovaplus_lock);266memcpy(buf, ((char const *)&kovaplus->profile_buttons[*(uint *)(attr->private)]) + off,267count);268mutex_unlock(&kovaplus->kovaplus_lock);269270return count;271}272273static ssize_t kovaplus_sysfs_write_profile_buttons(struct file *fp,274struct kobject *kobj, struct bin_attribute *attr, char *buf,275loff_t off, size_t count)276{277struct device *dev =278container_of(kobj, struct device, kobj)->parent->parent;279struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev));280struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));281int retval = 0;282int difference;283uint profile_index;284struct kovaplus_profile_buttons *profile_buttons;285286if (off != 0 || count != sizeof(struct kovaplus_profile_buttons))287return -EINVAL;288289profile_index = ((struct kovaplus_profile_buttons const *)buf)->profile_index;290profile_buttons = &kovaplus->profile_buttons[profile_index];291292mutex_lock(&kovaplus->kovaplus_lock);293difference = memcmp(buf, profile_buttons,294sizeof(struct kovaplus_profile_buttons));295if (difference) {296retval = kovaplus_set_profile_buttons(usb_dev,297(struct kovaplus_profile_buttons const *)buf);298if (!retval)299memcpy(profile_buttons, buf,300sizeof(struct kovaplus_profile_buttons));301}302mutex_unlock(&kovaplus->kovaplus_lock);303304if (retval)305return retval;306307return sizeof(struct kovaplus_profile_buttons);308}309310static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev,311struct device_attribute *attr, char *buf)312{313struct kovaplus_device *kovaplus =314hid_get_drvdata(dev_get_drvdata(dev->parent->parent));315return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile);316}317318static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,319struct device_attribute *attr, char const *buf, size_t size)320{321struct kovaplus_device *kovaplus;322struct usb_device *usb_dev;323unsigned long profile;324int retval;325326dev = dev->parent->parent;327kovaplus = hid_get_drvdata(dev_get_drvdata(dev));328usb_dev = interface_to_usbdev(to_usb_interface(dev));329330retval = strict_strtoul(buf, 10, &profile);331if (retval)332return retval;333334if (profile >= 5)335return -EINVAL;336337mutex_lock(&kovaplus->kovaplus_lock);338retval = kovaplus_set_actual_profile(usb_dev, profile);339kovaplus->actual_profile = profile;340mutex_unlock(&kovaplus->kovaplus_lock);341if (retval)342return retval;343344return size;345}346347static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev,348struct device_attribute *attr, char *buf)349{350struct kovaplus_device *kovaplus =351hid_get_drvdata(dev_get_drvdata(dev->parent->parent));352return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi);353}354355static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev,356struct device_attribute *attr, char *buf)357{358struct kovaplus_device *kovaplus =359hid_get_drvdata(dev_get_drvdata(dev->parent->parent));360return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity);361}362363static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev,364struct device_attribute *attr, char *buf)365{366struct kovaplus_device *kovaplus =367hid_get_drvdata(dev_get_drvdata(dev->parent->parent));368return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity);369}370371static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev,372struct device_attribute *attr, char *buf)373{374struct kovaplus_device *kovaplus =375hid_get_drvdata(dev_get_drvdata(dev->parent->parent));376return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->info.firmware_version);377}378379static struct device_attribute kovaplus_attributes[] = {380__ATTR(actual_cpi, 0440,381kovaplus_sysfs_show_actual_cpi, NULL),382__ATTR(firmware_version, 0440,383kovaplus_sysfs_show_firmware_version, NULL),384__ATTR(actual_profile, 0660,385kovaplus_sysfs_show_actual_profile,386kovaplus_sysfs_set_actual_profile),387__ATTR(actual_sensitivity_x, 0440,388kovaplus_sysfs_show_actual_sensitivity_x, NULL),389__ATTR(actual_sensitivity_y, 0440,390kovaplus_sysfs_show_actual_sensitivity_y, NULL),391__ATTR_NULL392};393394static struct bin_attribute kovaplus_bin_attributes[] = {395{396.attr = { .name = "profile_settings", .mode = 0220 },397.size = sizeof(struct kovaplus_profile_settings),398.write = kovaplus_sysfs_write_profile_settings399},400{401.attr = { .name = "profile1_settings", .mode = 0440 },402.size = sizeof(struct kovaplus_profile_settings),403.read = kovaplus_sysfs_read_profilex_settings,404.private = &profile_numbers[0]405},406{407.attr = { .name = "profile2_settings", .mode = 0440 },408.size = sizeof(struct kovaplus_profile_settings),409.read = kovaplus_sysfs_read_profilex_settings,410.private = &profile_numbers[1]411},412{413.attr = { .name = "profile3_settings", .mode = 0440 },414.size = sizeof(struct kovaplus_profile_settings),415.read = kovaplus_sysfs_read_profilex_settings,416.private = &profile_numbers[2]417},418{419.attr = { .name = "profile4_settings", .mode = 0440 },420.size = sizeof(struct kovaplus_profile_settings),421.read = kovaplus_sysfs_read_profilex_settings,422.private = &profile_numbers[3]423},424{425.attr = { .name = "profile5_settings", .mode = 0440 },426.size = sizeof(struct kovaplus_profile_settings),427.read = kovaplus_sysfs_read_profilex_settings,428.private = &profile_numbers[4]429},430{431.attr = { .name = "profile_buttons", .mode = 0220 },432.size = sizeof(struct kovaplus_profile_buttons),433.write = kovaplus_sysfs_write_profile_buttons434},435{436.attr = { .name = "profile1_buttons", .mode = 0440 },437.size = sizeof(struct kovaplus_profile_buttons),438.read = kovaplus_sysfs_read_profilex_buttons,439.private = &profile_numbers[0]440},441{442.attr = { .name = "profile2_buttons", .mode = 0440 },443.size = sizeof(struct kovaplus_profile_buttons),444.read = kovaplus_sysfs_read_profilex_buttons,445.private = &profile_numbers[1]446},447{448.attr = { .name = "profile3_buttons", .mode = 0440 },449.size = sizeof(struct kovaplus_profile_buttons),450.read = kovaplus_sysfs_read_profilex_buttons,451.private = &profile_numbers[2]452},453{454.attr = { .name = "profile4_buttons", .mode = 0440 },455.size = sizeof(struct kovaplus_profile_buttons),456.read = kovaplus_sysfs_read_profilex_buttons,457.private = &profile_numbers[3]458},459{460.attr = { .name = "profile5_buttons", .mode = 0440 },461.size = sizeof(struct kovaplus_profile_buttons),462.read = kovaplus_sysfs_read_profilex_buttons,463.private = &profile_numbers[4]464},465__ATTR_NULL466};467468static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev,469struct kovaplus_device *kovaplus)470{471int retval, i;472static uint wait = 70; /* device will freeze with just 60 */473474mutex_init(&kovaplus->kovaplus_lock);475476retval = kovaplus_get_info(usb_dev, &kovaplus->info);477if (retval)478return retval;479480for (i = 0; i < 5; ++i) {481msleep(wait);482retval = kovaplus_get_profile_settings(usb_dev,483&kovaplus->profile_settings[i], i);484if (retval)485return retval;486487msleep(wait);488retval = kovaplus_get_profile_buttons(usb_dev,489&kovaplus->profile_buttons[i], i);490if (retval)491return retval;492}493494msleep(wait);495retval = kovaplus_get_actual_profile(usb_dev);496if (retval < 0)497return retval;498kovaplus_profile_activated(kovaplus, retval);499500return 0;501}502503static int kovaplus_init_specials(struct hid_device *hdev)504{505struct usb_interface *intf = to_usb_interface(hdev->dev.parent);506struct usb_device *usb_dev = interface_to_usbdev(intf);507struct kovaplus_device *kovaplus;508int retval;509510if (intf->cur_altsetting->desc.bInterfaceProtocol511== USB_INTERFACE_PROTOCOL_MOUSE) {512513kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL);514if (!kovaplus) {515hid_err(hdev, "can't alloc device descriptor\n");516return -ENOMEM;517}518hid_set_drvdata(hdev, kovaplus);519520retval = kovaplus_init_kovaplus_device_struct(usb_dev, kovaplus);521if (retval) {522hid_err(hdev, "couldn't init struct kovaplus_device\n");523goto exit_free;524}525526retval = roccat_connect(kovaplus_class, hdev,527sizeof(struct kovaplus_roccat_report));528if (retval < 0) {529hid_err(hdev, "couldn't init char dev\n");530} else {531kovaplus->chrdev_minor = retval;532kovaplus->roccat_claimed = 1;533}534535} else {536hid_set_drvdata(hdev, NULL);537}538539return 0;540exit_free:541kfree(kovaplus);542return retval;543}544545static void kovaplus_remove_specials(struct hid_device *hdev)546{547struct usb_interface *intf = to_usb_interface(hdev->dev.parent);548struct kovaplus_device *kovaplus;549550if (intf->cur_altsetting->desc.bInterfaceProtocol551== USB_INTERFACE_PROTOCOL_MOUSE) {552kovaplus = hid_get_drvdata(hdev);553if (kovaplus->roccat_claimed)554roccat_disconnect(kovaplus->chrdev_minor);555kfree(kovaplus);556}557}558559static int kovaplus_probe(struct hid_device *hdev,560const struct hid_device_id *id)561{562int retval;563564retval = hid_parse(hdev);565if (retval) {566hid_err(hdev, "parse failed\n");567goto exit;568}569570retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);571if (retval) {572hid_err(hdev, "hw start failed\n");573goto exit;574}575576retval = kovaplus_init_specials(hdev);577if (retval) {578hid_err(hdev, "couldn't install mouse\n");579goto exit_stop;580}581582return 0;583584exit_stop:585hid_hw_stop(hdev);586exit:587return retval;588}589590static void kovaplus_remove(struct hid_device *hdev)591{592kovaplus_remove_specials(hdev);593hid_hw_stop(hdev);594}595596static void kovaplus_keep_values_up_to_date(struct kovaplus_device *kovaplus,597u8 const *data)598{599struct kovaplus_mouse_report_button const *button_report;600601if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON)602return;603604button_report = (struct kovaplus_mouse_report_button const *)data;605606switch (button_report->type) {607case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1:608kovaplus_profile_activated(kovaplus, button_report->data1 - 1);609break;610case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI:611kovaplus->actual_cpi = kovaplus_convert_event_cpi(button_report->data1);612case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY:613kovaplus->actual_x_sensitivity = button_report->data1;614kovaplus->actual_y_sensitivity = button_report->data2;615}616}617618static void kovaplus_report_to_chrdev(struct kovaplus_device const *kovaplus,619u8 const *data)620{621struct kovaplus_roccat_report roccat_report;622struct kovaplus_mouse_report_button const *button_report;623624if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON)625return;626627button_report = (struct kovaplus_mouse_report_button const *)data;628629if (button_report->type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2)630return;631632roccat_report.type = button_report->type;633roccat_report.profile = kovaplus->actual_profile + 1;634635if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO ||636roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT ||637roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH ||638roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER)639roccat_report.button = button_report->data1;640else641roccat_report.button = 0;642643if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI)644roccat_report.data1 = kovaplus_convert_event_cpi(button_report->data1);645else646roccat_report.data1 = button_report->data1;647648roccat_report.data2 = button_report->data2;649650roccat_report_event(kovaplus->chrdev_minor,651(uint8_t const *)&roccat_report);652}653654static int kovaplus_raw_event(struct hid_device *hdev,655struct hid_report *report, u8 *data, int size)656{657struct usb_interface *intf = to_usb_interface(hdev->dev.parent);658struct kovaplus_device *kovaplus = hid_get_drvdata(hdev);659660if (intf->cur_altsetting->desc.bInterfaceProtocol661!= USB_INTERFACE_PROTOCOL_MOUSE)662return 0;663664kovaplus_keep_values_up_to_date(kovaplus, data);665666if (kovaplus->roccat_claimed)667kovaplus_report_to_chrdev(kovaplus, data);668669return 0;670}671672static const struct hid_device_id kovaplus_devices[] = {673{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },674{ }675};676677MODULE_DEVICE_TABLE(hid, kovaplus_devices);678679static struct hid_driver kovaplus_driver = {680.name = "kovaplus",681.id_table = kovaplus_devices,682.probe = kovaplus_probe,683.remove = kovaplus_remove,684.raw_event = kovaplus_raw_event685};686687static int __init kovaplus_init(void)688{689int retval;690691kovaplus_class = class_create(THIS_MODULE, "kovaplus");692if (IS_ERR(kovaplus_class))693return PTR_ERR(kovaplus_class);694kovaplus_class->dev_attrs = kovaplus_attributes;695kovaplus_class->dev_bin_attrs = kovaplus_bin_attributes;696697retval = hid_register_driver(&kovaplus_driver);698if (retval)699class_destroy(kovaplus_class);700return retval;701}702703static void __exit kovaplus_exit(void)704{705hid_unregister_driver(&kovaplus_driver);706class_destroy(kovaplus_class);707}708709module_init(kovaplus_init);710module_exit(kovaplus_exit);711712MODULE_AUTHOR("Stefan Achatz");713MODULE_DESCRIPTION("USB Roccat Kova[+] driver");714MODULE_LICENSE("GPL v2");715716717