1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * HID driver for Google Hammer device. 4 * 5 * Copyright (c) 2017 Google Inc. 6 * Author: Wei-Ning Huang <wnhuang@google.com> 7 */ 8 9 /* 10 * This program is free software; you can redistribute it and/or modify it 11 * under the terms of the GNU General Public License as published by the Free 12 * Software Foundation; either version 2 of the License, or (at your option) 13 * any later version. 14 */ 15 16 #include <linux/hid.h> 17 #include <linux/leds.h> 18 #include <linux/module.h> 19 20 #include "hid-ids.h" 21 22 #define MAX_BRIGHTNESS 100 23 24 /* HID usage for keyboard backlight (Alphanumeric display brightness) */ 25 #define HID_AD_BRIGHTNESS 0x00140046 26 27 struct hammer_kbd_leds { 28 struct led_classdev cdev; 29 struct hid_device *hdev; 30 u8 buf[2] ____cacheline_aligned; 31 }; 32 33 static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev, 34 enum led_brightness br) 35 { 36 struct hammer_kbd_leds *led = container_of(cdev, 37 struct hammer_kbd_leds, 38 cdev); 39 int ret; 40 41 led->buf[0] = 0; 42 led->buf[1] = br; 43 44 /* 45 * Request USB HID device to be in Full On mode, so that sending 46 * hardware output report and hardware raw request won't fail. 47 */ 48 ret = hid_hw_power(led->hdev, PM_HINT_FULLON); 49 if (ret < 0) { 50 hid_err(led->hdev, "failed: device not resumed %d\n", ret); 51 return ret; 52 } 53 54 ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf)); 55 if (ret == -ENOSYS) 56 ret = hid_hw_raw_request(led->hdev, 0, led->buf, 57 sizeof(led->buf), 58 HID_OUTPUT_REPORT, 59 HID_REQ_SET_REPORT); 60 if (ret < 0) 61 hid_err(led->hdev, "failed to set keyboard backlight: %d\n", 62 ret); 63 64 /* Request USB HID device back to Normal Mode. */ 65 hid_hw_power(led->hdev, PM_HINT_NORMAL); 66 67 return ret; 68 } 69 70 static int hammer_register_leds(struct hid_device *hdev) 71 { 72 struct hammer_kbd_leds *kbd_backlight; 73 74 kbd_backlight = devm_kzalloc(&hdev->dev, 75 sizeof(*kbd_backlight), 76 GFP_KERNEL); 77 if (!kbd_backlight) 78 return -ENOMEM; 79 80 kbd_backlight->hdev = hdev; 81 kbd_backlight->cdev.name = "hammer::kbd_backlight"; 82 kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS; 83 kbd_backlight->cdev.brightness_set_blocking = 84 hammer_kbd_brightness_set_blocking; 85 kbd_backlight->cdev.flags = LED_HW_PLUGGABLE; 86 87 /* Set backlight to 0% initially. */ 88 hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0); 89 90 return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev); 91 } 92 93 static int hammer_input_configured(struct hid_device *hdev, 94 struct hid_input *hi) 95 { 96 struct list_head *report_list = 97 &hdev->report_enum[HID_OUTPUT_REPORT].report_list; 98 struct hid_report *report; 99 100 if (list_empty(report_list)) 101 return 0; 102 103 report = list_first_entry(report_list, struct hid_report, list); 104 105 if (report->maxfield == 1 && 106 report->field[0]->application == HID_GD_KEYBOARD && 107 report->field[0]->maxusage == 1 && 108 report->field[0]->usage[0].hid == HID_AD_BRIGHTNESS) { 109 int err = hammer_register_leds(hdev); 110 111 if (err) 112 hid_warn(hdev, 113 "Failed to register keyboard backlight: %d\n", 114 err); 115 } 116 117 return 0; 118 } 119 120 static const struct hid_device_id hammer_devices[] = { 121 { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 122 USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, 123 { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 124 USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) }, 125 { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 126 USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) }, 127 { } 128 }; 129 MODULE_DEVICE_TABLE(hid, hammer_devices); 130 131 static struct hid_driver hammer_driver = { 132 .name = "hammer", 133 .id_table = hammer_devices, 134 .input_configured = hammer_input_configured, 135 }; 136 module_hid_driver(hammer_driver); 137 138 MODULE_LICENSE("GPL"); 139