* [PATCH 2/4] usb: typec: add CIX Type-C PD controller driver
2026-11-07 1:10 [PATCH 0/4] usb: typec: add CIX Type-C PD controller driver Gary Yang
2026-11-07 1:10 ` [PATCH 1/4] dt-bindings: usb: add Realtek RTS5453H Type-C port controller Gary Yang
@ 2026-11-07 1:10 ` Gary Yang
2026-11-07 1:10 ` [PATCH 3/4] arm64: dts: cix: fix I2C bus address and size cells on Sky1 Gary Yang
2026-11-07 1:10 ` [PATCH 4/4] arm64: dts: cix: add RTS5453 PD controllers to Orion O6 Gary Yang
3 siblings, 0 replies; 5+ messages in thread
From: Gary Yang @ 2026-11-07 1:10 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Fugang Duan
Cc: linux-usb, devicetree, linux-arm-kernel, linux-kernel,
cix-kernel-upstream, gary.yang
Add a driver for the CIX Type-C PD controller, which is connected over
I2C and reports power, data and plug orientation changes through a
level-triggered interrupt. The Realtek RTS5453H is the first supported
device.
The controller runs the Type-C and PD state machines in firmware and
exposes the current port status over I2C. The driver translates that
into Type-C class notifications, driving the orientation switch and
mode mux registered by the platform PHY for the DisplayPort alternate
mode. A USB role switch is registered so that the USB controller can
follow the negotiated data role.
Several controller instances can share a single interrupt line, so each
instance registers a handler on it with IRQF_SHARED and only accesses
its own registers.
The driver is named after the CIX platform integration rather than the
RTS5453H so that further register-compatible PD controllers can be
supported without renaming it.
Signed-off-by: Gary Yang <gary.yang@cixtech.com>
---
MAINTAINERS | 7 +
drivers/usb/typec/Kconfig | 11 +
drivers/usb/typec/Makefile | 1 +
drivers/usb/typec/cix_pd.c | 777 +++++++++++++++++++++++++++++++++++++
4 files changed, 796 insertions(+)
create mode 100644 drivers/usb/typec/cix_pd.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065..bd99313bf91d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6522,6 +6522,13 @@ M: Satish Kharat <satishkh@cisco.com>
S: Supported
F: drivers/infiniband/hw/usnic/
+CIX USB TYPE-C PD CONTROLLER
+M: Gary Yang <gary.yang@cixtech.com>
+L: linux-usb@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/usb/realtek,rts5453h.yaml
+F: drivers/usb/typec/cix_pd.c
+
CLANG CONTEXT ANALYSIS
M: Marco Elver <elver@google.com>
R: Bart Van Assche <bvanassche@acm.org>
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 2f80c2792dbd..b81a0d946d00 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -110,6 +110,17 @@ config TYPEC_WUSB3801
If you choose to build this driver as a dynamically linked module, the
module will be called wusb3801.ko.
+config TYPEC_CIX_PD
+ tristate "CIX Type-C PD controller driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y or M here if your system has a CIX Type-C PD controller.
+ This driver currently supports the Realtek RTS5453H controller.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called cix_pd.ko.
+
source "drivers/usb/typec/mux/Kconfig"
source "drivers/usb/typec/altmodes/Kconfig"
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 8a6a1c663eb6..bb3f3139fd7e 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -11,4 +11,5 @@ obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o
obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
obj-$(CONFIG_TYPEC_RT1719) += rt1719.o
obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o
+obj-$(CONFIG_TYPEC_CIX_PD) += cix_pd.o
obj-$(CONFIG_TYPEC) += mux/
diff --git a/drivers/usb/typec/cix_pd.c b/drivers/usb/typec/cix_pd.c
new file mode 100644
index 000000000000..b65e1a4760b7
--- /dev/null
+++ b/drivers/usb/typec/cix_pd.c
@@ -0,0 +1,777 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 Cix Technology Group Co., Ltd.
+ *
+ * Driver for the CIX Type-C PD controller.
+ */
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_tbt.h>
+#include <linux/usb/role.h>
+
+#define RTS_DATA_CONTROL 0x50
+#define I2C_INT_ACK_BIT BIT(2)
+#define HPD_IRQ_ACK BIT(5)
+#define RTS_DATA_STATUS 0x5F
+/* byte 1 */
+#define CONNECTION_ORIENTATION BIT(1)
+#define USB2_CONNECTION BIT(4)
+#define USB3_2_CONNECTION BIT(5)
+#define USB_DATA_ROLE BIT(7)
+/* byte 2 */
+#define DP_CONNECTION BIT(0)
+#define DP_SOURCE_SINK BIT(1)
+#define DEBUG_ACCESSORY_ATTACHED BIT(4)
+#define HPD_IRQ BIT(6)
+#define HPD_STATE BIT(7)
+/* byte 3 */
+#define POWER_SOURCE BIT(0)
+#define POWER_SINK BIT(1)
+#define POWER_MASK 3
+
+#define DATA_STATUS_COUNT 6
+
+struct cix_pd {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock; /* device lock */
+ struct mutex irq_lock; /* protects IRQ state transitions */
+ int id;
+ int irq;
+ u8 data_status[DATA_STATUS_COUNT];
+ struct typec_displayport_data dp_data;
+ struct typec_mux_state state;
+ struct typec_capability caps;
+ struct typec_mux *mux;
+ struct usb_role_switch *role_sw;
+ struct typec_altmode *port_altmode;
+ struct typec_altmode *partner_altmode;
+ struct typec_port *port;
+ enum usb_role role;
+ unsigned long mode;
+ u32 hpd_status;
+ struct typec_partner *partner;
+ struct typec_partner_desc desc;
+ bool pd_int_disabled;
+ bool irq_requested;
+ bool irq_disabled;
+ bool suspended;
+ bool wake_enabled;
+};
+
+static int cix_pd_block_read(struct cix_pd *rts, u8 reg, void *val, size_t len)
+{
+ return regmap_bulk_read(rts->regmap, reg, val, len);
+}
+
+static int cix_pd_block_write(struct cix_pd *rts, u8 reg,
+ const void *val, size_t len)
+{
+ return regmap_bulk_write(rts->regmap, reg, val, len);
+}
+
+static const struct regmap_config cix_pd_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x7F,
+};
+
+static const char *const dp_only_mode = "Alt Mode:DP";
+static const char *const dp_usb_mode = "Alt Mode:DP + USB";
+static const char *const usb_mode = "Alt Mode:USB";
+
+static const char *const power_source = "power source";
+static const char *const power_sink = "power sink";
+
+static const char *const orientation_normal = "orientation normal";
+static const char *const orientation_reverse = "orientation reverse";
+
+static const char *const data_host = "usb host";
+static const char *const data_device = "usb device";
+
+static const char *cix_pd_pwr_role_str(const struct cix_pd *rts)
+{
+ switch (rts->data_status[3] & POWER_MASK) {
+ case POWER_SOURCE:
+ return power_source;
+ case POWER_SINK:
+ return power_sink;
+ default:
+ return "unknown";
+ }
+}
+
+static const struct typec_altmode_desc cix_pd_dp_altmode = {
+ .svid = USB_TYPEC_DP_SID,
+ .mode = USB_TYPEC_DP_MODE,
+};
+
+static int cix_pd_typec_port_update(struct cix_pd *typec)
+{
+ struct typec_displayport_data *dp_data = &typec->dp_data;
+ enum typec_orientation orientation;
+ enum usb_role role;
+ bool connected;
+ int ret = 0;
+
+ if (!(typec->data_status[1] & USB3_2_CONNECTION) &&
+ !(typec->data_status[2] & DP_CONNECTION))
+ orientation = TYPEC_ORIENTATION_NONE;
+ else if (typec->data_status[1] & CONNECTION_ORIENTATION)
+ orientation = TYPEC_ORIENTATION_REVERSE;
+ else
+ orientation = TYPEC_ORIENTATION_NORMAL;
+
+ ret = typec_set_orientation(typec->port, orientation);
+ if (ret)
+ return ret;
+
+ if (!(typec->data_status[1] & USB3_2_CONNECTION) &&
+ !(typec->data_status[1] & USB2_CONNECTION))
+ role = USB_ROLE_NONE;
+ else if (typec->data_status[1] & USB_DATA_ROLE)
+ role = USB_ROLE_DEVICE;
+ else
+ role = USB_ROLE_HOST;
+
+ if (typec->data_status[2] & DEBUG_ACCESSORY_ATTACHED)
+ dev_err(typec->dev,
+ "debug accessory mode not supported, try another usb cable\n");
+
+ /*
+ * Update the partner before building the mux state so that the DP
+ * alternate mode handed to the mux is always a registered one.
+ * Debug accessories are treated as not connected, since they are
+ * not supported.
+ */
+ connected = !(typec->data_status[2] & DEBUG_ACCESSORY_ATTACHED) &&
+ ((typec->data_status[1] & (USB2_CONNECTION | USB3_2_CONNECTION)) ||
+ (typec->data_status[2] & DP_CONNECTION));
+ if (!connected) {
+ if (typec->partner) {
+ dev_info(typec->dev, "typec port(%d): unregister partner\n",
+ typec->id);
+ typec_unregister_altmode(typec->partner_altmode);
+ typec->partner_altmode = NULL;
+ typec_unregister_partner(typec->partner);
+ typec->partner = NULL;
+ }
+ } else {
+ if (!typec->partner) {
+ dev_info(typec->dev, "typec port(%d): register partner\n",
+ typec->id);
+ typec->partner = typec_register_partner(typec->port,
+ &typec->desc);
+ if (IS_ERR(typec->partner)) {
+ dev_err(typec->dev,
+ "typec port(%d): failed to register partner: %ld\n",
+ typec->id, PTR_ERR(typec->partner));
+ typec->partner = NULL;
+ }
+ }
+
+ if (typec->partner && !typec->partner_altmode) {
+ typec->partner_altmode =
+ typec_partner_register_altmode(typec->partner,
+ &cix_pd_dp_altmode);
+ if (IS_ERR(typec->partner_altmode)) {
+ dev_err(typec->dev,
+ "typec port(%d): failed to register DP altmode: %ld\n",
+ typec->id,
+ PTR_ERR(typec->partner_altmode));
+ typec->partner_altmode = NULL;
+ }
+ }
+ }
+
+ if ((typec->data_status[1] & USB3_2_CONNECTION) &&
+ (typec->data_status[2] & DP_CONNECTION)) {
+ /* usb + dp */
+ dp_data->status = DP_STATUS_ENABLED;
+ if (typec->data_status[2] & HPD_STATE)
+ dp_data->status |= DP_STATUS_HPD_STATE;
+ if (typec->data_status[2] & HPD_IRQ)
+ dp_data->status |= DP_STATUS_IRQ_HPD;
+
+ typec->state.data = dp_data;
+ typec->state.mode = TYPEC_DP_STATE_D;
+ typec->state.alt = typec->partner_altmode;
+ } else if (typec->data_status[2] & DP_CONNECTION) {
+ /* dp only */
+ dp_data->status = DP_STATUS_ENABLED;
+ if (typec->data_status[2] & HPD_STATE)
+ dp_data->status |= DP_STATUS_HPD_STATE;
+ if (typec->data_status[2] & HPD_IRQ)
+ dp_data->status |= DP_STATUS_IRQ_HPD;
+ typec->state.data = dp_data;
+ typec->state.mode = TYPEC_DP_STATE_E;
+ typec->state.alt = typec->partner_altmode;
+ } else if (typec->data_status[1] & USB3_2_CONNECTION) {
+ /* usb only */
+ dp_data->status = 0;
+ typec->state.mode = TYPEC_STATE_USB;
+ typec->state.alt = NULL;
+ typec->state.data = NULL;
+ } else {
+ /* disconnect */
+ dp_data->status = 0;
+ typec->state.mode = TYPEC_STATE_SAFE;
+ typec->state.alt = NULL;
+ typec->state.data = NULL;
+ }
+
+ ret = typec_mux_set(typec->mux, &typec->state);
+ if (ret)
+ return ret;
+
+ if ((typec->data_status[3] & POWER_MASK) == POWER_SOURCE) {
+ typec_set_pwr_role(typec->port, TYPEC_SOURCE);
+ } else if ((typec->data_status[3] & POWER_MASK) == POWER_SINK) {
+ typec_set_pwr_role(typec->port, TYPEC_SINK);
+ } else {
+ dev_dbg(typec->dev, "typec port(%d): unknown power state %d\n",
+ typec->id, typec->data_status[3]);
+ }
+
+ if (typec->role != role) {
+ typec->role = role;
+ ret = usb_role_switch_set_role(typec->role_sw, role);
+ if (role == USB_ROLE_DEVICE)
+ typec_set_data_role(typec->port, TYPEC_DEVICE);
+ else if (role == USB_ROLE_HOST)
+ typec_set_data_role(typec->port, TYPEC_HOST);
+ }
+
+ if (typec->mode == typec->state.mode &&
+ typec->hpd_status == dp_data->status)
+ return ret;
+
+ if (typec->state.mode == TYPEC_DP_STATE_D)
+ dev_info(typec->dev,
+ "typec port(%d): alt mode(%s) orientation(%s), usb_data_role(%s), power role(%s), hpd_state(%d)\n",
+ typec->id, dp_usb_mode,
+ orientation == TYPEC_ORIENTATION_REVERSE ?
+ orientation_reverse : orientation_normal,
+ role == USB_ROLE_DEVICE ? data_device : data_host,
+ cix_pd_pwr_role_str(typec),
+ (int)(dp_data->status & DP_STATUS_HPD_STATE));
+ else if (typec->state.mode == TYPEC_DP_STATE_E)
+ dev_info(typec->dev,
+ "typec port(%d): alt mode(%s) orientation(%s), power role(%s), hpd_state(%d)\n",
+ typec->id, dp_only_mode,
+ orientation == TYPEC_ORIENTATION_REVERSE ?
+ orientation_reverse : orientation_normal,
+ cix_pd_pwr_role_str(typec),
+ (int)(dp_data->status & DP_STATUS_HPD_STATE));
+ else if (typec->state.mode == TYPEC_STATE_USB)
+ dev_info(typec->dev,
+ "typec port(%d): alt mode(%s) orientation(%s), power role(%s), usb_data_role(%s)\n",
+ typec->id, usb_mode,
+ orientation == TYPEC_ORIENTATION_REVERSE ?
+ orientation_reverse : orientation_normal,
+ cix_pd_pwr_role_str(typec),
+ role == USB_ROLE_DEVICE ? data_device : data_host);
+ else if (typec->state.mode == TYPEC_STATE_SAFE)
+ dev_info(typec->dev, "typec port(%d): disconnect state\n",
+ typec->id);
+
+ typec->mode = typec->state.mode;
+ typec->hpd_status = dp_data->status;
+
+ return ret;
+}
+
+static void cix_pd_typec_init_state(struct cix_pd *typec)
+{
+ typec->state.alt = NULL;
+ typec->state.mode = TYPEC_STATE_USB;
+ typec->state.data = NULL;
+ typec->partner = NULL;
+ typec->desc.accessory = TYPEC_ACCESSORY_NONE;
+ /*
+ * The data status registers only report the source/sink state, not
+ * whether USB PD is being used, so the partner is not advertised as
+ * PD capable.
+ */
+ typec->desc.usb_pd = 0;
+}
+
+static int cix_pd_event_handler(struct cix_pd *rts, u8 clear_irq)
+{
+ int ret, err = 0;
+ u8 data_control[3] = {0};
+
+ mutex_lock(&rts->lock);
+ if (clear_irq) {
+ data_control[0] = 2;
+ data_control[1] = I2C_INT_ACK_BIT;
+ data_control[2] = 0;
+
+ ret = cix_pd_block_write(rts, RTS_DATA_CONTROL, data_control, 3);
+ if (ret)
+ goto i2c_err;
+ }
+
+ ret = cix_pd_block_read(rts, RTS_DATA_STATUS, rts->data_status, 6);
+ if (ret)
+ goto i2c_err;
+
+ dev_dbg(rts->dev,
+ "typec port(%d): data status = 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n",
+ rts->id, rts->data_status[0], rts->data_status[1],
+ rts->data_status[2], rts->data_status[3], rts->data_status[4],
+ rts->data_status[5]);
+
+ err = cix_pd_typec_port_update(rts);
+ if (err)
+ dev_err(rts->dev, "typec port(%d): failed to update port: %d\n",
+ rts->id, err);
+
+ if (rts->data_status[2] & HPD_IRQ) {
+ data_control[0] = 2;
+ data_control[1] = 0;
+ data_control[2] = HPD_IRQ_ACK;
+ ret = cix_pd_block_write(rts, RTS_DATA_CONTROL, data_control, 3);
+ if (ret)
+ goto i2c_err;
+ }
+
+ mutex_unlock(&rts->lock);
+ return err;
+
+i2c_err:
+ /*
+ * Report the controller as unresponsive; the IRQ handler turns this
+ * into IRQ_NONE so a stuck line is throttled by the IRQ core.
+ */
+ dev_err(rts->dev, "typec port(%d): i2c transfer error = %d\n",
+ rts->id, ret);
+ mutex_unlock(&rts->lock);
+ return -ESHUTDOWN;
+}
+
+static int cix_pd_typec_parse_port_props(struct typec_capability *cap,
+ struct fwnode_handle *fwnode,
+ struct device *dev)
+{
+ const char *buf;
+ int ret;
+
+ memset(cap, 0, sizeof(*cap));
+ ret = fwnode_property_read_string(fwnode, "power-role", &buf);
+ if (ret) {
+ dev_err(dev, "power-role not found: %d\n", ret);
+ return ret;
+ }
+ ret = typec_find_port_power_role(buf);
+ if (ret < 0)
+ return ret;
+ cap->type = ret;
+ ret = fwnode_property_read_string(fwnode, "data-role", &buf);
+ if (ret) {
+ dev_err(dev, "data-role not found: %d\n", ret);
+ return ret;
+ }
+ ret = typec_find_port_data_role(buf);
+ if (ret < 0)
+ return ret;
+ cap->data = ret;
+ ret = fwnode_property_read_string(fwnode, "try-power-role", &buf);
+ if (ret) {
+ dev_err(dev, "try-power-role not found: %d\n", ret);
+ return ret;
+ }
+ ret = typec_find_power_role(buf);
+ if (ret < 0)
+ return ret;
+ cap->prefer_role = ret;
+ cap->fwnode = fwnode_handle_get(fwnode);
+ cap->orientation_aware = 1;
+ return 0;
+}
+
+static int cix_pd_typec_get_handles(struct cix_pd *typec,
+ struct fwnode_handle *fwnode,
+ struct device *dev)
+{
+ typec->mux = fwnode_typec_mux_get(fwnode);
+ if (IS_ERR(typec->mux))
+ return dev_err_probe(dev, PTR_ERR(typec->mux),
+ "failed to get mux\n");
+
+ typec->role_sw = fwnode_usb_role_switch_get(fwnode);
+ if (IS_ERR(typec->role_sw)) {
+ typec_mux_put(typec->mux);
+ typec->mux = NULL;
+ return dev_err_probe(dev, PTR_ERR(typec->role_sw),
+ "failed to get USB role switch\n");
+ }
+
+ if (!typec->mux || !typec->role_sw)
+ dev_dbg(dev, "mux or role switch not found\n");
+
+ return 0;
+}
+
+static void cix_pd_unregister_ports(struct cix_pd *typec)
+{
+ if (!IS_ERR_OR_NULL(typec->port)) {
+ typec->state.alt = NULL;
+ typec->state.mode = TYPEC_STATE_USB;
+ typec->state.data = NULL;
+ usb_role_switch_set_role(typec->role_sw, USB_ROLE_NONE);
+ typec_set_orientation(typec->port, TYPEC_ORIENTATION_NONE);
+ typec_mux_set(typec->mux, &typec->state);
+
+ typec_unregister_altmode(typec->partner_altmode);
+ typec->partner_altmode = NULL;
+ typec_unregister_partner(typec->partner);
+ typec->partner = NULL;
+ typec_unregister_altmode(typec->port_altmode);
+ typec->port_altmode = NULL;
+
+ typec_unregister_port(typec->port);
+ typec->port = NULL;
+ }
+
+ usb_role_switch_put(typec->role_sw);
+ typec->role_sw = NULL;
+ typec_mux_put(typec->mux);
+ typec->mux = NULL;
+ fwnode_handle_put(typec->caps.fwnode);
+ typec->caps.fwnode = NULL;
+}
+
+static irqreturn_t cix_pd_irq_handle(int irq, void *data)
+{
+ struct cix_pd *rts = data;
+
+ if (cix_pd_event_handler(rts, 1) == -ESHUTDOWN)
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static int cix_pd_init_ports(struct cix_pd *typec)
+{
+ struct device *dev = typec->dev;
+ struct typec_altmode_desc desc = cix_pd_dp_altmode;
+ int ret, nports;
+
+ nports = device_get_child_node_count(dev);
+ if (nports == 0) {
+ dev_err(dev, "No port entries found.\n");
+ return -ENODEV;
+ }
+
+ if (nports > 1) {
+ dev_err(dev, "More ports listed than can be supported.\n");
+ return -EINVAL;
+ }
+
+ device_for_each_child_node_scoped(dev, fwnode) {
+ ret = fwnode_property_read_u32(fwnode, "reg", &typec->id);
+ if (ret)
+ goto unregister_ports;
+
+ ret = cix_pd_typec_parse_port_props(&typec->caps, fwnode,
+ dev);
+ if (ret)
+ goto unregister_ports;
+
+ typec->port = typec_register_port(dev, &typec->caps);
+ if (IS_ERR(typec->port)) {
+ ret = PTR_ERR(typec->port);
+ typec->port = NULL;
+ dev_err(dev, "Failed to register port: %d\n", ret);
+ goto unregister_ports;
+ }
+
+ desc.roles = typec->caps.data;
+ typec->port_altmode = typec_port_register_altmode(typec->port,
+ &desc);
+ if (IS_ERR(typec->port_altmode)) {
+ ret = PTR_ERR(typec->port_altmode);
+ typec->port_altmode = NULL;
+ dev_err(dev, "Failed to register DP altmode: %d\n", ret);
+ goto unregister_ports;
+ }
+
+ ret = cix_pd_typec_get_handles(typec, fwnode, dev);
+ if (ret)
+ goto unregister_ports;
+ }
+
+ return 0;
+
+unregister_ports:
+ cix_pd_unregister_ports(typec);
+ return ret;
+}
+
+/*
+ * The IRQ is disabled when either the user asked for it or the device is
+ * suspended. Keep the disable/enable calls balanced and idempotent.
+ */
+static void cix_pd_sync_irq(struct cix_pd *rts)
+{
+ bool disable = rts->pd_int_disabled || rts->suspended;
+
+ if (!rts->irq_requested || rts->irq_disabled == disable)
+ return;
+
+ rts->irq_disabled = disable;
+ if (disable)
+ disable_irq(rts->irq);
+ else
+ enable_irq(rts->irq);
+}
+
+static ssize_t disable_pd_interrupt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cix_pd *rts = i2c_get_clientdata(client);
+ const char *p;
+
+ if (rts->pd_int_disabled)
+ p = "disabled";
+ else
+ p = "enabled";
+
+ return sysfs_emit(buf, "%s\n", p);
+}
+
+static ssize_t disable_pd_interrupt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cix_pd *rts = i2c_get_clientdata(client);
+ bool value;
+ int ret;
+
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+
+ mutex_lock(&rts->irq_lock);
+ rts->pd_int_disabled = value;
+ cix_pd_sync_irq(rts);
+ mutex_unlock(&rts->irq_lock);
+
+ dev_alert(dev, "Only for USB Compliance test, PD int is %s\n",
+ value ? "disabled" : "enabled");
+
+ return count;
+}
+
+DEVICE_ATTR_RW(disable_pd_interrupt);
+
+static int cix_pd_probe(struct i2c_client *client)
+{
+ struct cix_pd *rts;
+ int ret;
+
+ dev_dbg(&client->dev, "IRQ %d supplied\n", client->irq);
+ rts = devm_kzalloc(&client->dev, sizeof(*rts), GFP_KERNEL);
+ if (!rts)
+ return -ENOMEM;
+
+ rts->dev = &client->dev;
+ rts->irq = client->irq;
+ mutex_init(&rts->lock);
+ mutex_init(&rts->irq_lock);
+ cix_pd_typec_init_state(rts);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return dev_err_probe(&client->dev, -ENODEV,
+ "i2c check functionality failed\n");
+
+ rts->regmap = devm_regmap_init_i2c(client, &cix_pd_regmap_config);
+ if (IS_ERR(rts->regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(rts->regmap),
+ "failed to init regmap\n");
+
+ i2c_set_clientdata(client, rts);
+
+ ret = cix_pd_init_ports(rts);
+ if (ret)
+ return ret;
+
+ ret = cix_pd_event_handler(rts, 0);
+ if (ret) {
+ dev_err(&client->dev, "failed to read initial status: %d\n", ret);
+ if (ret == -ESHUTDOWN)
+ ret = -EIO;
+ goto err_unregister_ports;
+ }
+
+ if (client->irq > 0) {
+ irq_set_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
+ ret = request_threaded_irq(client->irq, NULL, cix_pd_irq_handle,
+ IRQF_SHARED | IRQF_ONESHOT,
+ dev_name(&client->dev), rts);
+ if (ret) {
+ dev_err(&client->dev, "failed to request irq: %d\n", ret);
+ goto err_unregister_ports;
+ }
+ rts->irq_requested = true;
+ }
+
+ ret = device_create_file(&client->dev, &dev_attr_disable_pd_interrupt);
+ if (ret)
+ dev_warn(&client->dev,
+ "Failed to create sysfs attribute, ret = %d\n", ret);
+
+ device_set_wakeup_capable(&client->dev, true);
+
+ return 0;
+
+err_unregister_ports:
+ cix_pd_unregister_ports(rts);
+ return ret;
+}
+
+static void cix_pd_remove(struct i2c_client *client)
+{
+ struct cix_pd *rts = i2c_get_clientdata(client);
+
+ device_remove_file(&client->dev, &dev_attr_disable_pd_interrupt);
+
+ mutex_lock(&rts->irq_lock);
+ if (rts->irq_requested) {
+ rts->suspended = true;
+ cix_pd_sync_irq(rts);
+ if (rts->wake_enabled) {
+ disable_irq_wake(rts->irq);
+ rts->wake_enabled = false;
+ }
+ free_irq(rts->irq, rts);
+ rts->irq_requested = false;
+ }
+ mutex_unlock(&rts->irq_lock);
+
+ cix_pd_unregister_ports(rts);
+}
+
+static const struct of_device_id cix_pd_of_match[] = {
+ { .compatible = "realtek,rts5453h", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cix_pd_of_match);
+
+static const struct i2c_device_id cix_pd_id[] = {
+ { "rts5453h" },
+ {}
+};
+
+static int __maybe_unused cix_pd_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cix_pd *rts = i2c_get_clientdata(client);
+
+ mutex_lock(&rts->irq_lock);
+ if (rts->irq_requested) {
+ rts->suspended = true;
+ cix_pd_sync_irq(rts);
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(rts->irq);
+ rts->wake_enabled = true;
+ }
+ }
+ mutex_unlock(&rts->irq_lock);
+
+ return 0;
+}
+
+static int __maybe_unused cix_pd_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cix_pd *rts = i2c_get_clientdata(client);
+
+ mutex_lock(&rts->irq_lock);
+ if (rts->irq_requested) {
+ if (rts->wake_enabled) {
+ disable_irq_wake(rts->irq);
+ rts->wake_enabled = false;
+ }
+ rts->suspended = false;
+ cix_pd_sync_irq(rts);
+ }
+ mutex_unlock(&rts->irq_lock);
+
+ return 0;
+}
+
+static int __maybe_unused cix_pd_restore(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cix_pd *rts = i2c_get_clientdata(client);
+ int ret;
+
+ ret = cix_pd_resume(dev);
+ if (ret)
+ return ret;
+
+ /* Re-trigger PD event if PD event occurs during std */
+ ret = cix_pd_event_handler(rts, 0);
+ if (ret)
+ dev_err(&client->dev, "typec event handling error %d\n", ret);
+
+ return ret;
+}
+
+static void cix_pd_shutdown(struct i2c_client *client)
+{
+ struct cix_pd *rts = i2c_get_clientdata(client);
+
+ mutex_lock(&rts->irq_lock);
+ rts->suspended = true;
+ cix_pd_sync_irq(rts);
+ mutex_unlock(&rts->irq_lock);
+}
+
+static const struct dev_pm_ops cix_pd_pm_ops = {
+ .suspend = pm_sleep_ptr(cix_pd_suspend),
+ .resume = pm_sleep_ptr(cix_pd_resume),
+ .freeze = pm_sleep_ptr(cix_pd_suspend),
+ .thaw = pm_sleep_ptr(cix_pd_resume),
+ .restore = pm_sleep_ptr(cix_pd_restore),
+};
+
+MODULE_DEVICE_TABLE(i2c, cix_pd_id);
+static struct i2c_driver cix_pd_i2c_driver = {
+ .driver = {
+ .name = "rts5453h",
+ .of_match_table = cix_pd_of_match,
+ .pm = pm_ptr(&cix_pd_pm_ops),
+ },
+ .id_table = cix_pd_id,
+ .probe = cix_pd_probe,
+ .remove = cix_pd_remove,
+ .shutdown = cix_pd_shutdown,
+};
+
+module_i2c_driver(cix_pd_i2c_driver);
+MODULE_DESCRIPTION("CIX Type-C PD controller driver");
+MODULE_AUTHOR("Chao Zeng <Chao.Zeng@cixtech.com>");
+MODULE_AUTHOR("Gary Yang <gary.yang@cixtech.com>");
+MODULE_LICENSE("GPL");
--
2.50.1
^ permalink raw reply [flat|nested] 5+ messages in thread