diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-driver-hid-srws1 | 20 | ||||
| -rw-r--r-- | drivers/hid/hid-steelseries-srws1.c | 199 |
2 files changed, 219 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1 new file mode 100644 index 000000000000..c27b34dcaf83 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-srws1 | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | What: /sys/class/leds/SRWS1::<serial>::RPM1 | ||
| 2 | What: /sys/class/leds/SRWS1::<serial>::RPM2 | ||
| 3 | What: /sys/class/leds/SRWS1::<serial>::RPM3 | ||
| 4 | What: /sys/class/leds/SRWS1::<serial>::RPM4 | ||
| 5 | What: /sys/class/leds/SRWS1::<serial>::RPM5 | ||
| 6 | What: /sys/class/leds/SRWS1::<serial>::RPM6 | ||
| 7 | What: /sys/class/leds/SRWS1::<serial>::RPM7 | ||
| 8 | What: /sys/class/leds/SRWS1::<serial>::RPM8 | ||
| 9 | What: /sys/class/leds/SRWS1::<serial>::RPM9 | ||
| 10 | What: /sys/class/leds/SRWS1::<serial>::RPM10 | ||
| 11 | What: /sys/class/leds/SRWS1::<serial>::RPM11 | ||
| 12 | What: /sys/class/leds/SRWS1::<serial>::RPM12 | ||
| 13 | What: /sys/class/leds/SRWS1::<serial>::RPM13 | ||
| 14 | What: /sys/class/leds/SRWS1::<serial>::RPM14 | ||
| 15 | What: /sys/class/leds/SRWS1::<serial>::RPM15 | ||
| 16 | Date: Jan 2013 | ||
| 17 | KernelVersion: 3.9 | ||
| 18 | Contact: Simon Wood <simon@mungewell.org> | ||
| 19 | Description: Provides a control for turning on/off the LEDs which form | ||
| 20 | an RPM meter on the front of the controller | ||
diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c index e95434d733eb..a7386699ba7d 100644 --- a/drivers/hid/hid-steelseries-srws1.c +++ b/drivers/hid/hid-steelseries-srws1.c | |||
| @@ -12,11 +12,21 @@ | |||
| 12 | */ | 12 | */ |
| 13 | 13 | ||
| 14 | #include <linux/device.h> | 14 | #include <linux/device.h> |
| 15 | #include <linux/usb.h> | ||
| 15 | #include <linux/hid.h> | 16 | #include <linux/hid.h> |
| 16 | #include <linux/module.h> | 17 | #include <linux/module.h> |
| 17 | 18 | ||
| 19 | #include "usbhid/usbhid.h" | ||
| 18 | #include "hid-ids.h" | 20 | #include "hid-ids.h" |
| 19 | 21 | ||
| 22 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) | ||
| 23 | #define SRWS1_NUMBER_LEDS 15 | ||
| 24 | struct steelseries_srws1_data { | ||
| 25 | __u16 led_state; | ||
| 26 | struct led_classdev *led[SRWS1_NUMBER_LEDS]; | ||
| 27 | }; | ||
| 28 | #endif | ||
| 29 | |||
| 20 | /* Fixed report descriptor for Steelseries SRW-S1 wheel controller | 30 | /* Fixed report descriptor for Steelseries SRW-S1 wheel controller |
| 21 | * | 31 | * |
| 22 | * The original descriptor hides the sensitivity and assists dials | 32 | * The original descriptor hides the sensitivity and assists dials |
| @@ -97,6 +107,191 @@ static __u8 steelseries_srws1_rdesc_fixed[] = { | |||
| 97 | 0xC0 /* End Collection */ | 107 | 0xC0 /* End Collection */ |
| 98 | }; | 108 | }; |
| 99 | 109 | ||
| 110 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) | ||
| 111 | static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) | ||
| 112 | { | ||
| 113 | struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; | ||
| 114 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
| 115 | __s32 *value = report->field[0]->value; | ||
| 116 | |||
| 117 | value[0] = 0x40; | ||
| 118 | value[1] = leds & 0xFF; | ||
| 119 | value[2] = leds >> 8; | ||
| 120 | value[3] = 0x00; | ||
| 121 | value[4] = 0x00; | ||
| 122 | value[5] = 0x00; | ||
| 123 | value[6] = 0x00; | ||
| 124 | value[7] = 0x00; | ||
| 125 | value[8] = 0x00; | ||
| 126 | value[9] = 0x00; | ||
| 127 | value[10] = 0x00; | ||
| 128 | value[11] = 0x00; | ||
| 129 | value[12] = 0x00; | ||
| 130 | value[13] = 0x00; | ||
| 131 | value[14] = 0x00; | ||
| 132 | value[15] = 0x00; | ||
| 133 | |||
| 134 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | ||
| 135 | |||
| 136 | /* Note: LED change does not show on device until the device is read/polled */ | ||
| 137 | } | ||
| 138 | |||
| 139 | static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, | ||
| 140 | enum led_brightness value) | ||
| 141 | { | ||
| 142 | struct device *dev = led_cdev->dev->parent; | ||
| 143 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
| 144 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); | ||
| 145 | int i, state = 0; | ||
| 146 | |||
| 147 | if (!drv_data) { | ||
| 148 | hid_err(hid, "Device data not found."); | ||
| 149 | return; | ||
| 150 | } | ||
| 151 | |||
| 152 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | ||
| 153 | if (led_cdev != drv_data->led[i]) | ||
| 154 | continue; | ||
| 155 | |||
| 156 | state = (drv_data->led_state >> i) & 1; | ||
| 157 | if (value == LED_OFF && state) { | ||
| 158 | drv_data->led_state &= ~(1 << i); | ||
| 159 | steelseries_srws1_set_leds(hid, drv_data->led_state); | ||
| 160 | } else if (value != LED_OFF && !state) { | ||
| 161 | drv_data->led_state |= 1 << i; | ||
| 162 | steelseries_srws1_set_leds(hid, drv_data->led_state); | ||
| 163 | } | ||
| 164 | break; | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev) | ||
| 169 | { | ||
| 170 | struct device *dev = led_cdev->dev->parent; | ||
| 171 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
| 172 | struct steelseries_srws1_data *drv_data; | ||
| 173 | int i, value = 0; | ||
| 174 | |||
| 175 | drv_data = hid_get_drvdata(hid); | ||
| 176 | |||
| 177 | if (!drv_data) { | ||
| 178 | hid_err(hid, "Device data not found."); | ||
| 179 | return LED_OFF; | ||
| 180 | } | ||
| 181 | |||
| 182 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) | ||
| 183 | if (led_cdev == drv_data->led[i]) { | ||
| 184 | value = (drv_data->led_state >> i) & 1; | ||
| 185 | break; | ||
| 186 | } | ||
| 187 | |||
| 188 | return value ? LED_FULL : LED_OFF; | ||
| 189 | } | ||
| 190 | |||
| 191 | static int steelseries_srws1_probe(struct hid_device *hdev, | ||
| 192 | const struct hid_device_id *id) | ||
| 193 | { | ||
| 194 | int ret, i; | ||
| 195 | struct led_classdev *led; | ||
| 196 | size_t name_sz; | ||
| 197 | char *name; | ||
| 198 | |||
| 199 | struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); | ||
| 200 | |||
| 201 | if (drv_data == NULL) { | ||
| 202 | hid_err(hdev, "can't alloc SRW-S1 memory\n"); | ||
| 203 | return -ENOMEM; | ||
| 204 | } | ||
| 205 | |||
| 206 | hid_set_drvdata(hdev, drv_data); | ||
| 207 | |||
| 208 | ret = hid_parse(hdev); | ||
| 209 | if (ret) { | ||
| 210 | hid_err(hdev, "parse failed\n"); | ||
| 211 | goto err_free; | ||
| 212 | } | ||
| 213 | |||
| 214 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | ||
| 215 | if (ret) { | ||
| 216 | hid_err(hdev, "hw start failed\n"); | ||
| 217 | goto err_free; | ||
| 218 | } | ||
| 219 | |||
| 220 | /* register led subsystem */ | ||
| 221 | drv_data->led_state = 0; | ||
| 222 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) | ||
| 223 | drv_data->led[i] = NULL; | ||
| 224 | |||
| 225 | steelseries_srws1_set_leds(hdev, 0); | ||
| 226 | |||
| 227 | name_sz = strlen(hdev->uniq) + 15; | ||
| 228 | |||
| 229 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | ||
| 230 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | ||
| 231 | if (!led) { | ||
| 232 | hid_err(hdev, "can't allocate memory for LED %d\n", i); | ||
| 233 | goto err_led; | ||
| 234 | } | ||
| 235 | |||
| 236 | name = (void *)(&led[1]); | ||
| 237 | snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1); | ||
| 238 | led->name = name; | ||
| 239 | led->brightness = 0; | ||
| 240 | led->max_brightness = 1; | ||
| 241 | led->brightness_get = steelseries_srws1_led_get_brightness; | ||
| 242 | led->brightness_set = steelseries_srws1_led_set_brightness; | ||
| 243 | |||
| 244 | drv_data->led[i] = led; | ||
| 245 | ret = led_classdev_register(&hdev->dev, led); | ||
| 246 | |||
| 247 | if (ret) { | ||
| 248 | hid_err(hdev, "failed to register LED %d. Aborting.\n", i); | ||
| 249 | err_led: | ||
| 250 | /* Deregister all LEDs (if any) */ | ||
| 251 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | ||
| 252 | led = drv_data->led[i]; | ||
| 253 | drv_data->led[i] = NULL; | ||
| 254 | if (!led) | ||
| 255 | continue; | ||
| 256 | led_classdev_unregister(led); | ||
| 257 | kfree(led); | ||
| 258 | } | ||
| 259 | goto out; /* but let the driver continue without LEDs */ | ||
| 260 | } | ||
| 261 | } | ||
| 262 | out: | ||
| 263 | return 0; | ||
| 264 | err_free: | ||
| 265 | kfree(drv_data); | ||
| 266 | return ret; | ||
| 267 | } | ||
| 268 | |||
| 269 | static void steelseries_srws1_remove(struct hid_device *hdev) | ||
| 270 | { | ||
| 271 | int i; | ||
| 272 | struct led_classdev *led; | ||
| 273 | |||
| 274 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); | ||
| 275 | |||
| 276 | if (drv_data) { | ||
| 277 | /* Deregister LEDs (if any) */ | ||
| 278 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | ||
| 279 | led = drv_data->led[i]; | ||
| 280 | drv_data->led[i] = NULL; | ||
| 281 | if (!led) | ||
| 282 | continue; | ||
| 283 | led_classdev_unregister(led); | ||
| 284 | kfree(led); | ||
| 285 | } | ||
| 286 | |||
| 287 | } | ||
| 288 | |||
| 289 | hid_hw_stop(hdev); | ||
| 290 | kfree(drv_data); | ||
| 291 | return; | ||
| 292 | } | ||
| 293 | #endif | ||
| 294 | |||
| 100 | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, | 295 | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
| 101 | unsigned int *rsize) | 296 | unsigned int *rsize) |
| 102 | { | 297 | { |
| @@ -118,6 +313,10 @@ MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); | |||
| 118 | static struct hid_driver steelseries_srws1_driver = { | 313 | static struct hid_driver steelseries_srws1_driver = { |
| 119 | .name = "steelseries_srws1", | 314 | .name = "steelseries_srws1", |
| 120 | .id_table = steelseries_srws1_devices, | 315 | .id_table = steelseries_srws1_devices, |
| 316 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) | ||
| 317 | .probe = steelseries_srws1_probe, | ||
| 318 | .remove = steelseries_srws1_remove, | ||
| 319 | #endif | ||
| 121 | .report_fixup = steelseries_srws1_report_fixup | 320 | .report_fixup = steelseries_srws1_report_fixup |
| 122 | }; | 321 | }; |
| 123 | 322 | ||
