2013-01-31 19:07:07 +04:00
/*
* 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>
2013-01-31 19:07:09 +04:00
# include <linux/usb.h>
2013-01-31 19:07:07 +04:00
# include <linux/hid.h>
# include <linux/module.h>
# include "hid-ids.h"
2013-05-03 05:43:14 +04:00
# if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
( IS_MODULE ( CONFIG_LEDS_CLASS ) & & IS_MODULE ( CONFIG_HID_STEELSERIES ) )
2013-01-31 19:07:09 +04:00
# define SRWS1_NUMBER_LEDS 15
struct steelseries_srws1_data {
__u16 led_state ;
2013-01-31 19:51:47 +04:00
/* the last element is used for setting all leds simultaneously */
struct led_classdev * led [ SRWS1_NUMBER_LEDS + 1 ] ;
2013-01-31 19:07:09 +04:00
} ;
# endif
2013-01-31 19:07:08 +04:00
/* 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 */
} ;
2013-05-03 05:43:14 +04:00
# if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
( IS_MODULE ( CONFIG_LEDS_CLASS ) & & IS_MODULE ( CONFIG_HID_STEELSERIES ) )
2013-01-31 19:07:09 +04:00
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 ;
2013-02-25 14:31:46 +04:00
hid_hw_request ( hdev , report , HID_REQ_SET_REPORT ) ;
2013-01-31 19:07:09 +04:00
/* Note: LED change does not show on device until the device is read/polled */
}
2013-01-31 19:07:10 +04:00
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 = container_of ( dev , struct 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 = container_of ( dev , struct 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 ;
}
2013-01-31 19:07:09 +04:00
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 = container_of ( dev , struct 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 = container_of ( dev , struct 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 ;
}
2013-09-11 23:56:53 +04:00
if ( ! hid_validate_values ( hdev , HID_OUTPUT_REPORT , 0 , 0 , 16 ) ) {
ret = - ENODEV ;
goto err_free ;
}
2013-01-31 19:07:09 +04:00
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 ;
2013-01-31 19:07:10 +04:00
for ( i = 0 ; i < SRWS1_NUMBER_LEDS + 1 ; i + + )
2013-01-31 19:07:09 +04:00
drv_data - > led [ i ] = NULL ;
steelseries_srws1_set_leds ( hdev , 0 ) ;
2013-01-31 19:07:10 +04:00
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 ;
2013-01-31 19:07:09 +04:00
2013-01-31 19:07:10 +04:00
/* Each individual LED */
2013-01-31 19:07:09 +04:00
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) */
2013-01-31 19:07:10 +04:00
for ( i = 0 ; i < SRWS1_NUMBER_LEDS + 1 ; i + + ) {
2013-01-31 19:07:09 +04:00
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) */
2013-01-31 19:07:10 +04:00
for ( i = 0 ; i < SRWS1_NUMBER_LEDS + 1 ; i + + ) {
2013-01-31 19:07:09 +04:00
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
2013-01-31 19:07:07 +04:00
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 " ) ;
2013-01-31 19:07:08 +04:00
rdesc = steelseries_srws1_rdesc_fixed ;
* rsize = sizeof ( steelseries_srws1_rdesc_fixed ) ;
2013-01-31 19:07:07 +04:00
}
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 ,
2013-05-03 05:43:14 +04:00
# if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
( IS_MODULE ( CONFIG_LEDS_CLASS ) & & IS_MODULE ( CONFIG_HID_STEELSERIES ) )
2013-01-31 19:07:09 +04:00
. probe = steelseries_srws1_probe ,
. remove = steelseries_srws1_remove ,
# endif
2013-01-31 19:07:07 +04:00
. report_fixup = steelseries_srws1_report_fixup
} ;
2013-03-15 19:07:02 +04:00
module_hid_driver ( steelseries_srws1_driver ) ;
2013-01-31 19:07:07 +04:00
MODULE_LICENSE ( " GPL " ) ;