mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 09:19:51 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 3029 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
385 lines
12 KiB
C
385 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* HID driver for Steelseries SRW-S1
|
|
*
|
|
* Copyright (c) 2013 Simon Wood
|
|
*/
|
|
|
|
/*
|
|
*/
|
|
|
|
#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");
|