| /* | 
 |  *  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"); |