449 lines
9.6 KiB
C
449 lines
9.6 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/usb_c/usbc.h>
|
|
#include <zephyr/usb_c/tcpci.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/drivers/usb_c/tcpci_priv.h>
|
|
|
|
LOG_MODULE_REGISTER(tcpci, CONFIG_USBC_LOG_LEVEL);
|
|
|
|
#define LOG_COMM_ERR_STR "Can't communicate with TCPC %s@%x (%s %x = %04x)"
|
|
|
|
const struct tcpci_reg_dump_map tcpci_std_regs[TCPCI_STD_REGS_SIZE] = {
|
|
{
|
|
.addr = TCPC_REG_VENDOR_ID,
|
|
.name = "VENDOR_ID",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_PRODUCT_ID,
|
|
.name = "PRODUCT_ID",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_BCD_DEV,
|
|
.name = "DEVICE_ID",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_TC_REV,
|
|
.name = "USBTYPEC_REV",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_PD_REV,
|
|
.name = "USBPD_REV_VER",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_PD_INT_REV,
|
|
.name = "PD_INTERFACE_REV",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_ALERT,
|
|
.name = "ALERT",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_ALERT_MASK,
|
|
.name = "ALERT_MASK",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_POWER_STATUS_MASK,
|
|
.name = "POWER_STATUS_MASK",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_FAULT_STATUS_MASK,
|
|
.name = "FAULT_STATUS_MASK",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_EXT_STATUS_MASK,
|
|
.name = "EXTENDED_STATUS_MASK",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_ALERT_EXT_MASK,
|
|
.name = "ALERT_EXTENDED_MASK",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_CONFIG_STD_OUTPUT,
|
|
.name = "CFG_STANDARD_OUTPUT",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_TCPC_CTRL,
|
|
.name = "TCPC_CONTROL",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_ROLE_CTRL,
|
|
.name = "ROLE_CONTROL",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_FAULT_CTRL,
|
|
.name = "FAULT_CONTROL",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_POWER_CTRL,
|
|
.name = "POWER_CONTROL",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_CC_STATUS,
|
|
.name = "CC_STATUS",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_POWER_STATUS,
|
|
.name = "POWER_STATUS",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_FAULT_STATUS,
|
|
.name = "FAULT_STATUS",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_EXT_STATUS,
|
|
.name = "EXTENDED_STATUS",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_ALERT_EXT,
|
|
.name = "ALERT_EXTENDED",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_DEV_CAP_1,
|
|
.name = "DEVICE_CAPABILITIES_1",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_DEV_CAP_2,
|
|
.name = "DEVICE_CAPABILITIES_2",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_STD_INPUT_CAP,
|
|
.name = "STANDARD_INPUT_CAPABILITIES",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_STD_OUTPUT_CAP,
|
|
.name = "STANDARD_OUTPUT_CAPABILITIES",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_CONFIG_EXT_1,
|
|
.name = "CFG_EXTENDED1",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_GENERIC_TIMER,
|
|
.name = "GENERIC_TIMER",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_MSG_HDR_INFO,
|
|
.name = "MESSAGE_HEADER_INFO",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_RX_DETECT,
|
|
.name = "RECEIVE_DETECT",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_TRANSMIT,
|
|
.name = "TRANSMIT",
|
|
.size = 1,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_VBUS_VOLTAGE,
|
|
.name = "VBUS_VOLTAGE",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_VBUS_SINK_DISCONNECT_THRESH,
|
|
.name = "VBUS_SINK_DISCONNECT_THRESHOLD",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_VBUS_STOP_DISCHARGE_THRESH,
|
|
.name = "VBUS_STOP_DISCHARGE_THRESHOLD",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG,
|
|
.name = "VBUS_VOLTAGE_ALARM_HI_CFG",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG,
|
|
.name = "VBUS_VOLTAGE_ALARM_LO_CFG",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_VBUS_NONDEFAULT_TARGET,
|
|
.name = "VBUS_NONDEFAULT_TARGET",
|
|
.size = 2,
|
|
},
|
|
{
|
|
.addr = TCPC_REG_DEV_CAP_3,
|
|
.name = "DEVICE_CAPABILITIES_3",
|
|
.size = 2,
|
|
},
|
|
};
|
|
|
|
int tcpci_read_reg8(const struct i2c_dt_spec *i2c, uint8_t reg, uint8_t *value)
|
|
{
|
|
int ret;
|
|
|
|
for (int a = 0; a < CONFIG_USBC_TCPC_TCPCI_I2C_RETRIES; a++) {
|
|
ret = i2c_write_read(i2c->bus, i2c->addr, ®, sizeof(reg), value, sizeof(*value));
|
|
|
|
if (ret == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
LOG_ERR(LOG_COMM_ERR_STR, i2c->bus->name, i2c->addr, "r8", reg, *value);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tcpci_write_reg8(const struct i2c_dt_spec *i2c, uint8_t reg, uint8_t value)
|
|
{
|
|
uint8_t buf[2] = {reg, value};
|
|
int ret;
|
|
|
|
for (int a = 0; a < CONFIG_USBC_TCPC_TCPCI_I2C_RETRIES; a++) {
|
|
ret = i2c_write(i2c->bus, buf, 2, i2c->addr);
|
|
|
|
if (ret == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
LOG_ERR(LOG_COMM_ERR_STR, i2c->bus->name, i2c->addr, "w8", reg, value);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tcpci_update_reg8(const struct i2c_dt_spec *i2c, uint8_t reg, uint8_t mask, uint8_t value)
|
|
{
|
|
uint8_t old_value;
|
|
int ret;
|
|
|
|
ret = tcpci_read_reg8(i2c, reg, &old_value);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
old_value &= ~mask;
|
|
old_value |= (value & mask);
|
|
|
|
ret = tcpci_write_reg8(i2c, reg, old_value);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tcpci_read_reg16(const struct i2c_dt_spec *i2c, uint8_t reg, uint16_t *value)
|
|
{
|
|
int ret;
|
|
|
|
for (int a = 0; a < CONFIG_USBC_TCPC_TCPCI_I2C_RETRIES; a++) {
|
|
ret = i2c_write_read(i2c->bus, i2c->addr, ®, sizeof(reg), value, sizeof(*value));
|
|
|
|
if (ret == 0) {
|
|
*value = sys_le16_to_cpu(*value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
LOG_ERR(LOG_COMM_ERR_STR, i2c->bus->name, i2c->addr, "r16", reg, *value);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tcpci_write_reg16(const struct i2c_dt_spec *i2c, uint8_t reg, uint16_t value)
|
|
{
|
|
value = sys_cpu_to_le16(value);
|
|
uint8_t *value_ptr = (uint8_t *)&value;
|
|
uint8_t buf[3] = {reg, value_ptr[0], value_ptr[1]};
|
|
int ret;
|
|
|
|
for (int a = 0; a < CONFIG_USBC_TCPC_TCPCI_I2C_RETRIES; a++) {
|
|
ret = i2c_write(i2c->bus, buf, 3, i2c->addr);
|
|
if (ret == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
LOG_ERR(LOG_COMM_ERR_STR, i2c->bus->name, i2c->addr, "w16", reg, value);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
enum tcpc_alert tcpci_alert_reg_to_enum(uint16_t reg)
|
|
{
|
|
/**
|
|
* Hard reset enum has priority since it causes other bits to be ignored. Other values
|
|
* are sorted by corresponding bits index in the register.
|
|
*/
|
|
if (reg & TCPC_REG_ALERT_RX_HARD_RST) {
|
|
/** Received Hard Reset message */
|
|
return TCPC_ALERT_HARD_RESET_RECEIVED;
|
|
} else if (reg & TCPC_REG_ALERT_CC_STATUS) {
|
|
/** CC status changed */
|
|
return TCPC_ALERT_CC_STATUS;
|
|
} else if (reg & TCPC_REG_ALERT_POWER_STATUS) {
|
|
/** Power status changed */
|
|
return TCPC_ALERT_POWER_STATUS;
|
|
} else if (reg & TCPC_REG_ALERT_RX_STATUS) {
|
|
/** Receive Buffer register changed */
|
|
return TCPC_ALERT_MSG_STATUS;
|
|
} else if (reg & TCPC_REG_ALERT_TX_FAILED) {
|
|
/** SOP* message transmission not successful */
|
|
return TCPC_ALERT_TRANSMIT_MSG_FAILED;
|
|
} else if (reg & TCPC_REG_ALERT_TX_DISCARDED) {
|
|
/**
|
|
* Reset or SOP* message transmission not sent
|
|
* due to an incoming receive message
|
|
*/
|
|
return TCPC_ALERT_TRANSMIT_MSG_DISCARDED;
|
|
} else if (reg & TCPC_REG_ALERT_TX_SUCCESS) {
|
|
/** Reset or SOP* message transmission successful */
|
|
return TCPC_ALERT_TRANSMIT_MSG_SUCCESS;
|
|
} else if (reg & TCPC_REG_ALERT_V_ALARM_HI) {
|
|
/** A high-voltage alarm has occurred */
|
|
return TCPC_ALERT_VBUS_ALARM_HI;
|
|
} else if (reg & TCPC_REG_ALERT_V_ALARM_LO) {
|
|
/** A low-voltage alarm has occurred */
|
|
return TCPC_ALERT_VBUS_ALARM_LO;
|
|
} else if (reg & TCPC_REG_ALERT_FAULT) {
|
|
/** A fault has occurred. Read the FAULT_STATUS register */
|
|
return TCPC_ALERT_FAULT_STATUS;
|
|
} else if (reg & TCPC_REG_ALERT_RX_BUF_OVF) {
|
|
/** TCPC RX buffer has overflowed */
|
|
return TCPC_ALERT_RX_BUFFER_OVERFLOW;
|
|
} else if (reg & TCPC_REG_ALERT_VBUS_DISCNCT) {
|
|
/** The TCPC in Attached.SNK state has detected a sink disconnect */
|
|
return TCPC_ALERT_VBUS_SNK_DISCONNECT;
|
|
} else if (reg & TCPC_REG_ALERT_RX_BEGINNING) {
|
|
/** Receive buffer register changed */
|
|
return TCPC_ALERT_BEGINNING_MSG_STATUS;
|
|
} else if (reg & TCPC_REG_ALERT_EXT_STATUS) {
|
|
/** Extended status changed */
|
|
return TCPC_ALERT_EXTENDED_STATUS;
|
|
} else if (reg & TCPC_REG_ALERT_ALERT_EXT) {
|
|
/**
|
|
* An extended interrupt event has occurred. Read the alert_extended
|
|
* register
|
|
*/
|
|
return TCPC_ALERT_EXTENDED;
|
|
} else if (reg & TCPC_REG_ALERT_VENDOR_DEF) {
|
|
/** A vendor defined alert has been detected */
|
|
return TCPC_ALERT_VENDOR_DEFINED;
|
|
}
|
|
|
|
LOG_ERR("Invalid alert register value");
|
|
return -1;
|
|
}
|
|
|
|
int tcpci_tcpm_get_cc(const struct i2c_dt_spec *bus, enum tc_cc_voltage_state *cc1,
|
|
enum tc_cc_voltage_state *cc2)
|
|
{
|
|
uint8_t role;
|
|
uint8_t status;
|
|
int cc1_present_rd, cc2_present_rd;
|
|
int rv;
|
|
|
|
if (cc1 == NULL || cc2 == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* errors will return CC as open */
|
|
*cc1 = TC_CC_VOLT_OPEN;
|
|
*cc2 = TC_CC_VOLT_OPEN;
|
|
|
|
/* Get the ROLE CONTROL and CC STATUS values */
|
|
rv = tcpci_read_reg8(bus, TCPC_REG_ROLE_CTRL, &role);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
rv = tcpci_read_reg8(bus, TCPC_REG_CC_STATUS, &status);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
/* Get the current CC values from the CC STATUS */
|
|
*cc1 = TCPC_REG_CC_STATUS_CC1_STATE(status);
|
|
*cc2 = TCPC_REG_CC_STATUS_CC2_STATE(status);
|
|
|
|
/* Determine if we are presenting Rd */
|
|
cc1_present_rd = 0;
|
|
cc2_present_rd = 0;
|
|
if (role & TCPC_REG_ROLE_CTRL_DRP_MASK) {
|
|
/*
|
|
* We are doing DRP. We will use the CC STATUS
|
|
* ConnectResult to determine if we are presenting
|
|
* Rd or Rp.
|
|
*/
|
|
int term;
|
|
|
|
term = !!(status & TCPC_REG_CC_STATUS_CONNECT_RESULT);
|
|
|
|
if (*cc1 != TC_CC_VOLT_OPEN) {
|
|
cc1_present_rd = term;
|
|
}
|
|
if (*cc2 != TC_CC_VOLT_OPEN) {
|
|
cc2_present_rd = term;
|
|
}
|
|
} else {
|
|
/*
|
|
* We are not doing DRP. We will use the ROLE CONTROL
|
|
* CC values to determine if we are presenting Rd or Rp.
|
|
*/
|
|
int role_cc1, role_cc2;
|
|
|
|
role_cc1 = TCPC_REG_ROLE_CTRL_CC1(role);
|
|
role_cc2 = TCPC_REG_ROLE_CTRL_CC2(role);
|
|
|
|
if (*cc1 != TC_CC_VOLT_OPEN) {
|
|
cc1_present_rd = (role_cc1 == TC_CC_RD);
|
|
}
|
|
if (*cc2 != TC_CC_VOLT_OPEN) {
|
|
cc2_present_rd = (role_cc2 == TC_CC_RD);
|
|
}
|
|
}
|
|
|
|
*cc1 |= cc1_present_rd << 2;
|
|
*cc2 |= cc2_present_rd << 2;
|
|
|
|
return 0;
|
|
}
|