diff options
| -rw-r--r-- | drivers/hid/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/hid/hid-asus.c | 183 |
2 files changed, 183 insertions, 1 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index de4ed3752a4e..502444fff869 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig | |||
| @@ -136,6 +136,7 @@ config HID_APPLEIR | |||
| 136 | 136 | ||
| 137 | config HID_ASUS | 137 | config HID_ASUS |
| 138 | tristate "Asus" | 138 | tristate "Asus" |
| 139 | depends on LEDS_CLASS | ||
| 139 | ---help--- | 140 | ---help--- |
| 140 | Support for Asus notebook built-in keyboard and touchpad via i2c, and | 141 | Support for Asus notebook built-in keyboard and touchpad via i2c, and |
| 141 | the Asus Republic of Gamers laptop keyboard special keys. | 142 | the Asus Republic of Gamers laptop keyboard special keys. |
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index bacba97668bf..16df6cc90235 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c | |||
| @@ -40,8 +40,12 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); | |||
| 40 | 40 | ||
| 41 | #define FEATURE_REPORT_ID 0x0d | 41 | #define FEATURE_REPORT_ID 0x0d |
| 42 | #define INPUT_REPORT_ID 0x5d | 42 | #define INPUT_REPORT_ID 0x5d |
| 43 | #define FEATURE_KBD_REPORT_ID 0x5a | ||
| 43 | 44 | ||
| 44 | #define INPUT_REPORT_SIZE 28 | 45 | #define INPUT_REPORT_SIZE 28 |
| 46 | #define FEATURE_KBD_REPORT_SIZE 16 | ||
| 47 | |||
| 48 | #define SUPPORT_KBD_BACKLIGHT BIT(0) | ||
| 45 | 49 | ||
| 46 | #define MAX_CONTACTS 5 | 50 | #define MAX_CONTACTS 5 |
| 47 | 51 | ||
| @@ -64,6 +68,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); | |||
| 64 | #define QUIRK_SKIP_INPUT_MAPPING BIT(2) | 68 | #define QUIRK_SKIP_INPUT_MAPPING BIT(2) |
| 65 | #define QUIRK_IS_MULTITOUCH BIT(3) | 69 | #define QUIRK_IS_MULTITOUCH BIT(3) |
| 66 | #define QUIRK_NO_CONSUMER_USAGES BIT(4) | 70 | #define QUIRK_NO_CONSUMER_USAGES BIT(4) |
| 71 | #define QUIRK_USE_KBD_BACKLIGHT BIT(5) | ||
| 67 | 72 | ||
| 68 | #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ | 73 | #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ |
| 69 | QUIRK_NO_INIT_REPORTS | \ | 74 | QUIRK_NO_INIT_REPORTS | \ |
| @@ -74,9 +79,19 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); | |||
| 74 | 79 | ||
| 75 | #define TRKID_SGN ((TRKID_MAX + 1) >> 1) | 80 | #define TRKID_SGN ((TRKID_MAX + 1) >> 1) |
| 76 | 81 | ||
| 82 | struct asus_kbd_leds { | ||
| 83 | struct led_classdev cdev; | ||
| 84 | struct hid_device *hdev; | ||
| 85 | struct work_struct work; | ||
| 86 | unsigned int brightness; | ||
| 87 | bool removed; | ||
| 88 | }; | ||
| 89 | |||
| 77 | struct asus_drvdata { | 90 | struct asus_drvdata { |
| 78 | unsigned long quirks; | 91 | unsigned long quirks; |
| 79 | struct input_dev *input; | 92 | struct input_dev *input; |
| 93 | struct asus_kbd_leds *kbd_backlight; | ||
| 94 | bool enable_backlight; | ||
| 80 | }; | 95 | }; |
| 81 | 96 | ||
| 82 | static void asus_report_contact_down(struct input_dev *input, | 97 | static void asus_report_contact_down(struct input_dev *input, |
| @@ -171,6 +186,148 @@ static int asus_raw_event(struct hid_device *hdev, | |||
| 171 | return 0; | 186 | return 0; |
| 172 | } | 187 | } |
| 173 | 188 | ||
| 189 | static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size) | ||
| 190 | { | ||
| 191 | unsigned char *dmabuf; | ||
| 192 | int ret; | ||
| 193 | |||
| 194 | dmabuf = kmemdup(buf, buf_size, GFP_KERNEL); | ||
| 195 | if (!dmabuf) | ||
| 196 | return -ENOMEM; | ||
| 197 | |||
| 198 | ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf, | ||
| 199 | buf_size, HID_FEATURE_REPORT, | ||
| 200 | HID_REQ_SET_REPORT); | ||
| 201 | kfree(dmabuf); | ||
| 202 | |||
| 203 | return ret; | ||
| 204 | } | ||
| 205 | |||
| 206 | static int asus_kbd_init(struct hid_device *hdev) | ||
| 207 | { | ||
| 208 | u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, | ||
| 209 | 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; | ||
| 210 | int ret; | ||
| 211 | |||
| 212 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); | ||
| 213 | if (ret < 0) | ||
| 214 | hid_err(hdev, "Asus failed to send init command: %d\n", ret); | ||
| 215 | |||
| 216 | return ret; | ||
| 217 | } | ||
| 218 | |||
| 219 | static int asus_kbd_get_functions(struct hid_device *hdev, | ||
| 220 | unsigned char *kbd_func) | ||
| 221 | { | ||
| 222 | u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 }; | ||
| 223 | u8 *readbuf; | ||
| 224 | int ret; | ||
| 225 | |||
| 226 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); | ||
| 227 | if (ret < 0) { | ||
| 228 | hid_err(hdev, "Asus failed to send configuration command: %d\n", ret); | ||
| 229 | return ret; | ||
| 230 | } | ||
| 231 | |||
| 232 | readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL); | ||
| 233 | if (!readbuf) | ||
| 234 | return -ENOMEM; | ||
| 235 | |||
| 236 | ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf, | ||
| 237 | FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, | ||
| 238 | HID_REQ_GET_REPORT); | ||
| 239 | if (ret < 0) { | ||
| 240 | hid_err(hdev, "Asus failed to request functions: %d\n", ret); | ||
| 241 | kfree(readbuf); | ||
| 242 | return ret; | ||
| 243 | } | ||
| 244 | |||
| 245 | *kbd_func = readbuf[6]; | ||
| 246 | |||
| 247 | kfree(readbuf); | ||
| 248 | return ret; | ||
| 249 | } | ||
| 250 | |||
| 251 | static void asus_kbd_backlight_set(struct led_classdev *led_cdev, | ||
| 252 | enum led_brightness brightness) | ||
| 253 | { | ||
| 254 | struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, | ||
| 255 | cdev); | ||
| 256 | if (led->brightness == brightness) | ||
| 257 | return; | ||
| 258 | |||
| 259 | led->brightness = brightness; | ||
| 260 | schedule_work(&led->work); | ||
| 261 | } | ||
| 262 | |||
| 263 | static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev) | ||
| 264 | { | ||
| 265 | struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, | ||
| 266 | cdev); | ||
| 267 | |||
| 268 | return led->brightness; | ||
| 269 | } | ||
| 270 | |||
| 271 | static void asus_kbd_backlight_work(struct work_struct *work) | ||
| 272 | { | ||
| 273 | struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); | ||
| 274 | u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 }; | ||
| 275 | int ret; | ||
| 276 | |||
| 277 | if (led->removed) | ||
| 278 | return; | ||
| 279 | |||
| 280 | buf[4] = led->brightness; | ||
| 281 | |||
| 282 | ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf)); | ||
| 283 | if (ret < 0) | ||
| 284 | hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); | ||
| 285 | } | ||
| 286 | |||
| 287 | static int asus_kbd_register_leds(struct hid_device *hdev) | ||
| 288 | { | ||
| 289 | struct asus_drvdata *drvdata = hid_get_drvdata(hdev); | ||
| 290 | unsigned char kbd_func; | ||
| 291 | int ret; | ||
| 292 | |||
| 293 | /* Initialize keyboard */ | ||
| 294 | ret = asus_kbd_init(hdev); | ||
| 295 | if (ret < 0) | ||
| 296 | return ret; | ||
| 297 | |||
| 298 | /* Get keyboard functions */ | ||
| 299 | ret = asus_kbd_get_functions(hdev, &kbd_func); | ||
| 300 | if (ret < 0) | ||
| 301 | return ret; | ||
| 302 | |||
| 303 | /* Check for backlight support */ | ||
| 304 | if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) | ||
| 305 | return -ENODEV; | ||
| 306 | |||
| 307 | drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, | ||
| 308 | sizeof(struct asus_kbd_leds), | ||
| 309 | GFP_KERNEL); | ||
| 310 | if (!drvdata->kbd_backlight) | ||
| 311 | return -ENOMEM; | ||
| 312 | |||
| 313 | drvdata->kbd_backlight->removed = false; | ||
| 314 | drvdata->kbd_backlight->brightness = 0; | ||
| 315 | drvdata->kbd_backlight->hdev = hdev; | ||
| 316 | drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight"; | ||
| 317 | drvdata->kbd_backlight->cdev.max_brightness = 3; | ||
| 318 | drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set; | ||
| 319 | drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get; | ||
| 320 | INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); | ||
| 321 | |||
| 322 | ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev); | ||
| 323 | if (ret < 0) { | ||
| 324 | /* No need to have this still around */ | ||
| 325 | devm_kfree(&hdev->dev, drvdata->kbd_backlight); | ||
| 326 | } | ||
| 327 | |||
| 328 | return ret; | ||
| 329 | } | ||
| 330 | |||
| 174 | static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) | 331 | static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) |
| 175 | { | 332 | { |
| 176 | struct input_dev *input = hi->input; | 333 | struct input_dev *input = hi->input; |
| @@ -198,6 +355,9 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) | |||
| 198 | 355 | ||
| 199 | drvdata->input = input; | 356 | drvdata->input = input; |
| 200 | 357 | ||
| 358 | if (drvdata->enable_backlight && asus_kbd_register_leds(hdev)) | ||
| 359 | hid_warn(hdev, "Failed to initialize backlight.\n"); | ||
| 360 | |||
| 201 | return 0; | 361 | return 0; |
| 202 | } | 362 | } |
| 203 | 363 | ||
| @@ -248,6 +408,16 @@ static int asus_input_mapping(struct hid_device *hdev, | |||
| 248 | * as some make the keyboard appear as a pointer device. */ | 408 | * as some make the keyboard appear as a pointer device. */ |
| 249 | return -1; | 409 | return -1; |
| 250 | } | 410 | } |
| 411 | |||
| 412 | /* | ||
| 413 | * Check and enable backlight only on devices with UsagePage == | ||
| 414 | * 0xff31 to avoid initializing the keyboard firmware multiple | ||
| 415 | * times on devices with multiple HID descriptors but same | ||
| 416 | * PID/VID. | ||
| 417 | */ | ||
| 418 | if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) | ||
| 419 | drvdata->enable_backlight = true; | ||
| 420 | |||
| 251 | return 1; | 421 | return 1; |
| 252 | } | 422 | } |
| 253 | 423 | ||
| @@ -358,6 +528,16 @@ err_stop_hw: | |||
| 358 | return ret; | 528 | return ret; |
| 359 | } | 529 | } |
| 360 | 530 | ||
| 531 | static void asus_remove(struct hid_device *hdev) | ||
| 532 | { | ||
| 533 | struct asus_drvdata *drvdata = hid_get_drvdata(hdev); | ||
| 534 | |||
| 535 | if (drvdata->kbd_backlight) { | ||
| 536 | drvdata->kbd_backlight->removed = true; | ||
| 537 | cancel_work_sync(&drvdata->kbd_backlight->work); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | |||
| 361 | static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, | 541 | static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
| 362 | unsigned int *rsize) | 542 | unsigned int *rsize) |
| 363 | { | 543 | { |
| @@ -379,7 +559,7 @@ static const struct hid_device_id asus_devices[] = { | |||
| 379 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, | 559 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 380 | USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, | 560 | USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, |
| 381 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, | 561 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 382 | USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2) }, | 562 | USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT }, |
| 383 | { } | 563 | { } |
| 384 | }; | 564 | }; |
| 385 | MODULE_DEVICE_TABLE(hid, asus_devices); | 565 | MODULE_DEVICE_TABLE(hid, asus_devices); |
| @@ -389,6 +569,7 @@ static struct hid_driver asus_driver = { | |||
| 389 | .id_table = asus_devices, | 569 | .id_table = asus_devices, |
| 390 | .report_fixup = asus_report_fixup, | 570 | .report_fixup = asus_report_fixup, |
| 391 | .probe = asus_probe, | 571 | .probe = asus_probe, |
| 572 | .remove = asus_remove, | ||
| 392 | .input_mapping = asus_input_mapping, | 573 | .input_mapping = asus_input_mapping, |
| 393 | .input_configured = asus_input_configured, | 574 | .input_configured = asus_input_configured, |
| 394 | #ifdef CONFIG_PM | 575 | #ifdef CONFIG_PM |
