| 1 | |
| 2 | |
| 3 | * HID driver for Steelseries devices
|
| 4 | *
|
| 5 | * Copyright (c) 2013 Simon Wood
|
| 6 |
|
| 7 |
|
| 8 | |
| 9 |
|
| 10 |
|
| 11 | #include <linux/device.h>
|
| 12 | #include <linux/dmi.h>
|
| 13 | #include <linux/hid.h>
|
| 14 | #include <linux/module.h>
|
| 15 | #include <linux/usb.h>
|
| 16 | #include <linux/leds.h>
|
| 17 | #include <linux/led-class-multicolor.h>
|
| 18 | #include <linux/slab.h>
|
| 19 |
|
| 20 | #include "hid-ids.h"
|
| 21 |
|
| 22 | #define STEELSERIES_SRWS1 BIT(0)
|
| 23 | #define STEELSERIES_MSI_RGB BIT(1)
|
| 24 |
|
| 25 | #define STEELSERIES_MSI_RGB_WVALUE 0x0300
|
| 26 | #define STEELSERIES_MSI_RGB_REPORT_LEN 524
|
| 27 | #define STEELSERIES_MSI_RGB_OPCODE 0x0c
|
| 28 | #define STEELSERIES_MSI_RGB_KLC_MODE 0x66
|
| 29 | #define STEELSERIES_MSI_RGB_ALC_MODE 0x06
|
| 30 |
|
| 31 | #define STEELSERIES_HAS_LEDS_MULTICOLOR \
|
| 32 | (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
|
| 33 | (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
|
| 34 |
|
| 35 | struct steelseries_device {
|
| 36 | struct hid_device *hdev;
|
| 37 | unsigned long quirks;
|
| 38 |
|
| 39 | #if STEELSERIES_HAS_LEDS_MULTICOLOR
|
| 40 | struct led_classdev_mc mc_cdev;
|
| 41 | struct mc_subled subled_info[3];
|
| 42 | struct mutex rgb_lock;
|
| 43 | u8 *rgb_buf;
|
| 44 | #endif
|
| 45 | };
|
| 46 |
|
| 47 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
| 48 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
| 49 | #define SRWS1_NUMBER_LEDS 15
|
| 50 | struct steelseries_srws1_data {
|
| 51 | __u16 led_state;
|
| 52 |
|
| 53 | struct led_classdev *led[SRWS1_NUMBER_LEDS + 1];
|
| 54 | };
|
| 55 | #endif
|
| 56 |
|
| 57 | |
| 58 | *
|
| 59 | * The original descriptor hides the sensitivity and assists dials
|
| 60 | * a custom vendor usage page. This inserts a patch to make them
|
| 61 | * appear in the 'Generic Desktop' usage.
|
| 62 |
|
| 63 |
|
| 64 | static const __u8 steelseries_srws1_rdesc_fixed[] = {
|
| 65 | 0x05, 0x01,
|
| 66 | 0x09, 0x08,
|
| 67 | 0xA1, 0x01,
|
| 68 | 0xA1, 0x02,
|
| 69 | 0x95, 0x01,
|
| 70 | 0x05, 0x01,
|
| 71 | 0x09, 0x30,
|
| 72 | 0x16, 0xF8, 0xF8,
|
| 73 | 0x26, 0x08, 0x07,
|
| 74 | 0x65, 0x14,
|
| 75 | 0x55, 0x0F,
|
| 76 | 0x75, 0x10,
|
| 77 | 0x81, 0x02,
|
| 78 | 0x09, 0x31,
|
| 79 | 0x15, 0x00,
|
| 80 | 0x26, 0xFF, 0x03,
|
| 81 | 0x75, 0x0C,
|
| 82 | 0x81, 0x02,
|
| 83 | 0x09, 0x32,
|
| 84 | 0x15, 0x00,
|
| 85 | 0x26, 0xFF, 0x03,
|
| 86 | 0x75, 0x0C,
|
| 87 | 0x81, 0x02,
|
| 88 | 0x05, 0x01,
|
| 89 | 0x09, 0x39,
|
| 90 | 0x25, 0x07,
|
| 91 | 0x35, 0x00,
|
| 92 | 0x46, 0x3B, 0x01,
|
| 93 | 0x65, 0x14,
|
| 94 | 0x75, 0x04,
|
| 95 | 0x95, 0x01,
|
| 96 | 0x81, 0x02,
|
| 97 | 0x25, 0x01,
|
| 98 | 0x45, 0x01,
|
| 99 | 0x65, 0x00,
|
| 100 | 0x75, 0x01,
|
| 101 | 0x95, 0x03,
|
| 102 | 0x81, 0x01,
|
| 103 | 0x05, 0x09,
|
| 104 | 0x19, 0x01,
|
| 105 | 0x29, 0x11,
|
| 106 | 0x95, 0x11,
|
| 107 | 0x81, 0x02,
|
| 108 |
|
| 109 | 0x05, 0x01,
|
| 110 | 0x09, 0x33,
|
| 111 | 0x75, 0x04,
|
| 112 | 0x95, 0x02,
|
| 113 | 0x15, 0x00,
|
| 114 | 0x25, 0x0b,
|
| 115 | 0x81, 0x02,
|
| 116 | 0x09, 0x35,
|
| 117 | 0x75, 0x04,
|
| 118 | 0x95, 0x01,
|
| 119 | 0x25, 0x03,
|
| 120 | 0x81, 0x02,
|
| 121 |
|
| 122 | 0x06, 0x00, 0xFF,
|
| 123 | 0x09, 0x01,
|
| 124 | 0x75, 0x04,
|
| 125 | 0x95, 0x0D,
|
| 126 | 0x81, 0x02,
|
| 127 | 0xC0,
|
| 128 | 0xA1, 0x02,
|
| 129 | 0x09, 0x02,
|
| 130 | 0x75, 0x08,
|
| 131 | 0x95, 0x10,
|
| 132 | 0x91, 0x02,
|
| 133 | 0xC0,
|
| 134 | 0xC0
|
| 135 | };
|
| 136 |
|
| 137 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
| 138 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
| 139 | static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds)
|
| 140 | {
|
| 141 | struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
|
| 142 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
|
| 143 | __s32 *value = report->field[0]->value;
|
| 144 |
|
| 145 | value[0] = 0x40;
|
| 146 | value[1] = leds & 0xFF;
|
| 147 | value[2] = leds >> 8;
|
| 148 | value[3] = 0x00;
|
| 149 | value[4] = 0x00;
|
| 150 | value[5] = 0x00;
|
| 151 | value[6] = 0x00;
|
| 152 | value[7] = 0x00;
|
| 153 | value[8] = 0x00;
|
| 154 | value[9] = 0x00;
|
| 155 | value[10] = 0x00;
|
| 156 | value[11] = 0x00;
|
| 157 | value[12] = 0x00;
|
| 158 | value[13] = 0x00;
|
| 159 | value[14] = 0x00;
|
| 160 | value[15] = 0x00;
|
| 161 |
|
| 162 | hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
|
| 163 |
|
| 164 |
|
| 165 | }
|
| 166 |
|
| 167 | static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev,
|
| 168 | enum led_brightness value)
|
| 169 | {
|
| 170 | struct device *dev = led_cdev->dev->parent;
|
| 171 | struct hid_device *hid = to_hid_device(dev);
|
| 172 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
|
| 173 |
|
| 174 | if (!drv_data) {
|
| 175 | hid_err(hid, "Device data not found.");
|
| 176 | return;
|
| 177 | }
|
| 178 |
|
| 179 | if (value == LED_OFF)
|
| 180 | drv_data->led_state = 0;
|
| 181 | else
|
| 182 | drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1;
|
| 183 |
|
| 184 | steelseries_srws1_set_leds(hid, drv_data->led_state);
|
| 185 | }
|
| 186 |
|
| 187 | static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev)
|
| 188 | {
|
| 189 | struct device *dev = led_cdev->dev->parent;
|
| 190 | struct hid_device *hid = to_hid_device(dev);
|
| 191 | struct steelseries_srws1_data *drv_data;
|
| 192 |
|
| 193 | drv_data = hid_get_drvdata(hid);
|
| 194 |
|
| 195 | if (!drv_data) {
|
| 196 | hid_err(hid, "Device data not found.");
|
| 197 | return LED_OFF;
|
| 198 | }
|
| 199 |
|
| 200 | return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF;
|
| 201 | }
|
| 202 |
|
| 203 | static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev,
|
| 204 | enum led_brightness value)
|
| 205 | {
|
| 206 | struct device *dev = led_cdev->dev->parent;
|
| 207 | struct hid_device *hid = to_hid_device(dev);
|
| 208 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
|
| 209 | int i, state = 0;
|
| 210 |
|
| 211 | if (!drv_data) {
|
| 212 | hid_err(hid, "Device data not found.");
|
| 213 | return;
|
| 214 | }
|
| 215 |
|
| 216 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
|
| 217 | if (led_cdev != drv_data->led[i])
|
| 218 | continue;
|
| 219 |
|
| 220 | state = (drv_data->led_state >> i) & 1;
|
| 221 | if (value == LED_OFF && state) {
|
| 222 | drv_data->led_state &= ~(1 << i);
|
| 223 | steelseries_srws1_set_leds(hid, drv_data->led_state);
|
| 224 | } else if (value != LED_OFF && !state) {
|
| 225 | drv_data->led_state |= 1 << i;
|
| 226 | steelseries_srws1_set_leds(hid, drv_data->led_state);
|
| 227 | }
|
| 228 | break;
|
| 229 | }
|
| 230 | }
|
| 231 |
|
| 232 | static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev)
|
| 233 | {
|
| 234 | struct device *dev = led_cdev->dev->parent;
|
| 235 | struct hid_device *hid = to_hid_device(dev);
|
| 236 | struct steelseries_srws1_data *drv_data;
|
| 237 | int i, value = 0;
|
| 238 |
|
| 239 | drv_data = hid_get_drvdata(hid);
|
| 240 |
|
| 241 | if (!drv_data) {
|
| 242 | hid_err(hid, "Device data not found.");
|
| 243 | return LED_OFF;
|
| 244 | }
|
| 245 |
|
| 246 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++)
|
| 247 | if (led_cdev == drv_data->led[i]) {
|
| 248 | value = (drv_data->led_state >> i) & 1;
|
| 249 | break;
|
| 250 | }
|
| 251 |
|
| 252 | return value ? LED_FULL : LED_OFF;
|
| 253 | }
|
| 254 |
|
| 255 | static int steelseries_srws1_probe(struct hid_device *hdev,
|
| 256 | const struct hid_device_id *id)
|
| 257 | {
|
| 258 | int ret, i;
|
| 259 | struct led_classdev *led;
|
| 260 | struct steelseries_srws1_data *drv_data;
|
| 261 | size_t name_sz;
|
| 262 | char *name;
|
| 263 |
|
| 264 | drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
|
| 265 | if (drv_data == NULL) {
|
| 266 | hid_err(hdev, "can't alloc SRW-S1 memory\n");
|
| 267 | return -ENOMEM;
|
| 268 | }
|
| 269 |
|
| 270 | hid_set_drvdata(hdev, drv_data);
|
| 271 |
|
| 272 | ret = hid_parse(hdev);
|
| 273 | if (ret) {
|
| 274 | hid_err(hdev, "parse failed\n");
|
| 275 | goto err;
|
| 276 | }
|
| 277 |
|
| 278 | if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
|
| 279 | ret = -ENODEV;
|
| 280 | goto err;
|
| 281 | }
|
| 282 |
|
| 283 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
| 284 | if (ret) {
|
| 285 | hid_err(hdev, "hw start failed\n");
|
| 286 | goto err;
|
| 287 | }
|
| 288 |
|
| 289 |
|
| 290 | drv_data->led_state = 0;
|
| 291 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++)
|
| 292 | drv_data->led[i] = NULL;
|
| 293 |
|
| 294 | steelseries_srws1_set_leds(hdev, 0);
|
| 295 |
|
| 296 | name_sz = strlen(hdev->uniq) + 16;
|
| 297 |
|
| 298 |
|
| 299 | led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
|
| 300 | if (!led) {
|
| 301 | hid_err(hdev, "can't allocate memory for LED ALL\n");
|
| 302 | goto out;
|
| 303 | }
|
| 304 |
|
| 305 | name = (void *)(&led[1]);
|
| 306 | snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq);
|
| 307 | led->name = name;
|
| 308 | led->brightness = 0;
|
| 309 | led->max_brightness = 1;
|
| 310 | led->brightness_get = steelseries_srws1_led_all_get_brightness;
|
| 311 | led->brightness_set = steelseries_srws1_led_all_set_brightness;
|
| 312 |
|
| 313 | drv_data->led[SRWS1_NUMBER_LEDS] = led;
|
| 314 | ret = devm_led_classdev_register(&hdev->dev, led);
|
| 315 | if (ret) {
|
| 316 | hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS);
|
| 317 | goto out;
|
| 318 | }
|
| 319 |
|
| 320 |
|
| 321 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
|
| 322 | led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
|
| 323 | if (!led) {
|
| 324 | hid_err(hdev, "can't allocate memory for LED %d\n", i);
|
| 325 | break;
|
| 326 | }
|
| 327 |
|
| 328 | name = (void *)(&led[1]);
|
| 329 | snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1);
|
| 330 | led->name = name;
|
| 331 | led->brightness = 0;
|
| 332 | led->max_brightness = 1;
|
| 333 | led->brightness_get = steelseries_srws1_led_get_brightness;
|
| 334 | led->brightness_set = steelseries_srws1_led_set_brightness;
|
| 335 |
|
| 336 | drv_data->led[i] = led;
|
| 337 | ret = devm_led_classdev_register(&hdev->dev, led);
|
| 338 |
|
| 339 | if (ret) {
|
| 340 | hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
|
| 341 | break;
|
| 342 | }
|
| 343 | }
|
| 344 | out:
|
| 345 | return 0;
|
| 346 | err:
|
| 347 | return ret;
|
| 348 | }
|
| 349 | #endif
|
| 350 |
|
| 351 | static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
|
| 352 | {
|
| 353 | .matches = {
|
| 354 | DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
|
| 355 | DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
|
| 356 | DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
|
| 357 | },
|
| 358 | },
|
| 359 | { }
|
| 360 | };
|
| 361 |
|
| 362 | static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
|
| 363 | {
|
| 364 | if (!hid_is_usb(hdev))
|
| 365 | return NULL;
|
| 366 |
|
| 367 | return to_usb_interface(hdev->dev.parent);
|
| 368 | }
|
| 369 |
|
| 370 | static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
|
| 371 | {
|
| 372 | struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
|
| 373 | struct usb_device *udev;
|
| 374 |
|
| 375 | if (!intf)
|
| 376 | return false;
|
| 377 |
|
| 378 | udev = interface_to_usbdev(intf);
|
| 379 |
|
| 380 | return intf == usb_ifnum_to_if(udev, 0);
|
| 381 | }
|
| 382 |
|
| 383 | #if STEELSERIES_HAS_LEDS_MULTICOLOR
|
| 384 |
|
| 385 | static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
|
| 386 | {
|
| 387 | struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
|
| 388 |
|
| 389 | if (!intf)
|
| 390 | return NULL;
|
| 391 |
|
| 392 | return interface_to_usbdev(intf);
|
| 393 | }
|
| 394 |
|
| 395 | static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
|
| 396 | enum led_brightness brightness)
|
| 397 | {
|
| 398 | struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
|
| 399 | struct steelseries_device *sd = container_of(mc_cdev,
|
| 400 | struct steelseries_device,
|
| 401 | mc_cdev);
|
| 402 | struct hid_device *hdev = sd->hdev;
|
| 403 | struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
|
| 404 | int i, ret;
|
| 405 | u8 r, g, b;
|
| 406 |
|
| 407 | static const u8 keys[] = {
|
| 408 | 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
| 409 | 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
|
| 410 | 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
| 411 | 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
|
| 412 | 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
| 413 | 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
|
| 414 | 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
|
| 415 | 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
|
| 416 | 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
|
| 417 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
| 418 | 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
| 419 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
|
| 420 | 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
|
| 421 | };
|
| 422 | static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
|
| 423 |
|
| 424 | if (!udev)
|
| 425 | return -ENODEV;
|
| 426 |
|
| 427 | mutex_lock(&sd->rgb_lock);
|
| 428 |
|
| 429 | led_mc_calc_color_components(mc_cdev, brightness);
|
| 430 |
|
| 431 | r = mc_cdev->subled_info[0].brightness;
|
| 432 | g = mc_cdev->subled_info[1].brightness;
|
| 433 | b = mc_cdev->subled_info[2].brightness;
|
| 434 |
|
| 435 | |
| 436 | * Report layout (524 bytes):
|
| 437 | * Byte 0: Opcode (0x0c)
|
| 438 | * Byte 1: 0x00
|
| 439 | * Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
|
| 440 | * Byte 3: 0x00
|
| 441 | * Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
|
| 442 |
|
| 443 | memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
|
| 444 | sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
|
| 445 | sd->rgb_buf[1] = 0x00;
|
| 446 | sd->rgb_buf[3] = 0x00;
|
| 447 |
|
| 448 | for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
|
| 449 | sd->rgb_buf[4 + i * 4] = 0xff;
|
| 450 |
|
| 451 | if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
|
| 452 | sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
|
| 453 | for (i = 0; i < ARRAY_SIZE(keys); i++) {
|
| 454 | sd->rgb_buf[4 + i * 4] = keys[i];
|
| 455 | sd->rgb_buf[5 + i * 4] = r;
|
| 456 | sd->rgb_buf[6 + i * 4] = g;
|
| 457 | sd->rgb_buf[7 + i * 4] = b;
|
| 458 | }
|
| 459 | } else {
|
| 460 | sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
|
| 461 | for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
|
| 462 | sd->rgb_buf[4 + i * 4] = alc_zones[i];
|
| 463 | sd->rgb_buf[5 + i * 4] = r;
|
| 464 | sd->rgb_buf[6 + i * 4] = g;
|
| 465 | sd->rgb_buf[7 + i * 4] = b;
|
| 466 | }
|
| 467 | }
|
| 468 |
|
| 469 | |
| 470 | * Send the vendor report verbatim with usb_control_msg(): byte 0 is a
|
| 471 | * protocol opcode (0x0c), not a HID report ID, and the controller
|
| 472 | * expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
|
| 473 | * would write the report number into byte 0, so the direct control
|
| 474 | * transfer is used to keep the payload byte-identical to the tested
|
| 475 | * userspace implementation.
|
| 476 |
|
| 477 | ret = hid_hw_power(hdev, PM_HINT_FULLON);
|
| 478 | if (ret < 0)
|
| 479 | goto out_unlock;
|
| 480 |
|
| 481 | ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
|
| 482 | HID_REQ_SET_REPORT,
|
| 483 | USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
| 484 | STEELSERIES_MSI_RGB_WVALUE, 0,
|
| 485 | sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
|
| 486 | USB_CTRL_SET_TIMEOUT);
|
| 487 |
|
| 488 | hid_hw_power(hdev, PM_HINT_NORMAL);
|
| 489 |
|
| 490 | out_unlock:
|
| 491 | mutex_unlock(&sd->rgb_lock);
|
| 492 | return ret < 0 ? ret : 0;
|
| 493 | }
|
| 494 |
|
| 495 | static void steelseries_msi_rgb_free_buf(void *data)
|
| 496 | {
|
| 497 | kfree(data);
|
| 498 | }
|
| 499 |
|
| 500 | static int steelseries_msi_rgb_register(struct steelseries_device *sd)
|
| 501 | {
|
| 502 | struct hid_device *hdev = sd->hdev;
|
| 503 | struct led_classdev *led_cdev;
|
| 504 | int ret;
|
| 505 |
|
| 506 | sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
|
| 507 | if (!sd->rgb_buf)
|
| 508 | return -ENOMEM;
|
| 509 |
|
| 510 | ret = devm_add_action_or_reset(&hdev->dev,
|
| 511 | steelseries_msi_rgb_free_buf,
|
| 512 | sd->rgb_buf);
|
| 513 | if (ret) {
|
| 514 | sd->rgb_buf = NULL;
|
| 515 | return ret;
|
| 516 | }
|
| 517 |
|
| 518 | ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
|
| 519 | if (ret) {
|
| 520 | devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
|
| 521 | sd->rgb_buf);
|
| 522 | kfree(sd->rgb_buf);
|
| 523 | sd->rgb_buf = NULL;
|
| 524 | return ret;
|
| 525 | }
|
| 526 |
|
| 527 | sd->subled_info[0].color_index = LED_COLOR_ID_RED;
|
| 528 | sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
|
| 529 | sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
|
| 530 | sd->subled_info[0].intensity = 255;
|
| 531 | sd->subled_info[1].intensity = 255;
|
| 532 | sd->subled_info[2].intensity = 255;
|
| 533 | sd->subled_info[0].channel = 0;
|
| 534 | sd->subled_info[1].channel = 1;
|
| 535 | sd->subled_info[2].channel = 2;
|
| 536 |
|
| 537 | sd->mc_cdev.subled_info = sd->subled_info;
|
| 538 | sd->mc_cdev.num_colors = 3;
|
| 539 |
|
| 540 | led_cdev = &sd->mc_cdev.led_cdev;
|
| 541 | if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
|
| 542 | led_cdev->name = "steelseries::kbd_backlight";
|
| 543 | else
|
| 544 | led_cdev->name = "steelseries::lightbar";
|
| 545 |
|
| 546 | led_cdev->max_brightness = 255;
|
| 547 | led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
|
| 548 |
|
| 549 | ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
|
| 550 | if (ret) {
|
| 551 | devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
|
| 552 | sd->rgb_buf);
|
| 553 | kfree(sd->rgb_buf);
|
| 554 | sd->rgb_buf = NULL;
|
| 555 | return ret;
|
| 556 | }
|
| 557 |
|
| 558 | return 0;
|
| 559 | }
|
| 560 | #else
|
| 561 | static int steelseries_msi_rgb_register(struct steelseries_device *sd)
|
| 562 | {
|
| 563 | return -ENODEV;
|
| 564 | }
|
| 565 | #endif
|
| 566 |
|
| 567 | static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
| 568 | {
|
| 569 | struct steelseries_device *sd;
|
| 570 | int ret;
|
| 571 |
|
| 572 | if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
|
| 573 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
| 574 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
| 575 | return steelseries_srws1_probe(hdev, id);
|
| 576 | #else
|
| 577 | return -ENODEV;
|
| 578 | #endif
|
| 579 | }
|
| 580 |
|
| 581 | sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
|
| 582 | if (!sd)
|
| 583 | return -ENOMEM;
|
| 584 | hid_set_drvdata(hdev, sd);
|
| 585 | sd->hdev = hdev;
|
| 586 | sd->quirks = id->driver_data;
|
| 587 |
|
| 588 | if (sd->quirks & STEELSERIES_MSI_RGB) {
|
| 589 | if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
|
| 590 | !steelseries_msi_rgb_is_interface0(hdev)) {
|
| 591 | hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
|
| 592 | sd->quirks &= ~STEELSERIES_MSI_RGB;
|
| 593 | }
|
| 594 | }
|
| 595 |
|
| 596 | ret = hid_parse(hdev);
|
| 597 | if (ret)
|
| 598 | return ret;
|
| 599 |
|
| 600 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
| 601 | if (ret)
|
| 602 | return ret;
|
| 603 |
|
| 604 | ret = hid_hw_open(hdev);
|
| 605 | if (ret)
|
| 606 | goto err_stop;
|
| 607 |
|
| 608 | if (sd->quirks & STEELSERIES_MSI_RGB) {
|
| 609 | ret = steelseries_msi_rgb_register(sd);
|
| 610 | if (ret) {
|
| 611 | hid_warn(hdev,
|
| 612 | "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
|
| 613 | ret);
|
| 614 | sd->quirks &= ~STEELSERIES_MSI_RGB;
|
| 615 | }
|
| 616 | }
|
| 617 |
|
| 618 | return 0;
|
| 619 |
|
| 620 | err_stop:
|
| 621 | hid_hw_stop(hdev);
|
| 622 | return ret;
|
| 623 | }
|
| 624 |
|
| 625 | static void steelseries_remove(struct hid_device *hdev)
|
| 626 | {
|
| 627 | if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
|
| 628 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
| 629 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
| 630 | hid_hw_stop(hdev);
|
| 631 | #endif
|
| 632 | return;
|
| 633 | }
|
| 634 |
|
| 635 | hid_hw_close(hdev);
|
| 636 | hid_hw_stop(hdev);
|
| 637 | }
|
| 638 |
|
| 639 | static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
|
| 640 | __u8 *rdesc, unsigned int *rsize)
|
| 641 | {
|
| 642 | if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
|
| 643 | hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
|
| 644 | return rdesc;
|
| 645 |
|
| 646 | if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
|
| 647 | && rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
|
| 648 | hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
|
| 649 | *rsize = sizeof(steelseries_srws1_rdesc_fixed);
|
| 650 | return steelseries_srws1_rdesc_fixed;
|
| 651 | }
|
| 652 | return rdesc;
|
| 653 | }
|
| 654 |
|
| 655 | static const struct hid_device_id steelseries_devices[] = {
|
| 656 | { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
|
| 657 | .driver_data = STEELSERIES_SRWS1 },
|
| 658 |
|
| 659 | #if STEELSERIES_HAS_LEDS_MULTICOLOR
|
| 660 | {
|
| 661 | HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
|
| 662 | .driver_data = STEELSERIES_MSI_RGB },
|
| 663 |
|
| 664 | {
|
| 665 | HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
|
| 666 | .driver_data = STEELSERIES_MSI_RGB },
|
| 667 | #endif
|
| 668 |
|
| 669 | { }
|
| 670 | };
|
| 671 | MODULE_DEVICE_TABLE(hid, steelseries_devices);
|
| 672 |
|
| 673 | static struct hid_driver steelseries_driver = {
|
| 674 | .name = "steelseries",
|
| 675 | .id_table = steelseries_devices,
|
| 676 | .probe = steelseries_probe,
|
| 677 | .remove = steelseries_remove,
|
| 678 | .report_fixup = steelseries_srws1_report_fixup,
|
| 679 | };
|
| 680 |
|
| 681 | module_hid_driver(steelseries_driver);
|
| 682 | MODULE_DESCRIPTION("HID driver for Steelseries devices");
|
| 683 | MODULE_LICENSE("GPL");
|
| 684 | MODULE_AUTHOR("Simon Wood <[email protected]>");
|