xref: /linux/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c (revision a93f3bf4e1d60777b1659b812c9e818cfc53b449)
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