|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | *  HID driver for Google Hammer device. | 
|  | * | 
|  | *  Copyright (c) 2017 Google Inc. | 
|  | *  Author: Wei-Ning Huang <wnhuang@google.com> | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms of the GNU General Public License as published by the Free | 
|  | * Software Foundation; either version 2 of the License, or (at your option) | 
|  | * any later version. | 
|  | */ | 
|  |  | 
|  | #include <linux/hid.h> | 
|  | #include <linux/leds.h> | 
|  | #include <linux/module.h> | 
|  |  | 
|  | #include "hid-ids.h" | 
|  |  | 
|  | #define MAX_BRIGHTNESS 100 | 
|  |  | 
|  | /* HID usage for keyboard backlight (Alphanumeric display brightness) */ | 
|  | #define HID_AD_BRIGHTNESS 0x00140046 | 
|  |  | 
|  | struct hammer_kbd_leds { | 
|  | struct led_classdev cdev; | 
|  | struct hid_device *hdev; | 
|  | u8 buf[2] ____cacheline_aligned; | 
|  | }; | 
|  |  | 
|  | static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev, | 
|  | enum led_brightness br) | 
|  | { | 
|  | struct hammer_kbd_leds *led = container_of(cdev, | 
|  | struct hammer_kbd_leds, | 
|  | cdev); | 
|  | int ret; | 
|  |  | 
|  | led->buf[0] = 0; | 
|  | led->buf[1] = br; | 
|  |  | 
|  | /* | 
|  | * Request USB HID device to be in Full On mode, so that sending | 
|  | * hardware output report and hardware raw request won't fail. | 
|  | */ | 
|  | ret = hid_hw_power(led->hdev, PM_HINT_FULLON); | 
|  | if (ret < 0) { | 
|  | hid_err(led->hdev, "failed: device not resumed %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf)); | 
|  | if (ret == -ENOSYS) | 
|  | ret = hid_hw_raw_request(led->hdev, 0, led->buf, | 
|  | sizeof(led->buf), | 
|  | HID_OUTPUT_REPORT, | 
|  | HID_REQ_SET_REPORT); | 
|  | if (ret < 0) | 
|  | hid_err(led->hdev, "failed to set keyboard backlight: %d\n", | 
|  | ret); | 
|  |  | 
|  | /* Request USB HID device back to Normal Mode. */ | 
|  | hid_hw_power(led->hdev, PM_HINT_NORMAL); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int hammer_register_leds(struct hid_device *hdev) | 
|  | { | 
|  | struct hammer_kbd_leds *kbd_backlight; | 
|  |  | 
|  | kbd_backlight = devm_kzalloc(&hdev->dev, | 
|  | sizeof(*kbd_backlight), | 
|  | GFP_KERNEL); | 
|  | if (!kbd_backlight) | 
|  | return -ENOMEM; | 
|  |  | 
|  | kbd_backlight->hdev = hdev; | 
|  | kbd_backlight->cdev.name = "hammer::kbd_backlight"; | 
|  | kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS; | 
|  | kbd_backlight->cdev.brightness_set_blocking = | 
|  | hammer_kbd_brightness_set_blocking; | 
|  | kbd_backlight->cdev.flags = LED_HW_PLUGGABLE; | 
|  |  | 
|  | /* Set backlight to 0% initially. */ | 
|  | hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0); | 
|  |  | 
|  | return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev); | 
|  | } | 
|  |  | 
|  | static int hammer_input_configured(struct hid_device *hdev, | 
|  | struct hid_input *hi) | 
|  | { | 
|  | struct list_head *report_list = | 
|  | &hdev->report_enum[HID_OUTPUT_REPORT].report_list; | 
|  | struct hid_report *report; | 
|  |  | 
|  | if (list_empty(report_list)) | 
|  | return 0; | 
|  |  | 
|  | report = list_first_entry(report_list, struct hid_report, list); | 
|  |  | 
|  | if (report->maxfield == 1 && | 
|  | report->field[0]->application == HID_GD_KEYBOARD && | 
|  | report->field[0]->maxusage == 1 && | 
|  | report->field[0]->usage[0].hid == HID_AD_BRIGHTNESS) { | 
|  | int err = hammer_register_leds(hdev); | 
|  |  | 
|  | if (err) | 
|  | hid_warn(hdev, | 
|  | "Failed to register keyboard backlight: %d\n", | 
|  | err); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct hid_device_id hammer_devices[] = { | 
|  | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | 
|  | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, | 
|  | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | 
|  | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MAGNEMITE) }, | 
|  | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | 
|  | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MASTERBALL) }, | 
|  | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | 
|  | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) }, | 
|  | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | 
|  | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) }, | 
|  | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | 
|  | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(hid, hammer_devices); | 
|  |  | 
|  | static struct hid_driver hammer_driver = { | 
|  | .name = "hammer", | 
|  | .id_table = hammer_devices, | 
|  | .input_configured = hammer_input_configured, | 
|  | }; | 
|  | module_hid_driver(hammer_driver); | 
|  |  | 
|  | MODULE_LICENSE("GPL"); |