@@ -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
@@ -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
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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 */