1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * HID over I2C driver for PRP0001 devices missing hid-descr-addr 4 * 5 * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3, use 6 * _HID "PRP0001" with _DSD compatible "hid-over-i2c" but lack "hid-descr-addr" 7 * from the _DSD. The HID descriptor address is provided only through an ACPI 8 * _DSM. The TPD0 node in the DSDT shows _DSM Function 1 returning 0x20. 9 * 10 * Copyright (C) 2026 谢致邦 (XIE Zhibang) <Yeking@Red54.com> 11 */ 12 13 #include <linux/delay.h> 14 #include <linux/device.h> 15 #include <linux/i2c.h> 16 #include <linux/module.h> 17 #include <linux/of.h> 18 19 #include "i2c-hid.h" 20 #include "i2c-hid-acpi.h" 21 22 static int i2c_hid_acpi_prp0001_power_up(struct i2chid_ops *ops) 23 { 24 /* give the device time to power up */ 25 msleep(750); 26 return 0; 27 } 28 29 static struct i2chid_ops i2c_hid_acpi_prp0001_ops = { 30 .power_up = i2c_hid_acpi_prp0001_power_up, 31 /* 32 * No .restore_sequence needed: the _DSM on these devices returns a 33 * constant (0x20) with no side effects, unlike some PNP0C50 _DSM 34 * implementations that switch the hardware between PS/2 and I2C modes. 35 */ 36 }; 37 38 static int i2c_hid_acpi_prp0001_probe(struct i2c_client *client) 39 { 40 struct device *dev = &client->dev; 41 struct acpi_device *adev; 42 u16 hid_descriptor_address; 43 int ret; 44 45 /* If hid-descr-addr is present, let i2c-hid-of handle it */ 46 if (device_property_present(dev, "hid-descr-addr")) 47 return -ENODEV; 48 49 adev = ACPI_COMPANION(dev); 50 if (!adev) 51 return -ENODEV; 52 53 ret = i2c_hid_acpi_get_descriptor(adev); 54 if (ret < 0) 55 return ret; 56 dev_warn(dev, 57 "hid-descr-addr device property NOT found, using ACPI _DSM fallback. Contact vendor for firmware update!\n"); 58 hid_descriptor_address = ret; 59 60 /* 61 * No acpi_device_fix_up_power() needed: TPD0 has no _PS0, _PS3, _PSC 62 * or _PRx methods and follows I2C bus power. 63 */ 64 return i2c_hid_core_probe(client, &i2c_hid_acpi_prp0001_ops, 65 hid_descriptor_address, 0); 66 } 67 68 static const struct of_device_id i2c_hid_acpi_prp0001_of_match[] = { 69 { .compatible = "hid-over-i2c" }, 70 {}, 71 }; 72 MODULE_DEVICE_TABLE(of, i2c_hid_acpi_prp0001_of_match); 73 74 static const struct i2c_device_id i2c_hid_acpi_prp0001_id[] = { 75 { .name = "hid-over-i2c" }, 76 { } 77 }; 78 MODULE_DEVICE_TABLE(i2c, i2c_hid_acpi_prp0001_id); 79 80 static struct i2c_driver i2c_hid_acpi_prp0001_driver = { 81 .driver = { 82 .name = "i2c_hid_acpi_prp0001", 83 .pm = &i2c_hid_core_pm, 84 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 85 /* 86 * of_match_ptr() makes this NULL when CONFIG_OF=n, but that's 87 * fine: the I2C id_table with "hid-over-i2c" handles matching 88 * via client->name (set by acpi_set_modalias() from the _DSD 89 * compatible property). 90 */ 91 .of_match_table = of_match_ptr(i2c_hid_acpi_prp0001_of_match), 92 }, 93 94 .probe = i2c_hid_acpi_prp0001_probe, 95 .remove = i2c_hid_core_remove, 96 .shutdown = i2c_hid_core_shutdown, 97 .id_table = i2c_hid_acpi_prp0001_id, 98 }; 99 100 module_i2c_driver(i2c_hid_acpi_prp0001_driver); 101 102 MODULE_DESCRIPTION("HID over I2C driver for PRP0001 devices missing hid-descr-addr"); 103 MODULE_AUTHOR("谢致邦 (XIE Zhibang) <Yeking@Red54.com>"); 104 MODULE_LICENSE("GPL"); 105