[PATCH 1/8] tcpc: Add driver for USB typec port controller (TCPC)

Peng Fan
2020-10-12T06:23:47Z

From: Ye Li <ye.li@nxp.com>

The functionalities in this driver include:
1. USB power delivery support at dead battery
2. Support configure to UFP or DFP mode
3. Support callback to setup external PD switch. When PD process is enabled,
we call this function only when SINK_VBUS is enabled to avoid system power
shut down.

Signed-off-by: Li Jun <jun.li@nxp.com>
Signed-off-by: Ye Li <ye.li@nxp.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
board/freescale/common/Kconfig | 6 +
board/freescale/common/Makefile | 4 +
board/freescale/common/tcpc.c | 1018 +++++++++++++++++++++++++++++++
board/freescale/common/tcpc.h | 469 ++++++++++++++
4 files changed, 1497 insertions(+)
create mode 100644 board/freescale/common/tcpc.c
create mode 100644 board/freescale/common/tcpc.h

diff --git a/board/freescale/common/Kconfig b/board/freescale/common/Kconfig
index 1b1fd69cb2..eccfc12bfc 100644
--- a/board/freescale/common/Kconfig
+++ b/board/freescale/common/Kconfig
@@ -36,3 +36,9 @@ config VOL_MONITOR_LTC3882_SET
help
This option enables LTC3882 voltage monitor set
functionality. It is used by common VID driver.
+
+config USB_TCPC
+ bool "USB Typec port controller simple driver"
+ default n
+ help
+ Enable USB type-c port controller (TCPC) driver
diff --git a/board/freescale/common/Makefile b/board/freescale/common/Makefile
index 04e04a6358..8f1ce67f66 100644
--- a/board/freescale/common/Makefile
+++ b/board/freescale/common/Makefile
@@ -79,4 +79,8 @@ obj-$(CONFIG_CMD_ESBC_VALIDATE) += fsl_validate.o cmd_esbc_validate.o
endif
obj-$(CONFIG_CHAIN_OF_TRUST) += fsl_chain_of_trust.o

