|  | /* | 
|  | *  HID driver for Steelseries SRW-S1 | 
|  | * | 
|  | *  Copyright (c) 2013 Simon Wood | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms of the GNU General Public License as published by the Free | 
|  | * Software Foundation; either version 2 of the License, or (at your option) | 
|  | * any later version. | 
|  | */ | 
|  |  | 
|  | #include <linux/device.h> | 
|  | #include <linux/hid.h> | 
|  | #include <linux/module.h> | 
|  |  | 
|  | #include "hid-ids.h" | 
|  |  | 
|  | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ | 
|  | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) | 
|  | #define SRWS1_NUMBER_LEDS 15 | 
|  | struct steelseries_srws1_data { | 
|  | __u16 led_state; | 
|  | /* the last element is used for setting all leds simultaneously */ | 
|  | struct led_classdev *led[SRWS1_NUMBER_LEDS + 1]; | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | /* Fixed report descriptor for Steelseries SRW-S1 wheel controller | 
|  | * | 
|  | * The original descriptor hides the sensitivity and assists dials | 
|  | * a custom vendor usage page. This inserts a patch to make them | 
|  | * appear in the 'Generic Desktop' usage. | 
|  | */ | 
|  |  | 
|  | static __u8 steelseries_srws1_rdesc_fixed[] = { | 
|  | 0x05, 0x01,         /*  Usage Page (Desktop)                */ | 
|  | 0x09, 0x08,         /*  Usage (MultiAxis), Changed          */ | 
|  | 0xA1, 0x01,         /*  Collection (Application),           */ | 
|  | 0xA1, 0x02,         /*      Collection (Logical),           */ | 
|  | 0x95, 0x01,         /*          Report Count (1),           */ | 
|  | 0x05, 0x01,         /* Changed  Usage Page (Desktop),       */ | 
|  | 0x09, 0x30,         /* Changed  Usage (X),                  */ | 
|  | 0x16, 0xF8, 0xF8,   /*          Logical Minimum (-1800),    */ | 
|  | 0x26, 0x08, 0x07,   /*          Logical Maximum (1800),     */ | 
|  | 0x65, 0x14,         /*          Unit (Degrees),             */ | 
|  | 0x55, 0x0F,         /*          Unit Exponent (15),         */ | 
|  | 0x75, 0x10,         /*          Report Size (16),           */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | 0x09, 0x31,         /* Changed  Usage (Y),                  */ | 
|  | 0x15, 0x00,         /*          Logical Minimum (0),        */ | 
|  | 0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */ | 
|  | 0x75, 0x0C,         /*          Report Size (12),           */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | 0x09, 0x32,         /* Changed  Usage (Z),                  */ | 
|  | 0x15, 0x00,         /*          Logical Minimum (0),        */ | 
|  | 0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */ | 
|  | 0x75, 0x0C,         /*          Report Size (12),           */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | 0x05, 0x01,         /*          Usage Page (Desktop),       */ | 
|  | 0x09, 0x39,         /*          Usage (Hat Switch),         */ | 
|  | 0x25, 0x07,         /*          Logical Maximum (7),        */ | 
|  | 0x35, 0x00,         /*          Physical Minimum (0),       */ | 
|  | 0x46, 0x3B, 0x01,   /*          Physical Maximum (315),     */ | 
|  | 0x65, 0x14,         /*          Unit (Degrees),             */ | 
|  | 0x75, 0x04,         /*          Report Size (4),            */ | 
|  | 0x95, 0x01,         /*          Report Count (1),           */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | 0x25, 0x01,         /*          Logical Maximum (1),        */ | 
|  | 0x45, 0x01,         /*          Physical Maximum (1),       */ | 
|  | 0x65, 0x00,         /*          Unit,                       */ | 
|  | 0x75, 0x01,         /*          Report Size (1),            */ | 
|  | 0x95, 0x03,         /*          Report Count (3),           */ | 
|  | 0x81, 0x01,         /*          Input (Constant),           */ | 
|  | 0x05, 0x09,         /*          Usage Page (Button),        */ | 
|  | 0x19, 0x01,         /*          Usage Minimum (01h),        */ | 
|  | 0x29, 0x11,         /*          Usage Maximum (11h),        */ | 
|  | 0x95, 0x11,         /*          Report Count (17),          */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | /*   ---- Dial patch starts here ----   */ | 
|  | 0x05, 0x01,         /*          Usage Page (Desktop),       */ | 
|  | 0x09, 0x33,         /*          Usage (RX),                 */ | 
|  | 0x75, 0x04,         /*          Report Size (4),            */ | 
|  | 0x95, 0x02,         /*          Report Count (2),           */ | 
|  | 0x15, 0x00,         /*          Logical Minimum (0),        */ | 
|  | 0x25, 0x0b,         /*          Logical Maximum (b),        */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | 0x09, 0x35,         /*          Usage (RZ),                 */ | 
|  | 0x75, 0x04,         /*          Report Size (4),            */ | 
|  | 0x95, 0x01,         /*          Report Count (1),           */ | 
|  | 0x25, 0x03,         /*          Logical Maximum (3),        */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | /*    ---- Dial patch ends here ----    */ | 
|  | 0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */ | 
|  | 0x09, 0x01,         /*          Usage (01h),                */ | 
|  | 0x75, 0x04,         /* Changed  Report Size (4),            */ | 
|  | 0x95, 0x0D,         /* Changed  Report Count (13),          */ | 
|  | 0x81, 0x02,         /*          Input (Variable),           */ | 
|  | 0xC0,               /*      End Collection,                 */ | 
|  | 0xA1, 0x02,         /*      Collection (Logical),           */ | 
|  | 0x09, 0x02,         /*          Usage (02h),                */ | 
|  | 0x75, 0x08,         /*          Report Size (8),            */ | 
|  | 0x95, 0x10,         /*          Report Count (16),          */ | 
|  | 0x91, 0x02,         /*          Output (Variable),          */ | 
|  | 0xC0,               /*      End Collection,                 */ | 
|  | 0xC0                /*  End Collection                      */ | 
|  | }; | 
|  |  | 
|  | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ | 
|  | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) | 
|  | static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) | 
|  | { | 
|  | struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; | 
|  | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | 
|  | __s32 *value = report->field[0]->value; | 
|  |  | 
|  | value[0] = 0x40; | 
|  | value[1] = leds & 0xFF; | 
|  | value[2] = leds >> 8; | 
|  | value[3] = 0x00; | 
|  | value[4] = 0x00; | 
|  | value[5] = 0x00; | 
|  | value[6] = 0x00; | 
|  | value[7] = 0x00; | 
|  | value[8] = 0x00; | 
|  | value[9] = 0x00; | 
|  | value[10] = 0x00; | 
|  | value[11] = 0x00; | 
|  | value[12] = 0x00; | 
|  | value[13] = 0x00; | 
|  | value[14] = 0x00; | 
|  | value[15] = 0x00; | 
|  |  | 
|  | hid_hw_request(hdev, report, HID_REQ_SET_REPORT); | 
|  |  | 
|  | /* Note: LED change does not show on device until the device is read/polled */ | 
|  | } | 
|  |  | 
|  | static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev, | 
|  | enum led_brightness value) | 
|  | { | 
|  | struct device *dev = led_cdev->dev->parent; | 
|  | struct hid_device *hid = to_hid_device(dev); | 
|  | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); | 
|  |  | 
|  | if (!drv_data) { | 
|  | hid_err(hid, "Device data not found."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (value == LED_OFF) | 
|  | drv_data->led_state = 0; | 
|  | else | 
|  | drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1; | 
|  |  | 
|  | steelseries_srws1_set_leds(hid, drv_data->led_state); | 
|  | } | 
|  |  | 
|  | static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev) | 
|  | { | 
|  | struct device *dev = led_cdev->dev->parent; | 
|  | struct hid_device *hid = to_hid_device(dev); | 
|  | struct steelseries_srws1_data *drv_data; | 
|  |  | 
|  | drv_data = hid_get_drvdata(hid); | 
|  |  | 
|  | if (!drv_data) { | 
|  | hid_err(hid, "Device data not found."); | 
|  | return LED_OFF; | 
|  | } | 
|  |  | 
|  | return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF; | 
|  | } | 
|  |  | 
|  | static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, | 
|  | enum led_brightness value) | 
|  | { | 
|  | struct device *dev = led_cdev->dev->parent; | 
|  | struct hid_device *hid = to_hid_device(dev); | 
|  | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); | 
|  | int i, state = 0; | 
|  |  | 
|  | if (!drv_data) { | 
|  | hid_err(hid, "Device data not found."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | 
|  | if (led_cdev != drv_data->led[i]) | 
|  | continue; | 
|  |  | 
|  | state = (drv_data->led_state >> i) & 1; | 
|  | if (value == LED_OFF && state) { | 
|  | drv_data->led_state &= ~(1 << i); | 
|  | steelseries_srws1_set_leds(hid, drv_data->led_state); | 
|  | } else if (value != LED_OFF && !state) { | 
|  | drv_data->led_state |= 1 << i; | 
|  | steelseries_srws1_set_leds(hid, drv_data->led_state); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev) | 
|  | { | 
|  | struct device *dev = led_cdev->dev->parent; | 
|  | struct hid_device *hid = to_hid_device(dev); | 
|  | struct steelseries_srws1_data *drv_data; | 
|  | int i, value = 0; | 
|  |  | 
|  | drv_data = hid_get_drvdata(hid); | 
|  |  | 
|  | if (!drv_data) { | 
|  | hid_err(hid, "Device data not found."); | 
|  | return LED_OFF; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) | 
|  | if (led_cdev == drv_data->led[i]) { | 
|  | value = (drv_data->led_state >> i) & 1; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return value ? LED_FULL : LED_OFF; | 
|  | } | 
|  |  | 
|  | static int steelseries_srws1_probe(struct hid_device *hdev, | 
|  | const struct hid_device_id *id) | 
|  | { | 
|  | int ret, i; | 
|  | struct led_classdev *led; | 
|  | size_t name_sz; | 
|  | char *name; | 
|  |  | 
|  | struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); | 
|  |  | 
|  | if (drv_data == NULL) { | 
|  | hid_err(hdev, "can't alloc SRW-S1 memory\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | hid_set_drvdata(hdev, drv_data); | 
|  |  | 
|  | ret = hid_parse(hdev); | 
|  | if (ret) { | 
|  | hid_err(hdev, "parse failed\n"); | 
|  | goto err_free; | 
|  | } | 
|  |  | 
|  | if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) { | 
|  | ret = -ENODEV; | 
|  | goto err_free; | 
|  | } | 
|  |  | 
|  | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | 
|  | if (ret) { | 
|  | hid_err(hdev, "hw start failed\n"); | 
|  | goto err_free; | 
|  | } | 
|  |  | 
|  | /* register led subsystem */ | 
|  | drv_data->led_state = 0; | 
|  | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) | 
|  | drv_data->led[i] = NULL; | 
|  |  | 
|  | steelseries_srws1_set_leds(hdev, 0); | 
|  |  | 
|  | name_sz = strlen(hdev->uniq) + 16; | 
|  |  | 
|  | /* 'ALL', for setting all LEDs simultaneously */ | 
|  | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | 
|  | if (!led) { | 
|  | hid_err(hdev, "can't allocate memory for LED ALL\n"); | 
|  | goto err_led; | 
|  | } | 
|  |  | 
|  | name = (void *)(&led[1]); | 
|  | snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq); | 
|  | led->name = name; | 
|  | led->brightness = 0; | 
|  | led->max_brightness = 1; | 
|  | led->brightness_get = steelseries_srws1_led_all_get_brightness; | 
|  | led->brightness_set = steelseries_srws1_led_all_set_brightness; | 
|  |  | 
|  | drv_data->led[SRWS1_NUMBER_LEDS] = led; | 
|  | ret = led_classdev_register(&hdev->dev, led); | 
|  | if (ret) | 
|  | goto err_led; | 
|  |  | 
|  | /* Each individual LED */ | 
|  | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | 
|  | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | 
|  | if (!led) { | 
|  | hid_err(hdev, "can't allocate memory for LED %d\n", i); | 
|  | goto err_led; | 
|  | } | 
|  |  | 
|  | name = (void *)(&led[1]); | 
|  | snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1); | 
|  | led->name = name; | 
|  | led->brightness = 0; | 
|  | led->max_brightness = 1; | 
|  | led->brightness_get = steelseries_srws1_led_get_brightness; | 
|  | led->brightness_set = steelseries_srws1_led_set_brightness; | 
|  |  | 
|  | drv_data->led[i] = led; | 
|  | ret = led_classdev_register(&hdev->dev, led); | 
|  |  | 
|  | if (ret) { | 
|  | hid_err(hdev, "failed to register LED %d. Aborting.\n", i); | 
|  | err_led: | 
|  | /* Deregister all LEDs (if any) */ | 
|  | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { | 
|  | led = drv_data->led[i]; | 
|  | drv_data->led[i] = NULL; | 
|  | if (!led) | 
|  | continue; | 
|  | led_classdev_unregister(led); | 
|  | kfree(led); | 
|  | } | 
|  | goto out;	/* but let the driver continue without LEDs */ | 
|  | } | 
|  | } | 
|  | out: | 
|  | return 0; | 
|  | err_free: | 
|  | kfree(drv_data); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void steelseries_srws1_remove(struct hid_device *hdev) | 
|  | { | 
|  | int i; | 
|  | struct led_classdev *led; | 
|  |  | 
|  | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); | 
|  |  | 
|  | if (drv_data) { | 
|  | /* Deregister LEDs (if any) */ | 
|  | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { | 
|  | led = drv_data->led[i]; | 
|  | drv_data->led[i] = NULL; | 
|  | if (!led) | 
|  | continue; | 
|  | led_classdev_unregister(led); | 
|  | kfree(led); | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | hid_hw_stop(hdev); | 
|  | kfree(drv_data); | 
|  | return; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, | 
|  | unsigned int *rsize) | 
|  | { | 
|  | if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 | 
|  | && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { | 
|  | hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); | 
|  | rdesc = steelseries_srws1_rdesc_fixed; | 
|  | *rsize = sizeof(steelseries_srws1_rdesc_fixed); | 
|  | } | 
|  | return rdesc; | 
|  | } | 
|  |  | 
|  | static const struct hid_device_id steelseries_srws1_devices[] = { | 
|  | { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); | 
|  |  | 
|  | static struct hid_driver steelseries_srws1_driver = { | 
|  | .name = "steelseries_srws1", | 
|  | .id_table = steelseries_srws1_devices, | 
|  | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ | 
|  | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) | 
|  | .probe = steelseries_srws1_probe, | 
|  | .remove = steelseries_srws1_remove, | 
|  | #endif | 
|  | .report_fixup = steelseries_srws1_report_fixup | 
|  | }; | 
|  |  | 
|  | module_hid_driver(steelseries_srws1_driver); | 
|  | MODULE_LICENSE("GPL"); |