232 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Roccat Savu driver for Linux4 *5 * Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>6 */7 8/*9 */10 11/* Roccat Savu is a gamer mouse with macro keys that can be configured in12 * 5 profiles.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-savu.h"24 25ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x4, 0x03);26ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x5, 0x03);27ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(general, 0x6, 0x10);28ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(buttons, 0x7, 0x2f);29ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(macro, 0x8, 0x0823);30ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(info, 0x9, 0x08);31ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0xc, 0x04);32 33static struct bin_attribute *savu_bin_attrs[] = {34 &bin_attr_control,35 &bin_attr_profile,36 &bin_attr_general,37 &bin_attr_buttons,38 &bin_attr_macro,39 &bin_attr_info,40 &bin_attr_sensor,41 NULL,42};43 44static const struct attribute_group savu_group = {45 .bin_attrs = savu_bin_attrs,46};47 48static const struct attribute_group *savu_groups[] = {49 &savu_group,50 NULL,51};52 53static const struct class savu_class = {54 .name = "savu",55 .dev_groups = savu_groups,56};57 58static int savu_init_specials(struct hid_device *hdev)59{60 struct usb_interface *intf = to_usb_interface(hdev->dev.parent);61 struct usb_device *usb_dev = interface_to_usbdev(intf);62 struct roccat_common2_device *savu;63 int retval;64 65 if (intf->cur_altsetting->desc.bInterfaceProtocol66 != USB_INTERFACE_PROTOCOL_MOUSE) {67 hid_set_drvdata(hdev, NULL);68 return 0;69 }70 71 savu = kzalloc(sizeof(*savu), GFP_KERNEL);72 if (!savu) {73 hid_err(hdev, "can't alloc device descriptor\n");74 return -ENOMEM;75 }76 hid_set_drvdata(hdev, savu);77 78 retval = roccat_common2_device_init_struct(usb_dev, savu);79 if (retval) {80 hid_err(hdev, "couldn't init Savu device\n");81 goto exit_free;82 }83 84 retval = roccat_connect(&savu_class, hdev,85 sizeof(struct savu_roccat_report));86 if (retval < 0) {87 hid_err(hdev, "couldn't init char dev\n");88 } else {89 savu->chrdev_minor = retval;90 savu->roccat_claimed = 1;91 }92 93 return 0;94exit_free:95 kfree(savu);96 return retval;97}98 99static void savu_remove_specials(struct hid_device *hdev)100{101 struct usb_interface *intf = to_usb_interface(hdev->dev.parent);102 struct roccat_common2_device *savu;103 104 if (intf->cur_altsetting->desc.bInterfaceProtocol105 != USB_INTERFACE_PROTOCOL_MOUSE)106 return;107 108 savu = hid_get_drvdata(hdev);109 if (savu->roccat_claimed)110 roccat_disconnect(savu->chrdev_minor);111 kfree(savu);112}113 114static int savu_probe(struct hid_device *hdev,115 const struct hid_device_id *id)116{117 int retval;118 119 if (!hid_is_usb(hdev))120 return -EINVAL;121 122 retval = hid_parse(hdev);123 if (retval) {124 hid_err(hdev, "parse failed\n");125 goto exit;126 }127 128 retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);129 if (retval) {130 hid_err(hdev, "hw start failed\n");131 goto exit;132 }133 134 retval = savu_init_specials(hdev);135 if (retval) {136 hid_err(hdev, "couldn't install mouse\n");137 goto exit_stop;138 }139 140 return 0;141 142exit_stop:143 hid_hw_stop(hdev);144exit:145 return retval;146}147 148static void savu_remove(struct hid_device *hdev)149{150 savu_remove_specials(hdev);151 hid_hw_stop(hdev);152}153 154static void savu_report_to_chrdev(struct roccat_common2_device const *savu,155 u8 const *data)156{157 struct savu_roccat_report roccat_report;158 struct savu_mouse_report_special const *special_report;159 160 if (data[0] != SAVU_MOUSE_REPORT_NUMBER_SPECIAL)161 return;162 163 special_report = (struct savu_mouse_report_special const *)data;164 165 roccat_report.type = special_report->type;166 roccat_report.data[0] = special_report->data[0];167 roccat_report.data[1] = special_report->data[1];168 roccat_report_event(savu->chrdev_minor,169 (uint8_t const *)&roccat_report);170}171 172static int savu_raw_event(struct hid_device *hdev,173 struct hid_report *report, u8 *data, int size)174{175 struct usb_interface *intf = to_usb_interface(hdev->dev.parent);176 struct roccat_common2_device *savu = hid_get_drvdata(hdev);177 178 if (intf->cur_altsetting->desc.bInterfaceProtocol179 != USB_INTERFACE_PROTOCOL_MOUSE)180 return 0;181 182 if (savu == NULL)183 return 0;184 185 if (savu->roccat_claimed)186 savu_report_to_chrdev(savu, data);187 188 return 0;189}190 191static const struct hid_device_id savu_devices[] = {192 { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },193 { }194};195 196MODULE_DEVICE_TABLE(hid, savu_devices);197 198static struct hid_driver savu_driver = {199 .name = "savu",200 .id_table = savu_devices,201 .probe = savu_probe,202 .remove = savu_remove,203 .raw_event = savu_raw_event204};205 206static int __init savu_init(void)207{208 int retval;209 210 retval = class_register(&savu_class);211 if (retval)212 return retval;213 214 retval = hid_register_driver(&savu_driver);215 if (retval)216 class_unregister(&savu_class);217 return retval;218}219 220static void __exit savu_exit(void)221{222 hid_unregister_driver(&savu_driver);223 class_unregister(&savu_class);224}225 226module_init(savu_init);227module_exit(savu_exit);228 229MODULE_AUTHOR("Stefan Achatz");230MODULE_DESCRIPTION("USB Roccat Savu driver");231MODULE_LICENSE("GPL v2");232