From: Peng Fan <[email protected]>

Add a generic TCPCI-compliant driver for USB Type-C Port Controller
Interface chips such as NXP PTN5110. The driver implements the
dm_tcpm_ops interface and communicates with the TCPC via I2C.

Key features:
- Full TCPCI register-level implementation for PD sink negotiation
- Proper TX buffer handling (burst write from TX_HDR)
- Alert-based polling for RX, TX completion, CC and VBUS changes
- Re-entrant poll protection to prevent state machine corruption
- Low power mode with RX_DETECT disable and alert masking to
  prevent post-negotiation Hard Reset from PD sources
- Stale RX buffer drain during initialization for dead-battery
  boot scenarios

Signed-off-by: Peng Fan <[email protected]>
---
 drivers/usb/tcpm/Kconfig  |   9 +
 drivers/usb/tcpm/Makefile |   1 +
 drivers/usb/tcpm/tcpci.c  | 778 ++++++++++++++++++++++++++++++++++++++++++++++
 include/usb/tcpci.h       | 185 +++++++++++
 4 files changed, 973 insertions(+)

diff --git a/drivers/usb/tcpm/Kconfig b/drivers/usb/tcpm/Kconfig
index 9be4b496e82..695c49b64ce 100644
--- a/drivers/usb/tcpm/Kconfig
+++ b/drivers/usb/tcpm/Kconfig
@@ -14,3 +14,12 @@ config TYPEC_FUSB302
          The Fairchild FUSB302 Type-C chip driver that works with
          Type-C Port Controller Manager to provide USB PD and USB
          Type-C functionalities.
+
+config TYPEC_TCPCI
+       tristate "TCPCI Type-C chip driver"
+       depends on DM && DM_I2C && TYPEC_TCPM
+       help
+         Generic TCPCI (USB Type-C Port Controller Interface)
+         compliant chip driver that works with Type-C Port
+         Controller Manager to provide USB PD and USB Type-C
+         functionalities. Supports NXP PTN5110 and similar.
diff --git a/drivers/usb/tcpm/Makefile b/drivers/usb/tcpm/Makefile
index 668d33155bf..5cabcc06e0a 100644
--- a/drivers/usb/tcpm/Makefile
+++ b/drivers/usb/tcpm/Makefile
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_TYPEC_TCPM) += tcpm.o tcpm-uclass.o
 obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o
+obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o
diff --git a/drivers/usb/tcpm/tcpci.c b/drivers/usb/tcpm/tcpci.c
new file mode 100644
index 00000000000..9ade6d16009
--- /dev/null
+++ b/drivers/usb/tcpm/tcpci.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 NXP
+ *
+ * USB Type-C Port Controller Interface I2C driver for U-Boot.
+ */
+
+#define LOG_CATEGORY UCLASS_TCPM
+
+#include <dm.h>
+#include <i2c.h>
+#include <watchdog.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <dm/device_compat.h>
+#include <time.h>
+#include <usb/tcpm.h>
+#include <usb/tcpci.h>
+#include "tcpm-internal.h"
+
+#define PD_RETRY_COUNT_DEFAULT         3
+#define PD_RETRY_COUNT_3_0_OR_HIGHER   2
+
+struct tcpci_chip {
+       u16 alert_mask;
+       bool controls_vbus;
+       bool in_poll;
+};
+
+static int tcpci_read8(struct udevice *dev, u8 reg, u8 *val)
+{
+       int ret;
+
+       ret = dm_i2c_read(dev, reg, val, 1);
+       if (ret)
+               dev_err(dev, "cannot read 0x%02x, ret=%d\n", reg, ret);
+
+       dev_dbg(dev, "read 0x%02x, val=0x%02x\n", reg, *val);
+
+       return ret;
+}
+
+static int tcpci_write8(struct udevice *dev, u8 reg, u8 val)
+{
+       int ret;
+
+       ret = dm_i2c_write(dev, reg, &val, 1);
+       if (ret)
+               dev_err(dev, "cannot write 0x%02x to 0x%02x, ret=%d\n",
+                       val, reg, ret);
+       dev_dbg(dev, "write 0x%02x, val=0x%02x\n", reg, val);
+
+       return ret;
+}
+
+static int tcpci_read16(struct udevice *dev, u8 reg, u16 *val)
+{
+       int ret;
+
+       ret = dm_i2c_read(dev, reg, (u8 *)val, 2);
+       if (ret)
+               dev_err(dev, "cannot read 16-bit 0x%02x, ret=%d\n", reg, ret);
+
+       dev_dbg(dev, "read16 0x%02x, val=0x%04x\n", reg, *val);
+
+       return ret;
+}
+
+static int tcpci_write16(struct udevice *dev, u8 reg, u16 val)
+{
+       int ret;
+
+       ret = dm_i2c_write(dev, reg, (u8 *)&val, 2);
+       if (ret)
+               dev_err(dev, "cannot write 16-bit 0x%04x to 0x%02x, ret=%d\n",
+                       val, reg, ret);
+       dev_dbg(dev, "write16 0x%02x, val=0x%04x\n", reg, val);
+
+       return ret;
+}
+
+static int tcpci_get_connector_node(struct udevice *dev, ofnode 
*connector_node)
+{
+       dev_dbg(dev, "%s: entering\n", __func__);
+
+       *connector_node = dev_read_subnode(dev, "connector");
+       if (!ofnode_valid(*connector_node)) {
+               dev_err(dev, "'connector' node is not found\n");
+               return -ENODEV;
+       }
+
+       dev_dbg(dev, "%s: connector node found\n", __func__);
+       return 0;
+}
+
+static int tcpci_set_cc(struct udevice *dev, enum typec_cc_status cc)
+{
+       unsigned int reg;
+       int ret;
+       u8 power_status;
+       bool vconn_pres;
+       enum typec_cc_polarity polarity = TYPEC_POLARITY_CC1;
+
+       dev_dbg(dev, "%s: entering cc=%d\n", __func__, cc);
+
+       ret = tcpci_read8(dev, TCPC_POWER_STATUS, &power_status);
+       if (ret < 0)
+               return ret;
+
+       vconn_pres = !!(power_status & TCPC_POWER_STATUS_VCONN_PRES);
+       if (vconn_pres) {
+               u8 ctrl;
+
+               ret = tcpci_read8(dev, TCPC_TCPC_CTRL, &ctrl);
+               if (ret < 0)
+                       return ret;
+
+               if (ctrl & TCPC_TCPC_CTRL_ORIENTATION)
+                       polarity = TYPEC_POLARITY_CC2;
+       }
+
+       switch (cc) {
+       case TYPEC_CC_RA:
+               reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RA) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RA));
+               break;
+       case TYPEC_CC_RD:
+               reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD));
+               break;
+       case TYPEC_CC_RP_DEF:
+               reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+                                 TCPC_ROLE_CTRL_RP_VAL_DEF));
+               break;
+       case TYPEC_CC_RP_1_5:
+               reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+                                 TCPC_ROLE_CTRL_RP_VAL_1_5));
+               break;
+       case TYPEC_CC_RP_3_0:
+               reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+                                 TCPC_ROLE_CTRL_RP_VAL_3_0));
+               break;
+       case TYPEC_CC_OPEN:
+       default:
+               reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_OPEN) |
+                      FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_OPEN));
+               break;
+       }
+
+       if (vconn_pres) {
+               if (polarity == TYPEC_POLARITY_CC2)
+                       reg = (reg & ~TCPC_ROLE_CTRL_CC1) |
+                             FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+                                        TCPC_ROLE_CTRL_CC_OPEN);
+               else
+                       reg = (reg & ~TCPC_ROLE_CTRL_CC2) |
+                             FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+                                        TCPC_ROLE_CTRL_CC_OPEN);
+       }
+
+       ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: done\n", __func__);
+       return 0;
+}
+
+static int tcpci_start_toggling(struct udevice *dev,
+                               enum typec_port_type port_type,
+                               enum typec_cc_status cc)
+{
+       unsigned int reg = 0;
+       int ret;
+
+       dev_dbg(dev, "%s: entering port_type=%d cc=%d\n", __func__,
+               port_type, cc);
+
+       switch (port_type) {
+       case TYPEC_PORT_DRP:
+               reg |= TCPC_ROLE_CTRL_DRP;
+               fallthrough;
+       case TYPEC_PORT_SRC:
+               switch (cc) {
+               default:
+               case TYPEC_CC_RP_DEF:
+                       reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+                                         TCPC_ROLE_CTRL_RP_VAL_DEF);
+                       break;
+               case TYPEC_CC_RP_1_5:
+                       reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+                                         TCPC_ROLE_CTRL_RP_VAL_1_5);
+                       break;
+               case TYPEC_CC_RP_3_0:
+                       reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+                                         TCPC_ROLE_CTRL_RP_VAL_3_0);
+                       break;
+               }
+
+               reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+                                  TCPC_ROLE_CTRL_CC_RP) |
+                       FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+                                  TCPC_ROLE_CTRL_CC_RP));
+               ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+               if (ret < 0)
+                       return ret;
+               ret = tcpci_write8(dev, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION);
+               if (ret < 0)
+                       return ret;
+               dev_dbg(dev, "%s: done\n", __func__);
+               return 0;
+       case TYPEC_PORT_SNK:
+               reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+                                  TCPC_ROLE_CTRL_CC_RD) |
+                       FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+                                  TCPC_ROLE_CTRL_CC_RD));
+               ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+               if (ret < 0)
+                       return ret;
+               dev_dbg(dev, "%s: done sink CC=Rd\n", __func__);
+               return 0;
+       }
+
+       return -EOPNOTSUPP;
+}
+
+static int tcpci_get_cc(struct udevice *dev, enum typec_cc_status *cc1,
+                       enum typec_cc_status *cc2)
+{
+       unsigned int role_control, reg;
+       int ret;
+
+       dev_dbg(dev, "%s: entering\n", __func__);
+
+       ret = tcpci_read8(dev, TCPC_ROLE_CTRL, (u8 *)&role_control);
+       if (ret < 0)
+               return ret;
+
+       ret = tcpci_read8(dev, TCPC_CC_STATUS, (u8 *)&reg);
+       if (ret < 0)
+               return ret;
+
+       *cc1 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC1, reg),
+                                reg & TCPC_CC_STATUS_TERM ||
+                                tcpc_presenting_rd(role_control, CC1));
+       *cc2 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC2, reg),
+                                reg & TCPC_CC_STATUS_TERM ||
+                                tcpc_presenting_rd(role_control, CC2));
+
+       dev_dbg(dev, "%s: cc1=%d cc2=%d\n", __func__, *cc1, *cc2);
+       return 0;
+}
+
+static int tcpci_set_polarity(struct udevice *dev,
+                             enum typec_cc_polarity polarity)
+{
+       unsigned int reg;
+       enum typec_cc_status cc1, cc2;
+       int ret;
+
+       dev_dbg(dev, "%s: entering polarity=%d\n", __func__, polarity);
+
+       ret = tcpci_read8(dev, TCPC_ROLE_CTRL, (u8 *)&reg);
+       if (ret < 0)
+               return ret;
+
+       ret = tcpci_get_cc(dev, &cc1, &cc2);
+       if (ret < 0)
+               return ret;
+
+       if (reg & TCPC_ROLE_CTRL_DRP) {
+               reg &= ~TCPC_ROLE_CTRL_DRP;
+
+               if (polarity == TYPEC_POLARITY_CC2) {
+                       reg &= ~TCPC_ROLE_CTRL_CC2;
+                       if (cc2 == TYPEC_CC_RD)
+                               reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+                                                 TCPC_ROLE_CTRL_CC_RP);
+                       else if (cc2 >= TYPEC_CC_RP_DEF)
+                               reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+                                                 TCPC_ROLE_CTRL_CC_RD);
+               } else {
+                       reg &= ~TCPC_ROLE_CTRL_CC1;
+                       if (cc1 == TYPEC_CC_RD)
+                               reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+                                                 TCPC_ROLE_CTRL_CC_RP);
+                       else if (cc1 >= TYPEC_CC_RP_DEF)
+                               reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+                                                 TCPC_ROLE_CTRL_CC_RD);
+               }
+
+               /* Transitioning from DRP toggling: set unused CC to OPEN */
+               if (polarity == TYPEC_POLARITY_CC2)
+                       reg = (reg & ~TCPC_ROLE_CTRL_CC1) |
+                             FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+                                        TCPC_ROLE_CTRL_CC_OPEN);
+               else
+                       reg = (reg & ~TCPC_ROLE_CTRL_CC2) |
+                             FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+                                        TCPC_ROLE_CTRL_CC_OPEN);
+       }
+
+       ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+       if (ret < 0)
+               return ret;
+
+       ret = tcpci_write8(dev, TCPC_TCPC_CTRL,
+                          (polarity == TYPEC_POLARITY_CC2) ?
+                          TCPC_TCPC_CTRL_ORIENTATION : 0);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: done\n", __func__);
+       return 0;
+}
+
+static int tcpci_set_vconn(struct udevice *dev, bool enable)
+{
+       int ret;
+       u8 reg;
+
+       dev_dbg(dev, "%s: entering enable=%d\n", __func__, enable);
+
+       ret = tcpci_read8(dev, TCPC_POWER_CTRL, &reg);
+       if (ret < 0)
+               return ret;
+
+       if (enable)
+               reg |= TCPC_POWER_CTRL_VCONN_ENABLE;
+       else
+               reg &= ~TCPC_POWER_CTRL_VCONN_ENABLE;
+
+       ret = tcpci_write8(dev, TCPC_POWER_CTRL, reg);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: done\n", __func__);
+       return 0;
+}
+
+static int tcpci_set_vbus(struct udevice *dev, bool on, bool charge)
+{
+       int ret;
+       u8 power_status;
+
+       dev_dbg(dev, "%s: entering on=%d charge=%d\n", __func__, on, charge);
+
+       ret = tcpci_read8(dev, TCPC_POWER_STATUS, &power_status);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: POWER_STATUS=0x%02x VBUS_PRES=%d SOURCING=%d 
SINKING=%d\n",
+               __func__, power_status,
+               !!(power_status & TCPC_POWER_STATUS_VBUS_PRES),
+               !!(power_status & TCPC_POWER_STATUS_SOURCING_VBUS),
+               !!(power_status & TCPC_POWER_STATUS_SINKING_VBUS));
+
+       /*
+        * If VBUS is present, don't send any VBUS COMMAND register writes
+        * at all - the PTN5110 uses a shared power path and disabling
+        * source or sink while VBUS is active cuts the board's power.
+        */
+       if (power_status & TCPC_POWER_STATUS_VBUS_PRES) {
+               dev_dbg(dev, "%s: VBUS present, skipping all VBUS commands\n",
+                       __func__);
+               return 0;
+       }
+
+       if (!on) {
+               ret = tcpci_write8(dev, TCPC_COMMAND,
+                                  TCPC_CMD_DISABLE_SRC_VBUS);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (!charge) {
+               ret = tcpci_write8(dev, TCPC_COMMAND,
+                                  TCPC_CMD_DISABLE_SINK_VBUS);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (on) {
+               ret = tcpci_write8(dev, TCPC_COMMAND,
+                                  TCPC_CMD_SRC_VBUS_DEFAULT);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (charge) {
+               ret = tcpci_write8(dev, TCPC_COMMAND,
+                                  TCPC_CMD_SINK_VBUS);
+               if (ret < 0)
+                       return ret;
+       }
+
+       dev_dbg(dev, "%s: done\n", __func__);
+       return 0;
+}
+
+static int tcpci_set_pd_rx(struct udevice *dev, bool enable)
+{
+       u8 reg = 0;
+       int ret;
+
+       dev_dbg(dev, "%s: entering enable=%d\n", __func__, enable);
+
+       if (enable)
+               reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
+
+       ret = tcpci_write8(dev, TCPC_RX_DETECT, reg);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: done\n", __func__);
+       return 0;
+}
+
+static int tcpci_set_roles(struct udevice *dev, bool attached,
+                          enum typec_role role, enum typec_data_role data)
+{
+       unsigned int reg;
+       int ret;
+
+       dev_dbg(dev, "%s: entering attached=%d role=%d data=%d\n",
+               __func__, attached, role, data);
+
+       reg = FIELD_PREP(TCPC_MSG_HDR_INFO_REV, PD_REV20);
+       if (role == TYPEC_SOURCE)
+               reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
+       if (data == TYPEC_HOST)
+               reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
+
+       ret = tcpci_write8(dev, TCPC_MSG_HDR_INFO, (u8)reg);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: done\n", __func__);
+       return 0;
+}
+
+static int tcpci_get_vbus(struct udevice *dev)
+{
+       u8 reg;
+       int ret;
+
+       ret = tcpci_read8(dev, TCPC_POWER_STATUS, &reg);
+       if (ret < 0)
+               return 0;
+
+       dev_dbg(dev, "%s: vbus=%d\n", __func__,
+               !!(reg & TCPC_POWER_STATUS_VBUS_PRES));
+       return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
+}
+
+static int tcpci_pd_transmit(struct udevice *dev,
+                            enum tcpm_transmit_type type,
+                            const struct pd_message *msg,
+                            unsigned int negotiated_rev)
+{
+       u16 header = msg ? le16_to_cpu(msg->header) : 0;
+       unsigned int reg, cnt;
+       int ret;
+       u8 byte_cnt;
+       u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN];
+       u8 pos = 0;
+
+       cnt = msg ? pd_header_cnt(header) * 4 : 0;
+
+       schedule();
+
+       byte_cnt = cnt + 2;
+
+       ret = dm_i2c_write(dev, TCPC_TX_BYTE_CNT, &byte_cnt, 1);
+       if (ret < 0)
+               return ret;
+
+       if (msg) {
+               memcpy(&buf[pos], &msg->header, sizeof(msg->header));
+               pos += sizeof(msg->header);
+
+               if (cnt > 0) {
+                       memcpy(&buf[pos], msg->payload, cnt);
+                       pos += cnt;
+               }
+
+               ret = dm_i2c_write(dev, TCPC_TX_HDR, buf, byte_cnt);
+               if (ret < 0)
+                       return ret;
+       }
+
+       reg = FIELD_PREP(TCPC_TRANSMIT_RETRY,
+                        (negotiated_rev > PD_REV20
+                         ? PD_RETRY_COUNT_3_0_OR_HIGHER
+                         : PD_RETRY_COUNT_DEFAULT));
+       reg |= FIELD_PREP(TCPC_TRANSMIT_TYPE, type);
+       ret = tcpci_write8(dev, TCPC_TRANSMIT, (u8)reg);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int tcpci_init(struct udevice *dev)
+{
+       struct tcpci_chip *chip = dev_get_priv(dev);
+       ulong timeout_ms = 1000;
+       u8 power_status;
+       u16 alert_mask;
+       ulong start;
+       int ret;
+       u8 cnt;
+       u8 rx_buf[64];
+
+       dev_dbg(dev, "%s: entering\n", __func__);
+       dev_dbg(dev, "TCPCI: init start\n");
+
+       start = get_timer(0);
+       do {
+               ret = tcpci_read8(dev, TCPC_POWER_STATUS, &power_status);
+               if (ret < 0)
+                       return ret;
+
+               if (!(power_status & TCPC_POWER_STATUS_UNINIT))
+                       break;
+
+       } while (get_timer(0) < (start + timeout_ms));
+
+       if (power_status & TCPC_POWER_STATUS_UNINIT) {
+               dev_err(dev, "TCPC initialization timed out\n");
+               return -ETIMEDOUT;
+       }
+
+       dev_dbg(dev, "%s: TCPC ready after wait\n", __func__);
+       dev_dbg(dev, "%s: POWER_STATUS=0x%02x\n", __func__, power_status);
+
+       ret = tcpci_write8(dev, TCPC_FAULT_STATUS, 0xff);
+       if (ret < 0)
+               return ret;
+
+       /* Mask all fault alerts (ALL_REG_RST_TO_DEFAULT, VCONN_OC) */
+       ret = tcpci_write8(dev, TCPC_FAULT_STATUS_MASK, 0xff);
+       if (ret < 0)
+               return ret;
+
+       ret = tcpci_write16(dev, TCPC_ALERT, 0xffff);
+       if (ret < 0)
+               return ret;
+
+       /* Drain any stale RX buffer (e.g. from dead battery phase) */
+       ret = tcpci_read8(dev, TCPC_RX_BYTE_CNT, &cnt);
+       if (ret == 0 && cnt > 0 && cnt <= sizeof(rx_buf)) {
+               dm_i2c_read(dev, TCPC_RX_BUF_FRAME_TYPE, rx_buf, cnt);
+               dev_dbg(dev, "%s: drained %d bytes from stale RX buffer\n",
+                       __func__, cnt);
+               tcpci_write16(dev, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+       }
+
+       power_status = chip->controls_vbus ? TCPC_POWER_STATUS_VBUS_PRES : 0;
+       ret = tcpci_write8(dev, TCPC_POWER_STATUS_MASK, power_status);
+       if (ret < 0)
+               return ret;
+
+       ret = tcpci_write8(dev, TCPC_COMMAND, TCPC_CMD_ENABLE_VBUS_DETECT);
+       if (ret < 0)
+               return ret;
+
+       alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
+                    TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
+                    TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
+       if (chip->controls_vbus)
+               alert_mask |= TCPC_ALERT_POWER_STATUS;
+
+       chip->alert_mask = alert_mask;
+
+       /* Program hardware ALERT_MASK so unwanted bits never fire */
+       ret = tcpci_write16(dev, TCPC_ALERT_MASK, chip->alert_mask);
+       if (ret < 0)
+               return ret;
+
+       dev_dbg(dev, "%s: done alert_mask=0x%04x controls_vbus=%d\n",
+               __func__, alert_mask, chip->controls_vbus);
+       return 0;
+}
+
+static void tcpci_poll_event(struct udevice *dev)
+{
+       struct tcpci_chip *chip = dev_get_priv(dev);
+       u16 status;
+       int max_iter = 5;
+       int ret;
+       bool was_in_poll = chip->in_poll;
+
+       schedule();
+       chip->in_poll = true;
+
+       do {
+               u16 raw;
+
+               ret = tcpci_read16(dev, TCPC_ALERT, &raw);
+               if (ret < 0)
+                       break;
+
+               if (raw & ~TCPC_ALERT_RX_STATUS)
+                       tcpci_write16(dev, TCPC_ALERT,
+                                     raw & ~TCPC_ALERT_RX_STATUS);
+
+               if (raw & TCPC_ALERT_FAULT) {
+                       u8 fault;
+                       tcpci_read8(dev, TCPC_FAULT_STATUS, &fault);
+                       tcpci_write8(dev, TCPC_FAULT_STATUS, fault);
+               }
+
+               status = raw & chip->alert_mask;
+               if (!status)
+                       break;
+
+               if (status & TCPC_ALERT_CC_STATUS)
+                       tcpm_cc_change(dev);
+
+               if (status & TCPC_ALERT_POWER_STATUS)
+                       tcpm_vbus_change(dev);
+
+               if (status & TCPC_ALERT_RX_STATUS) {
+                       struct pd_message msg;
+                       u16 header = 0;
+                       u8 cnt;
+                       u8 rx_buf[64];
+
+                       /*
+                        * On re-entrant calls (during TX poll helper), the
+                        * state machine is locked.  Drain and clear the
+                        * FIFO without processing, letting the NEXT poll
+                        * (after state machine unlocks) handle the message.
+                        */
+                       if (was_in_poll) {
+                               ret = tcpci_read8(dev, TCPC_RX_BYTE_CNT, &cnt);
+                               if (ret == 0 && cnt > 0 && cnt <= 
sizeof(rx_buf))
+                                       dm_i2c_read(dev, TCPC_RX_BUF_FRAME_TYPE,
+                                                   rx_buf, cnt);
+                               tcpci_write16(dev, TCPC_ALERT, 
TCPC_ALERT_RX_STATUS);
+                               continue;
+                       }
+
+                       memset(&msg, 0, sizeof(msg));
+
+                       ret = tcpci_read8(dev, TCPC_RX_BYTE_CNT, &cnt);
+                       if (ret < 0)
+                               continue;
+
+                       if (cnt > 0 && cnt <= sizeof(rx_buf)) {
+                               ret = dm_i2c_read(dev, TCPC_RX_BUF_FRAME_TYPE,
+                                                 rx_buf, cnt);
+                               if (ret < 0)
+                                       continue;
+
+                               if (cnt >= 3) {
+                                       header = rx_buf[1] | (rx_buf[2] << 8);
+                                       msg.header = cpu_to_le16(header);
+
+                                       if (cnt > 3) {
+                                               unsigned int payload_cnt = cnt 
- 3;
+                                               if (payload_cnt > 
sizeof(msg.payload))
+                                                       payload_cnt = 
sizeof(msg.payload);
+                                               memcpy(&msg.payload, &rx_buf[3],
+                                                      payload_cnt);
+                                       }
+                               }
+                       }
+
+                       tcpci_write16(dev, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+
+                       if (header != 0)
+                               tcpm_pd_receive(dev, &msg);
+                       }
+
+               if (status & TCPC_ALERT_RX_HARD_RST) {
+                       pd_trace("ALERT_RX_HARD_RST", raw);
+                       dev_dbg(dev, "%s: RX_HARD_RST\n", __func__);
+                       tcpm_pd_hard_reset(dev);
+               }
+
+               if (status & TCPC_ALERT_TX_SUCCESS) {
+                       dev_dbg(dev, "%s: TX_SUCCESS\n", __func__);
+                       tcpm_pd_transmit_complete(dev, TCPC_TX_SUCCESS);
+               } else if (status & TCPC_ALERT_TX_DISCARDED) {
+                       dev_dbg(dev, "%s: TX_DISCARDED\n", __func__);
+                       tcpm_pd_transmit_complete(dev, TCPC_TX_DISCARDED);
+               } else if (status & TCPC_ALERT_TX_FAILED) {
+                       dev_dbg(dev, "%s: TX_FAILED\n", __func__);
+                       tcpm_pd_transmit_complete(dev, TCPC_TX_FAILED);
+               }
+       } while (--max_iter > 0);
+
+       chip->in_poll = was_in_poll;
+}
+
+static int tcpci_enter_low_power_mode(struct udevice *dev, bool attached,
+                                     bool pd_capable)
+{
+       int ret;
+
+       dev_dbg(dev, "%s: entering attached=%d pd_capable=%d\n",
+               __func__, attached, pd_capable);
+
+       /*
+        * Stop SOP reception so TCPCI hardware won't auto-send GoodCRC
+        * for incoming messages.  Without this, the source may send
+        * Get_Sink_Cap or Get_Status after negotiation completes;
+        * TCPCI auto-ACKs with GoodCRC, source starts SenderResponseTimer,
+        * nobody sends a response, timer expires -> Hard Reset -> VBUS
+        * drops -> board loses power.
+        *
+        * Per PD spec 6.5.2, failure to receive GoodCRC does NOT trigger
+        * Hard Reset, so source stays in Ready state with VBUS up.
+        */
+       u8 pwr_ctrl, pwr_status;
+
+       tcpci_read8(dev, TCPC_POWER_CTRL, &pwr_ctrl);
+       tcpci_read8(dev, TCPC_POWER_STATUS, &pwr_status);
+       pd_trace("LOW_PWR_PWR_CTRL", pwr_ctrl);
+       pd_trace("LOW_PWR_PWR_STAT", pwr_status);
+
+       pd_trace("LOW_PWR_ENTER", 0);
+       ret = tcpci_write8(dev, TCPC_RX_DETECT, 0);
+       if (ret < 0)
+               return ret;
+
+       pd_trace("LOW_PWR_RX_OFF", 0);
+       ret = tcpci_write16(dev, TCPC_ALERT_MASK, 0);
+       if (ret < 0)
+               return ret;
+
+       ret = tcpci_write16(dev, TCPC_ALERT, 0xffff);
+       if (ret < 0)
+               return ret;
+
+       pd_trace("LOW_PWR_DONE", 0);
+       return 0;
+}
+
+static struct dm_tcpm_ops tcpci_ops = {
+       .get_connector_node = tcpci_get_connector_node,
+       .init = tcpci_init,
+       .get_vbus = tcpci_get_vbus,
+       .set_cc = tcpci_set_cc,
+       .get_cc = tcpci_get_cc,
+       .set_polarity = tcpci_set_polarity,
+       .set_vconn = tcpci_set_vconn,
+       .set_vbus = tcpci_set_vbus,
+       .set_pd_rx = tcpci_set_pd_rx,
+       .set_roles = tcpci_set_roles,
+       .start_toggling = tcpci_start_toggling,
+       .pd_transmit = tcpci_pd_transmit,
+       .poll_event = tcpci_poll_event,
+       .enter_low_power_mode = tcpci_enter_low_power_mode,
+};
+
+static const struct udevice_id tcpci_ids[] = {
+       { .compatible = "nxp,ptn5110" },
+       { .compatible = "tcpci" },
+       { }
+};
+
+U_BOOT_DRIVER(tcpci) = {
+       .name = "tcpci",
+       .id = UCLASS_TCPM,
+       .of_match = tcpci_ids,
+       .ops = &tcpci_ops,
+       .priv_auto = sizeof(struct tcpci_chip),
+};
diff --git a/include/usb/tcpci.h b/include/usb/tcpci.h
new file mode 100644
index 00000000000..f87d10c6c53
--- /dev/null
+++ b/include/usb/tcpci.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * USB Type-C Port Controller Interface.
+ */
+
+#ifndef __USB_TCPCI_H
+#define __USB_TCPCI_H
+
+#include <linux/bitops.h>
+#include <usb/pd.h>
+
+#define TCPC_VENDOR_ID                 0x0
+#define TCPC_PRODUCT_ID                        0x2
+#define TCPC_BCD_DEV                   0x4
+#define TCPC_TC_REV                    0x6
+#define TCPC_PD_REV                    0x8
+#define TCPC_PD_INT_REV                        0xa
+
+#define TCPC_ALERT                     0x10
+#define TCPC_ALERT_EXTND               BIT(14)
+#define TCPC_ALERT_EXTENDED_STATUS     BIT(13)
+#define TCPC_ALERT_VBUS_DISCNCT                BIT(11)
+#define TCPC_ALERT_RX_BUF_OVF          BIT(10)
+#define TCPC_ALERT_FAULT               BIT(9)
+#define TCPC_ALERT_V_ALARM_LO          BIT(8)
+#define TCPC_ALERT_V_ALARM_HI          BIT(7)
+#define TCPC_ALERT_TX_SUCCESS          BIT(6)
+#define TCPC_ALERT_TX_DISCARDED                BIT(5)
+#define TCPC_ALERT_TX_FAILED           BIT(4)
+#define TCPC_ALERT_RX_HARD_RST         BIT(3)
+#define TCPC_ALERT_RX_STATUS           BIT(2)
+#define TCPC_ALERT_POWER_STATUS                BIT(1)
+#define TCPC_ALERT_CC_STATUS           BIT(0)
+
+#define TCPC_ALERT_MASK                        0x12
+#define TCPC_POWER_STATUS_MASK         0x14
+
+#define TCPC_FAULT_STATUS_MASK                 0x15
+#define TCPC_FAULT_STATUS_MASK_VCONN_OC                BIT(1)
+
+#define TCPC_EXTENDED_STATUS_MASK              0x16
+#define TCPC_EXTENDED_STATUS_MASK_VSAFE0V      BIT(0)
+
+#define TCPC_ALERT_EXTENDED_MASK       0x17
+#define TCPC_SINK_FAST_ROLE_SWAP       BIT(0)
+
+#define TCPC_CONFIG_STD_OUTPUT         0x18
+#define TCPC_CONFIG_STD_OUTPUT_ORIENTATION_MASK                BIT(0)
+#define TCPC_CONFIG_STD_OUTPUT_ORIENTATION_NORMAL      0
+#define TCPC_CONFIG_STD_OUTPUT_ORIENTATION_FLIPPED     1
+
+#define TCPC_TCPC_CTRL                 0x19
+#define TCPC_TCPC_CTRL_ORIENTATION     BIT(0)
+#define PLUG_ORNT_CC1                  0
+#define PLUG_ORNT_CC2                  1
+#define TCPC_TCPC_CTRL_BIST_TM         BIT(1)
+#define TCPC_TCPC_CTRL_EN_LK4CONN_ALRT BIT(6)
+
+#define TCPC_EXTENDED_STATUS           0x20
+#define TCPC_EXTENDED_STATUS_VSAFE0V   BIT(0)
+
+#define TCPC_ROLE_CTRL                 0x1a
+#define TCPC_ROLE_CTRL_DRP             BIT(6)
+#define TCPC_ROLE_CTRL_RP_VAL          GENMASK(5, 4)
+#define TCPC_ROLE_CTRL_RP_VAL_DEF      0x0
+#define TCPC_ROLE_CTRL_RP_VAL_1_5      0x1
+#define TCPC_ROLE_CTRL_RP_VAL_3_0      0x2
+#define TCPC_ROLE_CTRL_CC2             GENMASK(3, 2)
+#define TCPC_ROLE_CTRL_CC1             GENMASK(1, 0)
+#define TCPC_ROLE_CTRL_CC_RA           0x0
+#define TCPC_ROLE_CTRL_CC_RP           0x1
+#define TCPC_ROLE_CTRL_CC_RD           0x2
+#define TCPC_ROLE_CTRL_CC_OPEN         0x3
+
+#define TCPC_FAULT_CTRL                        0x1b
+
+#define TCPC_POWER_CTRL                        0x1c
+#define TCPC_POWER_CTRL_VCONN_ENABLE   BIT(0)
+#define TCPC_POWER_CTRL_BLEED_DISCHARGE        BIT(3)
+#define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4)
+#define TCPC_DIS_VOLT_ALRM             BIT(5)
+#define TCPC_POWER_CTRL_VBUS_VOLT_MON  BIT(6)
+#define TCPC_FAST_ROLE_SWAP_EN         BIT(7)
+
+#define TCPC_CC_STATUS                 0x1d
+#define TCPC_CC_STATUS_TOGGLING                BIT(5)
+#define TCPC_CC_STATUS_TERM            BIT(4)
+#define TCPC_CC_STATUS_CC2             GENMASK(3, 2)
+#define TCPC_CC_STATUS_CC1             GENMASK(1, 0)
+#define TCPC_CC_STATE_SRC_OPEN         0
+
+#define TCPC_POWER_STATUS              0x1e
+#define TCPC_POWER_STATUS_DBG_ACC_CON  BIT(7)
+#define TCPC_POWER_STATUS_UNINIT       BIT(6)
+#define TCPC_POWER_STATUS_SOURCING_VBUS        BIT(4)
+#define TCPC_POWER_STATUS_VBUS_DET     BIT(3)
+#define TCPC_POWER_STATUS_VBUS_PRES    BIT(2)
+#define TCPC_POWER_STATUS_VCONN_PRES   BIT(1)
+#define TCPC_POWER_STATUS_SINKING_VBUS BIT(0)
+
+#define TCPC_FAULT_STATUS              0x1f
+#define TCPC_FAULT_STATUS_ALL_REG_RST_TO_DEFAULT BIT(7)
+#define TCPC_FAULT_STATUS_VCONN_OC     BIT(1)
+
+#define TCPC_ALERT_EXTENDED            0x21
+
+#define TCPC_COMMAND                   0x23
+#define TCPC_CMD_WAKE_I2C              0x11
+#define TCPC_CMD_DISABLE_VBUS_DETECT   0x22
+#define TCPC_CMD_ENABLE_VBUS_DETECT    0x33
+#define TCPC_CMD_DISABLE_SINK_VBUS     0x44
+#define TCPC_CMD_SINK_VBUS             0x55
+#define TCPC_CMD_DISABLE_SRC_VBUS      0x66
+#define TCPC_CMD_SRC_VBUS_DEFAULT      0x77
+#define TCPC_CMD_SRC_VBUS_HIGH         0x88
+#define TCPC_CMD_LOOK4CONNECTION       0x99
+#define TCPC_CMD_RXONEMORE             0xAA
+#define TCPC_CMD_I2C_IDLE              0xFF
+
+#define TCPC_DEV_CAP_1                 0x24
+#define TCPC_DEV_CAP_2                 0x26
+#define TCPC_STD_INPUT_CAP             0x28
+#define TCPC_STD_OUTPUT_CAP            0x29
+#define TCPC_STD_OUTPUT_CAP_ORIENTATION        BIT(0)
+
+#define TCPC_MSG_HDR_INFO              0x2e
+#define TCPC_MSG_HDR_INFO_DATA_ROLE    BIT(3)
+#define TCPC_MSG_HDR_INFO_REV          GENMASK(2, 1)
+#define TCPC_MSG_HDR_INFO_PWR_ROLE     BIT(0)
+
+#define TCPC_RX_DETECT                 0x2f
+#define TCPC_RX_DETECT_HARD_RESET      BIT(5)
+#define TCPC_RX_DETECT_SOP             BIT(0)
+#define TCPC_RX_DETECT_SOP1            BIT(1)
+#define TCPC_RX_DETECT_SOP2            BIT(2)
+#define TCPC_RX_DETECT_DBG1            BIT(3)
+#define TCPC_RX_DETECT_DBG2            BIT(4)
+
+#define TCPC_RX_BYTE_CNT               0x30
+#define TCPC_RX_BUF_FRAME_TYPE         0x31
+#define TCPC_RX_BUF_FRAME_TYPE_SOP     0
+#define TCPC_RX_BUF_FRAME_TYPE_SOP1    1
+#define TCPC_RX_HDR                    0x32
+#define TCPC_RX_DATA                   0x34
+
+#define TCPC_TRANSMIT                  0x50
+#define TCPC_TRANSMIT_RETRY            GENMASK(5, 4)
+#define TCPC_TRANSMIT_TYPE             GENMASK(2, 0)
+
+#define TCPC_TX_BYTE_CNT               0x51
+#define TCPC_TX_HDR                    0x52
+#define TCPC_TX_DATA                   0x54
+
+#define TCPC_VBUS_VOLTAGE                      0x70
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH       0x72
+#define TCPC_VBUS_STOP_DISCHARGE_THRESH                0x74
+#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG         0x76
+#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG         0x78
+
+#define TCPC_TRANSMIT_BUFFER_MAX_LEN           31
+
+#define tcpc_presenting_rd(reg, cc) \
+       (!(TCPC_ROLE_CTRL_DRP & (reg)) && \
+        FIELD_GET(TCPC_ROLE_CTRL_## cc, reg) == TCPC_ROLE_CTRL_CC_RD)
+
+static inline enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool 
sink)
+{
+       switch (cc) {
+       case 0x1:
+               return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
+       case 0x2:
+               return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
+       case 0x3:
+               if (sink)
+                       return TYPEC_CC_RP_3_0;
+               fallthrough;
+       case TCPC_CC_STATE_SRC_OPEN:
+       default:
+               return TYPEC_CC_OPEN;
+       }
+}
+
+#endif /* __USB_TCPCI_H */

-- 
2.51.0


Reply via email to