* [PATCH 1/2] i2c: acpi: skip generic I2C device when vendor-specific sibling exists
2026-03-14 1:31 [PATCH 0/2] Add UCSI I2C transport driver for ITE885x USB-C controllers Edward Blair
@ 2026-03-14 1:31 ` Edward Blair
2026-03-16 13:12 ` Mika Westerberg
2026-03-16 14:07 ` Heikki Krogerus
2026-03-14 1:31 ` [PATCH 2/2] usb: typec: ucsi: add ITE885x I2C transport driver Edward Blair
1 sibling, 2 replies; 6+ messages in thread
From: Edward Blair @ 2026-03-14 1:31 UTC (permalink / raw)
To: linux-i2c, linux-usb
Cc: heikki.krogerus, gregkh, wsa+renesas, westeri, linux-acpi,
linux-kernel, Edward Blair
Some BIOS implementations (notably ASUS Z690/Z790/X670E motherboards)
declare both a generic UCSI device (MSFT8000) and a vendor-specific
device (e.g., ITE8853) as ACPI children of the same I2C controller,
both referencing the same I2C slave address.
During ACPI I2C enumeration, whichever device is walked first claims
the address, causing the second to fail with -EBUSY. When the generic
MSFT8000 device registers first, the vendor-specific driver cannot
bind, losing access to device-specific features like GPIO interrupt
resources that are only declared on the vendor-specific ACPI device.
Fix this by checking, before registering a known generic I2C device,
whether a sibling ACPI device exists at the same address on the same
adapter. If so, skip the generic device to let the vendor-specific
one register instead.
Signed-off-by: Edward Blair <edward.blair@gmail.com>
---
drivers/i2c/i2c-core-acpi.c | 88 +++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c
index 2cbd31f77..87582eac7 100644
--- a/drivers/i2c/i2c-core-acpi.c
+++ b/drivers/i2c/i2c-core-acpi.c
@@ -137,6 +137,17 @@ static const struct acpi_device_id i2c_acpi_ignored_device_ids[] = {
{}
};
+/*
+ * Generic I2C device IDs that may be duplicated by vendor-specific devices.
+ * When a vendor-specific sibling exists at the same address, the generic
+ * device is skipped to avoid -EBUSY address conflicts.
+ */
+static const struct acpi_device_id i2c_acpi_generic_device_ids[] = {
+ /* Microsoft UCSI - often paired with vendor-specific UCSI device */
+ { "MSFT8000" },
+ {}
+};
+
struct i2c_acpi_irq_context {
int irq;
bool wake_capable;
@@ -274,6 +285,76 @@ static int i2c_acpi_get_info(struct acpi_device *adev,
return 0;
}
+struct i2c_acpi_sibling_check {
+ struct acpi_device *self;
+ struct i2c_adapter *adapter;
+ unsigned short addr;
+ bool found;
+};
+
+static int i2c_acpi_check_sibling_addr(struct acpi_device *adev, void *data)
+{
+ struct i2c_acpi_sibling_check *check = data;
+ struct i2c_acpi_lookup lookup;
+ struct i2c_board_info info;
+
+ if (adev == check->self)
+ return 0;
+
+ /* Only yield to vendor-specific devices, not other generic ones */
+ if (!acpi_match_device_ids(adev, i2c_acpi_generic_device_ids))
+ return 0;
+
+ memset(&lookup, 0, sizeof(lookup));
+ lookup.info = &info;
+ lookup.index = -1;
+
+ if (i2c_acpi_do_lookup(adev, &lookup))
+ return 0;
+
+ if (!device_match_acpi_handle(&check->adapter->dev,
+ lookup.adapter_handle))
+ return 0;
+
+ if (info.addr == check->addr) {
+ check->found = true;
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether this generic ACPI device has a vendor-specific sibling at the
+ * same I2C address. Some BIOS implementations (e.g., ASUS Z690/Z790/X670E)
+ * declare both a generic UCSI device (MSFT8000) and a vendor-specific device
+ * (e.g., ITE8853) at the same address. Skip the generic one so the vendor
+ * driver can bind with proper interrupt and device-specific resources.
+ */
+static bool i2c_acpi_has_vendor_sibling(struct acpi_device *adev,
+ struct i2c_adapter *adapter,
+ struct i2c_board_info *info)
+{
+ struct acpi_device *parent;
+ struct i2c_acpi_sibling_check check;
+
+ if (acpi_match_device_ids(adev, i2c_acpi_generic_device_ids))
+ return false;
+
+ parent = acpi_dev_parent(adev);
+ if (!parent)
+ return false;
+
+ check.self = adev;
+ check.adapter = adapter;
+ check.addr = info->addr;
+ check.found = false;
+
+ acpi_dev_for_each_child(parent, i2c_acpi_check_sibling_addr, &check);
+
+ return check.found;
+}
+
static void i2c_acpi_register_device(struct i2c_adapter *adapter,
struct acpi_device *adev,
struct i2c_board_info *info)
@@ -302,6 +383,13 @@ static acpi_status i2c_acpi_add_device(acpi_handle handle, u32 level,
if (!adev || i2c_acpi_get_info(adev, &info, adapter, NULL))
return AE_OK;
+ if (i2c_acpi_has_vendor_sibling(adev, adapter, &info)) {
+ dev_info(&adapter->dev,
+ "skipping %s in favor of vendor-specific device at 0x%02x\n",
+ dev_name(&adev->dev), info.addr);
+ return AE_OK;
+ }
+
i2c_acpi_register_device(adapter, adev, &info);
return AE_OK;
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/2] usb: typec: ucsi: add ITE885x I2C transport driver
2026-03-14 1:31 [PATCH 0/2] Add UCSI I2C transport driver for ITE885x USB-C controllers Edward Blair
2026-03-14 1:31 ` [PATCH 1/2] i2c: acpi: skip generic I2C device when vendor-specific sibling exists Edward Blair
@ 2026-03-14 1:31 ` Edward Blair
2026-03-16 14:57 ` Heikki Krogerus
1 sibling, 1 reply; 6+ messages in thread
From: Edward Blair @ 2026-03-14 1:31 UTC (permalink / raw)
To: linux-i2c, linux-usb
Cc: heikki.krogerus, gregkh, wsa+renesas, westeri, linux-acpi,
linux-kernel, Edward Blair
Add a UCSI transport driver for ITE8853/ITE8800-ITE8805 USB Type-C
controllers, commonly found on ASUS Z690/Z790/X670E motherboards.
These controllers implement the UCSI protocol over I2C with
ITE-proprietary register offsets and dedicated interrupt registers:
- CCI at 0x84, MESSAGE_IN at 0x88, CONTROL at 0x98
- INT_STATUS at 0xBD, INT_ACK at 0xBC
- GPIO interrupt (level-triggered, active-low)
The ITE8853 does not accept PPM_RESET over I2C (the Windows driver
handles it internally), so the driver intercepts this command and
returns a synthetic reset-complete response.
Tested on ASUS ROG Strix Z790-E Gaming WiFi with ITE8853 at I2C
address 0x40 on the DesignWare I2C controller.
Signed-off-by: Edward Blair <edward.blair@gmail.com>
---
drivers/usb/typec/ucsi/Kconfig | 11 ++
drivers/usb/typec/ucsi/Makefile | 1 +
drivers/usb/typec/ucsi/ucsi_ite.c | 285 ++++++++++++++++++++++++++++++
3 files changed, 297 insertions(+)
create mode 100644 drivers/usb/typec/ucsi/ucsi_ite.c
diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
index 87dd992a4..455272b5a 100644
--- a/drivers/usb/typec/ucsi/Kconfig
+++ b/drivers/usb/typec/ucsi/Kconfig
@@ -104,4 +104,15 @@ config UCSI_HUAWEI_GAOKUN
To compile the driver as a module, choose M here: the module will be
called ucsi_huawei_gaokun.
+config UCSI_ITE
+ tristate "UCSI Interface Driver for ITE885x"
+ depends on I2C
+ help
+ This driver enables UCSI support on platforms that expose an ITE8853
+ or ITE8800-ITE8805 USB Type-C controller over I2C, commonly found
+ on ASUS Z690/Z790/X670E motherboards.
+
+ To compile the driver as a module, choose M here: the module will be
+ called ucsi_ite.
+
endif
diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
index c7e38bf01..9bc1d6bbb 100644
--- a/drivers/usb/typec/ucsi/Makefile
+++ b/drivers/usb/typec/ucsi/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o
obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
+obj-$(CONFIG_UCSI_ITE) += ucsi_ite.o
diff --git a/drivers/usb/typec/ucsi/ucsi_ite.c b/drivers/usb/typec/ucsi/ucsi_ite.c
new file mode 100644
index 000000000..400b844a1
--- /dev/null
+++ b/drivers/usb/typec/ucsi/ucsi_ite.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UCSI I2C transport driver for ITE885x USB-C controllers
+ *
+ * ITE8853/ITE8800-ITE8805 are UCSI-compliant USB-C controllers found on
+ * ASUS Z690/Z790/X670E motherboards. They communicate via I2C with
+ * ITE-proprietary register offsets and interrupt registers.
+ *
+ * Note: Some BIOS implementations declare both MSFT8000 (generic UCSI) and
+ * ITE8853 (vendor-specific) ACPI devices at the same I2C address. The i2c
+ * core skips the generic device when a vendor-specific sibling exists,
+ * allowing this driver to bind to the ITE8853 client with proper IRQ.
+ */
+
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#include "ucsi.h"
+
+/* ITE-specific I2C register offsets */
+#define ITE_REG_CCI 0x84
+#define ITE_REG_MESSAGE_IN 0x88
+#define ITE_REG_CONTROL 0x98
+#define ITE_REG_INT_ACK 0xbc
+#define ITE_REG_INT_STATUS 0xbd
+
+/* INT_STATUS register bits */
+#define ITE_INT_VENDOR_ALERT BIT(0)
+#define ITE_INT_CCI BIT(1)
+
+/* INT_ACK register values */
+#define ITE_ACK_VENDOR 0x01
+#define ITE_ACK_CCI 0x02
+
+struct ucsi_ite {
+ struct device *dev;
+ struct i2c_client *client;
+ struct ucsi *ucsi;
+};
+
+static int ucsi_ite_read(struct ucsi_ite *ite, u8 reg, void *val, size_t len)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = ite->client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = ®,
+ },
+ {
+ .addr = ite->client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = val,
+ },
+ };
+ int ret;
+
+ ret = i2c_transfer(ite->client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret != ARRAY_SIZE(msgs)) {
+ dev_err(ite->dev, "i2c read 0x%02x failed: %d\n", reg, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+
+static int ucsi_ite_write(struct ucsi_ite *ite, u8 reg, const void *val,
+ size_t len)
+{
+ struct i2c_msg msg = {
+ .addr = ite->client->addr,
+ .flags = 0,
+ };
+ u8 *buf;
+ int ret;
+
+ buf = kmalloc(len + 1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = reg;
+ memcpy(&buf[1], val, len);
+ msg.len = len + 1;
+ msg.buf = buf;
+
+ ret = i2c_transfer(ite->client->adapter, &msg, 1);
+ kfree(buf);
+ if (ret != 1) {
+ dev_err(ite->dev, "i2c write 0x%02x failed: %d\n", reg, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+
+static int ucsi_ite_read_version(struct ucsi *ucsi, u16 *version)
+{
+ /*
+ * The ITE8853 does not expose a UCSI VERSION register over I2C.
+ * The Windows driver never reads it. Report UCSI 1.0.
+ */
+ *version = UCSI_VERSION_1_0;
+ return 0;
+}
+
+static int ucsi_ite_read_cci(struct ucsi *ucsi, u32 *cci)
+{
+ struct ucsi_ite *ite = ucsi_get_drvdata(ucsi);
+
+ return ucsi_ite_read(ite, ITE_REG_CCI, cci, sizeof(*cci));
+}
+
+static int ucsi_ite_read_message_in(struct ucsi *ucsi, void *val, size_t len)
+{
+ struct ucsi_ite *ite = ucsi_get_drvdata(ucsi);
+
+ return ucsi_ite_read(ite, ITE_REG_MESSAGE_IN, val, len);
+}
+
+static int ucsi_ite_async_control(struct ucsi *ucsi, u64 command)
+{
+ struct ucsi_ite *ite = ucsi_get_drvdata(ucsi);
+
+ return ucsi_ite_write(ite, ITE_REG_CONTROL, &command, sizeof(command));
+}
+
+static int ucsi_ite_sync_control(struct ucsi *ucsi, u64 command, u32 *cci,
+ void *data, size_t size)
+{
+ /*
+ * The ITE8853 handles PPM_RESET internally and does not accept it
+ * over I2C — the Windows driver never sends it. Fake a successful
+ * reset so the UCSI core can proceed with initialization.
+ */
+ if ((command & 0xff) == UCSI_PPM_RESET) {
+ if (cci)
+ *cci = UCSI_CCI_RESET_COMPLETE;
+ return 0;
+ }
+
+ return ucsi_sync_control_common(ucsi, command, cci, data, size);
+}
+
+static const struct ucsi_operations ucsi_ite_ops = {
+ .read_version = ucsi_ite_read_version,
+ .read_cci = ucsi_ite_read_cci,
+ .poll_cci = ucsi_ite_read_cci,
+ .read_message_in = ucsi_ite_read_message_in,
+ .sync_control = ucsi_ite_sync_control,
+ .async_control = ucsi_ite_async_control,
+};
+
+static irqreturn_t ucsi_ite_irq(int irq, void *data)
+{
+ struct ucsi_ite *ite = data;
+ u8 status;
+ u32 cci;
+ u8 ack;
+ int ret;
+
+ ret = ucsi_ite_read(ite, ITE_REG_INT_STATUS, &status, sizeof(status));
+ if (ret)
+ return IRQ_NONE;
+
+ if (!(status & (ITE_INT_CCI | ITE_INT_VENDOR_ALERT)))
+ return IRQ_NONE;
+
+ if (status & ITE_INT_CCI) {
+ ack = ITE_ACK_CCI;
+ ucsi_ite_write(ite, ITE_REG_INT_ACK, &ack, sizeof(ack));
+
+ ret = ucsi_ite_read(ite, ITE_REG_CCI, &cci, sizeof(cci));
+ if (!ret)
+ ucsi_notify_common(ite->ucsi, cci);
+ }
+
+ if (status & ITE_INT_VENDOR_ALERT) {
+ ack = ITE_ACK_VENDOR;
+ ucsi_ite_write(ite, ITE_REG_INT_ACK, &ack, sizeof(ack));
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ucsi_ite_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ucsi_ite *ite;
+ int ret;
+
+ ite = devm_kzalloc(dev, sizeof(*ite), GFP_KERNEL);
+ if (!ite)
+ return -ENOMEM;
+
+ ite->dev = dev;
+ ite->client = client;
+ i2c_set_clientdata(client, ite);
+
+ ite->ucsi = ucsi_create(dev, &ucsi_ite_ops);
+ if (IS_ERR(ite->ucsi))
+ return PTR_ERR(ite->ucsi);
+
+ ucsi_set_drvdata(ite->ucsi, ite);
+
+ ret = request_threaded_irq(client->irq, NULL, ucsi_ite_irq,
+ IRQF_ONESHOT, dev_name(dev), ite);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ: %d\n", ret);
+ goto err_destroy;
+ }
+
+ ret = ucsi_register(ite->ucsi);
+ if (ret) {
+ dev_err(dev, "failed to register UCSI: %d\n", ret);
+ goto err_free_irq;
+ }
+
+ return 0;
+
+err_free_irq:
+ free_irq(client->irq, ite);
+err_destroy:
+ ucsi_destroy(ite->ucsi);
+ return ret;
+}
+
+static void ucsi_ite_remove(struct i2c_client *client)
+{
+ struct ucsi_ite *ite = i2c_get_clientdata(client);
+
+ ucsi_unregister(ite->ucsi);
+ free_irq(client->irq, ite);
+ ucsi_destroy(ite->ucsi);
+}
+
+static int ucsi_ite_suspend(struct device *dev)
+{
+ struct ucsi_ite *ite = dev_get_drvdata(dev);
+
+ disable_irq(ite->client->irq);
+
+ return 0;
+}
+
+static int ucsi_ite_resume(struct device *dev)
+{
+ struct ucsi_ite *ite = dev_get_drvdata(dev);
+
+ enable_irq(ite->client->irq);
+
+ return ucsi_resume(ite->ucsi);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_ite_pm, ucsi_ite_suspend,
+ ucsi_ite_resume);
+
+static const struct acpi_device_id ucsi_ite_acpi_ids[] = {
+ { "ITE8853" },
+ { "ITE8800" },
+ { "ITE8801" },
+ { "ITE8802" },
+ { "ITE8803" },
+ { "ITE8804" },
+ { "ITE8805" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, ucsi_ite_acpi_ids);
+
+static struct i2c_driver ucsi_ite_driver = {
+ .driver = {
+ .name = "ucsi_ite",
+ .acpi_match_table = ucsi_ite_acpi_ids,
+ .pm = pm_sleep_ptr(&ucsi_ite_pm),
+ },
+ .probe = ucsi_ite_probe,
+ .remove = ucsi_ite_remove,
+};
+module_i2c_driver(ucsi_ite_driver);
+
+MODULE_AUTHOR("Edward Blair <edward.blair@gmail.com>");
+MODULE_DESCRIPTION("UCSI I2C transport driver for ITE885x USB-C controllers");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread