From: Edward Blair <edward.blair@gmail.com>
To: linux-i2c@vger.kernel.org, linux-usb@vger.kernel.org
Cc: heikki.krogerus@linux.intel.com, gregkh@linuxfoundation.org,
wsa+renesas@sang-engineering.com, westeri@kernel.org,
linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org,
Edward Blair <edward.blair@gmail.com>
Subject: [PATCH 2/2] usb: typec: ucsi: add ITE885x I2C transport driver
Date: Sat, 14 Mar 2026 01:31:56 +0000 [thread overview]
Message-ID: <20260314013157.7181-3-edward.blair@gmail.com> (raw)
In-Reply-To: <20260314013157.7181-1-edward.blair@gmail.com>
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
next prev parent reply other threads:[~2026-03-14 1:32 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
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-16 13:12 ` Mika Westerberg
2026-03-16 14:32 ` Edward Blair
2026-03-16 14:45 ` Mika Westerberg
2026-03-16 15:04 ` Edward Blair
2026-03-16 14:07 ` Heikki Krogerus
2026-03-14 1:31 ` Edward Blair [this message]
2026-03-16 14:57 ` [PATCH 2/2] usb: typec: ucsi: add ITE885x I2C transport driver Heikki Krogerus
2026-03-16 15:01 ` Edward Blair
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260314013157.7181-3-edward.blair@gmail.com \
--to=edward.blair@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=heikki.krogerus@linux.intel.com \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=westeri@kernel.org \
--cc=wsa+renesas@sang-engineering.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox