brintos

brintos / linux-shallow public Read only

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