public inbox for linux-usb@vger.kernel.org
 help / color / mirror / Atom feed
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 = &reg,
+		},
+		{
+			.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


  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