Re: [PATCH v2 04/10] usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver
Sebastian Reichel <[email protected]>
| Newsgroups | gmane.comp.boot-loaders.u-boot.general,gmane.comp.boot-loaders.u-boot |
|---|---|
| Message-ID | <anpFm-cB8KSVUC5G@venus> |
Hi, On Sun, Jun 21, 2026 at 10:06:40AM +0800, Peng Fan (OSS) wrote: > 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 I think it makes sense to move TYPEC_TCPM dependency as follows to avoid having it in all drivers: if TYPEC_TCPM menu "TCPM drivers" config TYPEC_FUSB302 ... config TYPEC_TCPCI ... endif > + 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> why? > +#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" why? This should not be used outside of the tcpm core itself, hence the internal suffix. > +#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; > +} I suppose each of the dev_dbg should be if (ret) dev_err(...) else dev_dbg(...) > +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; > +} I think it would be sensible to have a get_connector_node helper provided in tcpm.c considering it is exactly the same for fusb302 and expected to be the same for other chips. > +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 *)®); > + 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 *)®); > + 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, ®); > + 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. > + */ This depends on the board, right? If you have a system with a battery, turning VBUS sourcing off should be safe. > + 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, ®); > + 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(); This should be moved up to generic TCPM code. > + 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(); same - this belongs into TCPM. > + 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; > + } > +} I would add these two helpers in tcpci.c. > + > +#endif /* __USB_TCPCI_H */ Greetings, -- Sebastian
signature.asc
(application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE----- iQIyBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmp6YPsACgkQ2O7X88g7 +prsig/4neIEJfYnNaQsWWoMEgeywbkUR3BwRRDijFhafwL4VXzOT1NUkI7UCCND MrqS7jQ7I6Tik4NM4ZcLPPCJUBOQI1SpO/OYg9xCQMMLItIsl8OQr/xz7jIQBZvQ f7yrCCtt4v0ET7q8CSQ5YBVzWKjAtYt+Mdod7lHaxhuoFipiEYoKv0wT14ZHqQJW 0jOepZpGqOQopmHYdl0eAbmcS+3q5NrThHODS1ifuBcZx0B2GR0fIUNfLQd5B88X GFXwVQNwtS9xDWyOridG4IUjUbkTaYPSOj2j4PZjNeUjG0TvbjnT3Jx8vcmJdSPb 9cHdZl3O02Vwo67YVdlUovqzpH6+oV1tgLnEI0tPWRgEsrdLK3WAAYzCX1sLBlVA BHu38yEQtOPyaxt8WGwjzMVumGtMIdGtqZLcm+P/8fXQvpPFMoDRk11Mp48spehZ LlUPHgCQ/ulwC7o1VSs34+97eWFtCZaqsajcwnJOGbtzCTtmItf1cfcXdpj/xsyh bPrVnF1UpNEESdUZcxIBGa0sd84uzYAcFuWBc+jObdmnioCpxY9YFMosJd7h+O9g l+wG9wUgG9WeD6T/FSFiuUHs/bsMcj2n102mPBqBUA7OfcgFHdMHd5auUEAnlFgN 4bHR/67cjM98g1A0f/+mjWA2BNei/0El36M88YNUdwIoMzH+Og== =UC/c -----END PGP SIGNATURE-----