+ifndef CONFIG_SPL_BUILD
+obj-$(CONFIG_USB_TCPC) += tcpc.o
+endif
+
endif
diff --git a/board/freescale/common/tcpc.c b/board/freescale/common/tcpc.c
new file mode 100644
index 0000000000..fb532a3d87
--- /dev/null
+++ b/board/freescale/common/tcpc.c
@@ -0,0 +1,1018 @@
+/*
+ * Copyright 2017,2019 NXP
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+#include <common.h>
+#include <i2c.h>
+#include <time.h>
+#include <linux/delay.h>
+#include "tcpc.h"
+
+#ifdef DEBUG
+#define tcpc_debug_log(port, fmt, args...) tcpc_log(port, fmt, ##args)
+#else
+#define tcpc_debug_log(port, fmt, args...)
+#endif
+
+static int tcpc_log(struct tcpc_port *port, const char *fmt, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, fmt);
+ i = vscnprintf(port->log_p, port->log_size, fmt, args);
+ va_end(args);
+
+ port->log_size -= i;
+ port->log_p += i;
+
+ return i;
+}
+
+int tcpc_set_cc_to_source(struct tcpc_port *port)
+{
+ uint8_t valb;
+ int err;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ valb = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+ (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+ (TCPC_ROLE_CTRL_RP_VAL_DEF <<
+ TCPC_ROLE_CTRL_RP_VAL_SHIFT) | TCPC_ROLE_CTRL_DRP;
+
+ err = dm_i2c_write(port->i2c_dev, TCPC_ROLE_CTRL, &valb, 1);
+ if (err)
+ tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err);
+ return err;
+}
+
+int tcpc_set_cc_to_sink(struct tcpc_port *port)
+{
+ uint8_t valb;
+ int err;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ valb = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
+ (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT) | TCPC_ROLE_CTRL_DRP;
+
+ err = dm_i2c_write(port->i2c_dev, TCPC_ROLE_CTRL, &valb, 1);
+ if (err)
+ tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err);
+ return err;
+}
+
+
+int tcpc_set_plug_orientation(struct tcpc_port *port, enum typec_cc_polarity polarity)
+{
+ uint8_t valb;
+ int err;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ err = dm_i2c_read(port->i2c_dev, TCPC_TCPC_CTRL, &valb, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ if (polarity == TYPEC_POLARITY_CC2)
+ valb |= TCPC_TCPC_CTRL_ORIENTATION;
+ else
+ valb &= ~TCPC_TCPC_CTRL_ORIENTATION;
+
+ err = dm_i2c_write(port->i2c_dev, TCPC_TCPC_CTRL, &valb, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int tcpc_get_cc_status(struct tcpc_port *port, enum typec_cc_polarity *polarity, enum typec_cc_state *state)
+{
+
+ uint8_t valb_cc, cc2, cc1;
+ int err;
+
+ if (port == NULL || polarity == NULL || state == NULL)
+ return -EINVAL;
+
+ err = dm_i2c_read(port->i2c_dev, TCPC_CC_STATUS, (uint8_t *)&valb_cc, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ tcpc_debug_log(port, "cc status 0x%x\n", valb_cc);
+
+ cc2 = (valb_cc >> TCPC_CC_STATUS_CC2_SHIFT) & TCPC_CC_STATUS_CC2_MASK;
+ cc1 = (valb_cc >> TCPC_CC_STATUS_CC1_SHIFT) & TCPC_CC_STATUS_CC1_MASK;
+
+ if (valb_cc & TCPC_CC_STATUS_LOOK4CONN)
+ return -EFAULT;
+
+ *state = TYPEC_STATE_OPEN;
+
+ if (valb_cc & TCPC_CC_STATUS_TERM) {
+ if (cc2) {
+ *polarity = TYPEC_POLARITY_CC2;
+
+ switch (cc2) {
+ case 0x1:
+ *state = TYPEC_STATE_SNK_DEFAULT;
+ tcpc_log(port, "SNK.Default on CC2\n");
+ break;
+ case 0x2:
+ *state = TYPEC_STATE_SNK_POWER15;
+ tcpc_log(port, "SNK.Power1.5 on CC2\n");
+ break;
+ case 0x3:
+ *state = TYPEC_STATE_SNK_POWER30;
+ tcpc_log(port, "SNK.Power3.0 on CC2\n");
+ break;
+ }
+ } else if (cc1) {
+ *polarity = TYPEC_POLARITY_CC1;
+
+ switch (cc1) {
+ case 0x1:
+ *state = TYPEC_STATE_SNK_DEFAULT;
+ tcpc_log(port, "SNK.Default on CC1\n");
+ break;
+ case 0x2:
+ *state = TYPEC_STATE_SNK_POWER15;
+ tcpc_log(port, "SNK.Power1.5 on CC1\n");
+ break;
+ case 0x3:
+ *state = TYPEC_STATE_SNK_POWER30;
+ tcpc_log(port, "SNK.Power3.0 on CC1\n");
+ break;
+ }
+ } else {
+ *state = TYPEC_STATE_OPEN;
+ return -EPERM;
+ }
+
+ } else {
+ if (cc2) {
+ *polarity = TYPEC_POLARITY_CC2;
+
+ switch (cc2) {
+ case 0x1:
+ if (cc1 == 0x1) {
+ *state = TYPEC_STATE_SRC_BOTH_RA;
+ tcpc_log(port, "SRC.Ra on both CC1 and CC2\n");
+ } else if (cc1 == 0x2) {
+ *state = TYPEC_STATE_SRC_RD_RA;
+ tcpc_log(port, "SRC.Ra on CC2, SRC.Rd on CC1\n");
+ } else if (cc1 == 0x0) {
+ tcpc_log(port, "SRC.Ra only on CC2\n");
+ return -EFAULT;
+ } else
+ return -EFAULT;
+ break;
+ case 0x2:
+ if (cc1 == 0x1) {
+ *state = TYPEC_STATE_SRC_RD_RA;
+ tcpc_log(port, "SRC.Ra on CC1, SRC.Rd on CC2\n");
+ } else if (cc1 == 0x0) {
+ *state = TYPEC_STATE_SRC_RD;
+ tcpc_log(port, "SRC.Rd on CC2\n");
+ } else
+ return -EFAULT;
+ break;
+ case 0x3:
+ *state = TYPEC_STATE_SRC_RESERVED;
+ return -EFAULT;
+ }
+ } else if (cc1) {
+ *polarity = TYPEC_POLARITY_CC1;
+
+ switch (cc1) {
+ case 0x1:
+ tcpc_log(port, "SRC.Ra only on CC1\n");
+ return -EFAULT;
+ case 0x2:
+ *state = TYPEC_STATE_SRC_RD;
+ tcpc_log(port, "SRC.Rd on CC1\n");
+ break;
+ case 0x3:
+ *state = TYPEC_STATE_SRC_RESERVED;
+ return -EFAULT;
+ }
+ } else {
+ *state = TYPEC_STATE_OPEN;
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
+int tcpc_clear_alert(struct tcpc_port *port, uint16_t clear_mask)
+{
+ int err;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ err = dm_i2c_write(port->i2c_dev, TCPC_ALERT, (const uint8_t *)&clear_mask, 2);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int tcpc_send_command(struct tcpc_port *port, uint8_t command)
+{
+ int err;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ err = dm_i2c_write(port->i2c_dev, TCPC_COMMAND, (const uint8_t *)&command, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int tcpc_polling_reg(struct tcpc_port *port, uint8_t reg,
+ uint8_t reg_width, uint16_t mask, uint16_t value, ulong timeout_ms)
+{
+ uint16_t val = 0;
+ int err;
+ ulong start;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ tcpc_debug_log(port, "%s reg 0x%x, mask 0x%x, value 0x%x\n", __func__, reg, mask, value);
+
+ /* TCPC registers is 8 bits or 16 bits */
+ if (reg_width != 1 && reg_width != 2)
+ return -EINVAL;
+
+ start = get_timer(0); /* Get current timestamp */
+ do {
+ err = dm_i2c_read(port->i2c_dev, reg, (uint8_t *)&val, reg_width);
+ if (err)
+ return -EIO;
+
+ if ((val & mask) == value)
+ return 0;
+ } while (get_timer(0) < (start + timeout_ms));
+
+ return -ETIME;
+}
+
+void tcpc_print_log(struct tcpc_port *port)
+{
+ if (port == NULL)
+ return;
+
+ if (port->log_print == port->log_p) /*nothing to output*/
+ return;
+
+ printf("%s", port->log_print);
+
+ port->log_print = port->log_p;
+}
+
+int tcpc_setup_dfp_mode(struct tcpc_port *port)
+{
+ enum typec_cc_polarity pol;
+ enum typec_cc_state state;
+ int ret;
+
+ if ((port == NULL) || (port->i2c_dev == NULL))
+ return -EINVAL;
+
+ if (tcpc_pd_sink_check_charging(port)) {
+ tcpc_log(port, "%s: Can't apply DFP mode when PD is charging\n",
+ __func__);
+ return -EPERM;
+ }
+
+ tcpc_set_cc_to_source(port);
+
+ ret = tcpc_send_command(port, TCPC_CMD_LOOK4CONNECTION);
+ if (ret)
+ return ret;
+
+ /* At least wait tCcStatusDelay + tTCPCFilter + tCcTCPCSampleRate (max) = 200us + 500us + ?ms
+ * PTN5110 datasheet does not contain the sample rate value, according other productions,
+ * the sample rate is at ms level, about 2 ms -10ms. So wait 100ms should be enough.
+ */
+ mdelay(100);
+
+ ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_CC_STATUS, TCPC_ALERT_CC_STATUS, 100);
+ if (ret) {
+ tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_CC_STATUS bit failed, ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = tcpc_get_cc_status(port, &pol, &state);
+ tcpc_clear_alert(port, TCPC_ALERT_CC_STATUS);
+
+ if (!ret) {
+ /* If presenting as Rd/audio mode/open, return */
+ if (state != TYPEC_STATE_SRC_RD_RA && state != TYPEC_STATE_SRC_RD)
+ return -EPERM;
+
+ if (pol == TYPEC_POLARITY_CC1)
+ tcpc_debug_log(port, "polarity cc1\n");
+ else
+ tcpc_debug_log(port, "polarity cc2\n");
+
+ if (port->ss_sel_func)
+ port->ss_sel_func(pol);
+
+ ret = tcpc_set_plug_orientation(port, pol);
+ if (ret)
+ return ret;
+
+ /* Enable source vbus default voltage */
+ ret = tcpc_send_command(port, TCPC_CMD_SRC_VBUS_DEFAULT);
+ if (ret)
+ return ret;
+
+ /* The max vbus on time is 200ms, we add margin 100ms */
+ mdelay(300);
+
+ }
+
+ return 0;
+}
+
+int tcpc_setup_ufp_mode(struct tcpc_port *port)
+{
+ enum typec_cc_polarity pol;
+ enum typec_cc_state state;
+ int ret;
+
+ if ((port == NULL) || (port->i2c_dev == NULL))
+ return -EINVAL;
+
+ /* Check if the PD charge is working. If not, need to configure CC role for UFP */
+ if (!tcpc_pd_sink_check_charging(port)) {
+
+ /* Disable the source vbus once it is enabled by DFP mode */
+ tcpc_disable_src_vbus(port);
+
+ tcpc_set_cc_to_sink(port);
+
+ ret = tcpc_send_command(port, TCPC_CMD_LOOK4CONNECTION);
+ if (ret)
+ return ret;
+
+ /* At least wait tCcStatusDelay + tTCPCFilter + tCcTCPCSampleRate (max) = 200us + 500us + ?ms
+ * PTN5110 datasheet does not contain the sample rate value, according other productions,
+ * the sample rate is at ms level, about 2 ms -10ms. So wait 100ms should be enough.
+ */
+ mdelay(100);
+
+ ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_CC_STATUS, TCPC_ALERT_CC_STATUS, 100);
+ if (ret) {
+ tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_CC_STATUS bit failed, ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = tcpc_get_cc_status(port, &pol, &state);
+ tcpc_clear_alert(port, TCPC_ALERT_CC_STATUS);
+
+ } else {
+ ret = tcpc_get_cc_status(port, &pol, &state);
+ }
+
+ if (!ret) {
+ /* If presenting not as sink, then return */
+ if (state != TYPEC_STATE_SNK_DEFAULT && state != TYPEC_STATE_SNK_POWER15 &&
+ state != TYPEC_STATE_SNK_POWER30)
+ return -EPERM;
+
+ if (pol == TYPEC_POLARITY_CC1)
+ tcpc_debug_log(port, "polarity cc1\n");
+ else
+ tcpc_debug_log(port, "polarity cc2\n");
+
+ if (port->ss_sel_func)
+ port->ss_sel_func(pol);
+
+ ret = tcpc_set_plug_orientation(port, pol);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int tcpc_disable_src_vbus(struct tcpc_port *port)
+{
+ int ret;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ /* Disable VBUS*/
+ ret = tcpc_send_command(port, TCPC_CMD_DISABLE_SRC_VBUS);
+ if (ret)
+ return ret;
+
+ /* The max vbus off time is 0.5ms, we add margin 0.5 ms */
+ mdelay(1);
+
+ return 0;
+}
+
+int tcpc_disable_sink_vbus(struct tcpc_port *port)
+{
+ int ret;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ /* Disable SINK VBUS*/
+ ret = tcpc_send_command(port, TCPC_CMD_DISABLE_SINK_VBUS);
+ if (ret)
+ return ret;
+
+ /* The max vbus off time is 0.5ms, we add margin 0.5 ms */
+ mdelay(1);
+
+ return 0;
+}
+
+
+static int tcpc_pd_receive_message(struct tcpc_port *port, struct pd_message *msg)
+{
+ int ret;
+ uint8_t cnt;
+ uint16_t val;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ /* Generally the max tSenderResponse is 30ms, max tTypeCSendSourceCap is 200ms, we set the timeout to 500ms */
+ ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_RX_STATUS, TCPC_ALERT_RX_STATUS, 500);
+ if (ret) {
+ tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_RX_STATUS bit failed, ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ cnt = 0;
+ ret = dm_i2c_read(port->i2c_dev, TCPC_RX_BYTE_CNT, (uint8_t *)&cnt, 1);
+ if (ret)
+ return -EIO;
+
+ if (cnt > 0) {
+ ret = dm_i2c_read(port->i2c_dev, TCPC_RX_BUF_FRAME_TYPE, (uint8_t *)msg, cnt);
+ if (ret)
+ return -EIO;
+
+ /* Clear RX status alert bit */
+ val = TCPC_ALERT_RX_STATUS;
+ ret = dm_i2c_write(port->i2c_dev, TCPC_ALERT, (const uint8_t *)&val, 2);
+ if (ret)
+ return -EIO;
+ }
+
+ return cnt;
+}
+
+static int tcpc_pd_transmit_message(struct tcpc_port *port, struct pd_message *msg_p, uint8_t bytes)
+{
+ int ret;
+ uint8_t valb;
+ uint16_t val;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ if (msg_p == NULL || bytes <= 0)
+ return -EINVAL;
+
+ ret = dm_i2c_write(port->i2c_dev, TCPC_TX_BYTE_CNT, (const uint8_t *)&bytes, 1);
+ if (ret)
+ return -EIO;
+
+ ret = dm_i2c_write(port->i2c_dev, TCPC_TX_HDR, (const uint8_t *)&(msg_p->header), bytes);
+ if (ret)
+ return -EIO;
+
+ valb = (3 << TCPC_TRANSMIT_RETRY_SHIFT) | (TCPC_TX_SOP << TCPC_TRANSMIT_TYPE_SHIFT);
+ ret = dm_i2c_write(port->i2c_dev, TCPC_TRANSMIT, (const uint8_t *)&valb, 1);
+ if (ret)
+ return -EIO;
+
+ /* Max tReceive is 1.1ms, we set to 5ms timeout */
+ ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_TX_SUCCESS, TCPC_ALERT_TX_SUCCESS, 5);
+ if (ret) {
+ if (ret == -ETIME) {
+ ret = dm_i2c_read(port->i2c_dev, TCPC_ALERT, (uint8_t *)&val, 2);
+ if (ret)
+ return -EIO;
+
+ if (val & TCPC_ALERT_TX_FAILED)
+ tcpc_log(port, "%s: PD TX FAILED, ALERT = 0x%x\n", __func__, val);
+
+ if (val & TCPC_ALERT_TX_DISCARDED)
+ tcpc_log(port, "%s: PD TX DISCARDED, ALERT = 0x%x\n", __func__, val);
+
+ } else {
+ tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_TX_SUCCESS bit failed, ret = %d\n",
+ __func__, ret);
+ }
+ } else {
+ port->tx_msg_id = (port->tx_msg_id + 1) & PD_HEADER_ID_MASK;
+ }
+
+ /* Clear ALERT status */
+ val &= (TCPC_ALERT_TX_FAILED | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_SUCCESS);
+ ret = dm_i2c_write(port->i2c_dev, TCPC_ALERT, (const uint8_t *)&val, 2);
+ if (ret)
+ return -EIO;
+
+ return ret;
+}
+
+static void tcpc_log_source_caps(struct tcpc_port *port, struct pd_message *msg, unsigned int capcount)
+{
+ int i;
+
+ for (i = 0; i < capcount; i++) {
+ u32 pdo = msg->payload[i];
+ enum pd_pdo_type type = pdo_type(pdo);
+
+ tcpc_log(port, "PDO %d: type %d, ",
+ i, type);
+
+ switch (type) {
+ case PDO_TYPE_FIXED:
+ tcpc_log(port, "%u mV, %u mA [%s%s%s%s%s%s]\n",
+ pdo_fixed_voltage(pdo),
+ pdo_max_current(pdo),
+ (pdo & PDO_FIXED_DUAL_ROLE) ?
+ "R" : "",
+ (pdo & PDO_FIXED_SUSPEND) ?
+ "S" : "",
+ (pdo & PDO_FIXED_HIGHER_CAP) ?
+ "H" : "",
+ (pdo & PDO_FIXED_USB_COMM) ?
+ "U" : "",
+ (pdo & PDO_FIXED_DATA_SWAP) ?
+ "D" : "",
+ (pdo & PDO_FIXED_EXTPOWER) ?
+ "E" : "");
+ break;
+ case PDO_TYPE_VAR:
+ tcpc_log(port, "%u-%u mV, %u mA\n",
+ pdo_min_voltage(pdo),
+ pdo_max_voltage(pdo),
+ pdo_max_current(pdo));
+ break;
+ case PDO_TYPE_BATT:
+ tcpc_log(port, "%u-%u mV, %u mW\n",
+ pdo_min_voltage(pdo),
+ pdo_max_voltage(pdo),
+ pdo_max_power(pdo));
+ break;
+ default:
+ tcpc_log(port, "undefined\n");
+ break;
+ }
+ }
+}
+
+static int tcpc_pd_select_pdo(struct pd_message *msg, uint32_t capcount, uint32_t max_snk_mv, uint32_t max_snk_ma)
+{
+ unsigned int i, max_mw = 0, max_mv = 0;
+ int ret = -EINVAL;
+
+ /*
+ * Select the source PDO providing the most power while staying within
+ * the board's voltage limits. Prefer PDO providing exp
+ */
+ for (i = 0; i < capcount; i++) {
+ u32 pdo = msg->payload[i];
+ enum pd_pdo_type type = pdo_type(pdo);
+ unsigned int mv, ma, mw;
+
+ if (type == PDO_TYPE_FIXED)
+ mv = pdo_fixed_voltage(pdo);
+ else
+ mv = pdo_min_voltage(pdo);
+
+ if (type == PDO_TYPE_BATT) {
+ mw = pdo_max_power(pdo);
+ } else {
+ ma = min(pdo_max_current(pdo),
+ max_snk_ma);
+ mw = ma * mv / 1000;
+ }
+
+ /* Perfer higher voltages if available */
+ if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
+ mv <= max_snk_mv) {
+ ret = i;
+ max_mw = mw;
+ max_mv = mv;
+ }
+ }
+
+ return ret;
+}
+
+static int tcpc_pd_build_request(struct tcpc_port *port,
+ struct pd_message *msg,
+ uint32_t capcount,
+ uint32_t max_snk_mv,
+ uint32_t max_snk_ma,
+ uint32_t max_snk_mw,
+ uint32_t operating_snk_mw,
+ uint32_t *rdo)
+{
+ unsigned int mv, ma, mw, flags;
+ unsigned int max_ma, max_mw;
+ enum pd_pdo_type type;
+ int index;
+ u32 pdo;
+
+ index = tcpc_pd_select_pdo(msg, capcount, max_snk_mv, max_snk_ma);
+ if (index < 0)
+ return -EINVAL;
+
+ pdo = msg->payload[index];
+ type = pdo_type(pdo);
+
+ if (type == PDO_TYPE_FIXED)
+ mv = pdo_fixed_voltage(pdo);
+ else
+ mv = pdo_min_voltage(pdo);
+
+ /* Select maximum available current within the board's power limit */
+ if (type == PDO_TYPE_BATT) {
+ mw = pdo_max_power(pdo);
+ ma = 1000 * min(mw, max_snk_mw) / mv;
+ } else {
+ ma = min(pdo_max_current(pdo),
+ 1000 * max_snk_mw / mv);
+ }
+ ma = min(ma, max_snk_ma);
+
+ /* XXX: Any other flags need to be set? */
+ flags = 0;
+
+ /* Set mismatch bit if offered power is less than operating power */
+ mw = ma * mv / 1000;
+ max_ma = ma;
+ max_mw = mw;
+ if (mw < operating_snk_mw) {
+ flags |= RDO_CAP_MISMATCH;
+ max_mw = operating_snk_mw;
+ max_ma = max_mw * 1000 / mv;
+ }
+
+ if (type == PDO_TYPE_BATT) {
+ *rdo = RDO_BATT(index + 1, mw, max_mw, flags);
+
+ tcpc_log(port, "Requesting PDO %d: %u mV, %u mW%s\n",
+ index, mv, mw,
+ flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+ } else {
+ *rdo = RDO_FIXED(index + 1, ma, max_ma, flags);
+
+ tcpc_log(port, "Requesting PDO %d: %u mV, %u mA%s\n",
+ index, mv, ma,
+ flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+ }
+
+ return 0;
+}
+
+static void tcpc_pd_sink_process(struct tcpc_port *port)
+{
+ int ret;
+ uint8_t msgtype;
+ uint32_t objcnt;
+ struct pd_message msg;
+ enum pd_sink_state pd_state = WAIT_SOURCE_CAP;
+
+ while (tcpc_pd_receive_message(port, &msg) > 0) {
+
+ msgtype = pd_header_type(msg.header);
+ objcnt = pd_header_cnt_le(msg.header);
+
+ tcpc_debug_log(port, "get msg, type %d, cnt %d\n", msgtype, objcnt);
+
+ switch (pd_state) {
+ case WAIT_SOURCE_CAP:
+ case SINK_READY:
+ if (msgtype != PD_DATA_SOURCE_CAP)
+ continue;
+
+ uint32_t rdo = 0;
+
+ tcpc_log_source_caps(port, &msg, objcnt);
+
+ tcpc_pd_build_request(port, &msg, objcnt,
+ port->cfg.max_snk_mv, port->cfg.max_snk_ma,
+ port->cfg.max_snk_mw, port->cfg.op_snk_mv,
+ &rdo);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.header = PD_HEADER(PD_DATA_REQUEST, 0, 0, port->tx_msg_id, 1); /* power sink, data device, id 0, len 1 */
+ msg.payload[0] = rdo;
+
+ ret = tcpc_pd_transmit_message(port, &msg, 6);
+ if (ret)
+ tcpc_log(port, "send request failed\n");
+ else
+ pd_state = WAIT_SOURCE_ACCEPT;
+
+ break;
+ case WAIT_SOURCE_ACCEPT:
+ if (objcnt > 0) /* Should be ctrl message */
+ continue;
+
+ if (msgtype == PD_CTRL_ACCEPT) {
+ pd_state = WAIT_SOURCE_READY;
+ tcpc_log(port, "Source accept request\n");
+ } else if (msgtype == PD_CTRL_REJECT) {
+ tcpc_log(port, "Source reject request\n");
+ return;
+ }
+
+ break;
+ case WAIT_SOURCE_READY:
+ if (objcnt > 0) /* Should be ctrl message */
+ continue;
+
+ if (msgtype == PD_CTRL_PS_RDY) {
+ tcpc_log(port, "PD source ready!\n");
+ pd_state = SINK_READY;
+ }
+
+ break;
+ default:
+ tcpc_log(port, "unexpect status: %u\n", pd_state);
+ break;
+ }
+ }
+}
+
+bool tcpc_pd_sink_check_charging(struct tcpc_port *port)
+{
+ uint8_t valb;
+ int err;
+ enum typec_cc_polarity pol;
+ enum typec_cc_state state;
+
+ if (port == NULL)
+ return false;
+
+ /* Check the CC status, must be sink */
+ err = tcpc_get_cc_status(port, &pol, &state);
+ if (err || (state != TYPEC_STATE_SNK_POWER15
+ && state != TYPEC_STATE_SNK_POWER30
+ && state != TYPEC_STATE_SNK_DEFAULT)) {
+ tcpc_debug_log(port, "TCPC wrong state for PD charging, err = %d, CC = 0x%x\n",
+ err, state);
+ return false;
+ }
+
+ /* Check the VBUS PRES and SINK VBUS for dead battery */
+ err = dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1);
+ if (err) {
+ tcpc_debug_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return false;
+ }
+
+ if (!(valb & TCPC_POWER_STATUS_VBUS_PRES)) {
+ tcpc_debug_log(port, "VBUS NOT PRES \n");
+ return false;
+ }
+
+ if (!(valb & TCPC_POWER_STATUS_SINKING_VBUS)) {
+ tcpc_debug_log(port, "SINK VBUS is not enabled for dead battery\n");
+ return false;
+ }
+
+ return true;
+}
+
+static int tcpc_pd_sink_disable(struct tcpc_port *port)
+{
+ uint8_t valb;
+ int err;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ port->pd_state = UNATTACH;
+
+ /* Check the VBUS PRES and SINK VBUS for dead battery */
+ err = dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ if ((valb & TCPC_POWER_STATUS_VBUS_PRES) && (valb & TCPC_POWER_STATUS_SINKING_VBUS)) {
+ dm_i2c_read(port->i2c_dev, TCPC_POWER_CTRL, (uint8_t *)&valb, 1);
+ valb &= ~TCPC_POWER_CTRL_AUTO_DISCH_DISCO; /* disable AutoDischargeDisconnect */
+ dm_i2c_write(port->i2c_dev, TCPC_POWER_CTRL, (const uint8_t *)&valb, 1);
+
+ tcpc_disable_sink_vbus(port);
+ }
+
+ if (port->cfg.switch_setup_func)
+ port->cfg.switch_setup_func(port);
+
+ return 0;
+}
+
+static int tcpc_pd_sink_init(struct tcpc_port *port)
+{
+ uint8_t valb;
+ uint16_t val;
+ int err;
+ enum typec_cc_polarity pol;
+ enum typec_cc_state state;
+
+ if (port == NULL)
+ return -EINVAL;
+
+ port->pd_state = UNATTACH;
+
+ /* Check the VBUS PRES and SINK VBUS for dead battery */
+ err = dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ if (!(valb & TCPC_POWER_STATUS_VBUS_PRES)) {
+ tcpc_debug_log(port, "VBUS NOT PRES \n");
+ return -EPERM;
+ }
+
+ if (!(valb & TCPC_POWER_STATUS_SINKING_VBUS)) {
+ tcpc_debug_log(port, "SINK VBUS is not enabled for dead battery\n");
+ return -EPERM;
+ }
+
+ err = dm_i2c_read(port->i2c_dev, TCPC_ALERT, (uint8_t *)&val, 2);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ if (!(val & TCPC_ALERT_CC_STATUS)) {
+ tcpc_debug_log(port, "CC STATUS not detected for dead battery\n");
+ return -EPERM;
+ }
+
+ err = tcpc_get_cc_status(port, &pol, &state);
+ if (err || (state != TYPEC_STATE_SNK_POWER15
+ && state != TYPEC_STATE_SNK_POWER30
+ && state != TYPEC_STATE_SNK_DEFAULT)) {
+ tcpc_log(port, "TCPC wrong state for dead battery, err = %d, CC = 0x%x\n",
+ err, state);
+ return -EPERM;
+ } else {
+ err = tcpc_set_plug_orientation(port, pol);
+ if (err) {
+ tcpc_log(port, "TCPC set plug orientation failed, err = %d\n", err);
+ return err;
+ }
+ port->pd_state = ATTACHED;
+ }
+
+ dm_i2c_read(port->i2c_dev, TCPC_POWER_CTRL, (uint8_t *)&valb, 1);
+ valb &= ~TCPC_POWER_CTRL_AUTO_DISCH_DISCO; /* disable AutoDischargeDisconnect */
+ dm_i2c_write(port->i2c_dev, TCPC_POWER_CTRL, (const uint8_t *)&valb, 1);
+
+ if (port->cfg.switch_setup_func)
+ port->cfg.switch_setup_func(port);
+
+ /* As sink role */
+ valb = 0x00;
+ err = dm_i2c_write(port->i2c_dev, TCPC_MSG_HDR_INFO, (const uint8_t *)&valb, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ /* Enable rx */
+ valb = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
+ err = dm_i2c_write(port->i2c_dev, TCPC_RX_DETECT, (const uint8_t *)&valb, 1);
+ if (err) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err);
+ return -EIO;
+ }
+
+ tcpc_pd_sink_process(port);
+
+ return 0;
+}
+
+int tcpc_init(struct tcpc_port *port, struct tcpc_port_config config, ss_mux_sel ss_sel_func)
+{
+ int ret;
+ uint8_t valb;
+ uint16_t vid, pid;
+ struct udevice *bus;
+ struct udevice *i2c_dev = NULL;
+
+ memset(port, 0, sizeof(struct tcpc_port));
+
+ if (port == NULL)
+ return -EINVAL;
+
+ port->cfg = config;
+ port->tx_msg_id = 0;
+ port->ss_sel_func = ss_sel_func;
+ port->log_p = (char *)&(port->logbuffer);
+ port->log_size = TCPC_LOG_BUFFER_SIZE;
+ port->log_print = port->log_p;
+ memset(&(port->logbuffer), 0, TCPC_LOG_BUFFER_SIZE);
+
+ ret = uclass_get_device_by_seq(UCLASS_I2C, port->cfg.i2c_bus, &bus);
+ if (ret) {
+ printf("%s: Can't find bus\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = dm_i2c_probe(bus, port->cfg.addr, 0, &i2c_dev);
+ if (ret) {
+ printf("%s: Can't find device id=0x%x\n",
+ __func__, config.addr);
+ return -ENODEV;
+ }
+
+ port->i2c_dev = i2c_dev;
+
+ /* Check the Initialization Status bit in 1s */
+ ret = tcpc_polling_reg(port, TCPC_POWER_STATUS, 1, TCPC_POWER_STATUS_UNINIT, 0, 1000);
+ if (ret) {
+ tcpc_log(port, "%s: Polling TCPC POWER STATUS Initialization Status bit failed, ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1);
+ tcpc_debug_log(port, "POWER STATUS: 0x%x\n", valb);
+
+ /* Clear AllRegistersResetToDefault */
+ valb = 0x80;
+ ret = dm_i2c_write(port->i2c_dev, TCPC_FAULT_STATUS, (const uint8_t *)&valb, 1);
+ if (ret) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, ret);
+ return -EIO;
+ }
+
+ /* Read Vendor ID and Product ID */
+ ret = dm_i2c_read(port->i2c_dev, TCPC_VENDOR_ID, (uint8_t *)&vid, 2);
+ if (ret) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, ret);
+ return -EIO;
+ }
+
+ ret = dm_i2c_read(port->i2c_dev, TCPC_PRODUCT_ID, (uint8_t *)&pid, 2);
+ if (ret) {
+ tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, ret);
+ return -EIO;
+ }
+
+ tcpc_log(port, "TCPC: Vendor ID [0x%x], Product ID [0x%x], Addr [I2C%u 0x%x]\n",
+ vid, pid, port->cfg.i2c_bus, port->cfg.addr);
+
+ if (!port->cfg.disable_pd) {
+ if (port->cfg.port_type == TYPEC_PORT_UFP
+ || port->cfg.port_type == TYPEC_PORT_DRP)
+ tcpc_pd_sink_init(port);
+ } else {
+ tcpc_pd_sink_disable(port);
+ }
+
+ tcpc_clear_alert(port, 0xffff);
+
+ tcpc_print_log(port);
+
+ return 0;
+}
diff --git a/board/freescale/common/tcpc.h b/board/freescale/common/tcpc.h
new file mode 100644
index 0000000000..ff749518a8
--- /dev/null
+++ b/board/freescale/common/tcpc.h
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef __TCPCI_H
+#define __TCPCI_H
+
+#include <dm.h>
+
+#define TCPC_VENDOR_ID 0x0
+#define TCPC_PRODUCT_ID 0x2
+
+#define TCPC_ALERT 0x10
+#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_TCPC_CTRL 0x19
+#define TCPC_TCPC_CTRL_BIST_MODE BIT(1)
+#define TCPC_TCPC_CTRL_ORIENTATION BIT(0)
+
+#define TCPC_ROLE_CTRL 0x1a
+#define TCPC_ROLE_CTRL_DRP BIT(6)
+#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4
+#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3
+#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_SHIFT 2
+#define TCPC_ROLE_CTRL_CC2_MASK 0x3
+#define TCPC_ROLE_CTRL_CC1_SHIFT 0
+#define TCPC_ROLE_CTRL_CC1_MASK 0x3
+#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_POWER_CTRL 0x1c
+#define TCPC_POWER_CTRL_EN_VCONN BIT(0)
+#define TCPC_POWER_CTRL_VCONN_POWER BIT(1)
+#define TCPC_POWER_CTRL_FORCE_DISCH BIT(2)
+#define TCPC_POWER_CTRL_EN_BLEED_CH BIT(3)
+#define TCPC_POWER_CTRL_AUTO_DISCH_DISCO BIT(4)
+#define TCPC_POWER_CTRL_DIS_V_ALARMS BIT(5)
+#define TCPC_POWER_CTRL_VBUS_V_MONITOR BIT(6)
+
+#define TCPC_CC_STATUS 0x1d
+#define TCPC_CC_STATUS_LOOK4CONN BIT(5)
+#define TCPC_CC_STATUS_TERM BIT(4)
+#define TCPC_CC_STATUS_CC2_SHIFT 2
+#define TCPC_CC_STATUS_CC2_MASK 0x3
+#define TCPC_CC_STATUS_CC1_SHIFT 0
+#define TCPC_CC_STATUS_CC1_MASK 0x3
+
+#define TCPC_POWER_STATUS 0x1e
+#define TCPC_POWER_STATUS_UNINIT BIT(6)
+#define TCPC_POWER_STATUS_VBUS_DET BIT(3)
+#define TCPC_POWER_STATUS_VBUS_PRES BIT(2)
+#define TCPC_POWER_STATUS_SINKING_VBUS BIT(0)
+
+#define TCPC_FAULT_STATUS 0x1f
+
+#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_MSG_HDR_INFO 0x2e
+#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3)
+#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0)
+#define TCPC_MSG_HDR_INFO_REV_SHIFT 1
+#define TCPC_MSG_HDR_INFO_REV_MASK 0x3
+
+#define TCPC_RX_DETECT 0x2f
+#define TCPC_RX_DETECT_HARD_RESET BIT(5)
+#define TCPC_RX_DETECT_SOP BIT(0)
+
+#define TCPC_RX_BYTE_CNT 0x30
+#define TCPC_RX_BUF_FRAME_TYPE 0x31
+#define TCPC_RX_HDR 0x32
+#define TCPC_RX_DATA 0x34 /* through 0x4f */
+
+#define TCPC_TRANSMIT 0x50
+#define TCPC_TRANSMIT_RETRY_SHIFT 4
+#define TCPC_TRANSMIT_RETRY_MASK 0x3
+#define TCPC_TRANSMIT_TYPE_SHIFT 0
+#define TCPC_TRANSMIT_TYPE_MASK 0x7
+
+#define TCPC_TX_BYTE_CNT 0x51
+#define TCPC_TX_HDR 0x52
+#define TCPC_TX_DATA 0x54 /* through 0x6f */
+
+#define TCPC_VBUS_VOLTAGE 0x70
+#define TCPC_VBUS_VOL_MASK 0x3ff
+#define TCPC_VBUS_VOL_SCALE_FACTOR_MASK 0xc00
+#define TCPC_VBUS_VOL_SCALE_FACTOR_SHIFT 10
+#define TCPC_VBUS_VOL_MV_UNIT 25
+
+#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
+
+enum typec_role {
+ TYPEC_SINK,
+ TYPEC_SOURCE,
+ TYPEC_ROLE_UNKNOWN,
+};
+
+enum typec_data_role {
+ TYPEC_DEVICE,
+ TYPEC_HOST,
+};
+
+enum typec_cc_polarity {
+ TYPEC_POLARITY_CC1,
+ TYPEC_POLARITY_CC2,
+};
+
+enum typec_cc_state {
+ TYPEC_STATE_OPEN,
+ TYPEC_STATE_SRC_BOTH_RA,
+ TYPEC_STATE_SRC_RD_RA,
+ TYPEC_STATE_SRC_RD,
+ TYPEC_STATE_SRC_RESERVED,
+ TYPEC_STATE_SNK_DEFAULT,
+ TYPEC_STATE_SNK_POWER15,
+ TYPEC_STATE_SNK_POWER30,
+};
+
+
+/* USB PD Messages */
+enum pd_ctrl_msg_type {
+ /* 0 Reserved */
+ PD_CTRL_GOOD_CRC = 1,
+ PD_CTRL_GOTO_MIN = 2,
+ PD_CTRL_ACCEPT = 3,
+ PD_CTRL_REJECT = 4,
+ PD_CTRL_PING = 5,
+ PD_CTRL_PS_RDY = 6,
+ PD_CTRL_GET_SOURCE_CAP = 7,
+ PD_CTRL_GET_SINK_CAP = 8,
+ PD_CTRL_DR_SWAP = 9,
+ PD_CTRL_PR_SWAP = 10,
+ PD_CTRL_VCONN_SWAP = 11,
+ PD_CTRL_WAIT = 12,
+ PD_CTRL_SOFT_RESET = 13,
+ /* 14-15 Reserved */
+};
+
+enum pd_data_msg_type {
+ /* 0 Reserved */
+ PD_DATA_SOURCE_CAP = 1,
+ PD_DATA_REQUEST = 2,
+ PD_DATA_BIST = 3,
+ PD_DATA_SINK_CAP = 4,
+ /* 5-14 Reserved */
+ PD_DATA_VENDOR_DEF = 15,
+};
+
+enum tcpc_transmit_type {
+ TCPC_TX_SOP = 0,
+ TCPC_TX_SOP_PRIME = 1,
+ TCPC_TX_SOP_PRIME_PRIME = 2,
+ TCPC_TX_SOP_DEBUG_PRIME = 3,
+ TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4,
+ TCPC_TX_HARD_RESET = 5,
+ TCPC_TX_CABLE_RESET = 6,
+ TCPC_TX_BIST_MODE_2 = 7
+};
+
+enum pd_sink_state{
+ UNATTACH = 0,
+ ATTACHED,
+ WAIT_SOURCE_CAP,
+ WAIT_SOURCE_ACCEPT,
+ WAIT_SOURCE_READY,
+ SINK_READY,
+};
+
+
+#define PD_REV10 0x0
+#define PD_REV20 0x1
+
+#define PD_HEADER_CNT_SHIFT 12
+#define PD_HEADER_CNT_MASK 0x7
+#define PD_HEADER_ID_SHIFT 9
+#define PD_HEADER_ID_MASK 0x7
+#define PD_HEADER_PWR_ROLE BIT(8)
+#define PD_HEADER_REV_SHIFT 6
+#define PD_HEADER_REV_MASK 0x3
+#define PD_HEADER_DATA_ROLE BIT(5)
+#define PD_HEADER_TYPE_SHIFT 0
+#define PD_HEADER_TYPE_MASK 0xf
+
+#define PD_HEADER(type, pwr, data, id, cnt) \
+ ((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) | \
+ ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) | \
+ ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) | \
+ (PD_REV20 << PD_HEADER_REV_SHIFT) | \
+ (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) | \
+ (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT))
+
+
+static inline unsigned int pd_header_cnt(uint16_t header)
+{
+ return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK;
+}
+
+static inline unsigned int pd_header_cnt_le(__le16 header)
+{
+ return pd_header_cnt(le16_to_cpu(header));
+}
+
+static inline unsigned int pd_header_type(uint16_t header)
+{
+ return (header >> PD_HEADER_TYPE_SHIFT) & PD_HEADER_TYPE_MASK;
+}
+
+static inline unsigned int pd_header_type_le(__le16 header)
+{
+ return pd_header_type(le16_to_cpu(header));
+}
+
+#define PD_MAX_PAYLOAD 7
+
+struct pd_message {
+ uint8_t frametype;
+ uint16_t header;
+ uint32_t payload[PD_MAX_PAYLOAD];
+} __packed;
+
+enum pd_pdo_type {
+ PDO_TYPE_FIXED = 0,
+ PDO_TYPE_BATT = 1,
+ PDO_TYPE_VAR = 2,
+};
+
+
+#define PDO_TYPE_SHIFT 30
+#define PDO_TYPE_MASK 0x3
+
+#define PDO_TYPE(t) ((t) << PDO_TYPE_SHIFT)
+
+#define PDO_VOLT_MASK 0x3ff
+#define PDO_CURR_MASK 0x3ff
+#define PDO_PWR_MASK 0x3ff
+
+#define PDO_FIXED_DUAL_ROLE BIT(29) /* Power role swap supported */
+#define PDO_FIXED_SUSPEND BIT(28) /* USB Suspend supported (Source) */
+#define PDO_FIXED_HIGHER_CAP BIT(28) /* Requires more than vSafe5V (Sink) */
+#define PDO_FIXED_EXTPOWER BIT(27) /* Externally powered */
+#define PDO_FIXED_USB_COMM BIT(26) /* USB communications capable */
+#define PDO_FIXED_DATA_SWAP BIT(25) /* Data role swap supported */
+#define PDO_FIXED_VOLT_SHIFT 10 /* 50mV units */
+#define PDO_FIXED_CURR_SHIFT 0 /* 10mA units */
+
+#define PDO_FIXED_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT)
+#define PDO_FIXED_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT)
+
+#define PDO_FIXED(mv, ma, flags) \
+ (PDO_TYPE(PDO_TYPE_FIXED) | (flags) | \
+ PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma))
+
+#define PDO_BATT_MAX_VOLT_SHIFT 20 /* 50mV units */
+#define PDO_BATT_MIN_VOLT_SHIFT 10 /* 50mV units */
+#define PDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */
+
+#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MIN_VOLT_SHIFT)
+#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MAX_VOLT_SHIFT)
+#define PDO_BATT_MAX_POWER(mw) ((((mw) / 250) & PDO_PWR_MASK) << PDO_BATT_MAX_PWR_SHIFT)
+
+#define PDO_BATT(min_mv, max_mv, max_mw) \
+ (PDO_TYPE(PDO_TYPE_BATT) | PDO_BATT_MIN_VOLT(min_mv) | \
+ PDO_BATT_MAX_VOLT(max_mv) | PDO_BATT_MAX_POWER(max_mw))
+
+#define PDO_VAR_MAX_VOLT_SHIFT 20 /* 50mV units */
+#define PDO_VAR_MIN_VOLT_SHIFT 10 /* 50mV units */
+#define PDO_VAR_MAX_CURR_SHIFT 0 /* 10mA units */
+
+#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT)
+#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT)
+#define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT)
+
+#define PDO_VAR(min_mv, max_mv, max_ma) \
+ (PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) | \
+ PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma))
+
+static inline enum pd_pdo_type pdo_type(uint32_t pdo)
+{
+ return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
+}
+
+static inline unsigned int pdo_fixed_voltage(uint32_t pdo)
+{
+ return ((pdo >> PDO_FIXED_VOLT_SHIFT) & PDO_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_min_voltage(uint32_t pdo)
+{
+ return ((pdo >> PDO_VAR_MIN_VOLT_SHIFT) & PDO_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_max_voltage(uint32_t pdo)
+{
+ return ((pdo >> PDO_VAR_MAX_VOLT_SHIFT) & PDO_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_max_current(uint32_t pdo)
+{
+ return ((pdo >> PDO_VAR_MAX_CURR_SHIFT) & PDO_CURR_MASK) * 10;
+}
+
+static inline unsigned int pdo_max_power(uint32_t pdo)
+{
+ return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250;
+}
+
+/* RDO: Request Data Object */
+#define RDO_OBJ_POS_SHIFT 28
+#define RDO_OBJ_POS_MASK 0x7
+#define RDO_GIVE_BACK BIT(27) /* Supports reduced operating current */
+#define RDO_CAP_MISMATCH BIT(26) /* Not satisfied by source caps */
+#define RDO_USB_COMM BIT(25) /* USB communications capable */
+#define RDO_NO_SUSPEND BIT(24) /* USB Suspend not supported */
+
+#define RDO_PWR_MASK 0x3ff
+#define RDO_CURR_MASK 0x3ff
+
+#define RDO_FIXED_OP_CURR_SHIFT 10
+#define RDO_FIXED_MAX_CURR_SHIFT 0
+
+#define RDO_OBJ(idx) (((idx) & RDO_OBJ_POS_MASK) << RDO_OBJ_POS_SHIFT)
+
+#define PDO_FIXED_OP_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_OP_CURR_SHIFT)
+#define PDO_FIXED_MAX_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_MAX_CURR_SHIFT)
+
+#define RDO_FIXED(idx, op_ma, max_ma, flags) \
+ (RDO_OBJ(idx) | (flags) | \
+ PDO_FIXED_OP_CURR(op_ma) | PDO_FIXED_MAX_CURR(max_ma))
+
+#define RDO_BATT_OP_PWR_SHIFT 10 /* 250mW units */
+#define RDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */
+
+#define RDO_BATT_OP_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_OP_PWR_SHIFT)
+#define RDO_BATT_MAX_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_MAX_PWR_SHIFT)
+
+#define RDO_BATT(idx, op_mw, max_mw, flags) \
+ (RDO_OBJ(idx) | (flags) | \
+ RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw))
+
+static inline unsigned int rdo_index(u32 rdo)
+{
+ return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK;
+}
+
+static inline unsigned int rdo_op_current(u32 rdo)
+{
+ return ((rdo >> RDO_FIXED_OP_CURR_SHIFT) & RDO_CURR_MASK) * 10;
+}
+
+static inline unsigned int rdo_max_current(u32 rdo)
+{
+ return ((rdo >> RDO_FIXED_MAX_CURR_SHIFT) &
+ RDO_CURR_MASK) * 10;
+}
+
+static inline unsigned int rdo_op_power(u32 rdo)
+{
+ return ((rdo >> RDO_BATT_OP_PWR_SHIFT) & RDO_PWR_MASK) * 250;
+}
+
+static inline unsigned int rdo_max_power(u32 rdo)
+{
+ return ((rdo >> RDO_BATT_MAX_PWR_SHIFT) & RDO_PWR_MASK) * 250;
+}
+
+#define TCPC_LOG_BUFFER_SIZE 1024
+
+struct tcpc_port;
+
+typedef void (*ss_mux_sel)(enum typec_cc_polarity pol);
+typedef int (*ext_pd_switch_setup)(struct tcpc_port *port_p);
+
+enum tcpc_port_type {
+ TYPEC_PORT_DFP,
+ TYPEC_PORT_UFP,
+ TYPEC_PORT_DRP,
+};
+
+struct tcpc_port_config {
+ uint8_t i2c_bus;
+ uint8_t addr;
+ enum tcpc_port_type port_type;
+ uint32_t max_snk_mv;
+ uint32_t max_snk_ma;
+ uint32_t max_snk_mw;
+ uint32_t op_snk_mv;
+ bool disable_pd;
+ ext_pd_switch_setup switch_setup_func;
+};
+
+struct tcpc_port {
+ struct tcpc_port_config cfg;
+ struct udevice *i2c_dev;
+ ss_mux_sel ss_sel_func;
+ enum pd_sink_state pd_state;
+ uint32_t tx_msg_id;
+ uint32_t log_size;
+ char logbuffer[TCPC_LOG_BUFFER_SIZE];
+ char *log_p;
+ char *log_print;
+};
+
+int tcpc_set_cc_to_source(struct tcpc_port *port);
+int tcpc_set_cc_to_sink(struct tcpc_port *port);
+int tcpc_set_plug_orientation(struct tcpc_port *port, enum typec_cc_polarity polarity);
+int tcpc_get_cc_status(struct tcpc_port *port, enum typec_cc_polarity *polarity, enum typec_cc_state *state);
+int tcpc_clear_alert(struct tcpc_port *port, uint16_t clear_mask);
+int tcpc_send_command(struct tcpc_port *port, uint8_t command);
+int tcpc_polling_reg(struct tcpc_port *port, uint8_t reg,
+ uint8_t reg_width, uint16_t mask, uint16_t value, ulong timeout_ms);
+int tcpc_setup_dfp_mode(struct tcpc_port *port);
+int tcpc_setup_ufp_mode(struct tcpc_port *port);
+int tcpc_disable_src_vbus(struct tcpc_port *port);
+int tcpc_init(struct tcpc_port *port, struct tcpc_port_config config, ss_mux_sel ss_sel_func);
+bool tcpc_pd_sink_check_charging(struct tcpc_port *port);
+void tcpc_print_log(struct tcpc_port *port);
+
+#ifdef CONFIG_SPL_BUILD
+int tcpc_setup_ufp_mode(struct tcpc_port *port)
+{
+ return 0;
+}
+int tcpc_setup_dfp_mode(struct tcpc_port *port)
+{
+ return 0;
+}
+
+int tcpc_disable_src_vbus(struct tcpc_port *port)
+{
+ return 0;
+}
+#endif
+#endif /* __TCPCI_H */
--
2.28.0