aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarlo Caione <carlo@endlessm.com>2017-04-06 06:18:17 -0400
committerJiri Kosina <jkosina@suse.cz>2017-04-12 16:30:29 -0400
commitaf22a610bc38508d5ea760507d31be6b6983dfa8 (patch)
tree98aea4d9d982ce18522e0dfc6ac37dec4507689e
parent802b24b475e459e985681d6e0815ae6cb52e5560 (diff)
HID: asus: support backlight on USB keyboards
The latest USB keyboards shipped on several ASUS laptop models (including ROG laptop models such as GL702VMK) have the keyboards backlight controlled by the keyboard firmware. The firmware implements at least 3 different commands: - Init command (to use when the system starts) - Configuration command (to get keyboard status/information) - Backlight level control (to change the level of the keyboard light) With this patch we create the usual 'asus::kbd_backlight' led class entry to control the keyboard backlight. [jkosina@suse.cz: remove pointless cancel_work_sync() call while handling an error in asus_kbd_register_leds(), as spotted by Benjamin] Signed-off-by: Carlo Caione <carlo@endlessm.com> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r--drivers/hid/Kconfig1
-rw-r--r--drivers/hid/hid-asus.c183
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
137config HID_ASUS 137config 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
82struct 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
77struct asus_drvdata { 90struct 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
82static void asus_report_contact_down(struct input_dev *input, 97static 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
189static 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
206static 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
219static 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
251static 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
263static 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
271static 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
287static 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
174static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) 331static 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
531static 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
361static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, 541static __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};
385MODULE_DEVICE_TABLE(hid, asus_devices); 565MODULE_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