162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * HID driver for Holtek keyboard 462306a36Sopenharmony_ci * Copyright (c) 2012 Tom Harwood 562306a36Sopenharmony_ci*/ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci/* 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/hid.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/usb.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "hid-ids.h" 1662306a36Sopenharmony_ci#include "usbhid/usbhid.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/* Holtek based keyboards (USB ID 04d9:a055) have the following issues: 1962306a36Sopenharmony_ci * - The report descriptor specifies an excessively large number of consumer 2062306a36Sopenharmony_ci * usages (2^15), which is more than HID_MAX_USAGES. This prevents proper 2162306a36Sopenharmony_ci * parsing of the report descriptor. 2262306a36Sopenharmony_ci * - The report descriptor reports on caps/scroll/num lock key presses, but 2362306a36Sopenharmony_ci * doesn't have an LED output usage block. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * The replacement descriptor below fixes the number of consumer usages, 2662306a36Sopenharmony_ci * and provides an LED output usage block. LED output events are redirected 2762306a36Sopenharmony_ci * to the boot interface. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic __u8 holtek_kbd_rdesc_fixed[] = { 3162306a36Sopenharmony_ci /* Original report descriptor, with reduced number of consumer usages */ 3262306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Desktop), */ 3362306a36Sopenharmony_ci 0x09, 0x80, /* Usage (Sys Control), */ 3462306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 3562306a36Sopenharmony_ci 0x85, 0x01, /* Report ID (1), */ 3662306a36Sopenharmony_ci 0x19, 0x81, /* Usage Minimum (Sys Power Down), */ 3762306a36Sopenharmony_ci 0x29, 0x83, /* Usage Maximum (Sys Wake Up), */ 3862306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 3962306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1), */ 4062306a36Sopenharmony_ci 0x95, 0x03, /* Report Count (3), */ 4162306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1), */ 4262306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 4362306a36Sopenharmony_ci 0x95, 0x01, /* Report Count (1), */ 4462306a36Sopenharmony_ci 0x75, 0x05, /* Report Size (5), */ 4562306a36Sopenharmony_ci 0x81, 0x01, /* Input (Constant), */ 4662306a36Sopenharmony_ci 0xC0, /* End Collection, */ 4762306a36Sopenharmony_ci 0x05, 0x0C, /* Usage Page (Consumer), */ 4862306a36Sopenharmony_ci 0x09, 0x01, /* Usage (Consumer Control), */ 4962306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 5062306a36Sopenharmony_ci 0x85, 0x02, /* Report ID (2), */ 5162306a36Sopenharmony_ci 0x19, 0x00, /* Usage Minimum (00h), */ 5262306a36Sopenharmony_ci 0x2A, 0xFF, 0x2F, /* Usage Maximum (0x2FFF), previously 0x7FFF */ 5362306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 5462306a36Sopenharmony_ci 0x26, 0xFF, 0x2F, /* Logical Maximum (0x2FFF),previously 0x7FFF*/ 5562306a36Sopenharmony_ci 0x95, 0x01, /* Report Count (1), */ 5662306a36Sopenharmony_ci 0x75, 0x10, /* Report Size (16), */ 5762306a36Sopenharmony_ci 0x81, 0x00, /* Input, */ 5862306a36Sopenharmony_ci 0xC0, /* End Collection, */ 5962306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Desktop), */ 6062306a36Sopenharmony_ci 0x09, 0x06, /* Usage (Keyboard), */ 6162306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 6262306a36Sopenharmony_ci 0x85, 0x03, /* Report ID (3), */ 6362306a36Sopenharmony_ci 0x95, 0x38, /* Report Count (56), */ 6462306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1), */ 6562306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 6662306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1), */ 6762306a36Sopenharmony_ci 0x05, 0x07, /* Usage Page (Keyboard), */ 6862306a36Sopenharmony_ci 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ 6962306a36Sopenharmony_ci 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ 7062306a36Sopenharmony_ci 0x19, 0x00, /* Usage Minimum (None), */ 7162306a36Sopenharmony_ci 0x29, 0x2F, /* Usage Maximum (KB Lboxbracket And Lbrace),*/ 7262306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 7362306a36Sopenharmony_ci 0xC0, /* End Collection, */ 7462306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Desktop), */ 7562306a36Sopenharmony_ci 0x09, 0x06, /* Usage (Keyboard), */ 7662306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 7762306a36Sopenharmony_ci 0x85, 0x04, /* Report ID (4), */ 7862306a36Sopenharmony_ci 0x95, 0x38, /* Report Count (56), */ 7962306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1), */ 8062306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 8162306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1), */ 8262306a36Sopenharmony_ci 0x05, 0x07, /* Usage Page (Keyboard), */ 8362306a36Sopenharmony_ci 0x19, 0x30, /* Usage Minimum (KB Rboxbracket And Rbrace),*/ 8462306a36Sopenharmony_ci 0x29, 0x67, /* Usage Maximum (KP Equals), */ 8562306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 8662306a36Sopenharmony_ci 0xC0, /* End Collection */ 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* LED usage for the boot protocol interface */ 8962306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Desktop), */ 9062306a36Sopenharmony_ci 0x09, 0x06, /* Usage (Keyboard), */ 9162306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 9262306a36Sopenharmony_ci 0x05, 0x08, /* Usage Page (LED), */ 9362306a36Sopenharmony_ci 0x19, 0x01, /* Usage Minimum (01h), */ 9462306a36Sopenharmony_ci 0x29, 0x03, /* Usage Maximum (03h), */ 9562306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 9662306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1), */ 9762306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1), */ 9862306a36Sopenharmony_ci 0x95, 0x03, /* Report Count (3), */ 9962306a36Sopenharmony_ci 0x91, 0x02, /* Output (Variable), */ 10062306a36Sopenharmony_ci 0x95, 0x05, /* Report Count (5), */ 10162306a36Sopenharmony_ci 0x91, 0x01, /* Output (Constant), */ 10262306a36Sopenharmony_ci 0xC0, /* End Collection */ 10362306a36Sopenharmony_ci}; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc, 10662306a36Sopenharmony_ci unsigned int *rsize) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(hdev->dev.parent); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { 11162306a36Sopenharmony_ci rdesc = holtek_kbd_rdesc_fixed; 11262306a36Sopenharmony_ci *rsize = sizeof(holtek_kbd_rdesc_fixed); 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci return rdesc; 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic int holtek_kbd_input_event(struct input_dev *dev, unsigned int type, 11862306a36Sopenharmony_ci unsigned int code, 11962306a36Sopenharmony_ci int value) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct hid_device *hid = input_get_drvdata(dev); 12262306a36Sopenharmony_ci struct usb_device *usb_dev = hid_to_usb_dev(hid); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Locate the boot interface, to receive the LED change events */ 12562306a36Sopenharmony_ci struct usb_interface *boot_interface = usb_ifnum_to_if(usb_dev, 0); 12662306a36Sopenharmony_ci struct hid_device *boot_hid; 12762306a36Sopenharmony_ci struct hid_input *boot_hid_input; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci if (unlikely(boot_interface == NULL)) 13062306a36Sopenharmony_ci return -ENODEV; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci boot_hid = usb_get_intfdata(boot_interface); 13362306a36Sopenharmony_ci if (list_empty(&boot_hid->inputs)) { 13462306a36Sopenharmony_ci hid_err(hid, "no inputs found\n"); 13562306a36Sopenharmony_ci return -ENODEV; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci boot_hid_input = list_first_entry(&boot_hid->inputs, 13862306a36Sopenharmony_ci struct hid_input, list); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return boot_hid_input->input->event(boot_hid_input->input, type, code, 14162306a36Sopenharmony_ci value); 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int holtek_kbd_probe(struct hid_device *hdev, 14562306a36Sopenharmony_ci const struct hid_device_id *id) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct usb_interface *intf; 14862306a36Sopenharmony_ci int ret; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (!hid_is_usb(hdev)) 15162306a36Sopenharmony_ci return -EINVAL; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci ret = hid_parse(hdev); 15462306a36Sopenharmony_ci if (!ret) 15562306a36Sopenharmony_ci ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci intf = to_usb_interface(hdev->dev.parent); 15862306a36Sopenharmony_ci if (!ret && intf->cur_altsetting->desc.bInterfaceNumber == 1) { 15962306a36Sopenharmony_ci struct hid_input *hidinput; 16062306a36Sopenharmony_ci list_for_each_entry(hidinput, &hdev->inputs, list) { 16162306a36Sopenharmony_ci hidinput->input->event = holtek_kbd_input_event; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci return ret; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic const struct hid_device_id holtek_kbd_devices[] = { 16962306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, 17062306a36Sopenharmony_ci USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) }, 17162306a36Sopenharmony_ci { } 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, holtek_kbd_devices); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic struct hid_driver holtek_kbd_driver = { 17662306a36Sopenharmony_ci .name = "holtek_kbd", 17762306a36Sopenharmony_ci .id_table = holtek_kbd_devices, 17862306a36Sopenharmony_ci .report_fixup = holtek_kbd_report_fixup, 17962306a36Sopenharmony_ci .probe = holtek_kbd_probe 18062306a36Sopenharmony_ci}; 18162306a36Sopenharmony_cimodule_hid_driver(holtek_kbd_driver); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 184