Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

USB: HID: Steelseries SRW-S1 Add support for LEDs

This patch to the SRW-S1 driver adds support for the LED RPM
meter on the front of the device. The LEDs are controlled via
/sys/class/leds interface, with an individual control for each
of the 15 LEDs.

Signed-off-by: Simon Wood <simon@mungewell.org>
Tested-by: John Murphy <rosegardener@freeode.co.uk>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>

authored by

Simon Wood and committed by
Jiri Kosina
2e2daff3 5492606d

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