10e70f97fSStefan Achatz /* 20e70f97fSStefan Achatz * Roccat Kova[+] driver for Linux 30e70f97fSStefan Achatz * 40e70f97fSStefan Achatz * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> 50e70f97fSStefan Achatz */ 60e70f97fSStefan Achatz 70e70f97fSStefan Achatz /* 80e70f97fSStefan Achatz * This program is free software; you can redistribute it and/or modify it 90e70f97fSStefan Achatz * under the terms of the GNU General Public License as published by the Free 100e70f97fSStefan Achatz * Software Foundation; either version 2 of the License, or (at your option) 110e70f97fSStefan Achatz * any later version. 120e70f97fSStefan Achatz */ 130e70f97fSStefan Achatz 140e70f97fSStefan Achatz /* 150e70f97fSStefan Achatz * Roccat Kova[+] is a bigger version of the Pyra with two more side buttons. 160e70f97fSStefan Achatz */ 170e70f97fSStefan Achatz 180e70f97fSStefan Achatz #include <linux/device.h> 190e70f97fSStefan Achatz #include <linux/input.h> 200e70f97fSStefan Achatz #include <linux/hid.h> 210e70f97fSStefan Achatz #include <linux/module.h> 220e70f97fSStefan Achatz #include <linux/slab.h> 235dc0c983SStefan Achatz #include <linux/hid-roccat.h> 240e70f97fSStefan Achatz #include "hid-ids.h" 250e70f97fSStefan Achatz #include "hid-roccat-common.h" 260e70f97fSStefan Achatz #include "hid-roccat-kovaplus.h" 270e70f97fSStefan Achatz 280e70f97fSStefan Achatz static uint profile_numbers[5] = {0, 1, 2, 3, 4}; 290e70f97fSStefan Achatz 300e70f97fSStefan Achatz static struct class *kovaplus_class; 310e70f97fSStefan Achatz 320e70f97fSStefan Achatz static uint kovaplus_convert_event_cpi(uint value) 330e70f97fSStefan Achatz { 340e70f97fSStefan Achatz return (value == 7 ? 4 : (value == 4 ? 3 : value)); 350e70f97fSStefan Achatz } 360e70f97fSStefan Achatz 370e70f97fSStefan Achatz static void kovaplus_profile_activated(struct kovaplus_device *kovaplus, 380e70f97fSStefan Achatz uint new_profile_index) 390e70f97fSStefan Achatz { 400e70f97fSStefan Achatz kovaplus->actual_profile = new_profile_index; 410e70f97fSStefan Achatz kovaplus->actual_cpi = kovaplus->profile_settings[new_profile_index].cpi_startup_level; 420e70f97fSStefan Achatz kovaplus->actual_x_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_x; 430e70f97fSStefan Achatz kovaplus->actual_y_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_y; 440e70f97fSStefan Achatz } 450e70f97fSStefan Achatz 460e70f97fSStefan Achatz static int kovaplus_send_control(struct usb_device *usb_dev, uint value, 470e70f97fSStefan Achatz enum kovaplus_control_requests request) 480e70f97fSStefan Achatz { 490e70f97fSStefan Achatz int retval; 507392d73bSStefan Achatz struct roccat_common2_control control; 510e70f97fSStefan Achatz 520e70f97fSStefan Achatz if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || 530e70f97fSStefan Achatz request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && 540e70f97fSStefan Achatz value > 4) 550e70f97fSStefan Achatz return -EINVAL; 560e70f97fSStefan Achatz 574728f2dcSStefan Achatz control.command = ROCCAT_COMMON_COMMAND_CONTROL; 580e70f97fSStefan Achatz control.value = value; 590e70f97fSStefan Achatz control.request = request; 600e70f97fSStefan Achatz 617392d73bSStefan Achatz retval = roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL, 627392d73bSStefan Achatz &control, sizeof(struct roccat_common2_control)); 630e70f97fSStefan Achatz 640e70f97fSStefan Achatz return retval; 650e70f97fSStefan Achatz } 660e70f97fSStefan Achatz 670e70f97fSStefan Achatz static int kovaplus_select_profile(struct usb_device *usb_dev, uint number, 680e70f97fSStefan Achatz enum kovaplus_control_requests request) 690e70f97fSStefan Achatz { 700e70f97fSStefan Achatz return kovaplus_send_control(usb_dev, number, request); 710e70f97fSStefan Achatz } 720e70f97fSStefan Achatz 730e70f97fSStefan Achatz static int kovaplus_get_profile_settings(struct usb_device *usb_dev, 740e70f97fSStefan Achatz struct kovaplus_profile_settings *buf, uint number) 750e70f97fSStefan Achatz { 760e70f97fSStefan Achatz int retval; 770e70f97fSStefan Achatz 780e70f97fSStefan Achatz retval = kovaplus_select_profile(usb_dev, number, 790e70f97fSStefan Achatz KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); 800e70f97fSStefan Achatz if (retval) 810e70f97fSStefan Achatz return retval; 820e70f97fSStefan Achatz 837392d73bSStefan Achatz return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS, 8494a8fcf9SStefan Achatz buf, KOVAPLUS_SIZE_PROFILE_SETTINGS); 850e70f97fSStefan Achatz } 860e70f97fSStefan Achatz 870e70f97fSStefan Achatz static int kovaplus_get_profile_buttons(struct usb_device *usb_dev, 880e70f97fSStefan Achatz struct kovaplus_profile_buttons *buf, int number) 890e70f97fSStefan Achatz { 900e70f97fSStefan Achatz int retval; 910e70f97fSStefan Achatz 920e70f97fSStefan Achatz retval = kovaplus_select_profile(usb_dev, number, 930e70f97fSStefan Achatz KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); 940e70f97fSStefan Achatz if (retval) 950e70f97fSStefan Achatz return retval; 960e70f97fSStefan Achatz 977392d73bSStefan Achatz return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS, 9894a8fcf9SStefan Achatz buf, KOVAPLUS_SIZE_PROFILE_BUTTONS); 990e70f97fSStefan Achatz } 1000e70f97fSStefan Achatz 1010e70f97fSStefan Achatz /* retval is 0-4 on success, < 0 on error */ 1020e70f97fSStefan Achatz static int kovaplus_get_actual_profile(struct usb_device *usb_dev) 1030e70f97fSStefan Achatz { 1040e70f97fSStefan Achatz struct kovaplus_actual_profile buf; 1050e70f97fSStefan Achatz int retval; 1060e70f97fSStefan Achatz 1077392d73bSStefan Achatz retval = roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE, 1080e70f97fSStefan Achatz &buf, sizeof(struct kovaplus_actual_profile)); 1090e70f97fSStefan Achatz 1100e70f97fSStefan Achatz return retval ? retval : buf.actual_profile; 1110e70f97fSStefan Achatz } 1120e70f97fSStefan Achatz 1130e70f97fSStefan Achatz static int kovaplus_set_actual_profile(struct usb_device *usb_dev, 1140e70f97fSStefan Achatz int new_profile) 1150e70f97fSStefan Achatz { 1160e70f97fSStefan Achatz struct kovaplus_actual_profile buf; 1170e70f97fSStefan Achatz 1180e70f97fSStefan Achatz buf.command = KOVAPLUS_COMMAND_ACTUAL_PROFILE; 1190e70f97fSStefan Achatz buf.size = sizeof(struct kovaplus_actual_profile); 1200e70f97fSStefan Achatz buf.actual_profile = new_profile; 1210e70f97fSStefan Achatz 1227392d73bSStefan Achatz return roccat_common2_send_with_status(usb_dev, 1234728f2dcSStefan Achatz KOVAPLUS_COMMAND_ACTUAL_PROFILE, 1240e70f97fSStefan Achatz &buf, sizeof(struct kovaplus_actual_profile)); 1250e70f97fSStefan Achatz } 1260e70f97fSStefan Achatz 12794a8fcf9SStefan Achatz static ssize_t kovaplus_sysfs_read(struct file *fp, struct kobject *kobj, 12894a8fcf9SStefan Achatz char *buf, loff_t off, size_t count, 12994a8fcf9SStefan Achatz size_t real_size, uint command) 13094a8fcf9SStefan Achatz { 13194a8fcf9SStefan Achatz struct device *dev = 13294a8fcf9SStefan Achatz container_of(kobj, struct device, kobj)->parent->parent; 13394a8fcf9SStefan Achatz struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); 13494a8fcf9SStefan Achatz struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); 13594a8fcf9SStefan Achatz int retval; 13694a8fcf9SStefan Achatz 13794a8fcf9SStefan Achatz if (off >= real_size) 13894a8fcf9SStefan Achatz return 0; 13994a8fcf9SStefan Achatz 14094a8fcf9SStefan Achatz if (off != 0 || count != real_size) 14194a8fcf9SStefan Achatz return -EINVAL; 14294a8fcf9SStefan Achatz 14394a8fcf9SStefan Achatz mutex_lock(&kovaplus->kovaplus_lock); 14494a8fcf9SStefan Achatz retval = roccat_common2_receive(usb_dev, command, buf, real_size); 14594a8fcf9SStefan Achatz mutex_unlock(&kovaplus->kovaplus_lock); 14694a8fcf9SStefan Achatz 14794a8fcf9SStefan Achatz if (retval) 14894a8fcf9SStefan Achatz return retval; 14994a8fcf9SStefan Achatz 15094a8fcf9SStefan Achatz return real_size; 15194a8fcf9SStefan Achatz } 15294a8fcf9SStefan Achatz 15394a8fcf9SStefan Achatz static ssize_t kovaplus_sysfs_write(struct file *fp, struct kobject *kobj, 15494a8fcf9SStefan Achatz void const *buf, loff_t off, size_t count, 15594a8fcf9SStefan Achatz size_t real_size, uint command) 15694a8fcf9SStefan Achatz { 15794a8fcf9SStefan Achatz struct device *dev = 15894a8fcf9SStefan Achatz container_of(kobj, struct device, kobj)->parent->parent; 15994a8fcf9SStefan Achatz struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); 16094a8fcf9SStefan Achatz struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); 16194a8fcf9SStefan Achatz int retval; 16294a8fcf9SStefan Achatz 16394a8fcf9SStefan Achatz if (off != 0 || count != real_size) 16494a8fcf9SStefan Achatz return -EINVAL; 16594a8fcf9SStefan Achatz 16694a8fcf9SStefan Achatz mutex_lock(&kovaplus->kovaplus_lock); 16794a8fcf9SStefan Achatz retval = roccat_common2_send_with_status(usb_dev, command, 16894a8fcf9SStefan Achatz buf, real_size); 16994a8fcf9SStefan Achatz mutex_unlock(&kovaplus->kovaplus_lock); 17094a8fcf9SStefan Achatz 17194a8fcf9SStefan Achatz if (retval) 17294a8fcf9SStefan Achatz return retval; 17394a8fcf9SStefan Achatz 17494a8fcf9SStefan Achatz return real_size; 17594a8fcf9SStefan Achatz } 17694a8fcf9SStefan Achatz 17794a8fcf9SStefan Achatz #define KOVAPLUS_SYSFS_W(thingy, THINGY) \ 17894a8fcf9SStefan Achatz static ssize_t kovaplus_sysfs_write_ ## thingy(struct file *fp, \ 17994a8fcf9SStefan Achatz struct kobject *kobj, struct bin_attribute *attr, char *buf, \ 18094a8fcf9SStefan Achatz loff_t off, size_t count) \ 18194a8fcf9SStefan Achatz { \ 18294a8fcf9SStefan Achatz return kovaplus_sysfs_write(fp, kobj, buf, off, count, \ 18394a8fcf9SStefan Achatz KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \ 18494a8fcf9SStefan Achatz } 18594a8fcf9SStefan Achatz 18694a8fcf9SStefan Achatz #define KOVAPLUS_SYSFS_R(thingy, THINGY) \ 18794a8fcf9SStefan Achatz static ssize_t kovaplus_sysfs_read_ ## thingy(struct file *fp, \ 18894a8fcf9SStefan Achatz struct kobject *kobj, struct bin_attribute *attr, char *buf, \ 18994a8fcf9SStefan Achatz loff_t off, size_t count) \ 19094a8fcf9SStefan Achatz { \ 19194a8fcf9SStefan Achatz return kovaplus_sysfs_read(fp, kobj, buf, off, count, \ 19294a8fcf9SStefan Achatz KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \ 19394a8fcf9SStefan Achatz } 19494a8fcf9SStefan Achatz 19594a8fcf9SStefan Achatz #define KOVAPLUS_SYSFS_RW(thingy, THINGY) \ 19694a8fcf9SStefan Achatz KOVAPLUS_SYSFS_W(thingy, THINGY) \ 19794a8fcf9SStefan Achatz KOVAPLUS_SYSFS_R(thingy, THINGY) 19894a8fcf9SStefan Achatz 19994a8fcf9SStefan Achatz #define KOVAPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \ 200975b53ccSGreg Kroah-Hartman KOVAPLUS_SYSFS_RW(thingy, THINGY); \ 201975b53ccSGreg Kroah-Hartman static struct bin_attribute bin_attr_##thingy = { \ 20294a8fcf9SStefan Achatz .attr = { .name = #thingy, .mode = 0660 }, \ 20394a8fcf9SStefan Achatz .size = KOVAPLUS_SIZE_ ## THINGY, \ 20494a8fcf9SStefan Achatz .read = kovaplus_sysfs_read_ ## thingy, \ 20594a8fcf9SStefan Achatz .write = kovaplus_sysfs_write_ ## thingy \ 20694a8fcf9SStefan Achatz } 20794a8fcf9SStefan Achatz 20894a8fcf9SStefan Achatz #define KOVAPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \ 209975b53ccSGreg Kroah-Hartman KOVAPLUS_SYSFS_W(thingy, THINGY); \ 210975b53ccSGreg Kroah-Hartman static struct bin_attribute bin_attr_##thingy = { \ 21194a8fcf9SStefan Achatz .attr = { .name = #thingy, .mode = 0220 }, \ 21294a8fcf9SStefan Achatz .size = KOVAPLUS_SIZE_ ## THINGY, \ 21394a8fcf9SStefan Achatz .write = kovaplus_sysfs_write_ ## thingy \ 21494a8fcf9SStefan Achatz } 215975b53ccSGreg Kroah-Hartman KOVAPLUS_BIN_ATTRIBUTE_W(control, CONTROL); 216975b53ccSGreg Kroah-Hartman KOVAPLUS_BIN_ATTRIBUTE_RW(info, INFO); 217975b53ccSGreg Kroah-Hartman KOVAPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS); 218975b53ccSGreg Kroah-Hartman KOVAPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS); 21994a8fcf9SStefan Achatz 2200e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp, 2210e70f97fSStefan Achatz struct kobject *kobj, struct bin_attribute *attr, char *buf, 2220e70f97fSStefan Achatz loff_t off, size_t count) 2230e70f97fSStefan Achatz { 2240e70f97fSStefan Achatz struct device *dev = 2250e70f97fSStefan Achatz container_of(kobj, struct device, kobj)->parent->parent; 2260e70f97fSStefan Achatz struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); 22794a8fcf9SStefan Achatz ssize_t retval; 2280e70f97fSStefan Achatz 22994a8fcf9SStefan Achatz retval = kovaplus_select_profile(usb_dev, *(uint *)(attr->private), 23094a8fcf9SStefan Achatz KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); 2310e70f97fSStefan Achatz if (retval) 2320e70f97fSStefan Achatz return retval; 2330e70f97fSStefan Achatz 23494a8fcf9SStefan Achatz return kovaplus_sysfs_read(fp, kobj, buf, off, count, 23594a8fcf9SStefan Achatz KOVAPLUS_SIZE_PROFILE_SETTINGS, 23694a8fcf9SStefan Achatz KOVAPLUS_COMMAND_PROFILE_SETTINGS); 2370e70f97fSStefan Achatz } 2380e70f97fSStefan Achatz 2390e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp, 2400e70f97fSStefan Achatz struct kobject *kobj, struct bin_attribute *attr, char *buf, 2410e70f97fSStefan Achatz loff_t off, size_t count) 2420e70f97fSStefan Achatz { 2430e70f97fSStefan Achatz struct device *dev = 2440e70f97fSStefan Achatz container_of(kobj, struct device, kobj)->parent->parent; 2450e70f97fSStefan Achatz struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); 24694a8fcf9SStefan Achatz ssize_t retval; 2470e70f97fSStefan Achatz 24894a8fcf9SStefan Achatz retval = kovaplus_select_profile(usb_dev, *(uint *)(attr->private), 24994a8fcf9SStefan Achatz KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); 2500e70f97fSStefan Achatz if (retval) 2510e70f97fSStefan Achatz return retval; 2520e70f97fSStefan Achatz 25394a8fcf9SStefan Achatz return kovaplus_sysfs_read(fp, kobj, buf, off, count, 25494a8fcf9SStefan Achatz KOVAPLUS_SIZE_PROFILE_BUTTONS, 25594a8fcf9SStefan Achatz KOVAPLUS_COMMAND_PROFILE_BUTTONS); 2560e70f97fSStefan Achatz } 2570e70f97fSStefan Achatz 258975b53ccSGreg Kroah-Hartman #define PROFILE_ATTR(number) \ 259975b53ccSGreg Kroah-Hartman static struct bin_attribute bin_attr_profile##number##_settings = { \ 260550dbf47SStefan Achatz .attr = { .name = "profile" #number "_settings", .mode = 0440 }, \ 261975b53ccSGreg Kroah-Hartman .size = KOVAPLUS_SIZE_PROFILE_SETTINGS, \ 262975b53ccSGreg Kroah-Hartman .read = kovaplus_sysfs_read_profilex_settings, \ 263975b53ccSGreg Kroah-Hartman .private = &profile_numbers[number-1], \ 264975b53ccSGreg Kroah-Hartman }; \ 265975b53ccSGreg Kroah-Hartman static struct bin_attribute bin_attr_profile##number##_buttons = { \ 266550dbf47SStefan Achatz .attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \ 267975b53ccSGreg Kroah-Hartman .size = KOVAPLUS_SIZE_PROFILE_BUTTONS, \ 268975b53ccSGreg Kroah-Hartman .read = kovaplus_sysfs_read_profilex_buttons, \ 269975b53ccSGreg Kroah-Hartman .private = &profile_numbers[number-1], \ 270975b53ccSGreg Kroah-Hartman }; 271975b53ccSGreg Kroah-Hartman PROFILE_ATTR(1); 272975b53ccSGreg Kroah-Hartman PROFILE_ATTR(2); 273975b53ccSGreg Kroah-Hartman PROFILE_ATTR(3); 274975b53ccSGreg Kroah-Hartman PROFILE_ATTR(4); 275975b53ccSGreg Kroah-Hartman PROFILE_ATTR(5); 276975b53ccSGreg Kroah-Hartman 2770e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev, 2780e70f97fSStefan Achatz struct device_attribute *attr, char *buf) 2790e70f97fSStefan Achatz { 2800e70f97fSStefan Achatz struct kovaplus_device *kovaplus = 2810e70f97fSStefan Achatz hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); 2820e70f97fSStefan Achatz return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile); 2830e70f97fSStefan Achatz } 2840e70f97fSStefan Achatz 2850e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev, 2860e70f97fSStefan Achatz struct device_attribute *attr, char const *buf, size_t size) 2870e70f97fSStefan Achatz { 2880e70f97fSStefan Achatz struct kovaplus_device *kovaplus; 2890e70f97fSStefan Achatz struct usb_device *usb_dev; 2900e70f97fSStefan Achatz unsigned long profile; 2910e70f97fSStefan Achatz int retval; 2926b9a57b9SStefan Achatz struct kovaplus_roccat_report roccat_report; 2930e70f97fSStefan Achatz 2940e70f97fSStefan Achatz dev = dev->parent->parent; 2950e70f97fSStefan Achatz kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); 2960e70f97fSStefan Achatz usb_dev = interface_to_usbdev(to_usb_interface(dev)); 2970e70f97fSStefan Achatz 298dfc450b5SJingoo Han retval = kstrtoul(buf, 10, &profile); 2990e70f97fSStefan Achatz if (retval) 3000e70f97fSStefan Achatz return retval; 3010e70f97fSStefan Achatz 3020e70f97fSStefan Achatz if (profile >= 5) 3030e70f97fSStefan Achatz return -EINVAL; 3040e70f97fSStefan Achatz 3050e70f97fSStefan Achatz mutex_lock(&kovaplus->kovaplus_lock); 3060e70f97fSStefan Achatz retval = kovaplus_set_actual_profile(usb_dev, profile); 3076b9a57b9SStefan Achatz if (retval) { 3080e70f97fSStefan Achatz mutex_unlock(&kovaplus->kovaplus_lock); 3090e70f97fSStefan Achatz return retval; 3106b9a57b9SStefan Achatz } 3116b9a57b9SStefan Achatz 3126b9a57b9SStefan Achatz kovaplus_profile_activated(kovaplus, profile); 3136b9a57b9SStefan Achatz 3146b9a57b9SStefan Achatz roccat_report.type = KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1; 3156b9a57b9SStefan Achatz roccat_report.profile = profile + 1; 3166b9a57b9SStefan Achatz roccat_report.button = 0; 3176b9a57b9SStefan Achatz roccat_report.data1 = profile + 1; 3186b9a57b9SStefan Achatz roccat_report.data2 = 0; 3196b9a57b9SStefan Achatz roccat_report_event(kovaplus->chrdev_minor, 3206b9a57b9SStefan Achatz (uint8_t const *)&roccat_report); 3216b9a57b9SStefan Achatz 3226b9a57b9SStefan Achatz mutex_unlock(&kovaplus->kovaplus_lock); 3230e70f97fSStefan Achatz 3240e70f97fSStefan Achatz return size; 3250e70f97fSStefan Achatz } 32646a58c44SGreg Kroah-Hartman static DEVICE_ATTR(actual_profile, 0660, 32746a58c44SGreg Kroah-Hartman kovaplus_sysfs_show_actual_profile, 32846a58c44SGreg Kroah-Hartman kovaplus_sysfs_set_actual_profile); 3290e70f97fSStefan Achatz 3300e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev, 3310e70f97fSStefan Achatz struct device_attribute *attr, char *buf) 3320e70f97fSStefan Achatz { 3330e70f97fSStefan Achatz struct kovaplus_device *kovaplus = 3340e70f97fSStefan Achatz hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); 3350e70f97fSStefan Achatz return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi); 3360e70f97fSStefan Achatz } 33746a58c44SGreg Kroah-Hartman static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL); 3380e70f97fSStefan Achatz 3390e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev, 3400e70f97fSStefan Achatz struct device_attribute *attr, char *buf) 3410e70f97fSStefan Achatz { 3420e70f97fSStefan Achatz struct kovaplus_device *kovaplus = 3430e70f97fSStefan Achatz hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); 3440e70f97fSStefan Achatz return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity); 3450e70f97fSStefan Achatz } 34646a58c44SGreg Kroah-Hartman static DEVICE_ATTR(actual_sensitivity_x, 0440, 34746a58c44SGreg Kroah-Hartman kovaplus_sysfs_show_actual_sensitivity_x, NULL); 3480e70f97fSStefan Achatz 3490e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev, 3500e70f97fSStefan Achatz struct device_attribute *attr, char *buf) 3510e70f97fSStefan Achatz { 3520e70f97fSStefan Achatz struct kovaplus_device *kovaplus = 3530e70f97fSStefan Achatz hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); 3540e70f97fSStefan Achatz return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity); 3550e70f97fSStefan Achatz } 35646a58c44SGreg Kroah-Hartman static DEVICE_ATTR(actual_sensitivity_y, 0440, 35746a58c44SGreg Kroah-Hartman kovaplus_sysfs_show_actual_sensitivity_y, NULL); 3580e70f97fSStefan Achatz 3590e70f97fSStefan Achatz static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev, 3600e70f97fSStefan Achatz struct device_attribute *attr, char *buf) 3610e70f97fSStefan Achatz { 36294a8fcf9SStefan Achatz struct kovaplus_device *kovaplus; 36394a8fcf9SStefan Achatz struct usb_device *usb_dev; 36494a8fcf9SStefan Achatz struct kovaplus_info info; 36594a8fcf9SStefan Achatz 36694a8fcf9SStefan Achatz dev = dev->parent->parent; 36794a8fcf9SStefan Achatz kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); 36894a8fcf9SStefan Achatz usb_dev = interface_to_usbdev(to_usb_interface(dev)); 36994a8fcf9SStefan Achatz 37094a8fcf9SStefan Achatz mutex_lock(&kovaplus->kovaplus_lock); 37194a8fcf9SStefan Achatz roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_INFO, 37294a8fcf9SStefan Achatz &info, KOVAPLUS_SIZE_INFO); 37394a8fcf9SStefan Achatz mutex_unlock(&kovaplus->kovaplus_lock); 37494a8fcf9SStefan Achatz 37594a8fcf9SStefan Achatz return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version); 3760e70f97fSStefan Achatz } 37746a58c44SGreg Kroah-Hartman static DEVICE_ATTR(firmware_version, 0440, 37846a58c44SGreg Kroah-Hartman kovaplus_sysfs_show_firmware_version, NULL); 3790e70f97fSStefan Achatz 38046a58c44SGreg Kroah-Hartman static struct attribute *kovaplus_attrs[] = { 38146a58c44SGreg Kroah-Hartman &dev_attr_actual_cpi.attr, 38246a58c44SGreg Kroah-Hartman &dev_attr_firmware_version.attr, 38346a58c44SGreg Kroah-Hartman &dev_attr_actual_profile.attr, 38446a58c44SGreg Kroah-Hartman &dev_attr_actual_sensitivity_x.attr, 38546a58c44SGreg Kroah-Hartman &dev_attr_actual_sensitivity_y.attr, 38646a58c44SGreg Kroah-Hartman NULL, 3870e70f97fSStefan Achatz }; 3880e70f97fSStefan Achatz 389975b53ccSGreg Kroah-Hartman static struct bin_attribute *kovaplus_bin_attributes[] = { 390975b53ccSGreg Kroah-Hartman &bin_attr_control, 391975b53ccSGreg Kroah-Hartman &bin_attr_info, 392975b53ccSGreg Kroah-Hartman &bin_attr_profile_settings, 393975b53ccSGreg Kroah-Hartman &bin_attr_profile_buttons, 394975b53ccSGreg Kroah-Hartman &bin_attr_profile1_settings, 395975b53ccSGreg Kroah-Hartman &bin_attr_profile2_settings, 396975b53ccSGreg Kroah-Hartman &bin_attr_profile3_settings, 397975b53ccSGreg Kroah-Hartman &bin_attr_profile4_settings, 398975b53ccSGreg Kroah-Hartman &bin_attr_profile5_settings, 399975b53ccSGreg Kroah-Hartman &bin_attr_profile1_buttons, 400975b53ccSGreg Kroah-Hartman &bin_attr_profile2_buttons, 401975b53ccSGreg Kroah-Hartman &bin_attr_profile3_buttons, 402975b53ccSGreg Kroah-Hartman &bin_attr_profile4_buttons, 403975b53ccSGreg Kroah-Hartman &bin_attr_profile5_buttons, 404975b53ccSGreg Kroah-Hartman NULL, 405975b53ccSGreg Kroah-Hartman }; 406975b53ccSGreg Kroah-Hartman 407975b53ccSGreg Kroah-Hartman static const struct attribute_group kovaplus_group = { 408975b53ccSGreg Kroah-Hartman .attrs = kovaplus_attrs, 409975b53ccSGreg Kroah-Hartman .bin_attrs = kovaplus_bin_attributes, 410975b53ccSGreg Kroah-Hartman }; 411975b53ccSGreg Kroah-Hartman 412975b53ccSGreg Kroah-Hartman static const struct attribute_group *kovaplus_groups[] = { 413975b53ccSGreg Kroah-Hartman &kovaplus_group, 414975b53ccSGreg Kroah-Hartman NULL, 4150e70f97fSStefan Achatz }; 4160e70f97fSStefan Achatz 4170e70f97fSStefan Achatz static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev, 4180e70f97fSStefan Achatz struct kovaplus_device *kovaplus) 4190e70f97fSStefan Achatz { 4200e70f97fSStefan Achatz int retval, i; 4210e70f97fSStefan Achatz static uint wait = 70; /* device will freeze with just 60 */ 4220e70f97fSStefan Achatz 4230e70f97fSStefan Achatz mutex_init(&kovaplus->kovaplus_lock); 4240e70f97fSStefan Achatz 4250e70f97fSStefan Achatz for (i = 0; i < 5; ++i) { 4260e70f97fSStefan Achatz msleep(wait); 4270e70f97fSStefan Achatz retval = kovaplus_get_profile_settings(usb_dev, 4280e70f97fSStefan Achatz &kovaplus->profile_settings[i], i); 4290e70f97fSStefan Achatz if (retval) 4300e70f97fSStefan Achatz return retval; 4310e70f97fSStefan Achatz 4320e70f97fSStefan Achatz msleep(wait); 4330e70f97fSStefan Achatz retval = kovaplus_get_profile_buttons(usb_dev, 4340e70f97fSStefan Achatz &kovaplus->profile_buttons[i], i); 4350e70f97fSStefan Achatz if (retval) 4360e70f97fSStefan Achatz return retval; 4370e70f97fSStefan Achatz } 4380e70f97fSStefan Achatz 4390e70f97fSStefan Achatz msleep(wait); 4400e70f97fSStefan Achatz retval = kovaplus_get_actual_profile(usb_dev); 4410e70f97fSStefan Achatz if (retval < 0) 4420e70f97fSStefan Achatz return retval; 4430e70f97fSStefan Achatz kovaplus_profile_activated(kovaplus, retval); 4440e70f97fSStefan Achatz 4450e70f97fSStefan Achatz return 0; 4460e70f97fSStefan Achatz } 4470e70f97fSStefan Achatz 4480e70f97fSStefan Achatz static int kovaplus_init_specials(struct hid_device *hdev) 4490e70f97fSStefan Achatz { 4500e70f97fSStefan Achatz struct usb_interface *intf = to_usb_interface(hdev->dev.parent); 4510e70f97fSStefan Achatz struct usb_device *usb_dev = interface_to_usbdev(intf); 4520e70f97fSStefan Achatz struct kovaplus_device *kovaplus; 4530e70f97fSStefan Achatz int retval; 4540e70f97fSStefan Achatz 4550e70f97fSStefan Achatz if (intf->cur_altsetting->desc.bInterfaceProtocol 4560e70f97fSStefan Achatz == USB_INTERFACE_PROTOCOL_MOUSE) { 4570e70f97fSStefan Achatz 4580e70f97fSStefan Achatz kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL); 4590e70f97fSStefan Achatz if (!kovaplus) { 4600e70f97fSStefan Achatz hid_err(hdev, "can't alloc device descriptor\n"); 4610e70f97fSStefan Achatz return -ENOMEM; 4620e70f97fSStefan Achatz } 4630e70f97fSStefan Achatz hid_set_drvdata(hdev, kovaplus); 4640e70f97fSStefan Achatz 4650e70f97fSStefan Achatz retval = kovaplus_init_kovaplus_device_struct(usb_dev, kovaplus); 4660e70f97fSStefan Achatz if (retval) { 4670e70f97fSStefan Achatz hid_err(hdev, "couldn't init struct kovaplus_device\n"); 4680e70f97fSStefan Achatz goto exit_free; 4690e70f97fSStefan Achatz } 4700e70f97fSStefan Achatz 4718211e460SStefan Achatz retval = roccat_connect(kovaplus_class, hdev, 4728211e460SStefan Achatz sizeof(struct kovaplus_roccat_report)); 4730e70f97fSStefan Achatz if (retval < 0) { 4740e70f97fSStefan Achatz hid_err(hdev, "couldn't init char dev\n"); 4750e70f97fSStefan Achatz } else { 4760e70f97fSStefan Achatz kovaplus->chrdev_minor = retval; 4770e70f97fSStefan Achatz kovaplus->roccat_claimed = 1; 4780e70f97fSStefan Achatz } 4790e70f97fSStefan Achatz 4800e70f97fSStefan Achatz } else { 4810e70f97fSStefan Achatz hid_set_drvdata(hdev, NULL); 4820e70f97fSStefan Achatz } 4830e70f97fSStefan Achatz 4840e70f97fSStefan Achatz return 0; 4850e70f97fSStefan Achatz exit_free: 4860e70f97fSStefan Achatz kfree(kovaplus); 4870e70f97fSStefan Achatz return retval; 4880e70f97fSStefan Achatz } 4890e70f97fSStefan Achatz 4900e70f97fSStefan Achatz static void kovaplus_remove_specials(struct hid_device *hdev) 4910e70f97fSStefan Achatz { 4920e70f97fSStefan Achatz struct usb_interface *intf = to_usb_interface(hdev->dev.parent); 4930e70f97fSStefan Achatz struct kovaplus_device *kovaplus; 4940e70f97fSStefan Achatz 4950e70f97fSStefan Achatz if (intf->cur_altsetting->desc.bInterfaceProtocol 4960e70f97fSStefan Achatz == USB_INTERFACE_PROTOCOL_MOUSE) { 4970e70f97fSStefan Achatz kovaplus = hid_get_drvdata(hdev); 4980e70f97fSStefan Achatz if (kovaplus->roccat_claimed) 4990e70f97fSStefan Achatz roccat_disconnect(kovaplus->chrdev_minor); 5000e70f97fSStefan Achatz kfree(kovaplus); 5010e70f97fSStefan Achatz } 5020e70f97fSStefan Achatz } 5030e70f97fSStefan Achatz 5040e70f97fSStefan Achatz static int kovaplus_probe(struct hid_device *hdev, 5050e70f97fSStefan Achatz const struct hid_device_id *id) 5060e70f97fSStefan Achatz { 5070e70f97fSStefan Achatz int retval; 5080e70f97fSStefan Achatz 5090e70f97fSStefan Achatz retval = hid_parse(hdev); 5100e70f97fSStefan Achatz if (retval) { 5110e70f97fSStefan Achatz hid_err(hdev, "parse failed\n"); 5120e70f97fSStefan Achatz goto exit; 5130e70f97fSStefan Achatz } 5140e70f97fSStefan Achatz 5150e70f97fSStefan Achatz retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 5160e70f97fSStefan Achatz if (retval) { 5170e70f97fSStefan Achatz hid_err(hdev, "hw start failed\n"); 5180e70f97fSStefan Achatz goto exit; 5190e70f97fSStefan Achatz } 5200e70f97fSStefan Achatz 5210e70f97fSStefan Achatz retval = kovaplus_init_specials(hdev); 5220e70f97fSStefan Achatz if (retval) { 5230e70f97fSStefan Achatz hid_err(hdev, "couldn't install mouse\n"); 5240e70f97fSStefan Achatz goto exit_stop; 5250e70f97fSStefan Achatz } 5260e70f97fSStefan Achatz 5270e70f97fSStefan Achatz return 0; 5280e70f97fSStefan Achatz 5290e70f97fSStefan Achatz exit_stop: 5300e70f97fSStefan Achatz hid_hw_stop(hdev); 5310e70f97fSStefan Achatz exit: 5320e70f97fSStefan Achatz return retval; 5330e70f97fSStefan Achatz } 5340e70f97fSStefan Achatz 5350e70f97fSStefan Achatz static void kovaplus_remove(struct hid_device *hdev) 5360e70f97fSStefan Achatz { 5370e70f97fSStefan Achatz kovaplus_remove_specials(hdev); 5380e70f97fSStefan Achatz hid_hw_stop(hdev); 5390e70f97fSStefan Achatz } 5400e70f97fSStefan Achatz 5410e70f97fSStefan Achatz static void kovaplus_keep_values_up_to_date(struct kovaplus_device *kovaplus, 5420e70f97fSStefan Achatz u8 const *data) 5430e70f97fSStefan Achatz { 5440e70f97fSStefan Achatz struct kovaplus_mouse_report_button const *button_report; 5450e70f97fSStefan Achatz 5460e70f97fSStefan Achatz if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON) 5470e70f97fSStefan Achatz return; 5480e70f97fSStefan Achatz 5490e70f97fSStefan Achatz button_report = (struct kovaplus_mouse_report_button const *)data; 5500e70f97fSStefan Achatz 5510e70f97fSStefan Achatz switch (button_report->type) { 5520e70f97fSStefan Achatz case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1: 5530e70f97fSStefan Achatz kovaplus_profile_activated(kovaplus, button_report->data1 - 1); 5540e70f97fSStefan Achatz break; 5550e70f97fSStefan Achatz case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI: 5560e70f97fSStefan Achatz kovaplus->actual_cpi = kovaplus_convert_event_cpi(button_report->data1); 557*7be63f20SStefan Achatz break; 5580e70f97fSStefan Achatz case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY: 5590e70f97fSStefan Achatz kovaplus->actual_x_sensitivity = button_report->data1; 5600e70f97fSStefan Achatz kovaplus->actual_y_sensitivity = button_report->data2; 561*7be63f20SStefan Achatz break; 562*7be63f20SStefan Achatz default: 563*7be63f20SStefan Achatz break; 5640e70f97fSStefan Achatz } 5650e70f97fSStefan Achatz } 5660e70f97fSStefan Achatz 5670e70f97fSStefan Achatz static void kovaplus_report_to_chrdev(struct kovaplus_device const *kovaplus, 5680e70f97fSStefan Achatz u8 const *data) 5690e70f97fSStefan Achatz { 5700e70f97fSStefan Achatz struct kovaplus_roccat_report roccat_report; 5710e70f97fSStefan Achatz struct kovaplus_mouse_report_button const *button_report; 5720e70f97fSStefan Achatz 5730e70f97fSStefan Achatz if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON) 5740e70f97fSStefan Achatz return; 5750e70f97fSStefan Achatz 5760e70f97fSStefan Achatz button_report = (struct kovaplus_mouse_report_button const *)data; 5770e70f97fSStefan Achatz 5780e70f97fSStefan Achatz if (button_report->type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2) 5790e70f97fSStefan Achatz return; 5800e70f97fSStefan Achatz 5810e70f97fSStefan Achatz roccat_report.type = button_report->type; 5820e70f97fSStefan Achatz roccat_report.profile = kovaplus->actual_profile + 1; 5830e70f97fSStefan Achatz 5840e70f97fSStefan Achatz if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO || 5850e70f97fSStefan Achatz roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT || 5860e70f97fSStefan Achatz roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH || 5870e70f97fSStefan Achatz roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER) 5880e70f97fSStefan Achatz roccat_report.button = button_report->data1; 5890e70f97fSStefan Achatz else 5900e70f97fSStefan Achatz roccat_report.button = 0; 5910e70f97fSStefan Achatz 5920e70f97fSStefan Achatz if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI) 5930e70f97fSStefan Achatz roccat_report.data1 = kovaplus_convert_event_cpi(button_report->data1); 5940e70f97fSStefan Achatz else 5950e70f97fSStefan Achatz roccat_report.data1 = button_report->data1; 5960e70f97fSStefan Achatz 5970e70f97fSStefan Achatz roccat_report.data2 = button_report->data2; 5980e70f97fSStefan Achatz 5990e70f97fSStefan Achatz roccat_report_event(kovaplus->chrdev_minor, 6008211e460SStefan Achatz (uint8_t const *)&roccat_report); 6010e70f97fSStefan Achatz } 6020e70f97fSStefan Achatz 6030e70f97fSStefan Achatz static int kovaplus_raw_event(struct hid_device *hdev, 6040e70f97fSStefan Achatz struct hid_report *report, u8 *data, int size) 6050e70f97fSStefan Achatz { 6060e70f97fSStefan Achatz struct usb_interface *intf = to_usb_interface(hdev->dev.parent); 6070e70f97fSStefan Achatz struct kovaplus_device *kovaplus = hid_get_drvdata(hdev); 6080e70f97fSStefan Achatz 6090e70f97fSStefan Achatz if (intf->cur_altsetting->desc.bInterfaceProtocol 6100e70f97fSStefan Achatz != USB_INTERFACE_PROTOCOL_MOUSE) 6110e70f97fSStefan Achatz return 0; 6120e70f97fSStefan Achatz 613901e64dbSStefan Achatz if (kovaplus == NULL) 614901e64dbSStefan Achatz return 0; 615901e64dbSStefan Achatz 6160e70f97fSStefan Achatz kovaplus_keep_values_up_to_date(kovaplus, data); 6170e70f97fSStefan Achatz 6180e70f97fSStefan Achatz if (kovaplus->roccat_claimed) 6190e70f97fSStefan Achatz kovaplus_report_to_chrdev(kovaplus, data); 6200e70f97fSStefan Achatz 6210e70f97fSStefan Achatz return 0; 6220e70f97fSStefan Achatz } 6230e70f97fSStefan Achatz 6240e70f97fSStefan Achatz static const struct hid_device_id kovaplus_devices[] = { 6250e70f97fSStefan Achatz { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, 6260e70f97fSStefan Achatz { } 6270e70f97fSStefan Achatz }; 6280e70f97fSStefan Achatz 6290e70f97fSStefan Achatz MODULE_DEVICE_TABLE(hid, kovaplus_devices); 6300e70f97fSStefan Achatz 6310e70f97fSStefan Achatz static struct hid_driver kovaplus_driver = { 6320e70f97fSStefan Achatz .name = "kovaplus", 6330e70f97fSStefan Achatz .id_table = kovaplus_devices, 6340e70f97fSStefan Achatz .probe = kovaplus_probe, 6350e70f97fSStefan Achatz .remove = kovaplus_remove, 6360e70f97fSStefan Achatz .raw_event = kovaplus_raw_event 6370e70f97fSStefan Achatz }; 6380e70f97fSStefan Achatz 6390e70f97fSStefan Achatz static int __init kovaplus_init(void) 6400e70f97fSStefan Achatz { 6410e70f97fSStefan Achatz int retval; 6420e70f97fSStefan Achatz 6430e70f97fSStefan Achatz kovaplus_class = class_create(THIS_MODULE, "kovaplus"); 6440e70f97fSStefan Achatz if (IS_ERR(kovaplus_class)) 6450e70f97fSStefan Achatz return PTR_ERR(kovaplus_class); 64646a58c44SGreg Kroah-Hartman kovaplus_class->dev_groups = kovaplus_groups; 6470e70f97fSStefan Achatz 6480e70f97fSStefan Achatz retval = hid_register_driver(&kovaplus_driver); 6490e70f97fSStefan Achatz if (retval) 6500e70f97fSStefan Achatz class_destroy(kovaplus_class); 6510e70f97fSStefan Achatz return retval; 6520e70f97fSStefan Achatz } 6530e70f97fSStefan Achatz 6540e70f97fSStefan Achatz static void __exit kovaplus_exit(void) 6550e70f97fSStefan Achatz { 6560e70f97fSStefan Achatz hid_unregister_driver(&kovaplus_driver); 65774b643daSStefan Achatz class_destroy(kovaplus_class); 6580e70f97fSStefan Achatz } 6590e70f97fSStefan Achatz 6600e70f97fSStefan Achatz module_init(kovaplus_init); 6610e70f97fSStefan Achatz module_exit(kovaplus_exit); 6620e70f97fSStefan Achatz 6630e70f97fSStefan Achatz MODULE_AUTHOR("Stefan Achatz"); 6640e70f97fSStefan Achatz MODULE_DESCRIPTION("USB Roccat Kova[+] driver"); 6650e70f97fSStefan Achatz MODULE_LICENSE("GPL v2"); 666