xref: /linux/drivers/hid/hid-roccat-kovaplus.c (revision 825e587af2e90e9b953849f3347a01d8f383d577)
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