1013 lines
24 KiB
C
1013 lines
24 KiB
C
/*
|
|
* 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;
|
|
}
|