Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions drivers/hid/i2c-hid/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ config I2C_HID_ACPI
will be called i2c-hid-acpi. It will also build/depend on the
module i2c-hid.

config I2C_HID_ACPI_PRP0001
tristate "Driver for PRP0001 devices missing hid-descr-addr"
depends on ACPI
depends on DRM || !DRM
select I2C_HID_CORE
help
Say Y here if you want support for I2C HID touchpads that
are declared with _HID "PRP0001" and _DSD compatible
"hid-over-i2c" but lack the "hid-descr-addr" property from
the _DSD. The HID descriptor address is instead provided
through an ACPI _DSM. Known affected devices include the
Lenovo KaiTian N60d and Inspur CP300L3.

If unsure, say N.

This support is also available as a module. If so, the
module will be called i2c-hid-acpi-prp0001.

config I2C_HID_OF
tristate "HID over I2C transport layer Open Firmware driver"
# No "depends on OF" because this can also be used for manually
Expand Down
1 change: 1 addition & 0 deletions drivers/hid/i2c-hid/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ i2c-hid-objs = i2c-hid-core.o
i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o

obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o
obj-$(CONFIG_I2C_HID_ACPI_PRP0001) += i2c-hid-acpi-prp0001.o
obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o
obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o
obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o
104 changes: 104 additions & 0 deletions drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HID over I2C driver for PRP0001 devices missing hid-descr-addr
*
* Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3, use
* _HID "PRP0001" with _DSD compatible "hid-over-i2c" but lack "hid-descr-addr"
* from the _DSD. The HID descriptor address is provided only through an ACPI
* _DSM. The TPD0 node in the DSDT shows _DSM Function 1 returning 0x20.
*
* Copyright (C) 2026 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
*/

#include <linux/delay.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>

#include "i2c-hid.h"
#include "i2c-hid-acpi.h"

static int i2c_hid_acpi_prp0001_power_up(struct i2chid_ops *ops)
{
/* give the device time to power up */
msleep(500);
return 0;
}

static struct i2chid_ops i2c_hid_acpi_prp0001_ops = {
.power_up = i2c_hid_acpi_prp0001_power_up,
/*
* No .restore_sequence needed: the _DSM on these devices returns a
* constant (0x20) with no side effects, unlike some PNP0C50 _DSM
* implementations that switch the hardware between PS/2 and I2C modes.
*/
};

static int i2c_hid_acpi_prp0001_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct acpi_device *adev;
u16 hid_descriptor_address;
int ret;

/* If hid-descr-addr is present, let i2c-hid-of handle it */
if (device_property_present(dev, "hid-descr-addr"))
return -ENODEV;

adev = ACPI_COMPANION(dev);
if (!adev)
return -ENODEV;

ret = i2c_hid_acpi_get_descriptor(adev);
if (ret < 0)
return ret;
dev_warn(dev,
"hid-descr-addr device property NOT found, using ACPI _DSM fallback. Contact vendor for firmware update!\n");
hid_descriptor_address = ret;

/*
* No acpi_device_fix_up_power() needed: TPD0 has no _PS0, _PS3, _PSC
* or _PRx methods and follows I2C bus power.
*/
return i2c_hid_core_probe(client, &i2c_hid_acpi_prp0001_ops,
hid_descriptor_address, 0);
}

static const struct of_device_id i2c_hid_acpi_prp0001_of_match[] = {
{ .compatible = "hid-over-i2c" },
{},
};
MODULE_DEVICE_TABLE(of, i2c_hid_acpi_prp0001_of_match);

static const struct i2c_device_id i2c_hid_acpi_prp0001_id[] = {
{ .name = "hid-over-i2c" },
{ }
};
MODULE_DEVICE_TABLE(i2c, i2c_hid_acpi_prp0001_id);

static struct i2c_driver i2c_hid_acpi_prp0001_driver = {
.driver = {
.name = "i2c_hid_acpi_prp0001",
.pm = &i2c_hid_core_pm,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
/*
* of_match_ptr() makes this NULL when CONFIG_OF=n, but that's
* fine: the I2C id_table with "hid-over-i2c" handles matching
* via client->name (set by acpi_set_modalias() from the _DSD
* compatible property).
*/
.of_match_table = of_match_ptr(i2c_hid_acpi_prp0001_of_match),
},

.probe = i2c_hid_acpi_prp0001_probe,
.remove = i2c_hid_core_remove,
.shutdown = i2c_hid_core_shutdown,
.id_table = i2c_hid_acpi_prp0001_id,
};

module_i2c_driver(i2c_hid_acpi_prp0001_driver);

MODULE_DESCRIPTION("HID over I2C driver for PRP0001 devices missing hid-descr-addr");
MODULE_AUTHOR("谢致邦 (XIE Zhibang) <Yeking@Red54.com>");
MODULE_LICENSE("GPL");
52 changes: 14 additions & 38 deletions drivers/hid/i2c-hid/i2c-hid-acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/uuid.h>

#include "i2c-hid.h"
#include "i2c-hid-acpi.h"

struct i2c_hid_acpi {
struct i2chid_ops ops;
Expand All @@ -48,39 +48,11 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
{ }
};

/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
static guid_t i2c_hid_guid =
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);

static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
{
struct acpi_device *adev = ihid_acpi->adev;
acpi_handle handle = acpi_device_handle(adev);
union acpi_object *obj;
u16 hid_descriptor_address;

if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
return -ENODEV;

obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
ACPI_TYPE_INTEGER);
if (!obj) {
acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
return -ENODEV;
}

hid_descriptor_address = obj->integer.value;
ACPI_FREE(obj);

return hid_descriptor_address;
}

static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
{
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);

i2c_hid_acpi_get_descriptor(ihid_acpi);
i2c_hid_acpi_get_descriptor(ihid_acpi->adev);
}

static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
Expand All @@ -93,24 +65,28 @@ static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
static int i2c_hid_acpi_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct acpi_device *adev = ACPI_COMPANION(dev);
struct i2c_hid_acpi *ihid_acpi;
u16 hid_descriptor_address;
int ret;

ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
return -ENODEV;

ret = i2c_hid_acpi_get_descriptor(adev);
if (ret < 0)
return ret;
hid_descriptor_address = ret;

ihid_acpi = devm_kzalloc(dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (!ihid_acpi)
return -ENOMEM;

ihid_acpi->adev = ACPI_COMPANION(dev);
ihid_acpi->adev = adev;
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;

ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
if (ret < 0)
return ret;
hid_descriptor_address = ret;

acpi_device_fix_up_power(ihid_acpi->adev);
acpi_device_fix_up_power(adev);

return i2c_hid_core_probe(client, &ihid_acpi->ops,
hid_descriptor_address, 0);
Expand Down
32 changes: 32 additions & 0 deletions drivers/hid/i2c-hid/i2c-hid-acpi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-only */

#ifndef _I2C_HID_ACPI_H
#define _I2C_HID_ACPI_H

#include <linux/acpi.h>
#include <linux/uuid.h>

static inline int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
{
/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
static const guid_t i2c_hid_guid =
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);

acpi_handle handle = acpi_device_handle(adev);
union acpi_object *obj;
u16 addr;

obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid,
1, 1, NULL, ACPI_TYPE_INTEGER);
if (!obj) {
acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
return -ENODEV;
}

addr = obj->integer.value;
ACPI_FREE(obj);
return addr;
}

#endif