diff options
author | Simon Wood <simon@mungewell.org> | 2013-01-31 10:07:09 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2013-01-31 10:39:31 -0500 |
commit | 2e2daff3a51f2d10155b03f461f4e29eaf80dcbd (patch) | |
tree | 8681877608d984a4396e9cde3f595bb440487de8 /drivers/hid/hid-steelseries-srws1.c | |
parent | 5492606dc39b2f1ce67cf718f09ade373a35f4eb (diff) |
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>
Diffstat (limited to 'drivers/hid/hid-steelseries-srws1.c')
-rw-r--r-- | drivers/hid/hid-steelseries-srws1.c | 199 |
1 files changed, 199 insertions, 0 deletions
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 | ||