diff --git a/drivers/usb_c/tcpc/CMakeLists.txt b/drivers/usb_c/tcpc/CMakeLists.txt index eaba920e1a5..8d05d9a06ea 100644 --- a/drivers/usb_c/tcpc/CMakeLists.txt +++ b/drivers/usb_c/tcpc/CMakeLists.txt @@ -5,3 +5,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_SHELL shell.c) zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_STM32 ucpd_stm32.c) zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_NUMAKER ucpd_numaker.c) +zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_TCPCI tcpci.c) diff --git a/drivers/usb_c/tcpc/Kconfig b/drivers/usb_c/tcpc/Kconfig index d5b0e9a65ab..2bfc6675e23 100644 --- a/drivers/usb_c/tcpc/Kconfig +++ b/drivers/usb_c/tcpc/Kconfig @@ -27,6 +27,7 @@ config USBC_TCPC_SHELL source "drivers/usb_c/tcpc/Kconfig.tcpc_stm32" source "drivers/usb_c/tcpc/Kconfig.tcpc_numaker" +source "drivers/usb_c/tcpc/Kconfig.tcpc_tcpci" module = USBC module-str = usbc diff --git a/drivers/usb_c/tcpc/Kconfig.tcpc_tcpci b/drivers/usb_c/tcpc/Kconfig.tcpc_tcpci new file mode 100644 index 00000000000..e0628c430ae --- /dev/null +++ b/drivers/usb_c/tcpc/Kconfig.tcpc_tcpci @@ -0,0 +1,25 @@ +# USB-C TCPCI-compliant devices configuration options + +# Copyright 2024 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config USBC_TCPC_TCPCI + bool + select I2C + help + Enable support for Type-C Port Controller Interface. + This symbol should be selected by TCPCI-compliant drivers to allow the use of generic + TCPCI functions for registers operations. + +if USBC_TCPC_TCPCI + +config USBC_TCPC_TCPCI_I2C_RETRIES + int "I2C communication retries" + default 2 + help + Number of I2C transaction tries that will be performed for each request. + Some TCPCs are going into deep sleep mode when no charger is connected and won't respond + to the i2c address immediately. If device won't respond after retries, it means that + it is not responsible or is not connected. + +endif diff --git a/drivers/usb_c/tcpc/tcpci.c b/drivers/usb_c/tcpc/tcpci.c new file mode 100644 index 00000000000..ffac0bc8195 --- /dev/null +++ b/drivers/usb_c/tcpc/tcpci.c @@ -0,0 +1,448 @@ +/* + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/include/zephyr/drivers/usb_c/tcpci_priv.h b/include/zephyr/drivers/usb_c/tcpci_priv.h new file mode 100644 index 00000000000..8c5d67f59e3 --- /dev/null +++ b/include/zephyr/drivers/usb_c/tcpci_priv.h @@ -0,0 +1,119 @@ +/* + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Helper functions to use by the TCPCI-compliant drivers + * + * This file contains generic TCPCI functions that may be used by the drivers to TCPCI-compliant + * devices that want to implement vendor-specific functionality without the need to reimplement the + * TCPCI generic functionality and register operations. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_USBC_TCPCI_PRIV_H_ +#define ZEPHYR_INCLUDE_DRIVERS_USBC_TCPCI_PRIV_H_ + +#include +#include +#include + +/** + * @brief Structure used to bind the register address to name in registers dump + */ +struct tcpci_reg_dump_map { + /** Address of I2C device register */ + uint8_t addr; + /** Human readable name of register */ + const char *name; + /** Size in bytes of the register */ + uint8_t size; +}; + +/** Size of the array containing the standard registers used by tcpci dump command */ +#define TCPCI_STD_REGS_SIZE 38 +/** + * @brief Array containing the standard TCPCI registers list. + * If the TCPC driver contain any vendor-specific registers, it may override the TCPCI dump_std_reg + * function tp dump them and should also dump the standard registers using this array. + * + */ +extern const struct tcpci_reg_dump_map tcpci_std_regs[TCPCI_STD_REGS_SIZE]; + +/** + * @brief Function to read the 8-bit register of TCPCI device + * + * @param bus I2C bus + * @param reg Address of TCPCI register + * @param value Pointer to variable that will store the register value + * @return int Status of I2C operation, 0 in case of success + */ +int tcpci_read_reg8(const struct i2c_dt_spec *bus, uint8_t reg, uint8_t *value); + +/** + * @brief Function to write a value to the 8-bit register of TCPCI device + * + * @param bus I2C bus + * @param reg Address of TCPCI register + * @param value Value that will be written to the device register + * @return int Status of I2C operation, 0 in case of success + */ +int tcpci_write_reg8(const struct i2c_dt_spec *bus, uint8_t reg, uint8_t value); + +/** + * @brief Function to read and update part of the 8-bit register of TCPCI device + * The function is NOT performing this operation atomically. + * + * @param bus I2C bus + * @param reg Address of TCPCI register + * @param mask Bitmask specifying which bits of the device register will be modified + * @param value Value that will be written to the device register after being ANDed with mask + * @return int Status of I2C operation, 0 in case of success + */ +int tcpci_update_reg8(const struct i2c_dt_spec *bus, uint8_t reg, uint8_t mask, uint8_t value); + +/** + * @brief Function to read the 16-bit register of TCPCI device + * + * @param bus I2C bus + * @param reg Address of TCPCI register + * @param value Pointer to variable that will store the register value + * @return int Status of I2C operation, 0 in case of success + */ +int tcpci_read_reg16(const struct i2c_dt_spec *bus, uint8_t reg, uint16_t *value); + +/** + * @brief Function to write a value to the 16-bit register of TCPCI device + * + * @param bus I2C bus + * @param reg Address of TCPCI register + * @param value Value that will be written to the device register + * @return int Status of I2C operation, 0 in case of success + */ +int tcpci_write_reg16(const struct i2c_dt_spec *bus, uint8_t reg, uint16_t value); + +/** + * @brief Function that converts the TCPCI alert register to the tcpc_alert enum + * The hard reset value takes priority, where the rest are returned in the bit order from least + * significant to most significant. + * + * @param reg Value of the TCPCI alert register. This parameter must have value other than zero. + * @return enum tcpc_alert Value of one of the flags being set in the alert register + */ +enum tcpc_alert tcpci_alert_reg_to_enum(uint16_t reg); + +/** + * @brief Function that reads the CC status registers and converts read values to enums + * representing voltages state and partner detection status. + * + * @param bus I2C bus + * @param cc1 pointer to variable where detected CC1 voltage state will be stored + * @param cc2 pointer to variable where detected CC2 voltage state will be stored + * @return -EINVAL if cc1 or cc2 pointer is NULL + * @return int Status of I2C operation, 0 in case of success + */ +int tcpci_tcpm_get_cc(const struct i2c_dt_spec *bus, enum tc_cc_voltage_state *cc1, + enum tc_cc_voltage_state *cc2); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_USBC_TCPCI_PRIV_H_ */