blob: 0b945218e007692cc861390e5f75bb922e4c8522 [file] [log] [blame]
/*
* Copyright 2017 NXP
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <i2c.h>
#include <time.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)
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)
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, uint32_t *caps, unsigned int capcount)
{
int i;
for (i = 0; i < capcount; i++) {
u32 pdo = caps[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(uint32_t *caps, 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 = caps[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,
uint32_t *caps,
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(caps, capcount, max_snk_mv, max_snk_ma);
if (index < 0)
return -EINVAL;
pdo = caps[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 *caps = (uint32_t *)&msg.payload;
uint32_t rdo = 0;
tcpc_log_source_caps(port, caps, objcnt);
tcpc_pd_build_request(port, caps, 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
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("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;
}