From 6af6ed6ebfadcf6e97723674eb723fa57396027c Mon Sep 17 00:00:00 2001 From: Wenting Zhang Date: Sun, 4 Sep 2022 15:16:18 -0400 Subject: [PATCH] fw: initial controller fw --- .gitignore | 2 + fw/CMakeLists.txt | 50 + fw/fusb302.c | 955 +++++++++ fw/fusb302.h | 258 +++ fw/fw.c | 64 + fw/pico_sdk_import.cmake | 62 + fw/ptn3460.c | 90 + fw/ptn3460.h | 24 + fw/tcpm.h | 277 +++ fw/tcpm_driver.c | 129 ++ fw/tcpm_driver.h | 41 + fw/usb_pd.h | 1807 ++++++++++++++++ fw/usb_pd_driver.c | 261 +++ fw/usb_pd_driver.h | 119 ++ fw/usb_pd_policy.c | 1163 +++++++++++ fw/usb_pd_protocol.c | 4278 ++++++++++++++++++++++++++++++++++++++ fw/usb_pd_tcpm.h | 351 ++++ fw/utils.c | 39 + fw/utils.h | 24 + 19 files changed, 9994 insertions(+) create mode 100644 .gitignore create mode 100644 fw/CMakeLists.txt create mode 100644 fw/fusb302.c create mode 100644 fw/fusb302.h create mode 100644 fw/fw.c create mode 100644 fw/pico_sdk_import.cmake create mode 100644 fw/ptn3460.c create mode 100644 fw/ptn3460.h create mode 100644 fw/tcpm.h create mode 100644 fw/tcpm_driver.c create mode 100644 fw/tcpm_driver.h create mode 100644 fw/usb_pd.h create mode 100644 fw/usb_pd_driver.c create mode 100644 fw/usb_pd_driver.h create mode 100644 fw/usb_pd_policy.c create mode 100644 fw/usb_pd_protocol.c create mode 100644 fw/usb_pd_tcpm.h create mode 100644 fw/utils.c create mode 100644 fw/utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..315ff84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.*/ +build/ diff --git a/fw/CMakeLists.txt b/fw/CMakeLists.txt new file mode 100644 index 0000000..7fa6836 --- /dev/null +++ b/fw/CMakeLists.txt @@ -0,0 +1,50 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# initalize pico_sdk from installed location +# (note this can come from environment, CMake cache etc) +set(PICO_SDK_PATH "/home/wenting/pico/pico-sdk") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(fw C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(fw + fw.c + utils.c + fusb302.c + ptn3460.c + tcpm_driver.c + usb_pd_driver.c + usb_pd_policy.c + usb_pd_protocol.c + ) + +pico_set_program_name(fw "fw") +pico_set_program_version(fw "0.1") + +pico_enable_stdio_uart(fw 0) +pico_enable_stdio_usb(fw 1) + +# Add the standard library to the build +target_link_libraries(fw pico_stdlib) + +# Add any user requested libraries +target_link_libraries(fw + hardware_spi + hardware_dma + hardware_i2c + ) + +pico_add_extra_outputs(fw) + diff --git a/fw/fusb302.c b/fw/fusb302.c new file mode 100644 index 0000000..d10aee0 --- /dev/null +++ b/fw/fusb302.c @@ -0,0 +1,955 @@ +/* + fusb302.c - Library for interacting with the FUSB302B chip. + Copyright 2015 The Chromium OS Authors + Copyright 2017 Jason Cerundolo + Copyright 2022 Wenting Zhang + Released under an MIT license. See LICENSE file. +*/ + +#include +#include "pico/stdlib.h" +#include "fusb302.h" +#include "usb_pd_tcpm.h" +#include "tcpm.h" +#include "usb_pd.h" + +#define PACKET_IS_GOOD_CRC(head) (PD_HEADER_TYPE(head) == PD_CTRL_GOOD_CRC && \ + PD_HEADER_CNT(head) == 0) + +static struct fusb302_chip_state { + int cc_polarity; + int vconn_enabled; + /* 1 = pulling up (DFP) 0 = pulling down (UFP) */ + int pulling_up; + int rx_enable; + uint8_t mdac_vnc; + uint8_t mdac_rd; +} state[CONFIG_USB_PD_PORT_COUNT]; + +/* + * Bring the FUSB302 out of reset after Hard Reset signaling. This will + * automatically flush both the Rx and Tx FIFOs. + */ +static void fusb302_pd_reset(int port) +{ + tcpc_write(port, TCPC_REG_RESET, TCPC_REG_RESET_PD_RESET); +} + +/* + * Flush our Rx FIFO. To prevent packet framing issues, this function should + * only be called when Rx is disabled. + */ +static void fusb302_flush_rx_fifo(int port) +{ + /* + * other bits in the register _should_ be 0 + * until the day we support other SOP* types... + * then we'll have to keep a shadow of what this register + * value should be so we don't clobber it here! + */ + tcpc_write(port, TCPC_REG_CONTROL1, TCPC_REG_CONTROL1_RX_FLUSH); +} + +static void fusb302_flush_tx_fifo(int port) +{ + int reg; + + tcpc_read(port, TCPC_REG_CONTROL0, ®); + reg |= TCPC_REG_CONTROL0_TX_FLUSH; + tcpc_write(port, TCPC_REG_CONTROL0, reg); +} + +static void fusb302_auto_goodcrc_enable(int port, int enable) +{ + int reg; + + tcpc_read(port, TCPC_REG_SWITCHES1, ®); + + if (enable) + reg |= TCPC_REG_SWITCHES1_AUTO_GCRC; + else + reg &= ~TCPC_REG_SWITCHES1_AUTO_GCRC; + + tcpc_write(port, TCPC_REG_SWITCHES1, reg); +} + +/* Convert BC LVL values (in FUSB302) to Type-C CC Voltage Status */ +static int convert_bc_lvl(int port, int bc_lvl) +{ + /* assume OPEN unless one of the following conditions is true... */ + int ret = TYPEC_CC_VOLT_OPEN; + + if (state[port].pulling_up) { + if (bc_lvl == 0x00) + ret = TYPEC_CC_VOLT_RA; + else if (bc_lvl < 0x3) + ret = TYPEC_CC_VOLT_RD; + } else { + if (bc_lvl == 0x1) + ret = TYPEC_CC_VOLT_SNK_DEF; + else if (bc_lvl == 0x2) + ret = TYPEC_CC_VOLT_SNK_1_5; + else if (bc_lvl == 0x3) + ret = TYPEC_CC_VOLT_SNK_3_0; + } + + return ret; +} + +static int measure_cc_pin_source(int port, int cc_measure) +{ + int switches0_reg; + int reg; + int cc_lvl; + + /* Read status register */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + /* Save current value */ + switches0_reg = reg; + /* Clear pull-up register settings and measure bits */ + reg &= ~(TCPC_REG_SWITCHES0_MEAS_CC1 | TCPC_REG_SWITCHES0_MEAS_CC2); + /* Set desired pullup register bit */ + if (cc_measure == TCPC_REG_SWITCHES0_MEAS_CC1) + reg |= TCPC_REG_SWITCHES0_CC1_PU_EN; + else + reg |= TCPC_REG_SWITCHES0_CC2_PU_EN; + /* Set CC measure bit */ + reg |= cc_measure; + + /* Set measurement switch */ + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + /* Set MDAC for Open vs Rd/Ra comparison */ + tcpc_write(port, TCPC_REG_MEASURE, state[port].mdac_vnc); + + /* Wait on measurement */ + sleep_us(250); + + /* Read status register */ + tcpc_read(port, TCPC_REG_STATUS0, ®); + + /* Assume open */ + cc_lvl = TYPEC_CC_VOLT_OPEN; + + /* CC level is below the 'no connect' threshold (vOpen) */ + if ((reg & TCPC_REG_STATUS0_COMP) == 0) { + /* Set MDAC for Rd vs Ra comparison */ + tcpc_write(port, TCPC_REG_MEASURE, state[port].mdac_rd); + + /* Wait on measurement */ + sleep_us(250); + + /* Read status register */ + tcpc_read(port, TCPC_REG_STATUS0, ®); + + cc_lvl = (reg & TCPC_REG_STATUS0_COMP) ? TYPEC_CC_VOLT_RD + : TYPEC_CC_VOLT_RA; + } + + /* Restore SWITCHES0 register to its value prior */ + tcpc_write(port, TCPC_REG_SWITCHES0, switches0_reg); + + return cc_lvl; +} + +/* Determine cc pin state for source when in manual detect mode */ +static void detect_cc_pin_source_manual(int port, int *cc1_lvl, int *cc2_lvl) +{ + int cc1_measure = TCPC_REG_SWITCHES0_MEAS_CC1; + int cc2_measure = TCPC_REG_SWITCHES0_MEAS_CC2; + + if (state[port].vconn_enabled) { + /* If VCONN enabled, measure cc_pin that matches polarity */ + if (state[port].cc_polarity) + *cc2_lvl = measure_cc_pin_source(port, cc2_measure); + else + *cc1_lvl = measure_cc_pin_source(port, cc1_measure); + } else { + /* If VCONN not enabled, measure both cc1 and cc2 */ + *cc1_lvl = measure_cc_pin_source(port, cc1_measure); + *cc2_lvl = measure_cc_pin_source(port, cc2_measure); + } + +} + +/* Determine cc pin state for sink */ +static void detect_cc_pin_sink(int port, int *cc1, int *cc2) +{ + int reg; + int orig_meas_cc1; + int orig_meas_cc2; + int bc_lvl_cc1; + int bc_lvl_cc2; + + /* + * Measure CC1 first. + */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + /* save original state to be returned to later... */ + if (reg & TCPC_REG_SWITCHES0_MEAS_CC1) + orig_meas_cc1 = 1; + else + orig_meas_cc1 = 0; + + if (reg & TCPC_REG_SWITCHES0_MEAS_CC2) + orig_meas_cc2 = 1; + else + orig_meas_cc2 = 0; + + + /* Disable CC2 measurement switch, enable CC1 measurement switch */ + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; + reg |= TCPC_REG_SWITCHES0_MEAS_CC1; + + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + /* CC1 is now being measured by FUSB302. */ + + /* Wait on measurement */ + sleep_us(250); + + tcpc_read(port, TCPC_REG_STATUS0, &bc_lvl_cc1); + + /* mask away unwanted bits */ + bc_lvl_cc1 &= (TCPC_REG_STATUS0_BC_LVL0 | TCPC_REG_STATUS0_BC_LVL1); + + /* + * Measure CC2 next. + */ + + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + /* Disable CC1 measurement switch, enable CC2 measurement switch */ + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; + reg |= TCPC_REG_SWITCHES0_MEAS_CC2; + + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + /* CC2 is now being measured by FUSB302. */ + + /* Wait on measurement */ + sleep_us(250); + + tcpc_read(port, TCPC_REG_STATUS0, &bc_lvl_cc2); + + /* mask away unwanted bits */ + bc_lvl_cc2 &= (TCPC_REG_STATUS0_BC_LVL0 | TCPC_REG_STATUS0_BC_LVL1); + + *cc1 = convert_bc_lvl(port, bc_lvl_cc1); + *cc2 = convert_bc_lvl(port, bc_lvl_cc2); + + /* return MEAS_CC1/2 switches to original state */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + if (orig_meas_cc1) + reg |= TCPC_REG_SWITCHES0_MEAS_CC1; + else + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; + if (orig_meas_cc2) + reg |= TCPC_REG_SWITCHES0_MEAS_CC2; + else + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; + + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + +} + +/* Parse header bytes for the size of packet */ +static int get_num_bytes(uint16_t header) +{ + int rv; + + /* Grab the Number of Data Objects field.*/ + rv = PD_HEADER_CNT(header); + + /* Multiply by four to go from 32-bit words -> bytes */ + rv *= 4; + + /* Plus 2 for header */ + rv += 2; + + return rv; +} + +static int fusb302_send_message(int port, uint16_t header, const uint32_t *data, + uint8_t *buf, int buf_pos) +{ + int rv; + int reg; + int len; + + len = get_num_bytes(header); + + /* + * packsym tells the TXFIFO that the next X bytes are payload, + * and should not be interpreted as special tokens. + * The 5 LSBs represent X, the number of bytes. + */ + reg = FUSB302_TKN_PACKSYM; + reg |= (len & 0x1F); + + buf[buf_pos++] = reg; + + /* write in the header */ + reg = header; + buf[buf_pos++] = reg & 0xFF; + + reg >>= 8; + buf[buf_pos++] = reg & 0xFF; + + /* header is done, subtract from length to make this for-loop simpler */ + len -= 2; + + /* write data objects, if present */ + memcpy(&buf[buf_pos], data, len); + buf_pos += len; + + /* put in the CRC */ + buf[buf_pos++] = FUSB302_TKN_JAMCRC; + + /* put in EOP */ + buf[buf_pos++] = FUSB302_TKN_EOP; + + /* Turn transmitter off after sending message */ + buf[buf_pos++] = FUSB302_TKN_TXOFF; + + /* Start transmission */ + reg = FUSB302_TKN_TXON; + buf[buf_pos++] = FUSB302_TKN_TXON; + + /* burst write for speed! */ + rv = tcpc_xfer(port, buf, buf_pos, 0, 0, I2C_XFER_SINGLE); + + return rv; +} + +static int fusb302_tcpm_select_rp_value(int port, int rp) +{ + int reg; + int rv; + uint8_t vnc, rd; + + rv = tcpc_read(port, TCPC_REG_CONTROL0, ®); + if (rv) + return rv; + + /* Set the current source for Rp value */ + reg &= ~TCPC_REG_CONTROL0_HOST_CUR_MASK; + switch (rp) { + case TYPEC_RP_1A5: + reg |= TCPC_REG_CONTROL0_HOST_CUR_1A5; + vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_1_5_VNC_MV); + rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_1_5_RD_THRESH_MV); + break; + case TYPEC_RP_3A0: + reg |= TCPC_REG_CONTROL0_HOST_CUR_3A0; + vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_3_0_VNC_MV); + rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_3_0_RD_THRESH_MV); + break; + case TYPEC_RP_USB: + default: + reg |= TCPC_REG_CONTROL0_HOST_CUR_USB; + vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_VNC_MV); + rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_RD_THRESH_MV); + } + state[port].mdac_vnc = vnc; + state[port].mdac_rd = rd; + rv = tcpc_write(port, TCPC_REG_CONTROL0, reg); + + return rv; +} + +static int fusb302_tcpm_init(int port) +{ + int reg; + + tcpc_i2c_init(); + + /* set default */ + state[port].cc_polarity = -1; + + /* set the voltage threshold for no connect detection (vOpen) */ + state[port].mdac_vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_VNC_MV); + /* set the voltage threshold for Rd vs Ra detection */ + state[port].mdac_rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_RD_THRESH_MV); + + /* all other variables assumed to default to 0 */ + + /* Restore default settings */ + tcpc_write(port, TCPC_REG_RESET, TCPC_REG_RESET_SW_RESET); + + /* Turn on retries and set number of retries */ + tcpc_read(port, TCPC_REG_CONTROL3, ®); + reg |= TCPC_REG_CONTROL3_AUTO_RETRY; + reg |= (PD_RETRY_COUNT & 0x3) << + TCPC_REG_CONTROL3_N_RETRIES_POS; + tcpc_write(port, TCPC_REG_CONTROL3, reg); + + /* Create interrupt masks */ + reg = 0xFF; + /* CC level changes */ + reg &= ~TCPC_REG_MASK_BC_LVL; + /* collisions */ + reg &= ~TCPC_REG_MASK_COLLISION; + /* misc alert */ + reg &= ~TCPC_REG_MASK_ALERT; + /* packet received with correct CRC */ + reg &= ~TCPC_REG_MASK_CRC_CHK; + tcpc_write(port, TCPC_REG_MASK, reg); + + reg = 0xFF; + /* when all pd message retries fail... */ + reg &= ~TCPC_REG_MASKA_RETRYFAIL; + /* when fusb302 send a hard reset. */ + reg &= ~TCPC_REG_MASKA_HARDSENT; + /* when fusb302 receives GoodCRC ack for a pd message */ + reg &= ~TCPC_REG_MASKA_TX_SUCCESS; + /* when fusb302 receives a hard reset */ + reg &= ~TCPC_REG_MASKA_HARDRESET; + tcpc_write(port, TCPC_REG_MASKA, reg); + + reg = 0xFF; + /* when fusb302 sends GoodCRC to ack a pd message */ + reg &= ~TCPC_REG_MASKB_GCRCSENT; + tcpc_write(port, TCPC_REG_MASKB, reg); + + /* Interrupt Enable */ + tcpc_read(port, TCPC_REG_CONTROL0, ®); + reg &= ~TCPC_REG_CONTROL0_INT_MASK; + tcpc_write(port, TCPC_REG_CONTROL0, reg); + + /* Set VCONN switch defaults */ + tcpm_set_polarity(port, 0); + tcpm_set_vconn(port, 0); + + fusb302_auto_goodcrc_enable(port, 0); + + /* Turn on the power! */ + /* TODO: Reduce power consumption */ + tcpc_write(port, TCPC_REG_POWER, TCPC_REG_POWER_PWR_ALL); + + return 0; +} + +static int fusb302_tcpm_release(int port) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +static int fusb302_tcpm_get_cc(int port, int *cc1, int *cc2) +{ + if (state[port].pulling_up) { + /* Source mode? */ + detect_cc_pin_source_manual(port, cc1, cc2); + } else { + /* Sink mode? */ + detect_cc_pin_sink(port, cc1, cc2); + } + + return 0; +} + +static int fusb302_tcpm_set_cc(int port, int pull) +{ + int reg; + + /* NOTE: FUSB302 toggles a single pull-up between CC1 and CC2 */ + /* NOTE: FUSB302 Does not support Ra. */ + switch (pull) { + case TYPEC_CC_RP: + /* enable the pull-up we know to be necessary */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + reg &= ~(TCPC_REG_SWITCHES0_CC2_PU_EN | + TCPC_REG_SWITCHES0_CC1_PU_EN | + TCPC_REG_SWITCHES0_CC1_PD_EN | + TCPC_REG_SWITCHES0_CC2_PD_EN | + TCPC_REG_SWITCHES0_VCONN_CC1 | + TCPC_REG_SWITCHES0_VCONN_CC2); + + reg |= TCPC_REG_SWITCHES0_CC1_PU_EN | + TCPC_REG_SWITCHES0_CC2_PU_EN; + + if (state[port].vconn_enabled) + reg |= state[port].cc_polarity ? + TCPC_REG_SWITCHES0_VCONN_CC1 : + TCPC_REG_SWITCHES0_VCONN_CC2; + + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + state[port].pulling_up = 1; + break; + case TYPEC_CC_RD: + /* Enable UFP Mode */ + + /* turn off toggle */ + tcpc_read(port, TCPC_REG_CONTROL2, ®); + reg &= ~TCPC_REG_CONTROL2_TOGGLE; + tcpc_write(port, TCPC_REG_CONTROL2, reg); + + /* enable pull-downs, disable pullups */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + reg &= ~(TCPC_REG_SWITCHES0_CC2_PU_EN); + reg &= ~(TCPC_REG_SWITCHES0_CC1_PU_EN); + reg |= (TCPC_REG_SWITCHES0_CC1_PD_EN); + reg |= (TCPC_REG_SWITCHES0_CC2_PD_EN); + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + state[port].pulling_up = 0; + break; + case TYPEC_CC_OPEN: + /* Disable toggling */ + tcpc_read(port, TCPC_REG_CONTROL2, ®); + reg &= ~TCPC_REG_CONTROL2_TOGGLE; + tcpc_write(port, TCPC_REG_CONTROL2, reg); + + /* Ensure manual switches are opened */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + reg &= ~TCPC_REG_SWITCHES0_CC1_PU_EN; + reg &= ~TCPC_REG_SWITCHES0_CC2_PU_EN; + reg &= ~TCPC_REG_SWITCHES0_CC1_PD_EN; + reg &= ~TCPC_REG_SWITCHES0_CC2_PD_EN; + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + state[port].pulling_up = 0; + break; + default: + /* Unsupported... */ + return EC_ERROR_UNIMPLEMENTED; + } + + return 0; +} + +static int fusb302_tcpm_set_polarity(int port, int polarity) +{ + /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ + int reg; + + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + /* clear VCONN switch bits */ + reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1; + reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2; + + if (state[port].vconn_enabled) { + /* set VCONN switch to be non-CC line */ + if (polarity) + reg |= TCPC_REG_SWITCHES0_VCONN_CC1; + else + reg |= TCPC_REG_SWITCHES0_VCONN_CC2; + } + + /* clear meas_cc bits (RX line select) */ + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; + + /* set rx polarity */ + if (polarity) + reg |= TCPC_REG_SWITCHES0_MEAS_CC2; + else + reg |= TCPC_REG_SWITCHES0_MEAS_CC1; + + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + tcpc_read(port, TCPC_REG_SWITCHES1, ®); + + /* clear tx_cc bits */ + reg &= ~TCPC_REG_SWITCHES1_TXCC1_EN; + reg &= ~TCPC_REG_SWITCHES1_TXCC2_EN; + + /* set tx polarity */ + if (polarity) + reg |= TCPC_REG_SWITCHES1_TXCC2_EN; + else + reg |= TCPC_REG_SWITCHES1_TXCC1_EN; + + tcpc_write(port, TCPC_REG_SWITCHES1, reg); + + /* Save the polarity for later */ + state[port].cc_polarity = polarity; + + return 0; +} + +static int fusb302_tcpm_set_vconn(int port, int enable) +{ + /* + * FUSB302 does not have dedicated VCONN Enable switch. + * We'll get through this by disabling both of the + * VCONN - CC* switches to disable, and enabling the + * saved polarity when enabling. + * Therefore at startup, tcpm_set_polarity should be called first, + * or else live with the default put into tcpm_init. + */ + int reg; + + /* save enable state for later use */ + state[port].vconn_enabled = enable; + + if (enable) { + /* set to saved polarity */ + tcpm_set_polarity(port, state[port].cc_polarity); + } else { + + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + /* clear VCONN switch bits */ + reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1; + reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2; + + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + } + + return 0; +} + +static int fusb302_tcpm_set_msg_header(int port, int power_role, int data_role) +{ + int reg; + + tcpc_read(port, TCPC_REG_SWITCHES1, ®); + + reg &= ~TCPC_REG_SWITCHES1_POWERROLE; + reg &= ~TCPC_REG_SWITCHES1_DATAROLE; + + if (power_role) + reg |= TCPC_REG_SWITCHES1_POWERROLE; + if (data_role) + reg |= TCPC_REG_SWITCHES1_DATAROLE; + + tcpc_write(port, TCPC_REG_SWITCHES1, reg); + + return 0; +} + +static int fusb302_tcpm_set_rx_enable(int port, int enable) +{ + int reg; + + state[port].rx_enable = enable; + + /* Get current switch state */ + tcpc_read(port, TCPC_REG_SWITCHES0, ®); + + /* Clear CC1/CC2 measure bits */ + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; + + if (enable) { + switch (state[port].cc_polarity) { + /* if CC polarity hasnt been determined, can't enable */ + case -1: + return EC_ERROR_UNKNOWN; + case 0: + reg |= TCPC_REG_SWITCHES0_MEAS_CC1; + break; + case 1: + reg |= TCPC_REG_SWITCHES0_MEAS_CC2; + break; + default: + /* "shouldn't get here" */ + return EC_ERROR_UNKNOWN; + } + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + /* Disable BC_LVL interrupt when enabling PD comm */ + if (!tcpc_read(port, TCPC_REG_MASK, ®)) + tcpc_write(port, TCPC_REG_MASK, + reg | TCPC_REG_MASK_BC_LVL); + + /* flush rx fifo in case messages have been coming our way */ + fusb302_flush_rx_fifo(port); + + + } else { + tcpc_write(port, TCPC_REG_SWITCHES0, reg); + + /* Enable BC_LVL interrupt when disabling PD comm */ + if (!tcpc_read(port, TCPC_REG_MASK, ®)) + tcpc_write(port, TCPC_REG_MASK, + reg & ~TCPC_REG_MASK_BC_LVL); + } + + fusb302_auto_goodcrc_enable(port, enable); + + return 0; +} + +/* Return true if our Rx FIFO is empty */ +static int fusb302_rx_fifo_is_empty(int port) +{ + int reg, ret; + + ret = (!tcpc_read(port, TCPC_REG_STATUS1, ®)) && + (reg & TCPC_REG_STATUS1_RX_EMPTY); + + return ret; +} + +static int fusb302_tcpm_get_message(int port, uint32_t *payload, int *head) +{ + /* + * This is the buffer that will get the burst-read data + * from the fusb302. + * + * It's re-used in a couple different spots, the worst of which + * is the PD packet (not header) and CRC. + * maximum size necessary = 28 + 4 = 32 + */ + uint8_t buf[32]; + int rv, len; + + /* If our FIFO is empty then we have no packet */ + if (fusb302_rx_fifo_is_empty(port)) + return EC_ERROR_UNKNOWN; + + /* Read until we have a non-GoodCRC packet or an empty FIFO */ + do { + buf[0] = TCPC_REG_FIFOS; + + /* + * PART 1 OF BURST READ: Write in register address. + * Issue a START, no STOP. + */ + rv = tcpc_xfer(port, buf, 1, 0, 0, I2C_XFER_START); + + /* + * PART 2 OF BURST READ: Read up to the header. + * Issue a repeated START, no STOP. + * only grab three bytes so we can get the header + * and determine how many more bytes we need to read. + * TODO: Check token to ensure valid packet. + */ + rv |= tcpc_xfer(port, 0, 0, buf, 3, I2C_XFER_START); + + /* Grab the header */ + *head = (buf[1] & 0xFF); + *head |= ((buf[2] << 8) & 0xFF00); + + /* figure out packet length, subtract header bytes */ + len = get_num_bytes(*head) - 2; + + /* + * PART 3 OF BURST READ: Read everything else. + * No START, but do issue a STOP at the end. + * add 4 to len to read CRC out + */ + rv |= tcpc_xfer(port, 0, 0, buf, len+4, I2C_XFER_STOP); + + } while (!rv && PACKET_IS_GOOD_CRC(*head) && + !fusb302_rx_fifo_is_empty(port)); + + if (!rv) { + /* Discard GoodCRC packets */ + if (PACKET_IS_GOOD_CRC(*head)) + rv = EC_ERROR_UNKNOWN; + else + memcpy(payload, buf, len); + } + + /* + * If our FIFO is non-empty then we may have a packet, we may get + * fewer interrupts than packets due to interrupt latency. + */ + //if (!fusb302_rx_fifo_is_empty(port)) + // task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_RX, 0); + + return rv; +} + +static int fusb302_tcpm_transmit(int port, enum tcpm_transmit_type type, + uint16_t header, const uint32_t *data) +{ + /* + * this is the buffer that will be burst-written into the fusb302 + * maximum size necessary = + * 1: FIFO register address + * 4: SOP* tokens + * 1: Token that signifies "next X bytes are not tokens" + * 30: 2 for header and up to 7*4 = 28 for rest of message + * 1: "Insert CRC" Token + * 1: EOP Token + * 1: "Turn transmitter off" token + * 1: "Star Transmission" Command + * - + * 40: 40 bytes worst-case + */ + uint8_t buf[40]; + int buf_pos = 0; + + int reg; + + /* Flush the TXFIFO */ + fusb302_flush_tx_fifo(port); + + switch (type) { + case TCPC_TX_SOP: + + /* put register address first for of burst tcpc write */ + buf[buf_pos++] = TCPC_REG_FIFOS; + + /* Write the SOP Ordered Set into TX FIFO */ + buf[buf_pos++] = FUSB302_TKN_SYNC1; + buf[buf_pos++] = FUSB302_TKN_SYNC1; + buf[buf_pos++] = FUSB302_TKN_SYNC1; + buf[buf_pos++] = FUSB302_TKN_SYNC2; + + fusb302_send_message(port, header, data, buf, buf_pos); + // wait for the GoodCRC to come back before we let the rest + // of the code do stuff like change polarity and miss it + sleep_us(1200); + return 0; + case TCPC_TX_HARD_RESET: + /* Simply hit the SEND_HARD_RESET bit */ + tcpc_read(port, TCPC_REG_CONTROL3, ®); + reg |= TCPC_REG_CONTROL3_SEND_HARDRESET; + tcpc_write(port, TCPC_REG_CONTROL3, reg); + + break; + case TCPC_TX_BIST_MODE_2: + /* Hit the BIST_MODE2 bit and start TX */ + tcpc_read(port, TCPC_REG_CONTROL1, ®); + reg |= TCPC_REG_CONTROL1_BIST_MODE2; + tcpc_write(port, TCPC_REG_CONTROL1, reg); + + tcpc_read(port, TCPC_REG_CONTROL0, ®); + reg |= TCPC_REG_CONTROL0_TX_START; + tcpc_write(port, TCPC_REG_CONTROL0, reg); + + //task_wait_event(PD_T_BIST_TRANSMIT); + + /* Clear BIST mode bit, TX_START is self-clearing */ + tcpc_read(port, TCPC_REG_CONTROL1, ®); + reg &= ~TCPC_REG_CONTROL1_BIST_MODE2; + tcpc_write(port, TCPC_REG_CONTROL1, reg); + + break; + default: + return EC_ERROR_UNIMPLEMENTED; + } + + return 0; +} + +#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC +static int fusb302_tcpm_get_vbus_level(int port) +{ + int reg; + + /* Read status register */ + tcpc_read(port, TCPC_REG_STATUS0, ®); + + return (reg & TCPC_REG_STATUS0_VBUSOK) ? 1 : 0; +} +#endif + +void fusb302_tcpc_alert(int port) +{ + /* interrupt has been received */ + int interrupt; + int interrupta; + int interruptb; + + /* reading interrupt registers clears them */ + + tcpc_read(port, TCPC_REG_INTERRUPT, &interrupt); + tcpc_read(port, TCPC_REG_INTERRUPTA, &interrupta); + tcpc_read(port, TCPC_REG_INTERRUPTB, &interruptb); + + /* + * Ignore BC_LVL changes when transmitting / receiving PD, + * since CC level will constantly change. + */ + if (state[port].rx_enable) + interrupt &= ~TCPC_REG_INTERRUPT_BC_LVL; + + if (interrupt & TCPC_REG_INTERRUPT_BC_LVL) { + /* CC Status change */ + //task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0); + } + + if (interrupt & TCPC_REG_INTERRUPT_COLLISION) { + /* packet sending collided */ + pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); + } + + /* GoodCRC was received, our FIFO is now non-empty */ + if (interrupta & TCPC_REG_INTERRUPTA_TX_SUCCESS) { + //task_set_event(PD_PORT_TO_TASK_ID(port), + // PD_EVENT_RX, 0); + + pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); + } + + if (interrupta & TCPC_REG_INTERRUPTA_RETRYFAIL) { + /* all retries have failed to get a GoodCRC */ + pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); + } + + if (interrupta & TCPC_REG_INTERRUPTA_HARDSENT) { + /* hard reset has been sent */ + + /* bring FUSB302 out of reset */ + fusb302_pd_reset(port); + + pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); + } + + if (interrupta & TCPC_REG_INTERRUPTA_HARDRESET) { + /* hard reset has been received */ + + /* bring FUSB302 out of reset */ + fusb302_pd_reset(port); + + pd_execute_hard_reset(port); + + //task_wake(PD_PORT_TO_TASK_ID(port)); + } + + if (interruptb & TCPC_REG_INTERRUPTB_GCRCSENT) { + /* Packet received and GoodCRC sent */ + /* (this interrupt fires after the GoodCRC finishes) */ + if (state[port].rx_enable) { + //task_set_event(PD_PORT_TO_TASK_ID(port), + // PD_EVENT_RX, 0); + } else { + /* flush rx fifo if rx isn't enabled */ + fusb302_flush_rx_fifo(port); + } + } +} + +/* For BIST receiving */ +void tcpm_set_bist_test_data(int port) +{ + int reg; + + /* Read control3 register */ + tcpc_read(port, TCPC_REG_CONTROL3, ®); + + /* Set the BIST_TMODE bit (Clears on Hard Reset) */ + reg |= TCPC_REG_CONTROL3_BIST_TMODE; + + /* Write the updated value */ + tcpc_write(port, TCPC_REG_CONTROL3, reg); +} + +const struct tcpm_drv fusb302_tcpm_drv = { + .init = &fusb302_tcpm_init, + .release = &fusb302_tcpm_release, + .get_cc = &fusb302_tcpm_get_cc, +#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC + .get_vbus_level = &fusb302_tcpm_get_vbus_level, +#endif + .select_rp_value = &fusb302_tcpm_select_rp_value, + .set_cc = &fusb302_tcpm_set_cc, + .set_polarity = &fusb302_tcpm_set_polarity, + .set_vconn = &fusb302_tcpm_set_vconn, + .set_msg_header = &fusb302_tcpm_set_msg_header, + .set_rx_enable = &fusb302_tcpm_set_rx_enable, + .get_message = &fusb302_tcpm_get_message, + .transmit = &fusb302_tcpm_transmit, + .tcpc_alert = &fusb302_tcpc_alert, +}; diff --git a/fw/fusb302.h b/fw/fusb302.h new file mode 100644 index 0000000..935fcb5 --- /dev/null +++ b/fw/fusb302.h @@ -0,0 +1,258 @@ +/* + fusb302.h - Library for interacting with the FUSB302B chip. + Copyright 2010 The Chromium OS Authors + Copyright 2017 Jason Cerundolo + Copyright 2022 Wenting Zhang + Released under an MIT license. See LICENSE file. +*/ + +#ifndef FUSB302_H +#define FUSB302_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "usb_pd_tcpm.h" +#include "usb_pd.h" + +/* Chip Device ID - 302A or 302B */ +#define FUSB302_DEVID_302A 0x08 +#define FUSB302_DEVID_302B 0x09 + +/* I2C slave address varies by part number */ +/* FUSB302BUCX / FUSB302BMPX */ +#define FUSB302_I2C_SLAVE_ADDR 0x22 // 7-bit address for Arduino +/* FUSB302B01MPX */ +#define FUSB302_I2C_SLAVE_ADDR_B01 0x23 +/* FUSB302B10MPX */ +#define FUSB302_I2C_SLAVE_ADDR_B10 0x24 +/* FUSB302B11MPX */ +#define FUSB302_I2C_SLAVE_ADDR_B11 0x25 + +/* Default retry count for transmitting */ +#define PD_RETRY_COUNT 3 + +/* Time to wait for TCPC to complete transmit */ +#define PD_T_TCPC_TX_TIMEOUT (100*MSEC_US) + +#define TCPC_REG_DEVICE_ID 0x01 + +#define TCPC_REG_SWITCHES0 0x02 +#define TCPC_REG_SWITCHES0_CC2_PU_EN (1<<7) +#define TCPC_REG_SWITCHES0_CC1_PU_EN (1<<6) +#define TCPC_REG_SWITCHES0_VCONN_CC2 (1<<5) +#define TCPC_REG_SWITCHES0_VCONN_CC1 (1<<4) +#define TCPC_REG_SWITCHES0_MEAS_CC2 (1<<3) +#define TCPC_REG_SWITCHES0_MEAS_CC1 (1<<2) +#define TCPC_REG_SWITCHES0_CC2_PD_EN (1<<1) +#define TCPC_REG_SWITCHES0_CC1_PD_EN (1<<0) + +#define TCPC_REG_SWITCHES1 0x03 +#define TCPC_REG_SWITCHES1_POWERROLE (1<<7) +#define TCPC_REG_SWITCHES1_SPECREV1 (1<<6) +#define TCPC_REG_SWITCHES1_SPECREV0 (1<<5) +#define TCPC_REG_SWITCHES1_DATAROLE (1<<4) +#define TCPC_REG_SWITCHES1_AUTO_GCRC (1<<2) +#define TCPC_REG_SWITCHES1_TXCC2_EN (1<<1) +#define TCPC_REG_SWITCHES1_TXCC1_EN (1<<0) + +#define TCPC_REG_MEASURE 0x04 +#define TCPC_REG_MEASURE_VBUS (1<<6) +#define TCPC_REG_MEASURE_MDAC_MV(mv) (((mv)/42) & 0x3f) + +#define TCPC_REG_CONTROL0 0x06 +#define TCPC_REG_CONTROL0_TX_FLUSH (1<<6) +#define TCPC_REG_CONTROL0_INT_MASK (1<<5) +#define TCPC_REG_CONTROL0_HOST_CUR_MASK (3<<2) +#define TCPC_REG_CONTROL0_HOST_CUR_3A0 (3<<2) +#define TCPC_REG_CONTROL0_HOST_CUR_1A5 (2<<2) +#define TCPC_REG_CONTROL0_HOST_CUR_USB (1<<2) +#define TCPC_REG_CONTROL0_TX_START (1<<0) + +#define TCPC_REG_CONTROL1 0x07 +#define TCPC_REG_CONTROL1_ENSOP2DB (1<<6) +#define TCPC_REG_CONTROL1_ENSOP1DB (1<<5) +#define TCPC_REG_CONTROL1_BIST_MODE2 (1<<4) +#define TCPC_REG_CONTROL1_RX_FLUSH (1<<2) +#define TCPC_REG_CONTROL1_ENSOP2 (1<<1) +#define TCPC_REG_CONTROL1_ENSOP1 (1<<0) + +#define TCPC_REG_CONTROL2 0x08 +/* two-bit field, valid values below */ +#define TCPC_REG_CONTROL2_MODE (1<<1) +#define TCPC_REG_CONTROL2_MODE_DFP (0x3) +#define TCPC_REG_CONTROL2_MODE_UFP (0x2) +#define TCPC_REG_CONTROL2_MODE_DRP (0x1) +#define TCPC_REG_CONTROL2_MODE_POS (1) +#define TCPC_REG_CONTROL2_TOGGLE (1<<0) + +#define TCPC_REG_CONTROL3 0x09 +#define TCPC_REG_CONTROL3_SEND_HARDRESET (1<<6) +#define TCPC_REG_CONTROL3_BIST_TMODE (1<<5) /* 302B Only */ +#define TCPC_REG_CONTROL3_AUTO_HARDRESET (1<<4) +#define TCPC_REG_CONTROL3_AUTO_SOFTRESET (1<<3) +/* two-bit field */ +#define TCPC_REG_CONTROL3_N_RETRIES (1<<1) +#define TCPC_REG_CONTROL3_N_RETRIES_POS (1) +#define TCPC_REG_CONTROL3_N_RETRIES_SIZE (2) +#define TCPC_REG_CONTROL3_AUTO_RETRY (1<<0) + +#define TCPC_REG_MASK 0x0A +#define TCPC_REG_MASK_VBUSOK (1<<7) +#define TCPC_REG_MASK_ACTIVITY (1<<6) +#define TCPC_REG_MASK_COMP_CHNG (1<<5) +#define TCPC_REG_MASK_CRC_CHK (1<<4) +#define TCPC_REG_MASK_ALERT (1<<3) +#define TCPC_REG_MASK_WAKE (1<<2) +#define TCPC_REG_MASK_COLLISION (1<<1) +#define TCPC_REG_MASK_BC_LVL (1<<0) + +#define TCPC_REG_POWER 0x0B +#define TCPC_REG_POWER_PWR (1<<0) /* four-bit field */ +#define TCPC_REG_POWER_PWR_LOW 0x1 /* Bandgap + Wake circuitry */ +#define TCPC_REG_POWER_PWR_MEDIUM 0x3 /* LOW + Receiver + Current refs */ +#define TCPC_REG_POWER_PWR_HIGH 0x7 /* MEDIUM + Measure block */ +#define TCPC_REG_POWER_PWR_ALL 0xF /* HIGH + Internal Oscillator */ + +#define TCPC_REG_RESET 0x0C +#define TCPC_REG_RESET_PD_RESET (1<<1) +#define TCPC_REG_RESET_SW_RESET (1<<0) + +#define TCPC_REG_MASKA 0x0E +#define TCPC_REG_MASKA_OCP_TEMP (1<<7) +#define TCPC_REG_MASKA_TOGDONE (1<<6) +#define TCPC_REG_MASKA_SOFTFAIL (1<<5) +#define TCPC_REG_MASKA_RETRYFAIL (1<<4) +#define TCPC_REG_MASKA_HARDSENT (1<<3) +#define TCPC_REG_MASKA_TX_SUCCESS (1<<2) +#define TCPC_REG_MASKA_SOFTRESET (1<<1) +#define TCPC_REG_MASKA_HARDRESET (1<<0) + +#define TCPC_REG_MASKB 0x0F +#define TCPC_REG_MASKB_GCRCSENT (1<<0) + +#define TCPC_REG_STATUS0A 0x3C +#define TCPC_REG_STATUS0A_SOFTFAIL (1<<5) +#define TCPC_REG_STATUS0A_RETRYFAIL (1<<4) +#define TCPC_REG_STATUS0A_POWER (1<<2) /* two-bit field */ +#define TCPC_REG_STATUS0A_RX_SOFT_RESET (1<<1) +#define TCPC_REG_STATUS0A_RX_HARD_RESET (1<<0) + +#define TCPC_REG_STATUS1A 0x3D +/* three-bit field, valid values below */ +#define TCPC_REG_STATUS1A_TOGSS (1<<3) +#define TCPC_REG_STATUS1A_TOGSS_RUNNING 0x0 +#define TCPC_REG_STATUS1A_TOGSS_SRC1 0x1 +#define TCPC_REG_STATUS1A_TOGSS_SRC2 0x2 +#define TCPC_REG_STATUS1A_TOGSS_SNK1 0x5 +#define TCPC_REG_STATUS1A_TOGSS_SNK2 0x6 +#define TCPC_REG_STATUS1A_TOGSS_AA 0x7 +#define TCPC_REG_STATUS1A_TOGSS_POS (3) +#define TCPC_REG_STATUS1A_TOGSS_MASK (0x7) + +#define TCPC_REG_STATUS1A_RXSOP2DB (1<<2) +#define TCPC_REG_STATUS1A_RXSOP1DB (1<<1) +#define TCPC_REG_STATUS1A_RXSOP (1<<0) + +#define TCPC_REG_INTERRUPTA 0x3E +#define TCPC_REG_INTERRUPTA_OCP_TEMP (1<<7) +#define TCPC_REG_INTERRUPTA_TOGDONE (1<<6) +#define TCPC_REG_INTERRUPTA_SOFTFAIL (1<<5) +#define TCPC_REG_INTERRUPTA_RETRYFAIL (1<<4) +#define TCPC_REG_INTERRUPTA_HARDSENT (1<<3) +#define TCPC_REG_INTERRUPTA_TX_SUCCESS (1<<2) +#define TCPC_REG_INTERRUPTA_SOFTRESET (1<<1) +#define TCPC_REG_INTERRUPTA_HARDRESET (1<<0) + +#define TCPC_REG_INTERRUPTB 0x3F +#define TCPC_REG_INTERRUPTB_GCRCSENT (1<<0) + +#define TCPC_REG_STATUS0 0x40 +#define TCPC_REG_STATUS0_VBUSOK (1<<7) +#define TCPC_REG_STATUS0_ACTIVITY (1<<6) +#define TCPC_REG_STATUS0_COMP (1<<5) +#define TCPC_REG_STATUS0_CRC_CHK (1<<4) +#define TCPC_REG_STATUS0_ALERT (1<<3) +#define TCPC_REG_STATUS0_WAKE (1<<2) +#define TCPC_REG_STATUS0_BC_LVL1 (1<<1) /* two-bit field */ +#define TCPC_REG_STATUS0_BC_LVL0 (1<<0) /* two-bit field */ + +#define TCPC_REG_STATUS1 0x41 +#define TCPC_REG_STATUS1_RXSOP2 (1<<7) +#define TCPC_REG_STATUS1_RXSOP1 (1<<6) +#define TCPC_REG_STATUS1_RX_EMPTY (1<<5) +#define TCPC_REG_STATUS1_RX_FULL (1<<4) +#define TCPC_REG_STATUS1_TX_EMPTY (1<<3) +#define TCPC_REG_STATUS1_TX_FULL (1<<2) + +#define TCPC_REG_INTERRUPT 0x42 +#define TCPC_REG_INTERRUPT_VBUSOK (1<<7) +#define TCPC_REG_INTERRUPT_ACTIVITY (1<<6) +#define TCPC_REG_INTERRUPT_COMP_CHNG (1<<5) +#define TCPC_REG_INTERRUPT_CRC_CHK (1<<4) +#define TCPC_REG_INTERRUPT_ALERT (1<<3) +#define TCPC_REG_INTERRUPT_WAKE (1<<2) +#define TCPC_REG_INTERRUPT_COLLISION (1<<1) +#define TCPC_REG_INTERRUPT_BC_LVL (1<<0) + +#define TCPC_REG_FIFOS 0x43 + +/* Tokens defined for the FUSB302 TX FIFO */ +enum fusb302_txfifo_tokens { + FUSB302_TKN_TXON = 0xA1, + FUSB302_TKN_SYNC1 = 0x12, + FUSB302_TKN_SYNC2 = 0x13, + FUSB302_TKN_SYNC3 = 0x1B, + FUSB302_TKN_RST1 = 0x15, + FUSB302_TKN_RST2 = 0x16, + FUSB302_TKN_PACKSYM = 0x80, + FUSB302_TKN_JAMCRC = 0xFF, + FUSB302_TKN_EOP = 0x14, + FUSB302_TKN_TXOFF = 0xFE, +}; + +extern const struct tcpm_drv fusb302_tcpm_drv; + +/* +// Common methods for TCPM implementations +int fusb302_init(void); +int fusb302_get_cc(int *cc1, int *cc2); +int fusb302_get_vbus_level(void); +int fusb302_select_rp_value(int rp); +int fusb302_set_cc(int pull); +int fusb302_set_polarity(int polarity); +int fusb302_set_vconn(int enable); +int fusb302_set_msg_header(int power_role, int data_role); +int fusb302_set_rx_enable(int enable); +int fusb302_get_message(uint32_t *payload, int *head); +int fusb302_transmit(enum tcpm_transmit_type type, + uint16_t header, const uint32_t *data); +//int alert(void); +void fusb302_pd_reset(int port); +void fusb302_auto_goodcrc_enable(int enable); +int fusb302_convert_bc_lvl(int bc_lvl); +void fusb302_detect_cc_pin_source_manual(int *cc1_lvl, int *cc2_lvl); +int fusb302_measure_cc_pin_source(int cc_measure); +void fusb302_detect_cc_pin_sink(int *cc1, int *cc2); +int fusb302_send_message(uint16_t header, const uint32_t *data, + uint8_t *buf, int buf_pos); +void fusb302_flush_rx_fifo(int port); +void fusb302_flush_tx_fifo(int port); +void fusb302_clear_int_pin(void); +void fusb302_set_bist_test_data(void); +int fusb302_get_chip_id(int *id); +uint32_t fusb302_get_interrupt_reason(void); +int fusb302_tcpc_write(int reg, int val); +int fusb302_tcpc_read(int reg, int *val); +int fusb302_tcpc_xfer(const uint8_t *out, + int out_size, uint8_t *in, + int in_size, int flags); +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* FUSB302_H */ diff --git a/fw/fw.c b/fw/fw.c new file mode 100644 index 0000000..63a984c --- /dev/null +++ b/fw/fw.c @@ -0,0 +1,64 @@ +// +// Copyright 2022 Wenting Zhang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/i2c.h" +#include "utils.h" +#include "tcpm_driver.h" +#include "usb_pd.h" +#include "ptn3460.h" + +int main() +{ + stdio_init_all(); + + sleep_ms(1000); + + int result = tcpm_init(0); + if (result) + fatal("Failed to initialize TCPC\n"); + + int cc1, cc2; + tcpc_config[0].drv->get_cc(0, &cc1, &cc2); + printf("CC status %d %d\n", cc1, cc2); + + ptn3460_init(); + pd_init(0); + sleep_ms(50); + + extern int dp_enabled; + bool hpd_sent = false; + + while (1) { + // TODO: Implement interrupt + fusb302_tcpc_alert(0); + pd_run_state_machine(0); + if (dp_enabled && !hpd_sent && !pd_is_vdm_busy(0)) { + printf("DP enabled\n"); + pd_send_hpd(0, hpd_high); + hpd_sent = true; + } + } + + return 0; +} diff --git a/fw/pico_sdk_import.cmake b/fw/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/fw/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/fw/ptn3460.c b/fw/ptn3460.c new file mode 100644 index 0000000..d2e7a07 --- /dev/null +++ b/fw/ptn3460.c @@ -0,0 +1,90 @@ +// +// Copyright 2022 Wenting Zhang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "ptn3460.h" +#include "utils.h" +//#include "edid.h" + +#define PTN3460_I2C_ADDRESS (0x60) +#define PTN3460_I2C (i2c0) +#define PTN3460_HPD_PIN (8) +#define PTN3460_PDN_PIN (9) + +void ptn3460_select_edid_emulation(uint8_t id) { + uint8_t buf[2]; + int result; + buf[0] = (uint8_t)0x84; + buf[1] = (uint8_t)0x01 | (id << 1); + result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS, + buf, 2, false); + if (result != 2) { + fatal("Failed writing data to PTN3460\n"); + } +} + +void ptn3460_load_edid(uint8_t *edid) { + uint8_t buf[1]; + int result; + buf[0] = 0; + result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS, + buf, 1, true); + result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS, + edid, 128, false); + if (result != 128) { + fatal("Failed writing data to PTN3460\n"); + } +} + +void ptn3460_init() { + gpio_init(PTN3460_HPD_PIN); + gpio_set_dir(PTN3460_HPD_PIN, GPIO_IN); + gpio_pull_down(PTN3460_HPD_PIN); + gpio_init(PTN3460_PDN_PIN); + gpio_put(PTN3460_PDN_PIN, 1); + gpio_set_dir(PTN3460_PDN_PIN, GPIO_OUT); + sleep_ms(100); + // wait for HPD to become high + int ticks = 0; + while (gpio_get(PTN3460_HPD_PIN) != true) { + ticks ++; + if (ticks > 500) { + fatal("PTN3460 boot timeout\n"); + } + sleep_ms(1); + } + printf("PTN3460 up after %d ms\n", ticks); + // Enable EDID emulation + ptn3460_select_edid_emulation(0); + //ptn3460_load_edid(); + + uint8_t buf[2]; + int result; + buf[0] = (uint8_t)0x80; + buf[1] = (uint8_t)0x02; + result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS, + buf, 2, false); + if (result != 2) { + fatal("Failed writing data to PTN3460\n"); + } +} \ No newline at end of file diff --git a/fw/ptn3460.h b/fw/ptn3460.h new file mode 100644 index 0000000..8e6e794 --- /dev/null +++ b/fw/ptn3460.h @@ -0,0 +1,24 @@ +// +// Copyright 2022 Wenting Zhang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +void ptn3460_init(); \ No newline at end of file diff --git a/fw/tcpm.h b/fw/tcpm.h new file mode 100644 index 0000000..12cfe89 --- /dev/null +++ b/fw/tcpm.h @@ -0,0 +1,277 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* USB Power delivery port management - common header for TCPM drivers */ + +#ifndef __CROS_EC_USB_PD_TCPM_TCPM_H +#define __CROS_EC_USB_PD_TCPM_TCPM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tcpm_driver.h" +#include "usb_pd_tcpm.h" + +#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \ + !defined(CONFIG_USB_PD_DUAL_ROLE) +#error "DRP auto toggle requires board to have DRP support" +#error "Please upgrade your board configuration" +#endif + +#ifndef CONFIG_USB_PD_TCPC +extern const struct tcpc_config_t tcpc_config[]; + +/* I2C wrapper functions - get I2C port / slave addr from config struct. */ +void tcpc_i2c_init(void); +int tcpc_write(int port, int reg, int val); +int tcpc_write16(int port, int reg, int val); +int tcpc_read(int port, int reg, int *val); +int tcpc_read16(int port, int reg, int *val); +int tcpc_xfer(int port, + const uint8_t *out, int out_size, + uint8_t *in, int in_size, + int flags); + +/* TCPM driver wrapper function */ +static inline int tcpm_init(int port) +{ + int rv; + + rv = tcpc_config[port].drv->init(port); + if (rv) + return rv; + + /* Board specific post TCPC init */ + if (board_tcpc_post_init) + rv = board_tcpc_post_init(port); + + return rv; +} + +static inline int tcpm_release(int port) +{ + return tcpc_config[port].drv->release(port); +} + +static inline int tcpm_get_cc(int port, int *cc1, int *cc2) +{ + return tcpc_config[port].drv->get_cc(port, cc1, cc2); +} + +static inline int tcpm_get_vbus_level(int port) +{ + return tcpc_config[port].drv->get_vbus_level(port); +} + +static inline int tcpm_select_rp_value(int port, int rp) +{ + return tcpc_config[port].drv->select_rp_value(port, rp); +} + +static inline int tcpm_set_cc(int port, int pull) +{ + return tcpc_config[port].drv->set_cc(port, pull); +} + +static inline int tcpm_set_polarity(int port, int polarity) +{ + return tcpc_config[port].drv->set_polarity(port, polarity); +} + +static inline int tcpm_set_vconn(int port, int enable) +{ + return tcpc_config[port].drv->set_vconn(port, enable); +} + +static inline int tcpm_set_msg_header(int port, int power_role, int data_role) +{ + return tcpc_config[port].drv->set_msg_header(port, power_role, + data_role); +} + +static inline int tcpm_set_rx_enable(int port, int enable) +{ + return tcpc_config[port].drv->set_rx_enable(port, enable); +} + +static inline int tcpm_get_message(int port, uint32_t *payload, int *head) +{ + return tcpc_config[port].drv->get_message(port, payload, head); +} + +static inline int tcpm_transmit(int port, enum tcpm_transmit_type type, + uint16_t header, const uint32_t *data) +{ + return tcpc_config[port].drv->transmit(port, type, header, data); +} + +static inline void tcpc_alert(int port) +{ + tcpc_config[port].drv->tcpc_alert(port); +} + +static inline void tcpc_discharge_vbus(int port, int enable) +{ + tcpc_config[port].drv->tcpc_discharge_vbus(port, enable); +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE +static inline int tcpm_auto_toggle_supported(int port) +{ + return !!tcpc_config[port].drv->drp_toggle; +} + +static inline int tcpm_set_drp_toggle(int port, int enable) +{ + return tcpc_config[port].drv->drp_toggle(port, enable); +} +#endif + +#ifdef CONFIG_CMD_I2C_STRESS_TEST_TCPC +static inline int tcpc_i2c_read(const int port, const int addr, + const int reg, int *data) +{ + return tcpc_read(port, reg, data); +} + +static inline int tcpc_i2c_write(const int port, const int addr, + const int reg, int data) +{ + return tcpc_write(port, reg, data); +} +#endif + +#else + +/** + * Initialize TCPM driver and wait for TCPC readiness. + * + * @param port Type-C port number + * + * @return EC_SUCCESS or error + */ +int tcpm_init(int port); + +/** + * Read the CC line status. + * + * @param port Type-C port number + * @param cc1 pointer to CC status for CC1 + * @param cc2 pointer to CC status for CC2 + * + * @return EC_SUCCESS or error + */ +int tcpm_get_cc(int port, int *cc1, int *cc2); + +/** + * Read VBUS + * + * @param port Type-C port number + * + * @return 0 => VBUS not detected, 1 => VBUS detected + */ +int tcpm_get_vbus_level(int port); + +/** + * Set the value of the CC pull-up used when we are a source. + * + * @param port Type-C port number + * @param rp One of enum tcpc_rp_value + * + * @return EC_SUCCESS or error + */ +int tcpm_select_rp_value(int port, int rp); + +/** + * Set the CC pull resistor. This sets our role as either source or sink. + * + * @param port Type-C port number + * @param pull One of enum tcpc_cc_pull + * + * @return EC_SUCCESS or error + */ +int tcpm_set_cc(int port, int pull); + +/** + * Set polarity + * + * @param port Type-C port number + * @param polarity 0=> transmit on CC1, 1=> transmit on CC2 + * + * @return EC_SUCCESS or error + */ +int tcpm_set_polarity(int port, int polarity); + +/** + * Set Vconn. + * + * @param port Type-C port number + * @param polarity Polarity of the CC line to read + * + * @return EC_SUCCESS or error + */ +int tcpm_set_vconn(int port, int enable); + +/** + * Set PD message header to use for goodCRC + * + * @param port Type-C port number + * @param power_role Power role to use in header + * @param data_role Data role to use in header + * + * @return EC_SUCCESS or error + */ +int tcpm_set_msg_header(int port, int power_role, int data_role); + +/** + * Set RX enable flag + * + * @param port Type-C port number + * @enable true for enable, false for disable + * + * @return EC_SUCCESS or error + */ +int tcpm_set_rx_enable(int port, int enable); + +/** + * Read last received PD message. + * + * @param port Type-C port number + * @param payload Pointer to location to copy payload of message + * @param header of message + * + * @return EC_SUCCESS or error + */ +int tcpm_get_message(int port, uint32_t *payload, int *head); + +/** + * Transmit PD message + * + * @param port Type-C port number + * @param type Transmit type + * @param header Packet header + * @param cnt Number of bytes in payload + * @param data Payload + * + * @return EC_SUCCESS or error + */ +int tcpm_transmit(int port, enum tcpm_transmit_type type, uint16_t header, + const uint32_t *data); + +/** + * TCPC is asserting alert + * + * @param port Type-C port number + */ +void tcpc_alert(int port); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fw/tcpm_driver.c b/fw/tcpm_driver.c new file mode 100644 index 0000000..f347f4a --- /dev/null +++ b/fw/tcpm_driver.c @@ -0,0 +1,129 @@ +// +// Copyright 2022 Wenting Zhang +// Copyright 2017 Jason Cerundolo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include "tcpm_driver.h" +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "utils.h" + +const struct tcpc_config_t tcpc_config[CONFIG_USB_PD_PORT_COUNT] = { + {0, FUSB302_I2C_SLAVE_ADDR, &fusb302_tcpm_drv}, +}; + +#define TCPC_I2C i2c0 +#define TCPC_I2C_SDA 0 +#define TCPC_I2C_SCL 1 + +void tcpc_i2c_init(void) { + i2c_init(TCPC_I2C, 100*1000); + gpio_set_function(TCPC_I2C_SDA, GPIO_FUNC_I2C); + gpio_set_function(TCPC_I2C_SCL, GPIO_FUNC_I2C); + gpio_pull_up(TCPC_I2C_SDA); + gpio_pull_up(TCPC_I2C_SCL); +} + +/* I2C wrapper functions - get I2C port / slave addr from config struct. */ +int tcpc_write(int port, int reg, int val) { + uint8_t buf[2]; + int result; + buf[0] = (uint8_t)reg; + buf[1] = (uint8_t)val; + result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 2, false); + if (result != 2) { + fatal("Failed writing data to TCPC"); + } + + return 0; +} + +int tcpc_write16(int port, int reg, int val) { + uint8_t buf[3]; + int result; + buf[0] = (uint8_t)reg; + buf[1] = (uint8_t)(val & 0xff); + buf[2] = (uint8_t)((val >> 8) & 0xff); + result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 3, false); + if (result != 3) { + fatal("Failed writing data to TCPC"); + } + + return 0; +} + +int tcpc_read(int port, int reg, int *val) { + int result; + uint8_t buf[1]; + buf[0] = reg; + result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 1, true); + if (result != 1) { + fatal("Failed writing data to TCPC"); + } + result = i2c_read_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 1, false); + if (result != 1) { + fatal("Failed reading data from TCPC"); + } + *val = (int)buf[0]; + + return 0; +} + +int tcpc_read16(int port, int reg, int *val) { + uint8_t buf[2]; + int result; + buf[0] = reg; + result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 1, true); + if (result != 1) { + fatal("Failed writing data to TCPC"); + } + result = i2c_read_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 2, false); + if (result != 2) { + fatal("Failed reading data from TCPC"); + } + *val = (int)buf[1] << 8 | buf[0]; + + return 0; +} + +int tcpc_xfer(int port, + const uint8_t *out, int out_size, + uint8_t *in, int in_size, + int flags) { + int result; + if (out_size) { + result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, out, + out_size, !!(flags & I2C_XFER_STOP)); + if (result != out_size) { + fatal("Failed writing data to TCPC"); + } + } + + if (in_size) { + result = i2c_read_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, in, + in_size, !!(flags & I2C_XFER_STOP)); + if (result != in_size) { + fatal("Failed reading data from TCPC"); + } + } + + return 0; +} + diff --git a/fw/tcpm_driver.h b/fw/tcpm_driver.h new file mode 100644 index 0000000..99e4d5d --- /dev/null +++ b/fw/tcpm_driver.h @@ -0,0 +1,41 @@ +// +// Copyright 2022 Wenting Zhang +// Copyright 2017 Jason Cerundolo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#ifndef TCPM_DRIVER_H_ +#define TCPM_DRIVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// USB-C Stuff +#include "tcpm.h" +#include "fusb302.h" +#define CONFIG_USB_PD_PORT_COUNT 1 + +#ifdef __cplusplus +} +#endif + +#endif /* TCPM_DRIVER_H_ */ diff --git a/fw/usb_pd.h b/fw/usb_pd.h new file mode 100644 index 0000000..038b2d8 --- /dev/null +++ b/fw/usb_pd.h @@ -0,0 +1,1807 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* USB Power delivery module */ + +#ifndef __USB_PD_H +#define __USB_PD_H + +#include "tcpm_driver.h" +#include "usb_pd_driver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Time units in microseconds */ +#define MSEC_US (1000ul) +#define SECOND_US (1000000ul) +#define MINUTE_US (60000000ul) +#define HOUR_US (3600000000ull) /* Too big to fit in a signed int */ + +/* PD Host command timeout */ +#define PD_HOST_COMMAND_TIMEOUT_US SECOND_US + +#ifdef CONFIG_USB_PD_PORT_COUNT +/* + * Define PD_PORT_TO_TASK_ID() and TASK_ID_TO_PD_PORT() macros to + * go between PD port number and task ID. Assume that TASK_ID_PD_C0 is the + * lowest task ID and IDs are on a continuous range. + */ +#ifdef HAS_TASK_PD_C0 +#define PD_PORT_TO_TASK_ID(port) (TASK_ID_PD_C0 + (port)) +#define TASK_ID_TO_PD_PORT(id) ((id) - TASK_ID_PD_C0) +#else +#define PD_PORT_TO_TASK_ID(port) -1 /* dummy task ID */ +#define TASK_ID_TO_PD_PORT(id) 0 +#endif /* CONFIG_COMMON_RUNTIME */ +#endif /* CONFIG_USB_PD_PORT_COUNT */ + +enum pd_rx_errors { + PD_RX_ERR_INVAL = -1, /* Invalid packet */ + PD_RX_ERR_HARD_RESET = -2, /* Got a Hard-Reset packet */ + PD_RX_ERR_CRC = -3, /* CRC mismatch */ + PD_RX_ERR_ID = -4, /* Invalid ID number */ + PD_RX_ERR_UNSUPPORTED_SOP = -5, /* Unsupported SOP */ + PD_RX_ERR_CABLE_RESET = -6 /* Got a Cable-Reset packet */ +}; + +/* Events for USB PD task */ +#define PD_EVENT_RX (1<<2) /* Incoming packet event */ +#define PD_EVENT_TX (1<<3) /* Outgoing packet event */ +#define PD_EVENT_CC (1<<4) /* CC line change event */ +#define PD_EVENT_TCPC_RESET (1<<5) /* TCPC has reset */ +#define PD_EVENT_UPDATE_DUAL_ROLE (1<<6) /* DRP state has changed */ + +/* --- PD data message helpers --- */ +#define PDO_MAX_OBJECTS 7 +#define PDO_MODES (PDO_MAX_OBJECTS - 1) + +/* PDO : Power Data Object */ +/* + * 1. The vSafe5V Fixed Supply Object shall always be the first object. + * 2. The remaining Fixed Supply Objects, + * if present, shall be sent in voltage order; lowest to highest. + * 3. The Battery Supply Objects, + * if present shall be sent in Minimum Voltage order; lowest to highest. + * 4. The Variable Supply (non battery) Objects, + * if present, shall be sent in Minimum Voltage order; lowest to highest. + * 5. (PD3.0) The Augmented PDO is defined to allow extension beyond the 4 PDOs + * above by examining bits <29:28> to determine the additional PDO function. + */ +#define PDO_TYPE_FIXED (0 << 30) +#define PDO_TYPE_BATTERY (1 << 30) +#define PDO_TYPE_VARIABLE (2 << 30) +#define PDO_TYPE_AUGMENTED (3 << 30) +#define PDO_TYPE_MASK (3 << 30) + +#define PDO_FIXED_DUAL_ROLE (1L << 29) /* Dual role device */ +#define PDO_FIXED_SUSPEND (1L << 28) /* USB Suspend supported */ +#define PDO_FIXED_EXTERNAL (1L << 27) /* Externally powered */ +#define PDO_FIXED_COMM_CAP (1L << 26) /* USB Communications Capable */ +#define PDO_FIXED_DATA_SWAP (1L << 25) /* Data role swap command supported */ +#define PDO_FIXED_PEAK_CURR () /* [21..20] Peak current */ +#define PDO_FIXED_VOLT(mv) (((mv)/50L) << 10) /* Voltage in 50mV units */ +#define PDO_FIXED_CURR(ma) (((ma)/10L) << 0) /* Max current in 10mA units */ + +#define PDO_FIXED(mv, ma, flags) (PDO_FIXED_VOLT(mv) |\ + PDO_FIXED_CURR(ma) | (flags)) + +#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50L) & 0x3FF) << 20) +#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50L) & 0x3FF) << 10) +#define PDO_VAR_OP_CURR(ma) ((((ma) / 10L) & 0x3FF) << 0) + +#define PDO_VAR(min_mv, max_mv, op_ma) \ + (PDO_VAR_MIN_VOLT(min_mv) | \ + PDO_VAR_MAX_VOLT(max_mv) | \ + PDO_VAR_OP_CURR(op_ma) | \ + PDO_TYPE_VARIABLE) + +#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50L) & 0x3FF) << 20) +#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50L) & 0x3FF) << 10) +#define PDO_BATT_OP_POWER(mw) ((((mw) / 250L) & 0x3FF) << 0) + +#define PDO_BATT(min_mv, max_mv, op_mw) \ + (PDO_BATT_MIN_VOLT(min_mv) | \ + PDO_BATT_MAX_VOLT(max_mv) | \ + PDO_BATT_OP_POWER(op_mw) | \ + PDO_TYPE_BATTERY) + +/* RDO : Request Data Object */ +#define RDO_OBJ_POS(n) (((n) & 0x7) << 28) +#define RDO_POS(rdo) (((rdo) >> 28) & 0x7) +#define RDO_GIVE_BACK (1 << 27) +#define RDO_CAP_MISMATCH (1 << 26) +#define RDO_COMM_CAP (1 << 25) +#define RDO_NO_SUSPEND (1 << 24) +#define RDO_FIXED_VAR_OP_CURR(ma) ((((ma) / 10) & 0x3FF) << 10) +#define RDO_FIXED_VAR_MAX_CURR(ma) ((((ma) / 10) & 0x3FF) << 0) + +#define RDO_BATT_OP_POWER(mw) ((((mw) / 250) & 0x3FF) << 10) +#define RDO_BATT_MAX_POWER(mw) ((((mw) / 250) & 0x3FF) << 10) + +#define RDO_FIXED(n, op_ma, max_ma, flags) \ + (RDO_OBJ_POS(n) | (flags) | \ + RDO_FIXED_VAR_OP_CURR(op_ma) | \ + RDO_FIXED_VAR_MAX_CURR(max_ma)) + + +#define RDO_BATT(n, op_mw, max_mw, flags) \ + (RDO_OBJ_POS(n) | (flags) | \ + RDO_BATT_OP_POWER(op_mw) | \ + RDO_BATT_MAX_POWER(max_mw)) + +/* BDO : BIST Data Object */ +#define BDO_MODE_RECV (0 << 28) +#define BDO_MODE_TRANSMIT (1 << 28) +#define BDO_MODE_COUNTERS (2 << 28) +#define BDO_MODE_CARRIER0 (3 << 28) +#define BDO_MODE_CARRIER1 (4 << 28) +#define BDO_MODE_CARRIER2 (5 << 28) +#define BDO_MODE_CARRIER3 (6 << 28) +#define BDO_MODE_EYE (7 << 28) + +#define BDO(mode, cnt) ((mode) | ((cnt) & 0xFFFF)) + +#define SVID_DISCOVERY_MAX 16 + +/* Timers */ +#define PD_T_SINK_TX (18*MSEC_US) /* between 16ms and 20 */ +#define PD_T_CHUNK_SENDER_RSP (24*MSEC_US) /* between 24ms and 30ms */ +#define PD_T_CHUNK_SENDER_REQ (24*MSEC_US) /* between 24ms and 30ms */ +#define PD_T_SEND_SOURCE_CAP (100*MSEC_US) /* between 100ms and 200ms */ +#define PD_T_SINK_WAIT_CAP (600*MSEC_US) /* between 310ms and 620ms */ +#define PD_T_SINK_TRANSITION (35*MSEC_US) /* between 20ms and 35ms */ +#define PD_T_SOURCE_ACTIVITY (45*MSEC_US) /* between 40ms and 50ms */ +//#define PD_T_SENDER_RESPONSE (30*MSEC_US) /* between 24ms and 30ms */ +#define PD_T_SENDER_RESPONSE (100*MSEC_US) /* between 24ms and 30ms */ +#define PD_T_PS_TRANSITION (500*MSEC_US) /* between 450ms and 550ms */ +#define PD_T_PS_SOURCE_ON (480*MSEC_US) /* between 390ms and 480ms */ +#define PD_T_PS_SOURCE_OFF (920*MSEC_US) /* between 750ms and 920ms */ +#define PD_T_PS_HARD_RESET (25*MSEC_US) /* between 25ms and 35ms */ +#define PD_T_ERROR_RECOVERY (25*MSEC_US) /* 25ms */ +#define PD_T_CC_DEBOUNCE (100*MSEC_US) /* between 100ms and 200ms */ +/* DRP_SNK + DRP_SRC must be between 50ms and 100ms with 30%-70% duty cycle */ +#define PD_T_DRP_SNK (40*MSEC_US) /* toggle time for sink DRP */ +#define PD_T_DRP_SRC (30*MSEC_US) /* toggle time for source DRP */ +#define PD_T_DEBOUNCE (15*MSEC_US) /* between 10ms and 20ms */ +#define PD_T_SINK_ADJ (55*MSEC_US) /* between PD_T_DEBOUNCE and 60ms */ +#define PD_T_SRC_RECOVER (760*MSEC_US) /* between 660ms and 1000ms */ +#define PD_T_SRC_RECOVER_MAX (1000*MSEC_US) /* 1000ms */ +#define PD_T_SRC_TURN_ON (275*MSEC_US) /* 275ms */ +#define PD_T_SAFE_0V (650*MSEC_US) /* 650ms */ +#define PD_T_NO_RESPONSE (5500*MSEC_US) /* between 4.5s and 5.5s */ +#define PD_T_BIST_TRANSMIT (50*MSEC_US) /* 50ms (used for task_wait arg) */ +#define PD_T_BIST_RECEIVE (60*MSEC_US) /* 60ms (max time to process bist) */ +#define PD_T_VCONN_SOURCE_ON (100*MSEC_US) /* 100ms */ +#define PD_T_TRY_SRC (125*MSEC_US) /* Max time for Try.SRC state */ +#define PD_T_TRY_WAIT (600*MSEC_US) /* Max time for TryWait.SNK state */ +#define PD_T_SINK_REQUEST (100*MSEC_US) /* Wait 100ms before next request */ + +/* number of edges and time window to detect CC line is not idle */ +#define PD_RX_TRANSITION_COUNT 3 +#define PD_RX_TRANSITION_WINDOW 20 /* between 12us and 20us */ + +/* from USB Type-C Specification Table 5-1 */ +#define PD_T_AME (1*SECOND_US) /* timeout from UFP attach to Alt Mode Entry */ + +/* VDM Timers ( USB PD Spec Rev2.0 Table 6-30 )*/ +#define PD_T_VDM_BUSY (100*MSEC_US) /* at least 100ms */ +#define PD_T_VDM_E_MODE (25*MSEC_US) /* enter/exit the same max */ +#define PD_T_VDM_RCVR_RSP (15*MSEC_US) /* max of 15ms */ +#define PD_T_VDM_SNDR_RSP (30*MSEC_US) /* max of 30ms */ +#define PD_T_VDM_WAIT_MODE_E (100*MSEC_US) /* enter/exit the same max */ + +/* function table for entered mode */ +struct amode_fx { + int (*status)(int port, uint32_t *payload); + int (*config)(int port, uint32_t *payload); +}; + +/* function table for alternate mode capable responders */ +struct svdm_response { + int (*identity)(int port, uint32_t *payload); + int (*svids)(int port, uint32_t *payload); + int (*modes)(int port, uint32_t *payload); + int (*enter_mode)(int port, uint32_t *payload); + int (*exit_mode)(int port, uint32_t *payload); + struct amode_fx *amode; +}; + +struct svdm_svid_data { + uint16_t svid; + int mode_cnt; + uint32_t mode_vdo[PDO_MODES]; +}; + +struct svdm_amode_fx { + uint16_t svid; + int (*enter)(int port, uint32_t mode_caps); + int (*status)(int port, uint32_t *payload); + int (*config)(int port, uint32_t *payload); + void (*post_config)(int port); + int (*attention)(int port, uint32_t *payload); + void (*exit)(int port); +}; + +/* defined in /usb_pd_policy.c */ +/* All UFP_U should have */ +extern const struct svdm_response svdm_rsp; +/* All DFP_U should have */ +extern const struct svdm_amode_fx supported_modes[]; +extern const int supported_modes_cnt; + +/* DFP data needed to support alternate mode entry and exit */ +struct svdm_amode_data { + const struct svdm_amode_fx *fx; + /* VDM object position */ + int opos; + /* mode capabilities specific to SVID amode. */ + struct svdm_svid_data *data; +}; + +enum hpd_event { + hpd_none, + hpd_low, + hpd_high, + hpd_irq, +}; + +/* DisplayPort flags */ +#define DP_FLAGS_DP_ON (1 << 0) /* Display port mode is on */ +#define DP_FLAGS_HPD_HI_PENDING (1 << 1) /* Pending HPD_HI */ + +/* supported alternate modes */ +enum pd_alternate_modes { + PD_AMODE_GOOGLE, + PD_AMODE_DISPLAYPORT, + /* not a real mode */ + PD_AMODE_COUNT, +}; + +/* Policy structure for driving alternate mode */ +struct pd_policy { + /* index of svid currently being operated on */ + int svid_idx; + /* count of svids discovered */ + int svid_cnt; + /* SVDM identity info (Id, Cert Stat, 0-4 Typec specific) */ + uint32_t identity[PDO_MAX_OBJECTS - 1]; + /* supported svids & corresponding vdo mode data */ + struct svdm_svid_data svids[SVID_DISCOVERY_MAX]; + /* active modes */ + struct svdm_amode_data amodes[PD_AMODE_COUNT]; + /* Next index to insert DFP alternate mode into amodes */ + int amode_idx; +}; + +/* + * VDO : Vendor Defined Message Object + * VDM object is minimum of VDM header + 6 additional data objects. + */ + +#define VDO_MAX_SIZE 7 + +#define VDM_VER10 0 +#define VDM_VER20 1 + +/* + * VDM header + * ---------- + * <31:16> :: SVID + * <15> :: VDM type ( 1b == structured, 0b == unstructured ) + * <14:13> :: Structured VDM version (00b == Rev 2.0, 01b == Rev 3.0 ) + * <12:11> :: reserved + * <10:8> :: object position (1-7 valid ... used for enter/exit mode only) + * <7:6> :: command type (SVDM only?) + * <5> :: reserved (SVDM), command type (UVDM) + * <4:0> :: command + */ +#define VDO(vid, type, custom) \ + (((vid) << 16) | \ + ((type) << 15) | \ + ((custom) & 0x7FFF)) + +#define VDO_SVDM_TYPE (1 << 15) +#define VDO_SVDM_VERS(x) (x << 13) +#define VDO_OPOS(x) (x << 8) +#define VDO_CMDT(x) (x << 6) +#define VDO_OPOS_MASK VDO_OPOS(0x7) +#define VDO_CMDT_MASK VDO_CMDT(0x3) + +#define CMDT_INIT 0 +#define CMDT_RSP_ACK 1 +#define CMDT_RSP_NAK 2 +#define CMDT_RSP_BUSY 3 + + +/* reserved for SVDM ... for Google UVDM */ +#define VDO_SRC_INITIATOR (0 << 5) +#define VDO_SRC_RESPONDER (1 << 5) + +#define CMD_DISCOVER_IDENT 1 +#define CMD_DISCOVER_SVID 2 +#define CMD_DISCOVER_MODES 3 +#define CMD_ENTER_MODE 4 +#define CMD_EXIT_MODE 5 +#define CMD_ATTENTION 6 +#define CMD_DP_STATUS 16 +#define CMD_DP_CONFIG 17 + +#define VDO_CMD_VENDOR(x) (((10 + (x)) & 0x1f)) + +/* ChromeOS specific commands */ +#define VDO_CMD_VERSION VDO_CMD_VENDOR(0) +#define VDO_CMD_SEND_INFO VDO_CMD_VENDOR(1) +#define VDO_CMD_READ_INFO VDO_CMD_VENDOR(2) +#define VDO_CMD_REBOOT VDO_CMD_VENDOR(5) +#define VDO_CMD_FLASH_ERASE VDO_CMD_VENDOR(6) +#define VDO_CMD_FLASH_WRITE VDO_CMD_VENDOR(7) +#define VDO_CMD_ERASE_SIG VDO_CMD_VENDOR(8) +#define VDO_CMD_PING_ENABLE VDO_CMD_VENDOR(10) +#define VDO_CMD_CURRENT VDO_CMD_VENDOR(11) +#define VDO_CMD_FLIP VDO_CMD_VENDOR(12) +#define VDO_CMD_GET_LOG VDO_CMD_VENDOR(13) +#define VDO_CMD_CCD_EN VDO_CMD_VENDOR(14) + +#define PD_VDO_VID(vdo) ((vdo) >> 16) +#define PD_VDO_SVDM(vdo) (((vdo) >> 15) & 1) +#define PD_VDO_OPOS(vdo) (((vdo) >> 8) & 0x7) +#define PD_VDO_CMD(vdo) ((vdo) & 0x1f) +#define PD_VDO_CMDT(vdo) (((vdo) >> 6) & 0x3) + +/* + * SVDM Identity request -> response + * + * Request is simply properly formatted SVDM header + * + * Response is 4 data objects: + * [0] :: SVDM header + * [1] :: Identitiy header + * [2] :: Cert Stat VDO + * [3] :: (Product | Cable) VDO + * [4] :: AMA VDO + * + */ +#define VDO_INDEX_HDR 0 +#define VDO_INDEX_IDH 1 +#define VDO_INDEX_CSTAT 2 +#define VDO_INDEX_CABLE 3 +#define VDO_INDEX_PRODUCT 3 +#define VDO_INDEX_AMA 4 +#define VDO_I(name) VDO_INDEX_##name + +/* + * SVDM Identity Header + * -------------------- + * <31> :: data capable as a USB host + * <30> :: data capable as a USB device + * <29:27> :: product type + * <26> :: modal operation supported (1b == yes) + * <25:16> :: SBZ + * <15:0> :: USB-IF assigned VID for this cable vendor + */ +#define IDH_PTYPE_UNDEF 0 +#define IDH_PTYPE_HUB 1 +#define IDH_PTYPE_PERIPH 2 +#define IDH_PTYPE_PCABLE 3 +#define IDH_PTYPE_ACABLE 4 +#define IDH_PTYPE_AMA 5 + +#define VDO_IDH(usbh, usbd, ptype, is_modal, vid) \ + ((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27 \ + | (is_modal) << 26 | ((vid) & 0xffff)) + +#define PD_IDH_PTYPE(vdo) (((vdo) >> 27) & 0x7) +#define PD_IDH_VID(vdo) ((vdo) & 0xffff) + +/* + * Cert Stat VDO + * ------------- + * <31:20> : SBZ + * <19:0> : USB-IF assigned TID for this cable + */ +#define VDO_CSTAT(tid) ((tid) & 0xfffff) +#define PD_CSTAT_TID(vdo) ((vdo) & 0xfffff) + +/* + * Product VDO + * ----------- + * <31:16> : USB Product ID + * <15:0> : USB bcdDevice + */ +#define VDO_PRODUCT(pid, bcd) (((pid) & 0xffff) << 16 | ((bcd) & 0xffff)) +#define PD_PRODUCT_PID(vdo) (((vdo) >> 16) & 0xffff) + +/* + * Cable VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:20> :: SBZ + * <19:18> :: type-C to Type-A/B/C (00b == A, 01 == B, 10 == C) + * <17> :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle) + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (11b == both ends active VCONN req) + * <10> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <9> :: SSTX2 Directionality support + * <8> :: SSRX1 Directionality support + * <7> :: SSRX2 Directionality support + * <6:5> :: Vbus current handling capability + * <4> :: Vbus through cable (0b == no, 1b == yes) + * <3> :: SOP" controller present? (0b == no, 1b == yes) + * <2:0> :: USB SS Signaling support + */ +#define CABLE_ATYPE 0 +#define CABLE_BTYPE 1 +#define CABLE_CTYPE 2 +#define CABLE_PLUG 0 +#define CABLE_RECEPTACLE 1 +#define CABLE_CURR_1A5 0 +#define CABLE_CURR_3A 1 +#define CABLE_CURR_5A 2 +#define CABLE_USBSS_U2_ONLY 0 +#define CABLE_USBSS_U31_GEN1 1 +#define CABLE_USBSS_U31_GEN2 2 +#define VDO_CABLE(hw, fw, cbl, gdr, lat, term, tx1d, tx2d, rx1d, rx2d, cur, vps, sopp, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18 \ + | (gdr) << 17 | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11 \ + | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7 \ + | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3 \ + | ((usbss) & 0x7)) + +/* + * AMA VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:12> :: SBZ + * <11> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <10> :: SSTX2 Directionality support + * <9> :: SSRX1 Directionality support + * <8> :: SSRX2 Directionality support + * <7:5> :: Vconn power + * <4> :: Vconn power required + * <3> :: Vbus power required + * <2:0> :: USB SS Signaling support + */ +#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 \ + | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8 \ + | ((vcpwr) & 0x3) << 5 | (vcr) << 4 | (vbr) << 3 \ + | ((usbss) & 0x7)) + +#define PD_VDO_AMA_VCONN_REQ(vdo) (((vdo) >> 4) & 1) +#define PD_VDO_AMA_VBUS_REQ(vdo) (((vdo) >> 3) & 1) + +#define AMA_VCONN_PWR_1W 0 +#define AMA_VCONN_PWR_1W5 1 +#define AMA_VCONN_PWR_2W 2 +#define AMA_VCONN_PWR_3W 3 +#define AMA_VCONN_PWR_4W 4 +#define AMA_VCONN_PWR_5W 5 +#define AMA_VCONN_PWR_6W 6 +#define AMA_USBSS_U2_ONLY 0 +#define AMA_USBSS_U31_GEN1 1 +#define AMA_USBSS_U31_GEN2 2 +#define AMA_USBSS_BBONLY 3 + +/* + * SVDM Discover SVIDs request -> response + * + * Request is properly formatted VDM Header with discover SVIDs command. + * Response is a set of SVIDs of all all supported SVIDs with all zero's to + * mark the end of SVIDs. If more than 12 SVIDs are supported command SHOULD be + * repeated. + */ +#define VDO_SVID(svid0, svid1) (((svid0) & 0xffff) << 16 | ((svid1) & 0xffff)) +#define PD_VDO_SVID_SVID0(vdo) ((vdo) >> 16) +#define PD_VDO_SVID_SVID1(vdo) ((vdo) & 0xffff) + +/* + * Google modes capabilities + * <31:8> : reserved + * <7:0> : mode + */ +#define VDO_MODE_GOOGLE(mode) (mode & 0xff) + +#define MODE_GOOGLE_FU 1 /* Firmware Update mode */ + +/* + * Mode Capabilities + * + * Number of VDOs supplied is SID dependent (but <= 6 VDOS?) + */ +#define VDO_MODE_CNT_DISPLAYPORT 1 + +/* + * DisplayPort modes capabilities + * ------------------------------- + * <31:24> : SBZ + * <23:16> : UFP_D pin assignment supported + * <15:8> : DFP_D pin assignment supported + * <7> : USB 2.0 signaling (0b=yes, 1b=no) + * <6> : Plug | Receptacle (0b == plug, 1b == receptacle) + * <5:2> : xxx1: Supports DPv1.3, xx1x Supports USB Gen 2 signaling + * Other bits are reserved. + * <1:0> : signal direction ( 00b=rsv, 01b=sink, 10b=src 11b=both ) + */ +#define VDO_MODE_DP(snkp, srcp, usb, gdr, sign, sdir) \ + (((snkp) & 0xff) << 16 | ((srcp) & 0xff) << 8 \ + | ((usb) & 1) << 7 | ((gdr) & 1) << 6 | ((sign) & 0xF) << 2 \ + | ((sdir) & 0x3)) +#define PD_DP_PIN_CAPS(x) ((((x) >> 6) & 0x1) ? (((x) >> 16) & 0x3f) \ + : (((x) >> 8) & 0x3f)) + +#define MODE_DP_PIN_A 0x01 +#define MODE_DP_PIN_B 0x02 +#define MODE_DP_PIN_C 0x04 +#define MODE_DP_PIN_D 0x08 +#define MODE_DP_PIN_E 0x10 +#define MODE_DP_PIN_F 0x20 + +/* Pin configs B/D/F support multi-function */ +#define MODE_DP_PIN_MF_MASK 0x2a +/* Pin configs A/B support BR2 signaling levels */ +#define MODE_DP_PIN_BR2_MASK 0x3 +/* Pin configs C/D/E/F support DP signaling levels */ +#define MODE_DP_PIN_DP_MASK 0x3c + +#define MODE_DP_V13 0x1 +#define MODE_DP_GEN2 0x2 + +#define MODE_DP_SNK 0x1 +#define MODE_DP_SRC 0x2 +#define MODE_DP_BOTH 0x3 + +/* + * DisplayPort Status VDO + * ---------------------- + * <31:9> : SBZ + * <8> : IRQ_HPD : 1 == irq arrived since last message otherwise 0. + * <7> : HPD state : 0 = HPD_LOW, 1 == HPD_HIGH + * <6> : Exit DP Alt mode: 0 == maintain, 1 == exit + * <5> : USB config : 0 == maintain current, 1 == switch to USB from DP + * <4> : Multi-function preference : 0 == no pref, 1 == MF preferred. + * <3> : enabled : is DPout on/off. + * <2> : power low : 0 == normal or LPM disabled, 1 == DP disabled for LPM + * <1:0> : connect status : 00b == no (DFP|UFP)_D is connected or disabled. + * 01b == DFP_D connected, 10b == UFP_D connected, 11b == both. + */ +#define VDO_DP_STATUS(irq, lvl, amode, usbc, mf, en, lp, conn) \ + (((irq) & 1) << 8 | ((lvl) & 1) << 7 | ((amode) & 1) << 6 \ + | ((usbc) & 1) << 5 | ((mf) & 1) << 4 | ((en) & 1) << 3 \ + | ((lp) & 1) << 2 | ((conn & 0x3) << 0)) + +#define PD_VDO_DPSTS_HPD_IRQ(x) (((x) >> 8) & 1) +#define PD_VDO_DPSTS_HPD_LVL(x) (((x) >> 7) & 1) +#define PD_VDO_DPSTS_MF_PREF(x) (((x) >> 4) & 1) + +/* Per DisplayPort Spec v1.3 Section 3.3 */ +#define HPD_USTREAM_DEBOUNCE_LVL (2*MSEC_US) +#define HPD_USTREAM_DEBOUNCE_IRQ (250) +#define HPD_DSTREAM_DEBOUNCE_IRQ (500) /* between 500-1000us */ + +/* + * DisplayPort Configure VDO + * ------------------------- + * <31:24> : SBZ + * <23:16> : SBZ + * <15:8> : Pin assignment requested. Choose one from mode caps. + * <7:6> : SBZ + * <5:2> : signalling : 1h == DP v1.3, 2h == Gen 2 + * Oh is only for USB, remaining values are reserved + * <1:0> : cfg : 00 == USB, 01 == DFP_D, 10 == UFP_D, 11 == reserved + */ +#define VDO_DP_CFG(pin, sig, cfg) \ + (((pin) & 0xff) << 8 | ((sig) & 0xf) << 2 | ((cfg) & 0x3)) + +#define PD_DP_CFG_DPON(x) (((x & 0x3) == 1) || ((x & 0x3) == 2)) +/* + * Get the pin assignment mask + * for backward compatibility, if it is null, + * get the former sink pin assignment we used to be in <23:16>. + */ +#define PD_DP_CFG_PIN(x) ((((x) >> 8) & 0xff) ? (((x) >> 8) & 0xff) \ + : (((x) >> 16) & 0xff)) +/* + * ChromeOS specific PD device Hardware IDs. Used to identify unique + * products and used in VDO_INFO. Note this field is 10 bits. + */ +#define USB_PD_HW_DEV_ID_RESERVED 0 +#define USB_PD_HW_DEV_ID_ZINGER 1 +#define USB_PD_HW_DEV_ID_MINIMUFFIN 2 +#define USB_PD_HW_DEV_ID_DINGDONG 3 +#define USB_PD_HW_DEV_ID_HOHO 4 +#define USB_PD_HW_DEV_ID_HONEYBUNS 5 + +/* + * ChromeOS specific VDO_CMD_READ_INFO responds with device info including: + * RW Hash: First 20 bytes of SHA-256 of RW (20 bytes) + * HW Device ID: unique descriptor for each ChromeOS model (2 bytes) + * top 6 bits are minor revision, bottom 10 bits are major + * SW Debug Version: Software version useful for debugging (15 bits) + * IS RW: True if currently in RW, False otherwise (1 bit) + */ +#define VDO_INFO(id, id_minor, ver, is_rw) ((id_minor) << 26 \ + | ((id) & 0x3ff) << 16 \ + | ((ver) & 0x7fff) << 1 \ + | ((is_rw) & 1)) +#define VDO_INFO_HW_DEV_ID(x) ((x) >> 16) +#define VDO_INFO_SW_DBG_VER(x) (((x) >> 1) & 0x7fff) +#define VDO_INFO_IS_RW(x) ((x) & 1) + +#define HW_DEV_ID_MAJ(x) (x & 0x3ff) +#define HW_DEV_ID_MIN(x) ((x) >> 10) + +/* USB-IF SIDs */ +#define USB_SID_PD 0xff00 /* power delivery */ +#define USB_SID_DISPLAYPORT 0xff01 + +#define USB_GOOGLE_TYPEC_URL "http://www.google.com/chrome/devices/typec" +/* USB Vendor ID assigned to Google Inc. */ +#define USB_VID_GOOGLE 0x18d1 + +/* Other Vendor IDs */ +#define USB_VID_APPLE 0x05ac + +/* Timeout for message receive in microseconds */ +#define USB_PD_RX_TMOUT_US 1800 + +/* --- Protocol layer functions --- */ + +enum pd_states { + PD_STATE_DISABLED, + PD_STATE_SUSPENDED, +#ifdef CONFIG_USB_PD_DUAL_ROLE + PD_STATE_SNK_DISCONNECTED, + PD_STATE_SNK_DISCONNECTED_DEBOUNCE, + PD_STATE_SNK_HARD_RESET_RECOVER, + PD_STATE_SNK_DISCOVERY, + PD_STATE_SNK_REQUESTED, + PD_STATE_SNK_TRANSITION, + PD_STATE_SNK_READY, + + PD_STATE_SNK_SWAP_INIT, + PD_STATE_SNK_SWAP_SNK_DISABLE, + PD_STATE_SNK_SWAP_SRC_DISABLE, + PD_STATE_SNK_SWAP_STANDBY, + PD_STATE_SNK_SWAP_COMPLETE, +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + + PD_STATE_SRC_DISCONNECTED, + PD_STATE_SRC_DISCONNECTED_DEBOUNCE, + PD_STATE_SRC_HARD_RESET_RECOVER, + PD_STATE_SRC_STARTUP, + PD_STATE_SRC_DISCOVERY, + PD_STATE_SRC_NEGOCIATE, + PD_STATE_SRC_ACCEPTED, + PD_STATE_SRC_POWERED, + PD_STATE_SRC_TRANSITION, + PD_STATE_SRC_READY, + PD_STATE_SRC_GET_SINK_CAP, + PD_STATE_DR_SWAP, + +#ifdef CONFIG_USB_PD_DUAL_ROLE + PD_STATE_SRC_SWAP_INIT, + PD_STATE_SRC_SWAP_SNK_DISABLE, + PD_STATE_SRC_SWAP_SRC_DISABLE, + PD_STATE_SRC_SWAP_STANDBY, + +#ifdef CONFIG_USBC_VCONN_SWAP + PD_STATE_VCONN_SWAP_SEND, + PD_STATE_VCONN_SWAP_INIT, + PD_STATE_VCONN_SWAP_READY, +#endif /* CONFIG_USBC_VCONN_SWAP */ +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + + PD_STATE_SOFT_RESET, + PD_STATE_HARD_RESET_SEND, + PD_STATE_HARD_RESET_EXECUTE, +#ifdef CONFIG_COMMON_RUNTIME + PD_STATE_BIST_RX, + PD_STATE_BIST_TX, +#endif + +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + PD_STATE_DRP_AUTO_TOGGLE, +#endif + /* Number of states. Not an actual state. */ + PD_STATE_COUNT, +}; + +#define PD_FLAGS_PING_ENABLED (1 << 0) /* SRC_READY pings enabled */ +#define PD_FLAGS_PARTNER_DR_POWER (1 << 1) /* port partner is dualrole power */ +#define PD_FLAGS_PARTNER_DR_DATA (1 << 2) /* port partner is dualrole data */ +#define PD_FLAGS_CHECK_IDENTITY (1 << 3) /* discover identity in READY */ +#define PD_FLAGS_SNK_CAP_RECVD (1 << 4) /* sink capabilities received */ +#define PD_FLAGS_TCPC_DRP_TOGGLE (1 << 5) /* TCPC-controlled DRP toggling */ +#define PD_FLAGS_EXPLICIT_CONTRACT (1 << 6) /* explicit pwr contract in place */ +#define PD_FLAGS_VBUS_NEVER_LOW (1 << 7) /* VBUS input has never been low */ +#define PD_FLAGS_PREVIOUS_PD_CONN (1 << 8) /* previously PD connected */ +#define PD_FLAGS_CHECK_PR_ROLE (1 << 9) /* check power role in READY */ +#define PD_FLAGS_CHECK_DR_ROLE (1 << 10)/* check data role in READY */ +#define PD_FLAGS_PARTNER_EXTPOWER (1 << 11)/* port partner has external pwr */ +#define PD_FLAGS_VCONN_ON (1 << 12)/* vconn is being sourced */ +#define PD_FLAGS_TRY_SRC (1 << 13)/* Try.SRC states are active */ +#define PD_FLAGS_PARTNER_USB_COMM (1 << 14)/* port partner is USB comms */ +#define PD_FLAGS_UPDATE_SRC_CAPS (1 << 15)/* send new source capabilities */ +#define PD_FLAGS_TS_DTS_PARTNER (1 << 16)/* partner has rp/rp or rd/rd */ +/* Flags to clear on a disconnect */ +#define PD_FLAGS_RESET_ON_DISCONNECT_MASK (PD_FLAGS_PARTNER_DR_POWER | \ + PD_FLAGS_PARTNER_DR_DATA | \ + PD_FLAGS_CHECK_IDENTITY | \ + PD_FLAGS_SNK_CAP_RECVD | \ + PD_FLAGS_TCPC_DRP_TOGGLE | \ + PD_FLAGS_EXPLICIT_CONTRACT | \ + PD_FLAGS_PREVIOUS_PD_CONN | \ + PD_FLAGS_CHECK_PR_ROLE | \ + PD_FLAGS_CHECK_DR_ROLE | \ + PD_FLAGS_PARTNER_EXTPOWER | \ + PD_FLAGS_VCONN_ON | \ + PD_FLAGS_TRY_SRC | \ + PD_FLAGS_PARTNER_USB_COMM | \ + PD_FLAGS_UPDATE_SRC_CAPS | \ + PD_FLAGS_TS_DTS_PARTNER) + +enum pd_cc_states { + PD_CC_NONE, + + /* From DFP perspective */ + PD_CC_NO_UFP, + PD_CC_AUDIO_ACC, + PD_CC_DEBUG_ACC, + PD_CC_UFP_ATTACHED, + + /* From UFP perspective */ + PD_CC_DFP_ATTACHED +}; + +#ifdef CONFIG_USB_PD_DUAL_ROLE +enum pd_dual_role_states { + /* While disconnected, toggle between src and sink */ + PD_DRP_TOGGLE_ON, + /* Stay in src until disconnect, then stay in sink forever */ + PD_DRP_TOGGLE_OFF, + /* Stay in current power role, don't switch. No auto-toggle support */ + PD_DRP_FREEZE, + /* Switch to sink */ + PD_DRP_FORCE_SINK, + /* Switch to source */ + PD_DRP_FORCE_SOURCE, +}; +/** + * Get dual role state + * + * @return Current dual-role state, from enum pd_dual_role_states + */ +enum pd_dual_role_states pd_get_dual_role(void); +/** + * Set dual role state, from among enum pd_dual_role_states + * + * @param state New state of dual-role port, selected from + * enum pd_dual_role_states + */ +void pd_set_dual_role(enum pd_dual_role_states state); + +/** + * Get role, from among PD_ROLE_SINK and PD_ROLE_SOURCE + * + * @param port Port number from which to get role + */ +int pd_get_role(int port); + +#endif + +/* Control Message type */ +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 */ + + /* Used for REV 3.0 */ + PD_CTRL_NOT_SUPPORTED = 16, + PD_CTRL_GET_SOURCE_CAP_EXT = 17, + PD_CTRL_GET_STATUS = 18, + PD_CTRL_FR_SWAP = 19, + PD_CTRL_GET_PPS_STATUS = 20, + PD_CTRL_GET_COUNTRY_CODES = 21, + /* 22-31 Reserved */ +}; + +/* Battery Status Data Object fields for REV 3.0 */ +#define BSDO_CAP_UNKNOWN 0xffff +#define BSDO_CAP(n) (((n) & 0xffff) << 16) +#define BSDO_INVALID (1 << 8) +#define BSDO_PRESENT (1 << 9) +#define BSDO_DISCHARGING (1 << 10) +#define BSDO_IDLE (1 << 11) + +/* Get Battery Cap Message fields for REV 3.0 */ +#define BATT_CAP_REF(n) (((n) >> 16) & 0xff) + +/* Extended message type for REV 3.0 */ +enum pd_ext_msg_type { + /* 0 Reserved */ + PD_EXT_SOURCE_CAP = 1, + PD_EXT_STATUS = 2, + PD_EXT_GET_BATTERY_CAP = 3, + PD_EXT_GET_BATTERY_STATUS = 4, + PD_EXT_BATTERY_CAP = 5, + PD_EXT_GET_MANUFACTURER_INFO = 6, + PD_EXT_MANUFACTURER_INFO = 7, + PD_EXT_SECURITY_REQUEST = 8, + PD_EXT_SECURITY_RESPONSE = 9, + PD_EXT_FIRMWARE_UPDATE_REQUEST = 10, + PD_EXT_FIRMWARE_UPDATE_RESPONSE = 11, + PD_EXT_PPS_STATUS = 12, + PD_EXT_COUNTRY_INFO = 13, + PD_EXT_COUNTRY_CODES = 14, + /* 15-31 Reserved */ +}; + +/* Data message type */ +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 for REV 2.0 */ + PD_DATA_BATTERY_STATUS = 5, + PD_DATA_ALERT = 6, + PD_DATA_GET_COUNTRY_INFO = 7, + /* 8-14 Reserved for REV 3.0 */ + PD_DATA_VENDOR_DEF = 15, +}; + +/* Protocol revision */ +#define PD_REV10 0 +#define PD_REV20 1 +#define PD_REV30 2 + +/* Power role */ +#define PD_ROLE_SINK 0 +#define PD_ROLE_SOURCE 1 +/* Data role */ +#define PD_ROLE_UFP 0 +#define PD_ROLE_DFP 1 +/* Vconn role */ +#define PD_ROLE_VCONN_OFF 0 +#define PD_ROLE_VCONN_ON 1 + +/* chunk is a request or response in REV 3.0 */ +#define CHUNK_RESPONSE 0 +#define CHUNK_REQUEST 1 + +/* collision avoidance Rp values in REV 3.0 */ +#define SINK_TX_OK TYPEC_RP_3A0 +#define SINK_TX_NG TYPEC_RP_1A5 + +/* Port role at startup */ +#ifndef PD_ROLE_DEFAULT +#ifdef CONFIG_USB_PD_DUAL_ROLE +#define PD_ROLE_DEFAULT(port) PD_ROLE_SINK +#else +#define PD_ROLE_DEFAULT(port) PD_ROLE_SOURCE +#endif +#endif + +/* Port default state at startup */ +#ifdef CONFIG_USB_PD_DUAL_ROLE +#define PD_DEFAULT_STATE(port) ((PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE) ? \ + PD_STATE_SRC_DISCONNECTED : \ + PD_STATE_SNK_DISCONNECTED) +#else +#define PD_DEFAULT_STATE(port) PD_STATE_SRC_DISCONNECTED +#endif + +/* build extended message header */ +/* All extended messages are chunked, so set bit 15 */ +#define PD_EXT_HEADER(cnum, rchk, dsize) \ + ((1 << 15) | ((cnum) << 11) | \ + ((rchk) << 10) | (dsize)) + +/* build message header */ +#define PD_HEADER(type, prole, drole, id, cnt, rev, ext) \ + ((type) | ((rev) << 6) | \ + ((drole) << 5) | ((prole) << 8) | \ + ((id) << 9) | ((cnt) << 12) | ((ext) << 15)) + +/* Used for processing pd header */ +#define PD_HEADER_EXT(header) (((header) >> 15) & 1) +#define PD_HEADER_CNT(header) (((header) >> 12) & 7) +#define PD_HEADER_TYPE(header) ((header) & 0xF) +#define PD_HEADER_ID(header) (((header) >> 9) & 7) +#define PD_HEADER_REV(header) (((header) >> 6) & 3) + +/* Used for processing pd extended header */ +#define PD_EXT_HEADER_CHUNKED(header) (((header) >> 15) & 1) +#define PD_EXT_HEADER_CHUNK_NUM(header) (((header) >> 11) & 0xf) +#define PD_EXT_HEADER_REQ_CHUNK(header) (((header) >> 10) & 1) +#define PD_EXT_HEADER_DATA_SIZE(header) ((header) & 0x1ff) + +/* K-codes for special symbols */ +#define PD_SYNC1 0x18 +#define PD_SYNC2 0x11 +#define PD_SYNC3 0x06 +#define PD_RST1 0x07 +#define PD_RST2 0x19 +#define PD_EOP 0x0D + +/* Minimum PD supply current (mA) */ +#define PD_MIN_MA 500 + +/* Minimum PD voltage (mV) */ +#define PD_MIN_MV 5000 + +/* No connect voltage threshold for sources based on Rp */ +#define PD_SRC_DEF_VNC_MV 1600 +#define PD_SRC_1_5_VNC_MV 1600 +#define PD_SRC_3_0_VNC_MV 2600 + +/* Rd voltage threshold for sources based on Rp */ +#define PD_SRC_DEF_RD_THRESH_MV 200 +#define PD_SRC_1_5_RD_THRESH_MV 400 +#define PD_SRC_3_0_RD_THRESH_MV 800 + +/* Voltage threshold to detect connection when presenting Rd */ +#define PD_SNK_VA_MV 250 + +/* --- Policy layer functions --- */ + +/* Request types for pd_build_request() */ +enum pd_request_type { + PD_REQUEST_VSAFE5V, + PD_REQUEST_MAX, +}; + +#ifdef CONFIG_USB_PD_REV30 +/** + * Get current PD Revision + * + * @param port USB-C port number + * @return 0 for PD_REV1.0, 1 for PD_REV2.0, 2 for PD_REV3.0 + */ +int pd_get_rev(int port); + +/** + * Get current PD VDO Version + * + * @param port USB-C port number + * @return 0 for PD_REV1.0, 1 for PD_REV2.0 + */ +int pd_get_vdo_ver(int port); +#else +#define pd_get_rev(n) PD_REV20 +#define pd_get_vdo_ver(n) VDM_VER10 +#endif +/** + * Decide which PDO to choose from the source capabilities. + * + * @param port USB-C port number + * @param rdo requested Request Data Object. + * @param ma selected current limit (stored on success) + * @param mv selected supply voltage (stored on success) + * @param req_type request type + * @return <0 if invalid, else EC_SUCCESS + */ +int pd_build_request(int port, uint32_t *rdo, uint32_t *ma, uint32_t *mv, + enum pd_request_type req_type); + +/** + * Check if max voltage request is allowed (only used if + * CONFIG_USB_PD_CHECK_MAX_REQUEST_ALLOWED is defined). + * + * @return True if max voltage request allowed, False otherwise + */ +int pd_is_max_request_allowed(void); + +/** + * Callback with source capabilities packet + * + * @param port USB-C port number + * @param cnt the number of Power Data Objects. + * @param src_caps Power Data Objects representing the source capabilities. + */ +void pd_process_source_cap_callback(int port, int cnt, uint32_t *src_caps); + +/** + * Process source capabilities packet + * + * @param port USB-C port number + * @param cnt the number of Power Data Objects. + * @param src_caps Power Data Objects representing the source capabilities. + */ +void pd_process_source_cap(int port, int cnt, uint32_t *src_caps); + +/** + * Find PDO index that offers the most amount of power and stays within + * max_mv voltage. + * + * @param port USB-C port number + * @param max_mv maximum voltage (or -1 if no limit) + * @param pdo raw pdo corresponding to index, or index 0 on error (output) + * @return index of PDO within source cap packet + */ +int pd_find_pdo_index(int port, int max_mv, uint32_t *pdo); + +/** + * Extract power information out of a Power Data Object (PDO) + * + * @param pdo raw pdo to extract + * @param ma current of the PDO (output) + * @param mv voltage of the PDO (output) + */ +void pd_extract_pdo_power(uint32_t pdo, uint32_t *ma, uint32_t *mv); + +/** + * Reduce the sink power consumption to a minimum value. + * + * @param port USB-C port number + * @param ma reduce current to minimum value. + * @param mv reduce voltage to minimum value. + */ +void pd_snk_give_back(int port, uint32_t * const ma, uint32_t * const mv); + +/** + * Put a cap on the max voltage requested as a sink. + * @param mv maximum voltage in millivolts. + */ +void pd_set_max_voltage(unsigned mv); + +/** + * Get the max voltage that can be requested as set by pd_set_max_voltage(). + * @return max voltage + */ +unsigned pd_get_max_voltage(void); + +/** + * Check if this board supports the given input voltage. + * + * @mv input voltage + * @return 1 if voltage supported, 0 if not + */ +int pd_is_valid_input_voltage(int mv); + +/** + * Request a new operating voltage. + * + * @param rdo Request Data Object with the selected operating point. + * @param port The port which the request came in on. + * @return EC_SUCCESS if we can get the requested voltage/OP, <0 else. + */ +int pd_check_requested_voltage(uint32_t rdo, const int port); + +/** + * Run board specific checks on request message + * + * @param rdo the request data object word sent by the sink. + * @param pdo_cnt the total number of source PDOs. + * @return EC_SUCCESS if request is ok , <0 else. + */ +int pd_board_check_request(uint32_t rdo, int pdo_cnt); + +/** + * Select a new output voltage. + * + * param idx index of the new voltage in the source PDO table. + */ +void pd_transition_voltage(int idx); + +/** + * Go back to the default/safe state of the power supply + * + * @param port USB-C port number + */ +void pd_power_supply_reset(int port); + +/** + * Enable or disable VBUS discharge for a given port. + * + * @param port USB-C port number + * @enable 1 if enabling discharge, 0 if disabling + */ +void pd_set_vbus_discharge(int port, int enable); + +/** + * Enable the power supply output after the ready delay. + * + * @param port USB-C port number + * @return EC_SUCCESS if the power supply is ready, <0 else. + */ +int pd_set_power_supply_ready(int port); + +/** + * Ask the specified voltage from the PD source. + * + * It triggers a new negotiation sequence with the source. + * @param port USB-C port number + * @param mv request voltage in millivolts. + */ +void pd_request_source_voltage(int port, int mv); + +/** + * Set a voltage limit from the PD source. + * + * If the source is currently active, it triggers a new negotiation. + * @param port USB-C port number + * @param mv limit voltage in millivolts. + */ +void pd_set_external_voltage_limit(int port, int mv); + +/** + * Set the PD input current limit. + * + * @param port USB-C port number + * @param max_ma Maximum current limit + * @param supply_voltage Voltage at which current limit is applied + */ +void pd_set_input_current_limit(int port, uint32_t max_ma, + uint32_t supply_voltage); + + +/** + * Update the power contract if it exists. + * + * @param port USB-C port number. + */ +void pd_update_contract(int port); + +/* Encode DTS status of port partner in current limit parameter */ +typedef uint32_t typec_current_t; +#define TYPEC_CURRENT_DTS_MASK (1 << 31) +#define TYPEC_CURRENT_ILIM_MASK (~TYPEC_CURRENT_DTS_MASK) + +/** + * Set the type-C input current limit. + * + * @param port USB-C port number + * @param max_ma Maximum current limit + * @param supply_voltage Voltage at which current limit is applied + */ +void typec_set_input_current_limit(int port, typec_current_t max_ma, + uint32_t supply_voltage); + +/** + * Set the type-C current limit when sourcing current.. + * + * @param port USB-C port number + * @param rp One of enum tcpc_rp_value (eg TYPEC_RP_3A0) defining the limit. + */ +void typec_set_source_current_limit(int port, int rp); + +/** + * Verify board specific health status : current, voltages... + * + * @return EC_SUCCESS if the board is good, <0 else. + */ +int pd_board_checks(void); + +/** + * Return if VBUS is detected on type-C port + * + * @param port USB-C port number + * @return VBUS is detected + */ +int pd_snk_is_vbus_provided(int port); + +/** + * Notify PD protocol that VBUS has gone low + * + * @param port USB-C port number + */ +void pd_vbus_low(int port); + +/** + * Check if power swap is allowed. + * + * @param port USB-C port number + * @return True if power swap is allowed, False otherwise + */ +int pd_check_power_swap(int port); + +/** + * Check if data swap is allowed. + * + * @param port USB-C port number + * @param data_role current data role + * @return True if data swap is allowed, False otherwise + */ +int pd_check_data_swap(int port, int data_role); + +/** + * Check if vconn swap is allowed. + * + * @param port USB-C port number + * @return True if vconn swap is allowed, False otherwise + */ + +int pd_check_vconn_swap(int port); + +/** + * Check current power role for potential power swap + * + * @param port USB-C port number + * @param pr_role Our power role + * @param flags PD flags + */ +void pd_check_pr_role(int port, int pr_role, int flags); + +/** + * Check current data role for potential data swap + * + * @param port USB-C port number + * @param dr_role Our data role + * @param flags PD flags + */ +void pd_check_dr_role(int port, int dr_role, int flags); + +/** + * Check if we should charge from this device. This is + * basically a white-list for chargers that are dual-role, + * don't set the externally powered bit, but we should charge + * from by default. + * + * @param vid Port partner Vendor ID + * @param pid Port partner Product ID + */ +int pd_charge_from_device(uint16_t vid, uint16_t pid); + +/** + * Execute data swap. + * + * @param port USB-C port number + * @param data_role new data role + */ +void pd_execute_data_swap(int port, int data_role); + +/** + * Get PD device info used for VDO_CMD_SEND_INFO / VDO_CMD_READ_INFO + * + * @param info_data pointer to info data array + */ +void pd_get_info(uint32_t *info_data); + +/** + * Handle Vendor Defined Messages + * + * @param port USB-C port number + * @param cnt number of data objects in the payload. + * @param payload payload data. + * @param rpayload pointer to the data to send back. + * @return if >0, number of VDOs to send back. + */ +int pd_custom_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload); + +/** + * Handle Structured Vendor Defined Messages + * + * @param port USB-C port number + * @param cnt number of data objects in the payload. + * @param payload payload data. + * @param rpayload pointer to the data to send back. + * @return if >0, number of VDOs to send back. + */ +int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload); + +/** + * Handle Custom VDMs for flashing. + * + * @param port USB-C port number + * @param cnt number of data objects in the payload. + * @param payload payload data. + * @return if >0, number of VDOs to send back. + */ +int pd_custom_flash_vdm(int port, int cnt, uint32_t *payload); + +/** + * Enter alternate mode on DFP + * + * @param port USB-C port number + * @param svid USB standard or vendor id to exit or zero for DFP amode reset. + * @param opos object position of mode to exit. + * @return vdm for UFP to be sent to enter mode or zero if not. + */ +uint32_t pd_dfp_enter_mode(int port, uint16_t svid, int opos); + +/** + * Get DisplayPort pin mode for DFP to request from UFP's capabilities. + * + * @param port USB-C port number. + * @param status DisplayPort Status VDO. + * @return one-hot PIN config to request. + */ +int pd_dfp_dp_get_pin_mode(int port, uint32_t status); + +/** + * Exit alternate mode on DFP + * + * @param port USB-C port number + * @param svid USB standard or vendor id to exit or zero for DFP amode reset. + * @param opos object position of mode to exit. + * @return 1 if UFP should be sent exit mode VDM. + */ +int pd_dfp_exit_mode(int port, uint16_t svid, int opos); + +/** + * Initialize policy engine for DFP + * + * @param port USB-C port number + */ +void pd_dfp_pe_init(int port); + +/** + * Return the VID of the USB PD accessory connected to a specified port + * + * @param port USB-C port number + * @return the USB Vendor Identifier or 0 if it doesn't exist + */ +uint16_t pd_get_identity_vid(int port); + +/** + * Return the PID of the USB PD accessory connected to a specified port + * + * @param port USB-C port number + * @return the USB Product Identifier or 0 if it doesn't exist + */ +uint16_t pd_get_identity_pid(int port); + +/** + * Store Device ID & RW hash of device + * + * @param port USB-C port number + * @param dev_id device identifier + * @param rw_hash pointer to rw_hash + * @param current_image current image: RW or RO + * @return true if the dev / hash match an existing hash + * in our table, false otherwise + */ +int pd_dev_store_rw_hash(int port, uint16_t dev_id, uint32_t *rw_hash, + uint32_t ec_current_image); + +/** + * Try to fetch one PD log entry from accessory + * + * @param port USB-C accessory port number + * @return EC_RES_SUCCESS if the VDM was sent properly else error code + */ +int pd_fetch_acc_log_entry(int port); + +/** + * Analyze the log entry received as the VDO_CMD_GET_LOG payload. + * + * @param port USB-C accessory port number + * @param cnt number of data objects in payload + * @param payload payload data + */ +void pd_log_recv_vdm(int port, int cnt, uint32_t *payload); + +/** + * Send Vendor Defined Message + * + * @param port USB-C port number + * @param vid Vendor ID + * @param cmd VDO command number + * @param data Pointer to payload to send + * @param count number of data objects in payload + */ +void pd_send_vdm(int port, uint32_t vid, int cmd, const uint32_t *data, + int count); + +/* Power Data Objects for the source and the sink */ +extern const uint32_t pd_src_pdo[]; +extern const int pd_src_pdo_cnt; +extern const uint32_t pd_src_pdo_max[]; +extern const int pd_src_pdo_max_cnt; +extern const uint32_t pd_snk_pdo[]; +extern const int pd_snk_pdo_cnt; + +/** + * Request that a host event be sent to notify the AP of a PD power event. + * + * @param mask host event mask. + */ +#if defined(HAS_TASK_HOSTCMD) && !defined(TEST_BUILD) +void pd_send_host_event(int mask); +#else +static inline void pd_send_host_event(int mask) { } +#endif + +/** + * Determine if in alternate mode or not. + * + * @param port port number. + * @param svid USB standard or vendor id + * @return object position of mode chosen in alternate mode otherwise zero. + */ +int pd_alt_mode(int port, uint16_t svid); + +/** + * Send hpd over USB PD. + * + * @param port port number. + * @param hpd hotplug detect type. + */ +void pd_send_hpd(int port, enum hpd_event hpd); + +/** + * Enable USB Billboard Device. + */ +extern const struct deferred_data pd_usb_billboard_deferred_data; +/* --- Physical layer functions : chip specific --- */ + +/* Packet preparation/retrieval */ + +/** + * Prepare packet reading state machine. + * + * @param port USB-C port number + */ +void pd_init_dequeue(int port); + +/** + * Prepare packet reading state machine. + * + * @param port USB-C port number + * @param off current position in the packet buffer. + * @param len minimum size to read in bits. + * @param val the read bits. + * @return new position in the packet buffer. + */ +int pd_dequeue_bits(int port, int off, int len, uint32_t *val); + +/** + * Advance until the end of the preamble. + * + * @param port USB-C port number + * @return new position in the packet buffer. + */ +int pd_find_preamble(int port); + +/** + * Write the preamble in the TX buffer. + * + * @param port USB-C port number + * @return new position in the packet buffer. + */ +int pd_write_preamble(int port); + +/** + * Write one 10-period symbol in the TX packet. + * corresponding to a quartet with 4b5b encoding + * and Biphase Mark Coding. + * + * @param port USB-C port number + * @param bit_off current position in the packet buffer. + * @param val10 the 10-bit integer. + * @return new position in the packet buffer. + */ +int pd_write_sym(int port, int bit_off, uint32_t val10); + + +/** + * Ensure that we have an edge after EOP and we end up at level 0, + * also fill the last byte. + * + * @param port USB-C port number + * @param bit_off current position in the packet buffer. + * @return new position in the packet buffer. + */ +int pd_write_last_edge(int port, int bit_off); + +/** + * Do 4B5B encoding on a 32-bit word. + * + * @param port USB-C port number + * @param off current offset in bits inside the message + * @param val32 32-bit word value to encode + * @return new offset in the message in bits. + */ +int encode_word(int port, int off, uint32_t val32); + +/** + * Ensure that we have an edge after EOP and we end up at level 0, + * also fill the last byte. + * + * @param port USB-C port number + * @param header PD packet header + * @param cnt number of payload words + * @param data payload content + * @return length of the message in bits. + */ +int prepare_message(int port, uint16_t header, uint8_t cnt, + const uint32_t *data); + +/** + * Dump the current PD packet on the console for debug. + * + * @param port USB-C port number + * @param msg context string. + */ +void pd_dump_packet(int port, const char *msg); + +/** + * Change the TX data clock frequency. + * + * @param port USB-C port number + * @param freq frequency in hertz. + */ +void pd_set_clock(int port, int freq); + +/* TX/RX callbacks */ + +/** + * Start sending over the wire the prepared packet. + * + * @param port USB-C port number + * @param polarity plug polarity (0=CC1, 1=CC2). + * @param bit_len size of the packet in bits. + * @return length transmitted or negative if error + */ +int pd_start_tx(int port, int polarity, int bit_len); + +/** + * Set PD TX DMA to use circular mode. Call this before pd_start_tx() to + * continually loop over the transmit buffer given in pd_start_tx(). + * + * @param port USB-C port number + */ +void pd_tx_set_circular_mode(int port); + +/** + * Stop PD TX DMA circular mode transaction already in progress. + * + * @param port USB-C port number + */ +void pd_tx_clear_circular_mode(int port); + +/** + * Call when we are done sending a packet. + * + * @param port USB-C port number + * @param polarity plug polarity (0=CC1, 1=CC2). + */ +void pd_tx_done(int port, int polarity); + +/** + * Check whether the PD reception is started. + * + * @param port USB-C port number + * @return true if the reception is on-going. + */ +int pd_rx_started(int port); + +/** + * Suspend the PD task. + * @param port USB-C port number + * @param enable pass 0 to resume, anything else to suspend + */ +void pd_set_suspend(int port, int enable); + +/** + * Check if the port has been initialized and PD task has not been + * suspended. + * + * @param port USB-C port number + * @return true if the PD task is not suspended. + */ +int pd_is_port_enabled(int port); + +/* Callback when the hardware has detected an incoming packet */ +void pd_rx_event(int port); +/* Start sampling the CC line for reception */ +void pd_rx_start(int port); +/* Call when we are done reading a packet */ +void pd_rx_complete(int port); + +/* restart listening to the CC wire */ +void pd_rx_enable_monitoring(int port); +/* stop listening to the CC wire during transmissions */ +void pd_rx_disable_monitoring(int port); + +/* get time since last RX edge interrupt */ +uint64_t get_time_since_last_edge(int port); + +/** + * Deinitialize the hardware used for PD. + * + * @param port USB-C port number + */ +void pd_hw_release(int port); + +/** + * Initialize the hardware used for PD RX/TX. + * + * @param port USB-C port number + * @param role Role to initialize pins in + */ +void pd_hw_init(int port, int role); + +/** + * Initialize the reception side of hardware used for PD. + * + * This is a subset of pd_hw_init() including only : + * the comparators + the RX edge delay timer + the RX DMA. + * + * @param port USB-C port number + */ +void pd_hw_init_rx(int port); + +/** + * Initialize the Power Delivery state machine + */ +void pd_init(int port); + +/** + * Run the state machine. This function must be called regularly + * to iterate through the state machine. It uses get_time() to + * determine what actions to take each call. + */ +void pd_run_state_machine(int port); + +/* --- Protocol layer functions --- */ + +/** + * Decode a raw packet in the RX buffer. + * + * @param port USB-C port number + * @param payload buffer to store the packet payload (must be 7x 32-bit) + * @return the packet header or <0 in case of error + */ +int pd_analyze_rx(int port, uint32_t *payload); + +/** + * Check if PD communication is enabled + * + * @return true if it's enabled or false otherwise + */ +int pd_comm_is_enabled(int port); + +/** + * Get connected state + * + * @param port USB-C port number + * @return True if port is in connected state + */ +int pd_is_connected(int port); + +/** + * Execute a hard reset + * + * @param port USB-C port number + */ +void pd_execute_hard_reset(int port); + +/** + * Signal to protocol layer that PD transmit is complete + * + * @param port USB-C port number + * @param status status of the transmission + */ +void pd_transmit_complete(int port, int status); + +/** + * Get port polarity. + * + * @param port USB-C port number + */ +int pd_get_polarity(int port); + +/** + * Get port partner data swap capable status + * + * @param port USB-C port number + */ +int pd_get_partner_data_swap_capable(int port); + +/** + * Request power swap command to be issued + * + * @param port USB-C port number + */ +void pd_request_power_swap(int port); + +/** + * Try to become the VCONN source, if we are not already the source and the + * other side is willing to accept a VCONN swap. + * + * @param port USB-C port number + */ +void pd_try_vconn_src(int port); + +/** + * Request data swap command to be issued + * + * @param port USB-C port number + */ +void pd_request_data_swap(int port); + +/** + * Set the PD communication enabled flag. When communication is disabled, + * the port can still detect connection and source power but will not + * send or respond to any PD communication. + * + * @param port USB-C port number + * @param enable Enable flag to set + */ +void pd_comm_enable(int port, int enable); + +/** + * Set the PD pings enabled flag. When source has negotiated power over + * PD successfully, it can optionally send pings periodically based on + * this enable flag. + * + * @param port USB-C port number + * @param enable Enable flag to set + */ +void pd_ping_enable(int port, int enable); + +/* Issue PD soft reset */ +void pd_soft_reset(void); + +/* Prepare PD communication for reset */ +void pd_prepare_reset(void); + +/** + * Signal power request to indicate a charger update that affects the port. + * + * @param port USB-C port number + */ +void pd_set_new_power_request(int port); + +/** + * Return true if partner port is a DTS or TS capable of entering debug + * mode (eg. is presenting Rp/Rp or Rd/Rd). + * + * @param port USB-C port number + */ +int pd_ts_dts_plugged(int port); + +/* ----- Logging ----- */ +#ifdef CONFIG_USB_PD_LOGGING +/** + * Record one event in the PD logging FIFO. + * + * @param type event type as defined by PD_EVENT_xx in ec_commands.h + * @param size_port payload size and port num (defined by PD_LOG_PORT_SIZE) + * @param data type-defined information + * @param payload pointer to the optional payload (0..16 bytes) + */ +void pd_log_event(uint8_t type, uint8_t size_port, + uint16_t data, void *payload); + +/** + * Retrieve one logged event and prepare a VDM with it. + * + * Used to answer the VDO_CMD_GET_LOG unstructured VDM. + * + * @param payload pointer to the payload data buffer (must be 7 words) + * @return number of 32-bit words in the VDM payload. + */ +int pd_vdm_get_log_entry(uint32_t *payload); +#else /* CONFIG_USB_PD_LOGGING */ +static inline void pd_log_event(uint8_t type, uint8_t size_port, + uint16_t data, void *payload) {} +static inline int pd_vdm_get_log_entry(uint32_t *payload) { return 0; } +#endif /* CONFIG_USB_PD_LOGGING */ + +#ifdef __cplusplus +} +#endif + +#endif /* __CROS_EC_USB_PD_H */ diff --git a/fw/usb_pd_driver.c b/fw/usb_pd_driver.c new file mode 100644 index 0000000..030cace --- /dev/null +++ b/fw/usb_pd_driver.c @@ -0,0 +1,261 @@ +// +// Copyright 2022 Wenting Zhang +// Copyright 2017 Jason Cerundolo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include "pico/stdlib.h" +#include "usb_pd_driver.h" +#include "usb_pd.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(t) (sizeof(t) / sizeof(t[0])) +#endif + +extern struct tc_module tc_instance; +extern uint32_t g_us_timestamp_upper_32bit; + +uint32_t pd_task_set_event(uint32_t event, int wait_for_reply) +{ + switch (event) + { + case PD_EVENT_TX: + break; + default: + break; + } + return 0; +} + +const uint32_t pd_src_pdo[] = { + PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), +}; +const int pd_src_pdo_cnt = ARRAY_SIZE(pd_src_pdo); + +const uint32_t pd_snk_pdo[] = { + PDO_FIXED(5000, 500, PDO_FIXED_FLAGS), +}; +const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo); + +void pd_set_input_current_limit(int port, uint32_t max_ma, + uint32_t supply_voltage) +{ + +} + +int pd_is_valid_input_voltage(int mv) +{ + return 1; +} + +int pd_snk_is_vbus_provided(int port) +{ + return 1; +} + +timestamp_t get_time(void) +{ + absolute_time_t t = get_absolute_time(); +#ifdef NDEBUG + return (timestamp_t)t; +#else + return (timestamp_t)t._private_us_since_boot; +#endif +} + +void pd_power_supply_reset(int port) +{ + return; +} + +int pd_custom_vdm(int port, int cnt, uint32_t *payload, + uint32_t **rpayload) +{ +#if 0 + int cmd = PD_VDO_CMD(payload[0]); + uint16_t dev_id = 0; + int is_rw, is_latest; + + /* make sure we have some payload */ + if (cnt == 0) + return 0; + + switch (cmd) { + case VDO_CMD_VERSION: + /* guarantee last byte of payload is null character */ + *(payload + cnt - 1) = 0; + //CPRINTF("version: %s\n", (char *)(payload+1)); + break; + case VDO_CMD_READ_INFO: + case VDO_CMD_SEND_INFO: + /* copy hash */ + if (cnt == 7) { + dev_id = VDO_INFO_HW_DEV_ID(payload[6]); + is_rw = VDO_INFO_IS_RW(payload[6]); + + is_latest = pd_dev_store_rw_hash(port, + dev_id, + payload + 1, + is_rw ? + SYSTEM_IMAGE_RW : + SYSTEM_IMAGE_RO); + + /* + * Send update host event unless our RW hash is + * already known to be the latest update RW. + */ + if (!is_rw || !is_latest) + pd_send_host_event(PD_EVENT_UPDATE_DEVICE); + + //CPRINTF("DevId:%d.%d SW:%d RW:%d\n", + // HW_DEV_ID_MAJ(dev_id), + // HW_DEV_ID_MIN(dev_id), + // VDO_INFO_SW_DBG_VER(payload[6]), + // is_rw); + } else if (cnt == 6) { + /* really old devices don't have last byte */ + pd_dev_store_rw_hash(port, dev_id, payload + 1, + SYSTEM_IMAGE_UNKNOWN); + } + break; + case VDO_CMD_CURRENT: + CPRINTF("Current: %dmA\n", payload[1]); + break; + case VDO_CMD_FLIP: + /* TODO: usb_mux_flip(port); */ + break; +#ifdef CONFIG_USB_PD_LOGGING + case VDO_CMD_GET_LOG: + pd_log_recv_vdm(port, cnt, payload); + break; +#endif /* CONFIG_USB_PD_LOGGING */ + } +#endif // if 0 + + return 0; +} + +void pd_execute_data_swap(int port, int data_role) +{ + /* Do nothing */ +} + +int pd_check_data_swap(int port, int data_role) +{ + // Never allow data swap + return 0; +} + +int pd_check_power_swap(int port) +{ + /* Always refuse power swap */ + return 0; +} + +int pd_board_checks(void) +{ + return EC_SUCCESS; +} + +int pd_set_power_supply_ready(int port) +{ +#if 0 + /* Disable charging */ + gpio_set_level(GPIO_USB_C0_CHARGE_L, 1); + + /* Enable VBUS source */ + gpio_set_level(GPIO_USB_C0_5V_EN, 1); + + /* notify host of power info change */ + pd_send_host_event(PD_EVENT_POWER_CHANGE); +#endif // if 0 + return EC_SUCCESS; /* we are ready */ +} + +void pd_transition_voltage(int idx) +{ + /* No-operation: we are always 5V */ + +#if 0 + timestamp_t deadline; + uint32_t mv = src_pdo_charge[idx - 1].mv; + + /* Is this a transition to a new voltage? */ + if (charge_port_is_active() && vbus[CHG].mv != mv) { + /* + * Alter voltage limit on charge port, this should cause + * the port to select the desired PDO. + */ + pd_set_external_voltage_limit(CHG, mv); + + /* Wait for CHG transition */ + deadline.val = get_time().val + PD_T_PS_TRANSITION; + CPRINTS("Waiting for CHG port transition"); + while (charge_port_is_active() && + vbus[CHG].mv != mv && + get_time().val < deadline.val) + msleep(10); + + if (vbus[CHG].mv != mv) { + CPRINTS("Missed CHG transition, resetting DUT"); + pd_power_supply_reset(DUT); + return; + } + + CPRINTS("CHG transitioned"); + } + + vbus[DUT].mv = vbus[CHG].mv; + vbus[DUT].ma = vbus[CHG].ma; +#endif // if 0 + +} + +void pd_check_dr_role(int port, int dr_role, int flags) +{ +#if 0 + /* If UFP, try to switch to DFP */ + if ((flags & PD_FLAGS_PARTNER_DR_DATA) && dr_role == PD_ROLE_UFP) + pd_request_data_swap(port); +#endif +} + +void pd_check_pr_role(int port, int pr_role, int flags) +{ +#if 0 + /* + * If partner is dual-role power and dualrole toggling is on, consider + * if a power swap is necessary. + */ + if ((flags & PD_FLAGS_PARTNER_DR_POWER) && + pd_get_dual_role() == PD_DRP_TOGGLE_ON) { + /* + * If we are a sink and partner is not externally powered, then + * swap to become a source. If we are source and partner is + * externally powered, swap to become a sink. + */ + int partner_extpower = flags & PD_FLAGS_PARTNER_EXTPOWER; + + if ((!partner_extpower && pr_role == PD_ROLE_SINK) || + (partner_extpower && pr_role == PD_ROLE_SOURCE)) + pd_request_power_swap(port); + } +#endif // if 0 +} + diff --git a/fw/usb_pd_driver.h b/fw/usb_pd_driver.h new file mode 100644 index 0000000..d2add13 --- /dev/null +++ b/fw/usb_pd_driver.h @@ -0,0 +1,119 @@ +// +// Copyright 2022 Wenting Zhang +// Copyright 2017 Jason Cerundolo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#ifndef USB_PD_DRIVER_H_ +#define USB_PD_DRIVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "usb_pd.h" + +#include + +//#define CONFIG_BBRAM +//#define CONFIG_CHARGE_MANAGER +//#define CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP +//#define CONFIG_USBC_VCONN_SWAP +#define CONFIG_USB_PD_ALT_MODE +//#define CONFIG_USB_PD_CHROMEOS +#define CONFIG_USB_PD_DUAL_ROLE +//#define CONFIG_USB_PD_GIVE_BACK +//#define CONFIG_USB_PD_SIMPLE_DFP +//#define CONFIG_USB_PD_TCPM_TCPCI + +/* USB configuration */ +#define CONFIG_USB_PID 0x500c +#define CONFIG_USB_BCD_DEV 0x0001 /* v 0.01 */ + +#define CONFIG_USB_PD_IDENTITY_HW_VERS 1 +#define CONFIG_USB_PD_IDENTITY_SW_VERS 1 + +/* Default pull-up value on the USB-C ports when they are used as source. */ +#define CONFIG_USB_PD_PULLUP TYPEC_RP_USB + +/* Override PD_ROLE_DEFAULT in usb_pd.h */ +#define PD_ROLE_DEFAULT(port) (PD_ROLE_SINK) + +/* Don't automatically change roles */ +#undef CONFIG_USB_PD_INITIAL_DRP_STATE +#define CONFIG_USB_PD_INITIAL_DRP_STATE PD_DRP_FREEZE + +/* board specific type-C power constants */ +/* + * delay to turn on the power supply max is ~16ms. + * delay to turn off the power supply max is about ~180ms. + */ +#define PD_POWER_SUPPLY_TURN_ON_DELAY 10000 /* us */ +#define PD_POWER_SUPPLY_TURN_OFF_DELAY 20000 /* us */ + +/* Define typical operating power and max power */ +#define PD_OPERATING_POWER_MW (2250ull) +#define PD_MAX_POWER_MW (15000ull) +#define PD_MAX_CURRENT_MA (3000ull) +#define PD_MAX_VOLTAGE_MV (5000ull) + +#define PDO_FIXED_FLAGS (PDO_FIXED_COMM_CAP) + +#define usleep(us) (delay_us(us)) +#define msleep(ms) (delay_ms(ms)) + +typedef union { + uint64_t val; + struct { + uint32_t lo; + uint32_t hi; + } le /* little endian words */; + } timestamp_t; + +uint32_t pd_task_set_event(uint32_t event, int wait_for_reply); +void pd_power_supply_reset(int port); + +// Get the current timestamp from the system timer. +timestamp_t get_time(void); + +/* Standard macros / definitions */ +#ifndef MAX +#define MAX(a, b) \ +({ \ + __typeof__(a) temp_a = (a); \ + __typeof__(b) temp_b = (b); \ + \ + temp_a > temp_b ? temp_a : temp_b; \ +}) +#endif +#ifndef MIN +#define MIN(a, b) \ +({ \ + __typeof__(a) temp_a = (a); \ + __typeof__(b) temp_b = (b); \ + \ + temp_a < temp_b ? temp_a : temp_b; \ +}) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* USB_PD_DRIVER_H_ */ diff --git a/fw/usb_pd_policy.c b/fw/usb_pd_policy.c new file mode 100644 index 0000000..274db9c --- /dev/null +++ b/fw/usb_pd_policy.c @@ -0,0 +1,1163 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include "tcpm.h" +#include "usb_pd.h" + +#include + +#ifdef CONFIG_COMMON_RUNTIME +#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) +#else +#define CPRINTS(format, args...) printf(format, ## args) +#define CPRINTF(format, args...) printf(format, ## args) +#endif + +static int rw_flash_changed = 1; + +int pd_check_requested_voltage(uint32_t rdo, const int port) +{ + int max_ma = rdo & 0x3FF; + int op_ma = (rdo >> 10) & 0x3FF; + int idx = RDO_POS(rdo); + uint32_t pdo; + uint32_t pdo_ma; +#if defined(CONFIG_USB_PD_DYNAMIC_SRC_CAP) || \ + defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) + const uint32_t *src_pdo; + const int pdo_cnt = charge_manager_get_source_pdo(&src_pdo, port); +#else + const uint32_t *src_pdo = pd_src_pdo; + const int pdo_cnt = pd_src_pdo_cnt; +#endif + + /* Board specific check for this request */ + if (pd_board_check_request(rdo, pdo_cnt)) + return EC_ERROR_INVAL; + + /* check current ... */ + pdo = src_pdo[idx - 1]; + pdo_ma = (pdo & 0x3ff); + if (op_ma > pdo_ma) + return EC_ERROR_INVAL; /* too much op current */ + if (max_ma > pdo_ma && !(rdo & RDO_CAP_MISMATCH)) + return EC_ERROR_INVAL; /* too much max current */ + + CPRINTF("Requested %d V %d mA (for %d/%d mA)\n", + ((pdo >> 10) & 0x3ff) * 50, (pdo & 0x3ff) * 10, + op_ma * 10, max_ma * 10); + + /* Accept the requested voltage */ + return EC_SUCCESS; +} + +static int stub_pd_board_check_request(uint32_t rdo, int pdo_cnt) +{ + int idx = RDO_POS(rdo); + + /* Check for invalid index */ + return (!idx || idx > pdo_cnt) ? + EC_ERROR_INVAL : EC_SUCCESS; +} +int pd_board_check_request(uint32_t, int) + __attribute__((weak, alias("stub_pd_board_check_request"))); + +#ifdef CONFIG_USB_PD_DUAL_ROLE +/* Last received source cap */ +static uint32_t pd_src_caps[CONFIG_USB_PD_PORT_COUNT][PDO_MAX_OBJECTS]; +static uint8_t pd_src_cap_cnt[CONFIG_USB_PD_PORT_COUNT]; + +/* Cap on the max voltage requested as a sink (in millivolts) */ +static unsigned max_request_mv = PD_MAX_VOLTAGE_MV; /* no cap */ + +int pd_find_pdo_index(int port, int max_mv, uint32_t *selected_pdo) +{ + int i, uw, mv, ma; + int ret = 0; + int __attribute__((unused)) cur_mv = 0; + int cur_uw = 0; + int prefer_cur; + const uint32_t *src_caps = pd_src_caps[port]; + + /* max voltage is always limited by this boards max request */ + max_mv = MIN(max_mv, PD_MAX_VOLTAGE_MV); + + /* Get max power that is under our max voltage input */ + for (i = 0; i < pd_src_cap_cnt[port]; i++) { + /* its an unsupported Augmented PDO (PD3.0) */ + if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_AUGMENTED) + continue; + + mv = ((src_caps[i] >> 10) & 0x3FF) * 50; + /* Skip invalid voltage */ + if (!mv) + continue; + /* Skip any voltage not supported by this board */ + if (!pd_is_valid_input_voltage(mv)) + continue; + + if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { + uw = 250000 * (src_caps[i] & 0x3FF); + } else { + ma = (src_caps[i] & 0x3FF) * 10; + ma = MIN(ma, PD_MAX_CURRENT_MA); + uw = ma * mv; + } + + if (mv > max_mv) + continue; + uw = MIN(uw, PD_MAX_POWER_MW * 1000); + prefer_cur = 0; + + /* Apply special rules in case of 'tie' */ +#ifdef PD_PREFER_LOW_VOLTAGE + if (uw == cur_uw && mv < cur_mv) + prefer_cur = 1; +#elif defined(PD_PREFER_HIGH_VOLTAGE) + if (uw == cur_uw && mv > cur_mv) + prefer_cur = 1; +#endif + /* Prefer higher power, except for tiebreaker */ + if (uw > cur_uw || prefer_cur) { + ret = i; + cur_uw = uw; + cur_mv = mv; + } + } + + if (selected_pdo) + *selected_pdo = src_caps[ret]; + + return ret; +} + +void pd_extract_pdo_power(uint32_t pdo, uint32_t *ma, uint32_t *mv) +{ + int max_ma, uw; + + *mv = ((pdo >> 10) & 0x3FF) * 50; + + if (*mv == 0) { + CPRINTF("ERR:PDO mv=0\n"); + *ma = 0; + return; + } + + if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { + uw = 250000 * (pdo & 0x3FF); + max_ma = 1000 * MIN(1000 * uw, PD_MAX_POWER_MW) / *mv; + } else { + max_ma = 10 * (pdo & 0x3FF); + max_ma = MIN(max_ma, PD_MAX_POWER_MW * 1000 / *mv); + } + + *ma = MIN(max_ma, PD_MAX_CURRENT_MA); +} + +int pd_build_request(int port, uint32_t *rdo, uint32_t *ma, uint32_t *mv, + enum pd_request_type req_type) +{ + uint32_t pdo; + int pdo_index, flags = 0; + int uw; + int max_or_min_ma; + int max_or_min_mw; + + if (req_type == PD_REQUEST_VSAFE5V) { + /* src cap 0 should be vSafe5V */ + pdo_index = 0; + pdo = pd_src_caps[port][0]; + } else { + /* find pdo index for max voltage we can request */ + pdo_index = pd_find_pdo_index(port, max_request_mv, &pdo); + } + + pd_extract_pdo_power(pdo, ma, mv); + uw = *ma * *mv; + /* Mismatch bit set if less power offered than the operating power */ + if (uw < (1000 * PD_OPERATING_POWER_MW)) + flags |= RDO_CAP_MISMATCH; + +#ifdef CONFIG_USB_PD_GIVE_BACK + /* Tell source we are give back capable. */ + flags |= RDO_GIVE_BACK; + + /* + * BATTERY PDO: Inform the source that the sink will reduce + * power to this minimum level on receipt of a GotoMin Request. + */ + max_or_min_mw = PD_MIN_POWER_MW; + + /* + * FIXED or VARIABLE PDO: Inform the source that the sink will reduce + * current to this minimum level on receipt of a GotoMin Request. + */ + max_or_min_ma = PD_MIN_CURRENT_MA; +#else + /* + * Can't give back, so set maximum current and power to operating + * level. + */ + max_or_min_ma = *ma; + max_or_min_mw = uw / 1000; +#endif + + if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { + int mw = uw / 1000; + *rdo = RDO_BATT(pdo_index + 1, mw, max_or_min_mw, flags); + } else { + *rdo = RDO_FIXED(pdo_index + 1, *ma, max_or_min_ma, flags); + } + return EC_SUCCESS; +} + +void pd_process_source_cap(int port, int cnt, uint32_t *src_caps) +{ +#ifdef CONFIG_CHARGE_MANAGER + uint32_t ma, mv, pdo; +#endif + int i; + + pd_src_cap_cnt[port] = cnt; + for (i = 0; i < cnt; i++) + pd_src_caps[port][i] = *src_caps++; + +#ifdef CONFIG_CHARGE_MANAGER + /* Get max power info that we could request */ + pd_find_pdo_index(port, PD_MAX_VOLTAGE_MV, &pdo); + pd_extract_pdo_power(pdo, &ma, &mv); + + /* Set max. limit, but apply 500mA ceiling */ + //charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, PD_MIN_MA); + pd_set_input_current_limit(port, ma, mv); +#endif +} + +#pragma weak pd_process_source_cap_callback +void pd_process_source_cap_callback(int port, int cnt, uint32_t *src_caps) {} + +void pd_set_max_voltage(unsigned mv) +{ + max_request_mv = mv; +} + +unsigned pd_get_max_voltage(void) +{ + return max_request_mv; +} + +int pd_charge_from_device(uint16_t vid, uint16_t pid) +{ + /* TODO: rewrite into table if we get more of these */ + /* + * White-list Apple charge-through accessory since it doesn't set + * externally powered bit, but we still need to charge from it when + * we are a sink. + */ + return (vid == USB_VID_APPLE && (pid == 0x1012 || pid == 0x1013)); +} +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + +#ifdef CONFIG_USB_PD_ALT_MODE + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + +static struct pd_policy pe[CONFIG_USB_PD_PORT_COUNT]; + +void pd_dfp_pe_init(int port) +{ + memset(&pe[port], 0, sizeof(struct pd_policy)); +} + +static void dfp_consume_identity(int port, int cnt, uint32_t *payload) +{ + int ptype = PD_IDH_PTYPE(payload[VDO_I(IDH)]); + size_t identity_size = MIN(sizeof(pe[port].identity), + (cnt - 1) * sizeof(uint32_t)); + pd_dfp_pe_init(port); + memcpy(&pe[port].identity, payload + 1, identity_size); + switch (ptype) { + case IDH_PTYPE_AMA: + /* TODO(tbroch) do I disable VBUS here if power contract + * requested it + */ + if (!PD_VDO_AMA_VBUS_REQ(payload[VDO_I(AMA)])) + pd_power_supply_reset(port); + +#if defined(CONFIG_USB_PD_DUAL_ROLE) && defined(CONFIG_USBC_VCONN_SWAP) + /* Adapter is requesting vconn, try to supply it */ + if (PD_VDO_AMA_VCONN_REQ(payload[VDO_I(AMA)])) + pd_try_vconn_src(port); +#endif + break; + default: + break; + } +} + +static int dfp_discover_svids(int port, uint32_t *payload) +{ + payload[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); + return 1; +} + +static void dfp_consume_svids(int port, uint32_t *payload) +{ + int i; + uint32_t *ptr = payload + 1; + uint16_t svid0, svid1; + + for (i = pe[port].svid_cnt; i < pe[port].svid_cnt + 12; i += 2) { + if (i == SVID_DISCOVERY_MAX) { + CPRINTF("ERR:SVIDCNT\n"); + break; + } + + svid0 = PD_VDO_SVID_SVID0(*ptr); + if (!svid0) + break; + pe[port].svids[i].svid = svid0; + pe[port].svid_cnt++; + + svid1 = PD_VDO_SVID_SVID1(*ptr); + if (!svid1) + break; + pe[port].svids[i + 1].svid = svid1; + pe[port].svid_cnt++; + ptr++; + } + /* TODO(tbroch) need to re-issue discover svids if > 12 */ + if (i && ((i % 12) == 0)) + CPRINTF("ERR:SVID+12\n"); +} + +static int dfp_discover_modes(int port, uint32_t *payload) +{ + uint16_t svid = pe[port].svids[pe[port].svid_idx].svid; + if (pe[port].svid_idx >= pe[port].svid_cnt) + return 0; + payload[0] = VDO(svid, 1, CMD_DISCOVER_MODES); + return 1; +} + +static void dfp_consume_modes(int port, int cnt, uint32_t *payload) +{ + int idx = pe[port].svid_idx; + pe[port].svids[idx].mode_cnt = cnt - 1; + if (pe[port].svids[idx].mode_cnt < 0) { + CPRINTF("ERR:NOMODE\n"); + } else { + memcpy(pe[port].svids[pe[port].svid_idx].mode_vdo, &payload[1], + sizeof(uint32_t) * pe[port].svids[idx].mode_cnt); + } + + pe[port].svid_idx++; +} + +static int get_mode_idx(int port, uint16_t svid) +{ + int i; + + for (i = 0; i < PD_AMODE_COUNT; i++) { + if (pe[port].amodes[i].fx->svid == svid) + return i; + } + return -1; +} + +static struct svdm_amode_data *get_modep(int port, uint16_t svid) +{ + int idx = get_mode_idx(port, svid); + + return (idx == -1) ? NULL : &pe[port].amodes[idx]; +} + +int pd_alt_mode(int port, uint16_t svid) +{ + struct svdm_amode_data *modep = get_modep(port, svid); + + return (modep) ? modep->opos : -1; +} + +int allocate_mode(int port, uint16_t svid) +{ + int i, j; + struct svdm_amode_data *modep; + int mode_idx = get_mode_idx(port, svid); + + if (mode_idx != -1) + return mode_idx; + + /* There's no space to enter another mode */ + if (pe[port].amode_idx == PD_AMODE_COUNT) { + CPRINTF("ERR:NO AMODE SPACE\n"); + return -1; + } + + /* Allocate ... if SVID == 0 enter default supported policy */ + for (i = 0; i < supported_modes_cnt; i++) { + if (!&supported_modes[i]) + continue; + + for (j = 0; j < pe[port].svid_cnt; j++) { + struct svdm_svid_data *svidp = &pe[port].svids[j]; + if ((svidp->svid != supported_modes[i].svid) || + (svid && (svidp->svid != svid))) + continue; + + modep = &pe[port].amodes[pe[port].amode_idx]; + modep->fx = &supported_modes[i]; + modep->data = &pe[port].svids[j]; + pe[port].amode_idx++; + return pe[port].amode_idx - 1; + } + } + return -1; +} + +/* + * Enter default mode ( payload[0] == 0 ) or attempt to enter mode via svid & + * opos +*/ +uint32_t pd_dfp_enter_mode(int port, uint16_t svid, int opos) +{ + int mode_idx = allocate_mode(port, svid); + struct svdm_amode_data *modep; + uint32_t mode_caps; + + if (mode_idx == -1) + return 0; + modep = &pe[port].amodes[mode_idx]; + + if (!opos) { + /* choose the lowest as default */ + modep->opos = 1; + } else if (opos <= modep->data->mode_cnt) { + modep->opos = opos; + } else { + CPRINTF("opos error\n"); + return 0; + } + + mode_caps = modep->data->mode_vdo[modep->opos - 1]; + if (modep->fx->enter(port, mode_caps) == -1) + return 0; + + /* SVDM to send to UFP for mode entry */ + return VDO(modep->fx->svid, 1, CMD_ENTER_MODE | VDO_OPOS(modep->opos)); +} + +static int validate_mode_request(struct svdm_amode_data *modep, + uint16_t svid, int opos) +{ + if (!modep->fx) + return 0; + + if (svid != modep->fx->svid) { + CPRINTF("ERR:svid r:0x%04x != c:0x%04x\n", + svid, modep->fx->svid); + return 0; + } + + if (opos != modep->opos) { + CPRINTF("ERR:opos r:%d != c:%d\n", + opos, modep->opos); + return 0; + } + + return 1; +} + +static void dfp_consume_attention(int port, uint32_t *payload) +{ + uint16_t svid = PD_VDO_VID(payload[0]); + int opos = PD_VDO_OPOS(payload[0]); + struct svdm_amode_data *modep = get_modep(port, svid); + + if (!modep || !validate_mode_request(modep, svid, opos)) + return; + + if (modep->fx->attention) + modep->fx->attention(port, payload); +} + +/* + * This algorithm defaults to choosing higher pin config over lower ones in + * order to prefer multi-function if desired. + * + * NAME | SIGNALING | OUTPUT TYPE | MULTI-FUNCTION | PIN CONFIG + * ------------------------------------------------------------- + * A | USB G2 | ? | no | 00_0001 + * B | USB G2 | ? | yes | 00_0010 + * C | DP | CONVERTED | no | 00_0100 + * D | PD | CONVERTED | yes | 00_1000 + * E | DP | DP | no | 01_0000 + * F | PD | DP | yes | 10_0000 + * + * if UFP has NOT asserted multi-function preferred code masks away B/D/F + * leaving only A/C/E. For single-output dongles that should leave only one + * possible pin config depending on whether its a converter DP->(VGA|HDMI) or DP + * output. If UFP is a USB-C receptacle it may assert C/D/E/F. The DFP USB-C + * receptacle must always choose C/D in those cases. + */ +int pd_dfp_dp_get_pin_mode(int port, uint32_t status) +{ + struct svdm_amode_data *modep = get_modep(port, USB_SID_DISPLAYPORT); + uint32_t mode_caps; + uint32_t pin_caps; + if (!modep) + return 0; + + mode_caps = modep->data->mode_vdo[modep->opos - 1]; + + /* TODO(crosbug.com/p/39656) revisit with DFP that can be a sink */ + pin_caps = PD_DP_PIN_CAPS(mode_caps); + + /* if don't want multi-function then ignore those pin configs */ + if (!PD_VDO_DPSTS_MF_PREF(status)) + pin_caps &= ~MODE_DP_PIN_MF_MASK; + + /* TODO(crosbug.com/p/39656) revisit if DFP drives USB Gen 2 signals */ + pin_caps &= ~MODE_DP_PIN_BR2_MASK; + + /* if C/D present they have precedence over E/F for USB-C->USB-C */ + if (pin_caps & (MODE_DP_PIN_C | MODE_DP_PIN_D)) + pin_caps &= ~(MODE_DP_PIN_E | MODE_DP_PIN_F); + + /* get_next_bit returns undefined for zero */ + if (!pin_caps) + return 0; + + return 1 << get_next_bit(&pin_caps); +} + +int pd_dfp_exit_mode(int port, uint16_t svid, int opos) +{ + struct svdm_amode_data *modep; + int idx; + + /* + * Empty svid signals we should reset DFP VDM state by exiting all + * entered modes then clearing state. This occurs when we've + * disconnected or for hard reset. + */ + if (!svid) { + for (idx = 0; idx < PD_AMODE_COUNT; idx++) + if (pe[port].amodes[idx].fx) + pe[port].amodes[idx].fx->exit(port); + + pd_dfp_pe_init(port); + return 0; + } + + /* + * TODO(crosbug.com/p/33946) : below needs revisited to allow multiple + * mode exit. Additionally it should honor OPOS == 7 as DFP's request + * to exit all modes. We currently don't have any UFPs that support + * multiple modes on one SVID. + */ + modep = get_modep(port, svid); + if (!modep || !validate_mode_request(modep, svid, opos)) + return 0; + + /* call DFPs exit function */ + modep->fx->exit(port); + /* exit the mode */ + modep->opos = 0; + return 1; +} + +uint16_t pd_get_identity_vid(int port) +{ + return PD_IDH_VID(pe[port].identity[0]); +} + +uint16_t pd_get_identity_pid(int port) +{ + return PD_PRODUCT_PID(pe[port].identity[2]); +} + +#ifdef CONFIG_CMD_USB_PD_PE +static void dump_pe(int port) +{ + const char * const idh_ptype_names[] = { + "UNDEF", "Hub", "Periph", "PCable", "ACable", "AMA", + "RSV6", "RSV7"}; + + int i, j, idh_ptype; + struct svdm_amode_data *modep; + uint32_t mode_caps; + + if (pe[port].identity[0] == 0) { + ccprintf("No identity discovered yet.\n"); + return; + } + idh_ptype = PD_IDH_PTYPE(pe[port].identity[0]); + ccprintf("IDENT:\n"); + ccprintf("\t[ID Header] %08x :: %s, VID:%04x\n", pe[port].identity[0], + idh_ptype_names[idh_ptype], pd_get_identity_vid(port)); + ccprintf("\t[Cert Stat] %08x\n", pe[port].identity[1]); + for (i = 2; i < ARRAY_SIZE(pe[port].identity); i++) { + ccprintf("\t"); + if (pe[port].identity[i]) + ccprintf("[%d] %08x ", i, pe[port].identity[i]); + } + ccprintf("\n"); + + if (pe[port].svid_cnt < 1) { + ccprintf("No SVIDS discovered yet.\n"); + return; + } + + for (i = 0; i < pe[port].svid_cnt; i++) { + ccprintf("SVID[%d]: %04x MODES:", i, pe[port].svids[i].svid); + for (j = 0; j < pe[port].svids[j].mode_cnt; j++) + ccprintf(" [%d] %08x", j + 1, + pe[port].svids[i].mode_vdo[j]); + ccprintf("\n"); + modep = get_modep(port, pe[port].svids[i].svid); + if (modep) { + mode_caps = modep->data->mode_vdo[modep->opos - 1]; + ccprintf("MODE[%d]: svid:%04x caps:%08x\n", modep->opos, + modep->fx->svid, mode_caps); + } + } +} + +static int command_pe(int argc, char **argv) +{ + int port; + char *e; + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + /* command: pe */ + port = strtoi(argv[1], &e, 10); + if (*e || port >= CONFIG_USB_PD_PORT_COUNT) + return EC_ERROR_PARAM2; + if (!strncasecmp(argv[2], "dump", 4)) + dump_pe(port); + + return EC_SUCCESS; +} + +DECLARE_CONSOLE_COMMAND(pe, command_pe, + " dump", + "USB PE"); +#endif /* CONFIG_CMD_USB_PD_PE */ + +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) +{ + int cmd = PD_VDO_CMD(payload[0]); + int cmd_type = PD_VDO_CMDT(payload[0]); + int (*func)(int port, uint32_t *payload) = NULL; + + int rsize = 1; /* VDM header at a minimum */ + + payload[0] &= ~VDO_CMDT_MASK; + *rpayload = payload; + + if (cmd_type == CMDT_INIT) { + switch (cmd) { + case CMD_DISCOVER_IDENT: + func = svdm_rsp.identity; + break; + case CMD_DISCOVER_SVID: + func = svdm_rsp.svids; + break; + case CMD_DISCOVER_MODES: + func = svdm_rsp.modes; + break; + case CMD_ENTER_MODE: + func = svdm_rsp.enter_mode; + break; + case CMD_DP_STATUS: + func = svdm_rsp.amode->status; + break; + case CMD_DP_CONFIG: + func = svdm_rsp.amode->config; + break; + case CMD_EXIT_MODE: + func = svdm_rsp.exit_mode; + break; +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + case CMD_ATTENTION: + /* + * attention is only SVDM with no response + * (just goodCRC) return zero here. + */ + dfp_consume_attention(port, payload); + return 0; +#endif + default: + CPRINTF("ERR:CMD:%d\n", cmd); + rsize = 0; + } + if (func) + rsize = func(port, payload); + else /* not supported : NACK it */ + rsize = 0; + if (rsize >= 1) + payload[0] |= VDO_CMDT(CMDT_RSP_ACK); + else if (!rsize) { + payload[0] |= VDO_CMDT(CMDT_RSP_NAK); + rsize = 1; + } else { + payload[0] |= VDO_CMDT(CMDT_RSP_BUSY); + rsize = 1; + } + payload[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port)); + } else if (cmd_type == CMDT_RSP_ACK) { +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + struct svdm_amode_data *modep; + + modep = get_modep(port, PD_VDO_VID(payload[0])); +#endif + switch (cmd) { +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + case CMD_DISCOVER_IDENT: + dfp_consume_identity(port, cnt, payload); + rsize = dfp_discover_svids(port, payload); +#ifdef CONFIG_CHARGE_MANAGER + if (pd_charge_from_device(pd_get_identity_vid(port), + pd_get_identity_pid(port))) + charge_manager_update_dualrole(port, + CAP_DEDICATED); +#endif + break; + case CMD_DISCOVER_SVID: + dfp_consume_svids(port, payload); + rsize = dfp_discover_modes(port, payload); + break; + case CMD_DISCOVER_MODES: + dfp_consume_modes(port, cnt, payload); + rsize = dfp_discover_modes(port, payload); + /* enter the default mode for DFP */ + if (!rsize) { + payload[0] = pd_dfp_enter_mode(port, 0, 0); + if (payload[0]) + rsize = 1; + } + break; + case CMD_ENTER_MODE: + if (!modep) { + rsize = 0; + } else { + if (!modep->opos) + pd_dfp_enter_mode(port, 0, 0); + + if (modep->opos) { + rsize = modep->fx->status(port, + payload); + payload[0] |= PD_VDO_OPOS(modep->opos); + } + } + break; + case CMD_DP_STATUS: + /* DP status response & UFP's DP attention have same + payload */ + dfp_consume_attention(port, payload); + if (modep && modep->opos) + rsize = modep->fx->config(port, payload); + else + rsize = 0; + break; + case CMD_DP_CONFIG: + if (modep && modep->opos && modep->fx->post_config) + modep->fx->post_config(port); + /* no response after DFPs ack */ + rsize = 0; + break; + case CMD_EXIT_MODE: + /* no response after DFPs ack */ + rsize = 0; + break; +#endif + case CMD_ATTENTION: + /* no response after DFPs ack */ + rsize = 0; + break; + default: + CPRINTF("ERR:CMD:%d\n", cmd); + rsize = 0; + } + + payload[0] |= VDO_CMDT(CMDT_INIT); + payload[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port)); +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + } else if (cmd_type == CMDT_RSP_BUSY) { + switch (cmd) { + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: + case CMD_DISCOVER_MODES: + /* resend if its discovery */ + rsize = 1; + break; + case CMD_ENTER_MODE: + /* Error */ + CPRINTF("ERR:ENTBUSY\n"); + rsize = 0; + break; + case CMD_EXIT_MODE: + rsize = 0; + break; + default: + rsize = 0; + } + } else if (cmd_type == CMDT_RSP_NAK) { + /* nothing to do */ + rsize = 0; +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + } else { + CPRINTF("ERR:CMDT:%d\n", cmd); + /* do not answer */ + rsize = 0; + } + return rsize; +} + +#else + +int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) +{ + return 0; +} + +#endif /* CONFIG_USB_PD_ALT_MODE */ + +#ifndef CONFIG_USB_PD_CUSTOM_VDM +int pd_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) +{ + return 0; +} +#endif /* !CONFIG_USB_PD_CUSTOM_VDM */ + +static void pd_usb_billboard_deferred(void) +{ +#if defined(CONFIG_USB_PD_ALT_MODE) && !defined(CONFIG_USB_PD_ALT_MODE_DFP) \ + && !defined(CONFIG_USB_PD_SIMPLE_DFP) && defined(CONFIG_USB_BOS) + + /* + * TODO(tbroch) + * 1. Will we have multiple type-C port UFPs + * 2. Will there be other modes applicable to DFPs besides DP + */ + if (!pd_alt_mode(0, USB_SID_DISPLAYPORT)) + usb_connect(); + +#endif +} +DECLARE_DEFERRED(pd_usb_billboard_deferred); + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +static int hc_remote_pd_discovery(struct host_cmd_handler_args *args) +{ + const uint8_t *port = args->params; + struct ec_params_usb_pd_discovery_entry *r = args->response; + + if (*port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + r->vid = pd_get_identity_vid(*port); + r->ptype = PD_IDH_PTYPE(pe[*port].identity[0]); + /* pid only included if vid is assigned */ + if (r->vid) + r->pid = PD_PRODUCT_PID(pe[*port].identity[2]); + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DISCOVERY, + hc_remote_pd_discovery, + EC_VER_MASK(0)); + +static int hc_remote_pd_get_amode(struct host_cmd_handler_args *args) +{ + struct svdm_amode_data *modep; + const struct ec_params_usb_pd_get_mode_request *p = args->params; + struct ec_params_usb_pd_get_mode_response *r = args->response; + + if (p->port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + /* no more to send */ + if (p->svid_idx >= pe[p->port].svid_cnt) { + r->svid = 0; + args->response_size = sizeof(r->svid); + return EC_RES_SUCCESS; + } + + r->svid = pe[p->port].svids[p->svid_idx].svid; + r->opos = 0; + memcpy(r->vdo, pe[p->port].svids[p->svid_idx].mode_vdo, 24); + modep = get_modep(p->port, r->svid); + + if (modep) + r->opos = pd_alt_mode(p->port, r->svid); + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_GET_AMODE, + hc_remote_pd_get_amode, + EC_VER_MASK(0)); + +#endif + +#define FW_RW_END (CONFIG_EC_WRITABLE_STORAGE_OFF + \ + CONFIG_RW_STORAGE_OFF + CONFIG_RW_SIZE) + +/* +uint8_t *flash_hash_rw(void) +{ + static struct sha256_ctx ctx; + + // re-calculate RW hash when changed as its time consuming + if (rw_flash_changed) { + rw_flash_changed = 0; + SHA256_init(&ctx); + SHA256_update(&ctx, (void *)CONFIG_PROGRAM_MEMORY_BASE + + CONFIG_RW_MEM_OFF, + CONFIG_RW_SIZE - RSANUMBYTES); + return SHA256_final(&ctx); + } else { + return ctx.buf; + } +} + + +void pd_get_info(uint32_t *info_data) +{ + void *rw_hash = flash_hash_rw(); + + // copy first 20 bytes of RW hash + memcpy(info_data, rw_hash, 5 * sizeof(uint32_t)); + // copy other info into data msg +#if defined(CONFIG_USB_PD_HW_DEV_ID_BOARD_MAJOR) && \ + defined(CONFIG_USB_PD_HW_DEV_ID_BOARD_MINOR) + info_data[5] = VDO_INFO(CONFIG_USB_PD_HW_DEV_ID_BOARD_MAJOR, + CONFIG_USB_PD_HW_DEV_ID_BOARD_MINOR, + ver_get_numcommits(), + (system_get_image_copy() != SYSTEM_IMAGE_RO)); +#else + info_data[5] = 0; +#endif +} + +int pd_custom_flash_vdm(int port, int cnt, uint32_t *payload) +{ + static int flash_offset; + int rsize = 1; // default is just VDM header returned + + switch (PD_VDO_CMD(payload[0])) { + case VDO_CMD_VERSION: + memcpy(payload + 1, ¤t_image_data.version, 24); + rsize = 7; + break; + case VDO_CMD_REBOOT: + // ensure the power supply is in a safe state + pd_power_supply_reset(0); + system_reset(0); + break; + case VDO_CMD_READ_INFO: + // copy info into response + pd_get_info(payload + 1); + rsize = 7; + break; + case VDO_CMD_FLASH_ERASE: + // do not kill the code under our feet + if (system_get_image_copy() != SYSTEM_IMAGE_RO) + break; + pd_log_event(PD_EVENT_ACC_RW_ERASE, 0, 0, NULL); + flash_offset = CONFIG_EC_WRITABLE_STORAGE_OFF + + CONFIG_RW_STORAGE_OFF; + flash_physical_erase(CONFIG_EC_WRITABLE_STORAGE_OFF + + CONFIG_RW_STORAGE_OFF, CONFIG_RW_SIZE); + rw_flash_changed = 1; + break; + case VDO_CMD_FLASH_WRITE: + // do not kill the code under our feet + if ((system_get_image_copy() != SYSTEM_IMAGE_RO) || + (flash_offset < CONFIG_EC_WRITABLE_STORAGE_OFF + + CONFIG_RW_STORAGE_OFF)) + break; + flash_physical_write(flash_offset, 4*(cnt - 1), + (const char *)(payload+1)); + flash_offset += 4*(cnt - 1); + rw_flash_changed = 1; + break; + case VDO_CMD_ERASE_SIG: + // this is not touching the code area + { + uint32_t zero = 0; + int offset; + // zeroes the area containing the RSA signature + for (offset = FW_RW_END - RSANUMBYTES; + offset < FW_RW_END; offset += 4) + flash_physical_write(offset, 4, + (const char *)&zero); + } + break; + default: + // Unknown : do not answer + return 0; + } + return rsize; +} +*/ +#ifdef CONFIG_USB_PD_DISCHARGE +void pd_set_vbus_discharge(int port, int enable) +{ + static struct mutex discharge_lock[CONFIG_USB_PD_PORT_COUNT]; + + mutex_lock(&discharge_lock[port]); + enable &= !board_vbus_source_enabled(port); +#ifdef CONFIG_USB_PD_DISCHARGE_GPIO + if (!port) + gpio_set_level(GPIO_USB_C0_DISCHARGE, enable); +#if CONFIG_USB_PD_PORT_COUNT > 1 + else + gpio_set_level(GPIO_USB_C1_DISCHARGE, enable); +#endif /* CONFIG_USB_PD_PORT_COUNT */ +#elif defined(CONFIG_USB_PD_DISCHARGE_TCPC) + tcpc_discharge_vbus(port, enable); +#else +#error "PD discharge implementation not defined" +#endif + mutex_unlock(&discharge_lock[port]); +} +#endif /* CONFIG_USB_PD_DISCHARGE */ + +/* Whether alternate mode has been entered or not */ +static int alt_mode = 0; +int dp_enabled = 0; + +/* ----------------- Vendor Defined Messages ------------------ */ +const uint32_t vdo_idh = VDO_IDH(0, /* data caps as USB host */ + 0, /* data caps as USB device */ + IDH_PTYPE_AMA, /* Alternate mode */ + 1, /* supports alt modes */ + USB_VID_GOOGLE); + +const uint32_t vdo_product = VDO_PRODUCT(CONFIG_USB_PID, CONFIG_USB_BCD_DEV); + +const uint32_t vdo_ama = VDO_AMA(CONFIG_USB_PD_IDENTITY_HW_VERS, + CONFIG_USB_PD_IDENTITY_SW_VERS, + 0, 0, 0, 0, /* SS[TR][12] */ + 0, /* Vconn power */ + 0, /* Vconn power required */ + 1, /* Vbus power required */ + AMA_USBSS_BBONLY /* USB SS support */); + +static int svdm_response_identity(int port, uint32_t *payload) +{ + payload[VDO_I(IDH)] = vdo_idh; + payload[VDO_I(CSTAT)] = VDO_CSTAT(0); + payload[VDO_I(PRODUCT)] = vdo_product; + payload[VDO_I(AMA)] = vdo_ama; + return VDO_I(AMA) + 1; +} + +static int svdm_response_svids(int port, uint32_t *payload) +{ + payload[1] = VDO_SVID(USB_SID_DISPLAYPORT, 0); + return 2; +} + +#define MODE_CNT 1 +#define OPOS 1 + +static int dp_status(int port, uint32_t *payload) +{ + CPRINTF("DP status %08x\n", payload[0]); + int opos = PD_VDO_OPOS(payload[0]); + int hpd = dp_enabled; //? + if (opos != OPOS) + return 0; /* nak */ + + payload[1] = VDO_DP_STATUS(0, /* IRQ_HPD */ + (hpd == 1), /* HPD_HI|LOW */ + 0, /* request exit DP */ + 0, /* request exit USB */ + 0, /* MF pref */ + dp_enabled, /* enabled */ + 0, /* power low */ + 0x2); + + return 2; +} + +static int dp_config(int port, uint32_t *payload) +{ + CPRINTF("DP config %08x\n", payload[1]); + if (PD_DP_CFG_DPON(payload[1])) { + dp_enabled = 1; + } + + return 1; +} + +const uint32_t vdo_dp_mode[MODE_CNT] = { + VDO_MODE_DP(0, /* UFP pin cfg supported : none */ + MODE_DP_PIN_C | MODE_DP_PIN_D | MODE_DP_PIN_E | MODE_DP_PIN_F, /* DFP pin cfg supported */ + 1, /* no usb2.0 signalling in AMode */ + CABLE_PLUG, /* its a plug */ + MODE_DP_V13, /* DPv1.3 Support, no Gen2 */ + MODE_DP_SNK) /* Its a sink only */ +}; + +static int svdm_response_modes(int port, uint32_t *payload) +{ + if (PD_VDO_VID(payload[0]) != USB_SID_DISPLAYPORT) + return 0; /* nak */ + + memcpy(payload + 1, vdo_dp_mode, sizeof(vdo_dp_mode)); + return MODE_CNT + 1; +} + + +int svdm_enter_mode(int port, uint32_t *payload) +{ + CPRINTF("SVDM enter mode\n"); + /* SID & mode request is valid */ + if ((PD_VDO_VID(payload[0]) != USB_SID_DISPLAYPORT) || + (PD_VDO_OPOS(payload[0]) != OPOS)) + return 0; /* will generate NAK */ + + alt_mode = OPOS; + return 1; +} + +int pd_alt_mode(int port, uint16_t svid) +{ + return alt_mode; +} + +static int svdm_exit_mode(int port, uint32_t *payload) +{ + CPRINTF("SVDM exit mode\n"); + alt_mode = 0; + dp_enabled = 0; + return 1; /* Must return ACK */ +} + +static struct amode_fx dp_fx = { + .status = &dp_status, + .config = &dp_config, +}; + +const struct svdm_response svdm_rsp = { + .identity = &svdm_response_identity, + .svids = &svdm_response_svids, + .modes = &svdm_response_modes, + .enter_mode = &svdm_enter_mode, + .amode = &dp_fx, + .exit_mode = &svdm_exit_mode, +}; \ No newline at end of file diff --git a/fw/usb_pd_protocol.c b/fw/usb_pd_protocol.c new file mode 100644 index 0000000..a4e56ed --- /dev/null +++ b/fw/usb_pd_protocol.c @@ -0,0 +1,4278 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include "pico/stdlib.h" +#include "usb_pd.h" +#include "usb_pd_tcpm.h" +#include "tcpm.h" +#include "usb_pd_driver.h" + +#ifdef CONFIG_COMMON_RUNTIME +#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) +#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) + +BUILD_ASSERT(CONFIG_USB_PD_PORT_COUNT <= EC_USB_PD_MAX_PORTS); + +/* + * Debug log level - higher number == more log + * Level 0: Log state transitions + * Level 1: Level 0, plus state name + * Level 2: Level 1, plus packet info + * Level 3: Level 2, plus ping packet and packet dump on error + * + * Note that higher log level causes timing changes and thus may affect + * performance. + * + * Can be limited to constant debug_level by CONFIG_USB_PD_DEBUG_LEVEL + */ +#ifdef CONFIG_USB_PD_DEBUG_LEVEL +static const int debug_level = CONFIG_USB_PD_DEBUG_LEVEL; +#else +static int debug_level; +#endif + +/* + * PD communication enabled flag. When false, PD state machine still + * detects source/sink connection and disconnection, and will still + * provide VBUS, but never sends any PD communication. + */ +static uint8_t pd_comm_enabled[CONFIG_USB_PD_PORT_COUNT]; +#else /* CONFIG_COMMON_RUNTIME */ +#define CPRINTF(format, args...) printf(format, ## args) +#define CPRINTS(format, args...) printf(format, ## args) +static const int debug_level = 1; +#endif + +#ifdef CONFIG_USB_PD_DUAL_ROLE +#define DUAL_ROLE_IF_ELSE(port, sink_clause, src_clause) \ + (pd[port].power_role == PD_ROLE_SINK ? (sink_clause) : (src_clause)) +#else +#define DUAL_ROLE_IF_ELSE(port, sink_clause, src_clause) (src_clause) +#endif + +#define READY_RETURN_STATE(port) DUAL_ROLE_IF_ELSE(port, PD_STATE_SNK_READY, \ + PD_STATE_SRC_READY) + +/* Type C supply voltage (mV) */ +#define TYPE_C_VOLTAGE 5000 /* mV */ + +/* PD counter definitions */ +#define PD_MESSAGE_ID_COUNT 7 +#define PD_HARD_RESET_COUNT 2 +#define PD_CAPS_COUNT 50 +#define PD_SNK_CAP_RETRIES 3 + +#ifdef CONFIG_USB_PD_DUAL_ROLE +/* Port dual-role state */ +enum pd_dual_role_states drp_state = CONFIG_USB_PD_INITIAL_DRP_STATE; + +/* Enable variable for Try.SRC states */ +static uint8_t pd_try_src_enable; +#endif + +#ifdef CONFIG_USB_PD_REV30 +/* + * The spec. revision is used to index into this array. + * Rev 0 (PD 1.0) - return PD_CTRL_REJECT + * Rev 1 (PD 2.0) - return PD_CTRL_REJECT + * Rev 2 (PD 3.0) - return PD_CTRL_NOT_SUPPORTED + */ +static const uint8_t refuse[] = { + PD_CTRL_REJECT, PD_CTRL_REJECT, PD_CTRL_NOT_SUPPORTED}; +#define REFUSE(r) refuse[r] +#else +#define REFUSE(r) PD_CTRL_REJECT +#endif + +#ifdef CONFIG_USB_PD_REV30 +/* + * The spec. revision is used to index into this array. + * Rev 0 (VDO 1.0) - return VDM_VER10 + * Rev 1 (VDO 1.0) - return VDM_VER10 + * Rev 2 (VDO 2.0) - return VDM_VER20 + */ +static const uint8_t vdo_ver[] = { + VDM_VER10, VDM_VER10, VDM_VER20}; +#define VDO_VER(v) vdo_ver[v] +#else +#define VDO_VER(v) VDM_VER10 +#endif + +// variables that used to be pd_task, but had to be promoted +// so both pd_init and pd_run_state_machine can see them +static int head; +static int port = TASK_ID_TO_PD_PORT(task_get_current()); +static uint32_t payload[7]; +static int timeout = 10*MSEC_US; +static int cc1, cc2; +static int res, incoming_packet = 0; +static int hard_reset_count = 0; +#ifdef CONFIG_USB_PD_DUAL_ROLE +static uint64_t next_role_swap = PD_T_DRP_SNK; +#ifndef CONFIG_USB_PD_VBUS_DETECT_NONE +static int snk_hard_reset_vbus_off = 0; +#endif +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE +static const int auto_toggle_supported = tcpm_auto_toggle_supported(port); +#endif +#if defined(CONFIG_CHARGE_MANAGER) +static typec_current_t typec_curr = 0, typec_curr_change = 0; +#endif /* CONFIG_CHARGE_MANAGER */ +#endif /* CONFIG_USB_PD_DUAL_ROLE */ +static enum pd_states this_state; +static enum pd_cc_states new_cc_state; +static timestamp_t now; +static int caps_count = 0, hard_reset_sent = 0; +static int snk_cap_count = 0; +static int evt; + +enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, + VDM_STATE_ERR_TMOUT = -1, + VDM_STATE_DONE = 0, + /* Anything >0 represents an active state */ + VDM_STATE_READY = 1, + VDM_STATE_BUSY = 2, + VDM_STATE_WAIT_RSP_BUSY = 3, +}; + +struct pd_protocol { + /* current port power role (SOURCE or SINK) */ + uint8_t power_role; + /* current port data role (DFP or UFP) */ + uint8_t data_role; + /* 3-bit rolling message ID counter */ + uint8_t msg_id; + /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ + uint8_t polarity; + /* PD state for port */ + enum pd_states task_state; + /* PD state when we run state handler the last time */ + enum pd_states last_state; + /* bool: request state change to SUSPENDED */ + uint8_t req_suspend_state; + /* The state to go to after timeout */ + enum pd_states timeout_state; + /* port flags, see PD_FLAGS_* */ + uint32_t flags; + /* Timeout for the current state. Set to 0 for no timeout. */ + uint64_t timeout; + /* Time for source recovery after hard reset */ + uint64_t src_recover; + /* Time for CC debounce end */ + uint64_t cc_debounce; + /* The cc state */ + enum pd_cc_states cc_state; + /* status of last transmit */ + uint8_t tx_status; + + /* last requested voltage PDO index */ + int requested_idx; +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* Current limit / voltage based on the last request message */ + uint32_t curr_limit; + uint32_t supply_voltage; + /* Signal charging update that affects the port */ + int new_power_request; + /* Store previously requested voltage request */ + int prev_request_mv; + /* Time for Try.SRC states */ + uint64_t try_src_marker; +#endif + + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + /* Timeout for the current vdm state. Set to 0 for no timeout. */ + timestamp_t vdm_timeout; + /* next Vendor Defined Message to send */ + uint32_t vdo_data[VDO_MAX_SIZE]; + uint8_t vdo_count; + /* VDO to retry if UFP responder replied busy. */ + uint32_t vdo_retry; + +#ifdef CONFIG_USB_PD_CHROMEOS + /* Attached ChromeOS device id, RW hash, and current RO / RW image */ + uint16_t dev_id; + uint32_t dev_rw_hash[PD_RW_HASH_SIZE/4]; + enum ec_current_image current_image; +#endif +#ifdef CONFIG_USB_PD_REV30 + /* PD Collision avoidance buffer */ + uint16_t ca_buffered; + uint16_t ca_header; + uint32_t ca_buffer[PDO_MAX_OBJECTS]; + enum tcpm_transmit_type ca_type; + /* protocol revision */ + uint8_t rev; +#endif +} pd[CONFIG_USB_PD_PORT_COUNT]; + +#ifdef CONFIG_COMMON_RUNTIME +static const char * const pd_state_names[] = { + "DISABLED", "SUSPENDED", +#ifdef CONFIG_USB_PD_DUAL_ROLE + "SNK_DISCONNECTED", "SNK_DISCONNECTED_DEBOUNCE", + "SNK_HARD_RESET_RECOVER", + "SNK_DISCOVERY", "SNK_REQUESTED", "SNK_TRANSITION", "SNK_READY", + "SNK_SWAP_INIT", "SNK_SWAP_SNK_DISABLE", + "SNK_SWAP_SRC_DISABLE", "SNK_SWAP_STANDBY", "SNK_SWAP_COMPLETE", +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + "SRC_DISCONNECTED", "SRC_DISCONNECTED_DEBOUNCE", + "SRC_HARD_RESET_RECOVER", "SRC_STARTUP", + "SRC_DISCOVERY", "SRC_NEGOCIATE", "SRC_ACCEPTED", "SRC_POWERED", + "SRC_TRANSITION", "SRC_READY", "SRC_GET_SNK_CAP", "DR_SWAP", +#ifdef CONFIG_USB_PD_DUAL_ROLE + "SRC_SWAP_INIT", "SRC_SWAP_SNK_DISABLE", "SRC_SWAP_SRC_DISABLE", + "SRC_SWAP_STANDBY", +#ifdef CONFIG_USBC_VCONN_SWAP + "VCONN_SWAP_SEND", "VCONN_SWAP_INIT", "VCONN_SWAP_READY", +#endif /* CONFIG_USBC_VCONN_SWAP */ +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + "SOFT_RESET", "HARD_RESET_SEND", "HARD_RESET_EXECUTE", "BIST_RX", + "BIST_TX", +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + "DRP_AUTO_TOGGLE", +#endif +}; +BUILD_ASSERT(ARRAY_SIZE(pd_state_names) == PD_STATE_COUNT); +#endif + +/* + * 4 entry rw_hash table of type-C devices that AP has firmware updates for. + */ +#ifdef CONFIG_COMMON_RUNTIME +#define RW_HASH_ENTRIES 4 +static struct ec_params_usb_pd_rw_hash_entry rw_hash_table[RW_HASH_ENTRIES]; +#endif + +int pd_comm_is_enabled(int port) +{ +#ifdef CONFIG_COMMON_RUNTIME + return pd_comm_enabled[port]; +#else + return 1; +#endif +} + +static inline void set_state_timeout(int port, + uint64_t timeout, + enum pd_states timeout_state) +{ + pd[port].timeout = timeout; + pd[port].timeout_state = timeout_state; +} + +#ifdef CONFIG_USB_PD_REV30 +int pd_get_rev(int port) +{ + return pd[port].rev; +} + +int pd_get_vdo_ver(int port) +{ + return vdo_ver[pd[port].rev]; +} +#endif + +/* Return flag for pd state is connected */ +int pd_is_connected(int port) +{ + if (pd[port].task_state == PD_STATE_DISABLED) + return 0; + +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + if (pd[port].task_state == PD_STATE_DRP_AUTO_TOGGLE) + return 0; +#endif + + return DUAL_ROLE_IF_ELSE(port, + /* sink */ + pd[port].task_state != PD_STATE_SNK_DISCONNECTED && + pd[port].task_state != PD_STATE_SNK_DISCONNECTED_DEBOUNCE, + /* source */ + pd[port].task_state != PD_STATE_SRC_DISCONNECTED && + pd[port].task_state != PD_STATE_SRC_DISCONNECTED_DEBOUNCE); +} + +/* + * Return true if partner port is a DTS or TS capable of entering debug + * mode (eg. is presenting Rp/Rp or Rd/Rd). + */ +int pd_ts_dts_plugged(int port) +{ + return pd[port].flags & PD_FLAGS_TS_DTS_PARTNER; +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +void pd_vbus_low(int port) +{ + pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; +} + +static inline int pd_is_vbus_present(int port) +{ +#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC + return tcpm_get_vbus_level(port); +#else + return pd_snk_is_vbus_provided(port); +#endif +} +#endif + +static inline void set_state(int port, enum pd_states next_state) +{ + enum pd_states last_state = pd[port].task_state; +#ifdef CONFIG_LOW_POWER_IDLE + int i; +#endif + + set_state_timeout(port, 0, 0); + pd[port].task_state = next_state; + + if (last_state == next_state) + return; + +#ifdef CONFIG_USB_PD_DUAL_ROLE +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + /* Clear flag to allow DRP auto toggle when possible */ + if (last_state != PD_STATE_DRP_AUTO_TOGGLE) + pd[port].flags &= ~PD_FLAGS_TCPC_DRP_TOGGLE; +#endif + + /* Ignore dual-role toggling between sink and source */ + if ((last_state == PD_STATE_SNK_DISCONNECTED && + next_state == PD_STATE_SRC_DISCONNECTED) || + (last_state == PD_STATE_SRC_DISCONNECTED && + next_state == PD_STATE_SNK_DISCONNECTED)) + return; + + if (next_state == PD_STATE_SRC_DISCONNECTED || + next_state == PD_STATE_SNK_DISCONNECTED) { + /* Clear the input current limit */ + pd_set_input_current_limit(port, 0, 0); +#ifdef CONFIG_CHARGE_MANAGER + //typec_set_input_current_limit(port, 0, 0); + //charge_manager_set_ceil(port, + // CEIL_REQUESTOR_PD, + // CHARGE_CEIL_NONE); +#endif +#ifdef CONFIG_USBC_VCONN + tcpm_set_vconn(port, 0); +#endif +#else /* CONFIG_USB_PD_DUAL_ROLE */ + if (next_state == PD_STATE_SRC_DISCONNECTED) { +#endif + /* + * If we are source, make sure VBUS is off and + * if PD REV3.0, restore RP. + */ + if (pd[port].power_role == PD_ROLE_SOURCE) { + /* + * Rp is restored by pd_power_supply_reset if + * CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT is defined. + */ + pd_power_supply_reset(port); +#if !defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) && \ + defined(CONFIG_USB_PD_REV30) + /* Restore Rp */ + tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); + tcpm_set_cc(port, TYPEC_CC_RP); +#endif + } +#ifdef CONFIG_USB_PD_REV30 + /* Adjust rev to highest level*/ + pd[port].rev = PD_REV30; +#endif +#ifdef CONFIG_USB_PD_CHROMEOS + pd[port].dev_id = 0; + pd[port].flags &= ~PD_FLAGS_RESET_ON_DISCONNECT_MASK; +#endif +#ifdef CONFIG_CHARGE_MANAGER + //charge_manager_update_dualrole(port, CAP_UNKNOWN); +#endif +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + pd_dfp_exit_mode(port, 0, 0); +#endif +#ifdef CONFIG_USBC_SS_MUX + usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT, + pd[port].polarity); +#endif + /* Disable TCPC RX */ + tcpm_set_rx_enable(port, 0); + } + +#ifdef CONFIG_LOW_POWER_IDLE + /* If a PD device is attached then disable deep sleep */ + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { + if (pd[i].flags & PD_FLAGS_PREVIOUS_PD_CONN) + break; + } + if (i == CONFIG_USB_PD_PORT_COUNT) + enable_sleep(SLEEP_MASK_USB_PD); + else + disable_sleep(SLEEP_MASK_USB_PD); +#endif + + CPRINTF("C%d st%d\n", port, next_state); +} + +/* increment message ID counter */ +static void inc_id(int port) +{ + pd[port].msg_id = (pd[port].msg_id + 1) & PD_MESSAGE_ID_COUNT; +} + +#ifdef CONFIG_USB_PD_REV30 +static void sink_can_xmit(int port, int rp) +{ + tcpm_select_rp_value(port, rp); + tcpm_set_cc(port, TYPEC_CC_RP); +} + +static inline void pd_ca_reset(int port) +{ + pd[port].ca_buffered = 0; +} +#endif + +void pd_transmit_complete(int port, int status) +{ + if (status == TCPC_TX_COMPLETE_SUCCESS) + inc_id(port); + + pd[port].tx_status = status; + //task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_TX, 0); + pd_task_set_event(PD_EVENT_TX, 0); +} + +static int pd_transmit(int port, enum tcpm_transmit_type type, + uint16_t header, const uint32_t *data) +{ + int evt; + + /* If comms are disabled, do not transmit, return error */ + if (!pd_comm_is_enabled(port)) + return -1; +#ifdef CONFIG_USB_PD_REV30 + /* Source-coordinated collision avoidance */ + /* + * In order to avoid message collisions due to asynchronous Messaging + * sent from the Sink, the Source sets Rp to SinkTxOk to indicate to + * the Sink that it is ok to initiate an AMS. When the Source wishes + * to initiate an AMS it sets Rp to SinkTxNG. When the Sink detects + * that Rp is set to SinkTxOk it May initiate an AMS. When the Sink + * detects that Rp is set to SinkTxNG it Shall Not initiate an AMS + * and Shall only send Messages that are part of an AMS the Source has + * initiated. Note that this restriction applies to SOP* AMS’s i.e. + * for both Port to Port and Port to Cable Plug communications. + * + * This starts after an Explicit Contract is in place + * PD R3 V1.1 Section 2.5.2. + * + * Note: a Sink can still send Hard Reset signaling at any time. + */ + if ((pd[port].rev == PD_REV30) && + (pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT)) { + if (pd[port].power_role == PD_ROLE_SOURCE) { + /* + * Inform Sink that it can't transmit. If a sink + * transmition is in progress and a collsion occurs, + * a reset is generated. This should be rare because + * all extended messages are chunked. This effectively + * defaults to PD REV 2.0 collision avoidance. + */ + sink_can_xmit(port, SINK_TX_NG); + } else if (type != TCPC_TX_HARD_RESET) { + int cc1; + int cc2; + + tcpm_get_cc(port, &cc1, &cc2); + if (cc1 == TYPEC_CC_VOLT_SNK_1_5 || + cc2 == TYPEC_CC_VOLT_SNK_1_5) { + /* Sink can't transmit now. */ + /* Check if message is already buffered. */ + if (pd[port].ca_buffered) + return -1; + + /* Buffer message and send later. */ + pd[port].ca_type = type; + pd[port].ca_header = header; + memcpy(pd[port].ca_buffer, + data, sizeof(uint32_t) * + PD_HEADER_CNT(header)); + pd[port].ca_buffered = 1; + return 1; + } + } + } +#endif + tcpm_transmit(port, type, header, data); + + /* Wait until TX is complete */ + // Would wait, except that we're making tcpm_transmit blocking + //evt = task_wait_event_mask(PD_EVENT_TX, PD_T_TCPC_TX_TIMEOUT); + +#ifdef CONFIG_USB_PD_REV30 + /* + * If the source just completed a transmit, tell + * the sink it can transmit if it wants to. + */ + if ((pd[port].rev == PD_REV30) && + (pd[port].power_role == PD_ROLE_SOURCE) && + (pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT)) { + sink_can_xmit(port, SINK_TX_OK); + } +#endif + + // removing task-based stuff from the library + //if (evt & TASK_EVENT_TIMER) + // return -1; + + /* TODO: give different error condition for failed vs discarded */ + return pd[port].tx_status == TCPC_TX_COMPLETE_SUCCESS ? 1 : -1; +} + +#ifdef CONFIG_USB_PD_REV30 +static void pd_ca_send_pending(int port) +{ + int cc1; + int cc2; + + /* Check if a message has been buffered. */ + if (!pd[port].ca_buffered) + return; + + tcpm_get_cc(port, &cc1, &cc2); + if ((cc1 != TYPEC_CC_VOLT_SNK_1_5) && + (cc2 != TYPEC_CC_VOLT_SNK_1_5)) + if (pd_transmit(port, pd[port].ca_type, + pd[port].ca_header, + pd[port].ca_buffer) < 0) + return; + + /* Message was sent, so free up the buffer. */ + pd[port].ca_buffered = 0; +} +#endif + +static void pd_update_roles(int port) +{ + /* Notify TCPC of role update */ + tcpm_set_msg_header(port, pd[port].power_role, pd[port].data_role); +} + +static int send_control(int port, int type) +{ + int bit_len; + uint16_t header = PD_HEADER(type, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, 0, + pd_get_rev(port), 0); + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, NULL); + if (debug_level >= 2) + CPRINTF("CTRL[%d]>%d\n", type, bit_len); + + return bit_len; +} + +static int send_source_cap(int port) +{ + int bit_len; +#if defined(CONFIG_USB_PD_DYNAMIC_SRC_CAP) || \ + defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) + const uint32_t *src_pdo; + const int src_pdo_cnt = charge_manager_get_source_pdo(&src_pdo, port); +#else + const uint32_t *src_pdo = pd_src_pdo; + const int src_pdo_cnt = pd_src_pdo_cnt; +#endif + uint16_t header; + + if (src_pdo_cnt == 0) + /* No source capabilities defined, sink only */ + header = PD_HEADER(PD_CTRL_REJECT, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, 0, + pd_get_rev(port), 0); + else + header = PD_HEADER(PD_DATA_SOURCE_CAP, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, src_pdo_cnt, + pd_get_rev(port), 0); + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, src_pdo); + if (debug_level >= 2) + CPRINTF("srcCAP>%d\n", bit_len); + + return bit_len; +} + +#ifdef CONFIG_USB_PD_REV30 +static int send_battery_cap(int port, uint32_t *payload) +{ + int bit_len; + uint16_t msg[6] = {0, 0, 0, 0, 0, 0}; + uint16_t header = PD_HEADER(PD_EXT_BATTERY_CAP, + pd[port].power_role, + pd[port].data_role, + pd[port].msg_id, + 3, /* Number of Data Objects */ + pd[port].rev, + 1 /* This is an exteded message */ + ); + + /* Set extended header */ + msg[0] = PD_EXT_HEADER(0, /* Chunk Number */ + 0, /* Request Chunk */ + 9 /* Data Size in bytes */ + ); + /* Set VID */ + msg[1] = USB_VID_GOOGLE; + + /* Set PID */ + msg[2] = CONFIG_USB_PID; + + if (battery_is_present()) { + /* + * We only have one fixed battery, + * so make sure batt cap ref is 0. + */ + if (BATT_CAP_REF(payload[0]) != 0) { + /* Invalid battery reference */ + msg[5] = 1; + } else { + uint32_t v; + uint32_t c; + + /* + * The Battery Design Capacity field shall return the + * Battery’s design capacity in tenths of Wh. If the + * Battery is Hot Swappable and is not present, the + * Battery Design Capacity field shall be set to 0. If + * the Battery is unable to report its Design Capacity, + * it shall return 0xFFFF + */ + msg[3] = 0xffff; + + /* + * The Battery Last Full Charge Capacity field shall + * return the Battery’s last full charge capacity in + * tenths of Wh. If the Battery is Hot Swappable and + * is not present, the Battery Last Full Charge Capacity + * field shall be set to 0. If the Battery is unable to + * report its Design Capacity, the Battery Last Full + * Charge Capacity field shall be set to 0xFFFF. + */ + msg[4] = 0xffff; + + if (battery_design_voltage(&v) == 0) { + if (battery_design_capacity(&c) == 0) { + /* + * Wh = (c * v) / 1000000 + * 10th of a Wh = Wh * 10 + */ + msg[3] = DIV_ROUND_NEAREST((c * v), + 100000); + } + + if (battery_full_charge_capacity(&c) == 0) { + /* + * Wh = (c * v) / 1000000 + * 10th of a Wh = Wh * 10 + */ + msg[4] = DIV_ROUND_NEAREST((c * v), + 100000); + } + } + } + } + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, (uint32_t *)msg); + if (debug_level >= 2) + CPRINTF("batCap>%d\n", bit_len); + return bit_len; +} + +static int send_battery_status(int port, uint32_t *payload) +{ + int bit_len; + uint32_t msg = 0; + uint16_t header = PD_HEADER(PD_DATA_BATTERY_STATUS, + pd[port].power_role, + pd[port].data_role, + pd[port].msg_id, + 1, /* Number of Data Objects */ + pd[port].rev, + 0 /* This is NOT an extended message */ + ); + + if (battery_is_present()) { + /* + * We only have one fixed battery, + * so make sure batt cap ref is 0. + */ + if (BATT_CAP_REF(payload[0]) != 0) { + /* Invalid battery reference */ + msg |= BSDO_INVALID; + } else { + uint32_t v; + uint32_t c; + + if (battery_design_voltage(&v) != 0 || + battery_remaining_capacity(&c) != 0) { + msg |= BSDO_CAP(BSDO_CAP_UNKNOWN); + } else { + /* + * Wh = (c * v) / 1000000 + * 10th of a Wh = Wh * 10 + */ + msg |= BSDO_CAP(DIV_ROUND_NEAREST((c * v), + 100000)); + } + + /* Battery is present */ + msg |= BSDO_PRESENT; + + /* + * For drivers that are not smart battery compliant, + * battery_status() returns EC_ERROR_UNIMPLEMENTED and + * the battery is assumed to be idle. + */ + if (battery_status(&c) != 0) { + msg |= BSDO_IDLE; /* assume idle */ + } else { + if (c & STATUS_FULLY_CHARGED) + /* Fully charged */ + msg |= BSDO_IDLE; + else if (c & STATUS_DISCHARGING) + /* Discharging */ + msg |= BSDO_DISCHARGING; + /* else battery is charging.*/ + } + } + } else { + msg = BSDO_CAP(BSDO_CAP_UNKNOWN); + } + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, &msg); + if (debug_level >= 2) + CPRINTF("batStat>%d\n", bit_len); + + return bit_len; +} +#endif + +#ifdef CONFIG_USB_PD_DUAL_ROLE +static void send_sink_cap(int port) +{ + int bit_len; + uint16_t header = PD_HEADER(PD_DATA_SINK_CAP, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, pd_snk_pdo_cnt, + pd_get_rev(port), 0); + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, pd_snk_pdo); + if (debug_level >= 2) + CPRINTF("snkCAP>%d\n", bit_len); +} + +static int send_request(int port, uint32_t rdo) +{ + int bit_len; + uint16_t header = PD_HEADER(PD_DATA_REQUEST, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, 1, + pd_get_rev(port), 0); + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, &rdo); + if (debug_level >= 2) + CPRINTF("REQ%d>\n", bit_len); + + return bit_len; +} +#ifdef CONFIG_BBRAM +static int pd_get_saved_active(int port) +{ + uint8_t val; + + if (system_get_bbram(port ? SYSTEM_BBRAM_IDX_PD1 : + SYSTEM_BBRAM_IDX_PD0, &val)) { + CPRINTS("PD NVRAM FAIL"); + return 0; + } + return !!val; +} + +static void pd_set_saved_active(int port, int val) +{ + if (system_set_bbram(port ? SYSTEM_BBRAM_IDX_PD1 : + SYSTEM_BBRAM_IDX_PD0, val)) + CPRINTS("PD NVRAM FAIL"); +} +#endif // CONFIG_BBRAM +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + +#ifdef CONFIG_COMMON_RUNTIME +static int send_bist_cmd(int port) +{ + /* currently only support sending bist carrier 2 */ + uint32_t bdo = BDO(BDO_MODE_CARRIER2, 0); + int bit_len; + uint16_t header = PD_HEADER(PD_DATA_BIST, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, 1, + pd_get_rev(port), 0); + + bit_len = pd_transmit(port, TCPC_TX_SOP, header, &bdo); + CPRINTF("BIST>%d\n", bit_len); + + return bit_len; +} +#endif + +static void queue_vdm(int port, uint32_t *header, const uint32_t *data, + int data_cnt) +{ + pd[port].vdo_count = data_cnt + 1; + pd[port].vdo_data[0] = header[0]; + memcpy(&pd[port].vdo_data[1], data, sizeof(uint32_t) * data_cnt); + /* Set ready, pd task will actually send */ + pd[port].vdm_state = VDM_STATE_READY; +} + +static void handle_vdm_request(int port, int cnt, uint32_t *payload) +{ + int rlen = 0; + uint32_t *rdata; + + CPRINTF("VDM request"); + if (pd[port].vdm_state == VDM_STATE_BUSY) { + /* If UFP responded busy retry after timeout */ + if (PD_VDO_CMDT(payload[0]) == CMDT_RSP_BUSY) { + pd[port].vdm_timeout.val = get_time().val + + PD_T_VDM_BUSY; + pd[port].vdm_state = VDM_STATE_WAIT_RSP_BUSY; + pd[port].vdo_retry = (payload[0] & ~VDO_CMDT_MASK) | + CMDT_INIT; + return; + } else { + pd[port].vdm_state = VDM_STATE_DONE; + } + } + + if (PD_VDO_SVDM(payload[0])) + rlen = pd_svdm(port, cnt, payload, &rdata); + else + rlen = pd_custom_vdm(port, cnt, payload, &rdata); + + if (rlen > 0) { + queue_vdm(port, rdata, &rdata[1], rlen - 1); + return; + } + if (debug_level >= 2) + CPRINTF("Unhandled VDM VID %04x CMD %04x\n", + PD_VDO_VID(payload[0]), payload[0] & 0xFFFF); +} + +void pd_execute_hard_reset(int port) +{ + if (pd[port].last_state == PD_STATE_HARD_RESET_SEND) + CPRINTF("C%d HARD RST TX\n", port); + else + CPRINTF("C%d HARD RST RX\n", port); + + pd[port].msg_id = 0; +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + pd_dfp_exit_mode(port, 0, 0); +#endif + +#ifdef CONFIG_USB_PD_REV30 + pd[port].rev = PD_REV30; + pd_ca_reset(port); +#endif + /* + * Fake set last state to hard reset to make sure that the next + * state to run knows that we just did a hard reset. + */ + pd[port].last_state = PD_STATE_HARD_RESET_EXECUTE; + +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* + * If we are swapping to a source and have changed to Rp, restore back + * to Rd and turn off vbus to match our power_role. + */ + if (pd[port].task_state == PD_STATE_SNK_SWAP_STANDBY || + pd[port].task_state == PD_STATE_SNK_SWAP_COMPLETE) { + tcpm_set_cc(port, TYPEC_CC_RD); + pd_power_supply_reset(port); + } + + if (pd[port].power_role == PD_ROLE_SINK) { + /* Clear the input current limit */ + pd_set_input_current_limit(port, 0, 0); +#ifdef CONFIG_CHARGE_MANAGER + //charge_manager_set_ceil(port, + // CEIL_REQUESTOR_PD, + // CHARGE_CEIL_NONE); +#endif /* CONFIG_CHARGE_MANAGER */ + + set_state(port, PD_STATE_SNK_HARD_RESET_RECOVER); + return; + } +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + + /* We are a source, cut power */ + pd_power_supply_reset(port); + pd[port].src_recover = get_time().val + PD_T_SRC_RECOVER; + set_state(port, PD_STATE_SRC_HARD_RESET_RECOVER); +} + +static void execute_soft_reset(int port) +{ + pd[port].msg_id = 0; + set_state(port, DUAL_ROLE_IF_ELSE(port, PD_STATE_SNK_DISCOVERY, + PD_STATE_SRC_DISCOVERY)); + CPRINTF("C%d Soft Rst\n", port); +} + +void pd_soft_reset(void) +{ + int i; + + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; ++i) + if (pd_is_connected(i)) { + set_state(i, PD_STATE_SOFT_RESET); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(i)); + } +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +/* + * Request desired charge voltage from source. + * Returns EC_SUCCESS on success or non-zero on failure. + */ +static int pd_send_request_msg(int port, int always_send_request) +{ + uint32_t rdo, curr_limit, supply_voltage; + int res; + +#ifdef CONFIG_CHARGE_MANAGER + //int charging = (charge_manager_get_active_charge_port() == port); + const int charging = 1; +#else + const int charging = 1; +#endif + +#ifdef CONFIG_USB_PD_CHECK_MAX_REQUEST_ALLOWED + int max_request_allowed = pd_is_max_request_allowed(); +#else + const int max_request_allowed = 1; +#endif + + /* Clear new power request */ + pd[port].new_power_request = 0; + + /* Build and send request RDO */ + /* + * If this port is not actively charging or we are not allowed to + * request the max voltage, then select vSafe5V + */ + res = pd_build_request(port, &rdo, &curr_limit, &supply_voltage, + charging && max_request_allowed ? + PD_REQUEST_MAX : PD_REQUEST_VSAFE5V); + + if (res != EC_SUCCESS) + /* + * If fail to choose voltage, do nothing, let source re-send + * source cap + */ + return -1; + + if (!always_send_request) { + /* Don't re-request the same voltage */ + if (pd[port].prev_request_mv == supply_voltage) + return EC_SUCCESS; +#ifdef CONFIG_CHARGE_MANAGER + /* Limit current to PD_MIN_MA during transition */ + //else + // charge_manager_force_ceil(port, PD_MIN_MA); +#endif + } + + CPRINTF("Req C%d [%d] %dmV %dmA", port, RDO_POS(rdo), + supply_voltage, curr_limit); + if (rdo & RDO_CAP_MISMATCH) + CPRINTF(" Mismatch"); + CPRINTF("\n"); + + pd[port].curr_limit = curr_limit; + pd[port].supply_voltage = supply_voltage; + pd[port].prev_request_mv = supply_voltage; + res = send_request(port, rdo); + if (res < 0) + return res; + set_state(port, PD_STATE_SNK_REQUESTED); + return EC_SUCCESS; +} +#endif + +static void pd_update_pdo_flags(int port, uint32_t pdo) +{ +#ifdef CONFIG_CHARGE_MANAGER +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + int charge_whitelisted = + (pd[port].power_role == PD_ROLE_SINK && + pd_charge_from_device(pd_get_identity_vid(port), + pd_get_identity_pid(port))); +#else + const int charge_whitelisted = 0; +#endif +#endif + + /* can only parse PDO flags if type is fixed */ + if ((pdo & PDO_TYPE_MASK) != PDO_TYPE_FIXED) + return; + +#ifdef CONFIG_USB_PD_DUAL_ROLE + if (pdo & PDO_FIXED_DUAL_ROLE) + pd[port].flags |= PD_FLAGS_PARTNER_DR_POWER; + else + pd[port].flags &= ~PD_FLAGS_PARTNER_DR_POWER; + + if (pdo & PDO_FIXED_EXTERNAL) + pd[port].flags |= PD_FLAGS_PARTNER_EXTPOWER; + else + pd[port].flags &= ~PD_FLAGS_PARTNER_EXTPOWER; + + if (pdo & PDO_FIXED_COMM_CAP) + pd[port].flags |= PD_FLAGS_PARTNER_USB_COMM; + else + pd[port].flags &= ~PD_FLAGS_PARTNER_USB_COMM; +#endif + + if (pdo & PDO_FIXED_DATA_SWAP) + pd[port].flags |= PD_FLAGS_PARTNER_DR_DATA; + else + pd[port].flags &= ~PD_FLAGS_PARTNER_DR_DATA; + +#ifdef CONFIG_CHARGE_MANAGER + /* + * Treat device as a dedicated charger (meaning we should charge + * from it) if it does not support power swap, or if it is externally + * powered, or if we are a sink and the device identity matches a + * charging white-list. + */ + /* + if (!(pd[port].flags & PD_FLAGS_PARTNER_DR_POWER) || + (pd[port].flags & PD_FLAGS_PARTNER_EXTPOWER) || + charge_whitelisted) + charge_manager_update_dualrole(port, CAP_DEDICATED); + else + charge_manager_update_dualrole(port, CAP_DUALROLE); + */ +#endif +} + +static void handle_data_request(int port, uint16_t head, + uint32_t *payload) +{ + int type = PD_HEADER_TYPE(head); + int cnt = PD_HEADER_CNT(head); + + switch (type) { +#ifdef CONFIG_USB_PD_DUAL_ROLE + case PD_DATA_SOURCE_CAP: + if ((pd[port].task_state == PD_STATE_SNK_DISCOVERY) + || (pd[port].task_state == PD_STATE_SNK_TRANSITION) +#ifdef CONFIG_USB_PD_VBUS_DETECT_NONE + || (pd[port].task_state == + PD_STATE_SNK_HARD_RESET_RECOVER) +#endif + || (pd[port].task_state == PD_STATE_SNK_READY)) { +#ifdef CONFIG_USB_PD_REV30 + /* + * Only adjust sink rev if source rev is higher. + */ + if (PD_HEADER_REV(head) < pd[port].rev) + pd[port].rev = PD_HEADER_REV(head); +#endif + /* Port partner is now known to be PD capable */ + pd[port].flags |= PD_FLAGS_PREVIOUS_PD_CONN; + + /* src cap 0 should be fixed PDO */ + pd_update_pdo_flags(port, payload[0]); + + pd_process_source_cap(port, cnt, payload); + + /* Source will resend source cap on failure */ + pd_send_request_msg(port, 1); + + // We call the callback after we send the request + // because the timing on Request seems to be sensitive + // User code can take the time until PS_RDY to do stuff + pd_process_source_cap_callback(port, cnt, payload); + } + break; +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + case PD_DATA_REQUEST: + if ((pd[port].power_role == PD_ROLE_SOURCE) && (cnt == 1)) { +#ifdef CONFIG_USB_PD_REV30 + /* + * Adjust the rev level to what the sink supports. If + * they're equal, no harm done. + */ + pd[port].rev = PD_HEADER_REV(head); +#endif + if (!pd_check_requested_voltage(payload[0], port)) { + if (send_control(port, PD_CTRL_ACCEPT) < 0) + /* + * if we fail to send accept, do + * nothing and let sink timeout and + * send hard reset + */ + return; + + /* explicit contract is now in place */ + pd[port].flags |= PD_FLAGS_EXPLICIT_CONTRACT; +#ifdef CONFIG_USB_PD_REV30 + /* + * Start Source-coordinated collision + * avoidance + */ + if (pd[port].rev == PD_REV30 && + pd[port].power_role == PD_ROLE_SOURCE) + sink_can_xmit(port, SINK_TX_OK); +#endif +#ifdef CONFIG_USB_PD_DUAL_ROLE +#ifdef CONFIG_BBRAM + pd_set_saved_active(port, 1); +#endif +#endif + pd[port].requested_idx = RDO_POS(payload[0]); + set_state(port, PD_STATE_SRC_ACCEPTED); + return; + } + } + /* the message was incorrect or cannot be satisfied */ + send_control(port, PD_CTRL_REJECT); + /* keep last contract in place (whether implicit or explicit) */ + set_state(port, PD_STATE_SRC_READY); + break; + case PD_DATA_BIST: + /* If not in READY state, then don't start BIST */ + if (DUAL_ROLE_IF_ELSE(port, + pd[port].task_state == PD_STATE_SNK_READY, + pd[port].task_state == PD_STATE_SRC_READY)) { + /* currently only support sending bist carrier mode 2 */ + if ((payload[0] >> 28) == 5) { + /* bist data object mode is 2 */ + pd_transmit(port, TCPC_TX_BIST_MODE_2, 0, + NULL); + /* Set to appropriate port disconnected state */ + set_state(port, DUAL_ROLE_IF_ELSE(port, + PD_STATE_SNK_DISCONNECTED, + PD_STATE_SRC_DISCONNECTED)); + } + } + break; + case PD_DATA_SINK_CAP: + pd[port].flags |= PD_FLAGS_SNK_CAP_RECVD; + /* snk cap 0 should be fixed PDO */ + pd_update_pdo_flags(port, payload[0]); + if (pd[port].task_state == PD_STATE_SRC_GET_SINK_CAP) + set_state(port, PD_STATE_SRC_READY); + break; +#ifdef CONFIG_USB_PD_REV30 + case PD_DATA_BATTERY_STATUS: + break; +#endif + case PD_DATA_VENDOR_DEF: + handle_vdm_request(port, cnt, payload); + break; + default: + CPRINTF("Unhandled data message type %d\n", type); + } +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +void pd_request_power_swap(int port) +{ + if (pd[port].task_state == PD_STATE_SRC_READY) + set_state(port, PD_STATE_SRC_SWAP_INIT); + else if (pd[port].task_state == PD_STATE_SNK_READY) + set_state(port, PD_STATE_SNK_SWAP_INIT); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); +} + +#ifdef CONFIG_USBC_VCONN_SWAP +static void pd_request_vconn_swap(int port) +{ + if (pd[port].task_state == PD_STATE_SRC_READY || + pd[port].task_state == PD_STATE_SNK_READY) + set_state(port, PD_STATE_VCONN_SWAP_SEND); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); +} + +void pd_try_vconn_src(int port) +{ + /* + * If we don't currently provide vconn, and we can supply it, send + * a vconn swap request. + */ + if (!(pd[port].flags & PD_FLAGS_VCONN_ON)) { + if (pd_check_vconn_swap(port)) + pd_request_vconn_swap(port); + } +} +#endif +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + +void pd_request_data_swap(int port) +{ + if (DUAL_ROLE_IF_ELSE(port, + pd[port].task_state == PD_STATE_SNK_READY, + pd[port].task_state == PD_STATE_SRC_READY)) + set_state(port, PD_STATE_DR_SWAP); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); +} + +static void pd_set_data_role(int port, int role) +{ + pd[port].data_role = role; + pd_execute_data_swap(port, role); + +#ifdef CONFIG_USBC_SS_MUX +#ifdef CONFIG_USBC_SS_MUX_DFP_ONLY + /* + * Need to connect SS mux for if new data role is DFP. + * If new data role is UFP, then disconnect the SS mux. + */ + if (role == PD_ROLE_DFP) + usb_mux_set(port, TYPEC_MUX_USB, USB_SWITCH_CONNECT, + pd[port].polarity); + else + usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT, + pd[port].polarity); +#else + usb_mux_set(port, TYPEC_MUX_USB, USB_SWITCH_CONNECT, + pd[port].polarity); +#endif +#endif + pd_update_roles(port); +} + +static void pd_dr_swap(int port) +{ + pd_set_data_role(port, !pd[port].data_role); + pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; +} + +static void handle_ctrl_request(int port, uint16_t head, + uint32_t *payload) +{ + int type = PD_HEADER_TYPE(head); + int res; + + switch (type) { + case PD_CTRL_GOOD_CRC: + /* should not get it */ + break; + case PD_CTRL_PING: + /* Nothing else to do */ + break; + case PD_CTRL_GET_SOURCE_CAP: + res = send_source_cap(port); + if ((res >= 0) && + (pd[port].task_state == PD_STATE_SRC_DISCOVERY)) + set_state(port, PD_STATE_SRC_NEGOCIATE); + break; + case PD_CTRL_GET_SINK_CAP: +#ifdef CONFIG_USB_PD_DUAL_ROLE + send_sink_cap(port); +#else + send_control(port, REFUSE(pd[port].rev)); +#endif + break; +#ifdef CONFIG_USB_PD_DUAL_ROLE + case PD_CTRL_GOTO_MIN: +#ifdef CONFIG_USB_PD_GIVE_BACK + if (pd[port].task_state == PD_STATE_SNK_READY) { + /* + * Reduce power consumption now! + * + * The source will restore power to this sink + * by sending a new source cap message at a + * later time. + */ + pd_snk_give_back(port, &pd[port].curr_limit, + &pd[port].supply_voltage); + set_state(port, PD_STATE_SNK_TRANSITION); + } +#endif + + break; + case PD_CTRL_PS_RDY: + if (pd[port].task_state == PD_STATE_SNK_SWAP_SRC_DISABLE) { + set_state(port, PD_STATE_SNK_SWAP_STANDBY); + } else if (pd[port].task_state == PD_STATE_SRC_SWAP_STANDBY) { + /* reset message ID and swap roles */ + pd[port].msg_id = 0; + pd[port].power_role = PD_ROLE_SINK; + pd_update_roles(port); + set_state(port, PD_STATE_SNK_DISCOVERY); +#ifdef CONFIG_USBC_VCONN_SWAP + } else if (pd[port].task_state == PD_STATE_VCONN_SWAP_INIT) { + /* + * If VCONN is on, then this PS_RDY tells us it's + * ok to turn VCONN off + */ + if (pd[port].flags & PD_FLAGS_VCONN_ON) + set_state(port, PD_STATE_VCONN_SWAP_READY); +#endif + } else if (pd[port].task_state == PD_STATE_SNK_DISCOVERY) { + /* Don't know what power source is ready. Reset. */ + set_state(port, PD_STATE_HARD_RESET_SEND); + } else if (pd[port].task_state == PD_STATE_SNK_SWAP_STANDBY) { + /* Do nothing, assume this is a redundant PD_RDY */ + } else if (pd[port].power_role == PD_ROLE_SINK) { + set_state(port, PD_STATE_SNK_READY); + pd_set_input_current_limit(port, pd[port].curr_limit, + pd[port].supply_voltage); +#ifdef CONFIG_CHARGE_MANAGER + /* Set ceiling based on what's negotiated */ + //charge_manager_set_ceil(port, + // CEIL_REQUESTOR_PD, + // pd[port].curr_limit); +#endif + } + break; +#endif + case PD_CTRL_REJECT: + case PD_CTRL_WAIT: + if (pd[port].task_state == PD_STATE_DR_SWAP) + set_state(port, READY_RETURN_STATE(port)); +#ifdef CONFIG_USBC_VCONN_SWAP + else if (pd[port].task_state == PD_STATE_VCONN_SWAP_SEND) + set_state(port, READY_RETURN_STATE(port)); +#endif +#ifdef CONFIG_USB_PD_DUAL_ROLE + else if (pd[port].task_state == PD_STATE_SRC_SWAP_INIT) + set_state(port, PD_STATE_SRC_READY); + else if (pd[port].task_state == PD_STATE_SNK_SWAP_INIT) + set_state(port, PD_STATE_SNK_READY); + else if (pd[port].task_state == PD_STATE_SNK_REQUESTED) { + /* + * Explicit Contract in place + * + * On reception of a WAIT message, transition to + * PD_STATE_SNK_READY after PD_T_SINK_REQUEST ms to + * send another reqest. + * + * On reception of a REJECT messag, transition to + * PD_STATE_SNK_READY but don't resend the request. + * + * NO Explicit Contract in place + * + * On reception of a WAIT or REJECT message, + * transition to PD_STATE_SNK_DISCOVERY + */ + if (pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT) { + /* We have an explicit contract */ + if (type == PD_CTRL_WAIT) { + /* + * Trigger a new power request when + * we enter PD_STATE_SNK_READY + */ + pd[port].new_power_request = 1; + + /* + * After the request is triggered, + * make sure the request is sent. + */ + pd[port].prev_request_mv = 0; + + /* + * Transition to PD_STATE_SNK_READY + * after PD_T_SINK_REQUEST ms. + */ + set_state_timeout(port, get_time().val + + PD_T_SINK_REQUEST, + PD_STATE_SNK_READY); + } else { + /* The request was rejected */ + set_state(port, PD_STATE_SNK_READY); + } + } else { + /* No explicit contract */ + set_state(port, PD_STATE_SNK_DISCOVERY); + } + } +#endif + break; + case PD_CTRL_ACCEPT: + if (pd[port].task_state == PD_STATE_SOFT_RESET) { + /* + * For the case that we sent soft reset in SNK_DISCOVERY + * on startup due to VBUS never low, clear the flag. + */ + pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; + execute_soft_reset(port); + } else if (pd[port].task_state == PD_STATE_DR_SWAP) { + /* switch data role */ + pd_dr_swap(port); + set_state(port, READY_RETURN_STATE(port)); +#ifdef CONFIG_USB_PD_DUAL_ROLE +#ifdef CONFIG_USBC_VCONN_SWAP + } else if (pd[port].task_state == PD_STATE_VCONN_SWAP_SEND) { + /* switch vconn */ + set_state(port, PD_STATE_VCONN_SWAP_INIT); +#endif + } else if (pd[port].task_state == PD_STATE_SRC_SWAP_INIT) { + /* explicit contract goes away for power swap */ + pd[port].flags &= ~PD_FLAGS_EXPLICIT_CONTRACT; + set_state(port, PD_STATE_SRC_SWAP_SNK_DISABLE); + } else if (pd[port].task_state == PD_STATE_SNK_SWAP_INIT) { + /* explicit contract goes away for power swap */ + pd[port].flags &= ~PD_FLAGS_EXPLICIT_CONTRACT; + set_state(port, PD_STATE_SNK_SWAP_SNK_DISABLE); + } else if (pd[port].task_state == PD_STATE_SNK_REQUESTED) { + /* explicit contract is now in place */ + pd[port].flags |= PD_FLAGS_EXPLICIT_CONTRACT; +#ifdef CONFIG_BBRAM + pd_set_saved_active(port, 1); +#endif + set_state(port, PD_STATE_SNK_TRANSITION); +#endif + } + break; + case PD_CTRL_SOFT_RESET: + execute_soft_reset(port); + /* We are done, acknowledge with an Accept packet */ + send_control(port, PD_CTRL_ACCEPT); + break; + case PD_CTRL_PR_SWAP: +#ifdef CONFIG_USB_PD_DUAL_ROLE + if (pd_check_power_swap(port)) { + send_control(port, PD_CTRL_ACCEPT); + /* + * Clear flag for checking power role to avoid + * immediately requesting another swap. + */ + pd[port].flags &= ~PD_FLAGS_CHECK_PR_ROLE; + set_state(port, + DUAL_ROLE_IF_ELSE(port, + PD_STATE_SNK_SWAP_SNK_DISABLE, + PD_STATE_SRC_SWAP_SNK_DISABLE)); + } else { + send_control(port, REFUSE(pd[port].rev)); + } +#else + send_control(port, REFUSE(pd[port].rev)); +#endif + break; + case PD_CTRL_DR_SWAP: + if (pd_check_data_swap(port, pd[port].data_role)) { + /* + * Accept switch and perform data swap. Clear + * flag for checking data role to avoid + * immediately requesting another swap. + */ + pd[port].flags &= ~PD_FLAGS_CHECK_DR_ROLE; + if (send_control(port, PD_CTRL_ACCEPT) >= 0) + pd_dr_swap(port); + } else { + send_control(port, REFUSE(pd[port].rev)); + + } + break; + case PD_CTRL_VCONN_SWAP: +#ifdef CONFIG_USBC_VCONN_SWAP + if (pd[port].task_state == PD_STATE_SRC_READY || + pd[port].task_state == PD_STATE_SNK_READY) { + if (pd_check_vconn_swap(port)) { + if (send_control(port, PD_CTRL_ACCEPT) > 0) + set_state(port, + PD_STATE_VCONN_SWAP_INIT); + } else { + send_control(port, REFUSE(pd[port].rev)); + } + } +#else + send_control(port, REFUSE(pd[port].rev)); +#endif + break; + default: +#ifdef CONFIG_USB_PD_REV30 + send_control(port, PD_CTRL_NOT_SUPPORTED); +#endif + CPRINTF("Unhandled ctrl message type %d\n", type); + } +} + +#ifdef CONFIG_USB_PD_REV30 +static void handle_ext_request(int port, uint16_t head, uint32_t *payload) +{ + int type = PD_HEADER_TYPE(head); + + switch (type) { + case PD_EXT_GET_BATTERY_CAP: + send_battery_cap(port, payload); + break; + case PD_EXT_GET_BATTERY_STATUS: + send_battery_status(port, payload); + break; + case PD_EXT_BATTERY_CAP: + break; + default: + send_control(port, PD_CTRL_NOT_SUPPORTED); + } +} +#endif + +static void handle_request(int port, uint16_t head, + uint32_t *payload) +{ + int cnt = PD_HEADER_CNT(head); + int p; + + /* dump received packet content (only dump ping at debug level 3) */ + if ((debug_level == 2 && PD_HEADER_TYPE(head) != PD_CTRL_PING) || + debug_level >= 3) { + CPRINTF("RECV %04x/%d ", head, cnt); + for (p = 0; p < cnt; p++) + CPRINTF("[%d]%08x ", p, payload[p]); + CPRINTF("\n"); + } + + /* + * If we are in disconnected state, we shouldn't get a request. + * Ignore it if we get one. + */ + if (!pd_is_connected(port)) + return; + +#ifdef CONFIG_USB_PD_REV30 + /* Check if this is an extended chunked data message. */ + if (pd[port].rev == PD_REV30 && PD_HEADER_EXT(head)) { + handle_ext_request(port, head, payload); + return; + } +#endif + if (cnt) + handle_data_request(port, head, payload); + else + handle_ctrl_request(port, head, payload); +} + +void pd_send_vdm(int port, uint32_t vid, int cmd, const uint32_t *data, + int count) +{ + if (count > VDO_MAX_SIZE - 1) { + CPRINTF("VDM over max size\n"); + return; + } + + /* set VDM header with VID & CMD */ + pd[port].vdo_data[0] = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? + 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd); +#ifdef CONFIG_USB_PD_REV30 + pd[port].vdo_data[0] |= VDO_SVDM_VERS(vdo_ver[pd[port].rev]); +#endif + queue_vdm(port, pd[port].vdo_data, data, count); + + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); +} + +static inline int pdo_busy(int port) +{ + /* + * Note, main PDO state machine (pd_task) uses READY state exclusively + * to denote port partners have successfully negociated a contract. All + * other protocol actions force state transitions. + */ + int rv = (pd[port].task_state != PD_STATE_SRC_READY); +#ifdef CONFIG_USB_PD_DUAL_ROLE + rv &= (pd[port].task_state != PD_STATE_SNK_READY); +#endif + return rv; +} + +static uint64_t vdm_get_ready_timeout(uint32_t vdm_hdr) +{ + uint64_t timeout; + int cmd = PD_VDO_CMD(vdm_hdr); + + /* its not a structured VDM command */ + if (!PD_VDO_SVDM(vdm_hdr)) + return 500*MSEC_US; + + switch (PD_VDO_CMDT(vdm_hdr)) { + case CMDT_INIT: + if ((cmd == CMD_ENTER_MODE) || (cmd == CMD_EXIT_MODE)) + timeout = PD_T_VDM_WAIT_MODE_E; + else + timeout = PD_T_VDM_SNDR_RSP; + break; + default: + if ((cmd == CMD_ENTER_MODE) || (cmd == CMD_EXIT_MODE)) + timeout = PD_T_VDM_E_MODE; + else + timeout = PD_T_VDM_RCVR_RSP; + break; + } + return timeout; +} + +static void pd_vdm_send_state_machine(int port) +{ + int res; + uint16_t header; + + /*static int old_vdm_state = 0; + if (pd[port].vdm_state != old_vdm_state) { + CPRINTF("VDM state: %d\n", pd[port].vdm_state); + old_vdm_state = pd[port].vdm_state; + }*/ + + switch (pd[port].vdm_state) { + case VDM_STATE_READY: + /* Only transmit VDM if connected. */ + if (!pd_is_connected(port)) { + pd[port].vdm_state = VDM_STATE_ERR_BUSY; + break; + } + + /* + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ + if (pdo_busy(port)) + break; + + /* Prepare and send VDM */ + header = PD_HEADER(PD_DATA_VENDOR_DEF, pd[port].power_role, + pd[port].data_role, pd[port].msg_id, + (int)pd[port].vdo_count, + pd_get_rev(port), 0); + res = pd_transmit(port, TCPC_TX_SOP, header, + pd[port].vdo_data); + if (res < 0) { + pd[port].vdm_state = VDM_STATE_ERR_SEND; + } else { + pd[port].vdm_state = VDM_STATE_BUSY; + pd[port].vdm_timeout.val = get_time().val + + vdm_get_ready_timeout(pd[port].vdo_data[0]); + } + break; + case VDM_STATE_WAIT_RSP_BUSY: + /* wait and then initiate request again */ + if (get_time().val > pd[port].vdm_timeout.val) { + pd[port].vdo_data[0] = pd[port].vdo_retry; + pd[port].vdo_count = 1; + pd[port].vdm_state = VDM_STATE_READY; + } + break; + case VDM_STATE_BUSY: + /* Wait for VDM response or timeout */ + if (pd[port].vdm_timeout.val && + (get_time().val > pd[port].vdm_timeout.val)) { + pd[port].vdm_state = VDM_STATE_ERR_TMOUT; + } + break; + default: + break; + } +} + +#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO +static inline void pd_dev_dump_info(uint16_t dev_id, uint8_t *hash) +{ + int j; + ccprintf("DevId:%d.%d Hash:", HW_DEV_ID_MAJ(dev_id), + HW_DEV_ID_MIN(dev_id)); + for (j = 0; j < PD_RW_HASH_SIZE; j += 4) { + ccprintf(" 0x%02x%02x%02x%02x", hash[j + 3], hash[j + 2], + hash[j + 1], hash[j]); + } + ccprintf("\n"); +} +#endif /* CONFIG_CMD_PD_DEV_DUMP_INFO */ + +int pd_dev_store_rw_hash(int port, uint16_t dev_id, uint32_t *rw_hash, + uint32_t current_image) +{ +#ifdef CONFIG_COMMON_RUNTIME + int i; +#endif + +#ifdef CONFIG_USB_PD_CHROMEOS + pd[port].dev_id = dev_id; + memcpy(pd[port].dev_rw_hash, rw_hash, PD_RW_HASH_SIZE); +#endif +#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO + if (debug_level >= 2) + pd_dev_dump_info(dev_id, (uint8_t *)rw_hash); +#endif +#ifdef CONFIG_USB_PD_CHROMEOS + pd[port].current_image = current_image; +#endif + +#ifdef CONFIG_COMMON_RUNTIME + /* Search table for matching device / hash */ + for (i = 0; i < RW_HASH_ENTRIES; i++) + if (dev_id == rw_hash_table[i].dev_id) + return !memcmp(rw_hash, + rw_hash_table[i].dev_rw_hash, + PD_RW_HASH_SIZE); +#endif + return 0; +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +enum pd_dual_role_states pd_get_dual_role(void) +{ + return drp_state; +} + +#ifdef CONFIG_USB_PD_TRY_SRC +static void pd_update_try_source(void) +{ + int i; + +#ifndef CONFIG_CHARGER + int batt_soc = board_get_battery_soc(); +#else + int batt_soc = charge_get_percent(); +#endif + + /* + * Enable try source when dual-role toggling AND battery is present + * and at some minimum percentage. + */ + pd_try_src_enable = drp_state == PD_DRP_TOGGLE_ON && + batt_soc >= CONFIG_USB_PD_TRY_SRC_MIN_BATT_SOC; +#if defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \ + defined(CONFIG_BATTERY_PRESENT_GPIO) + /* + * When battery is cutoff in ship mode it may not be reliable to + * check if battery is present with its state of charge. + * Also check if battery is initialized and ready to provide power. + */ + pd_try_src_enable &= (battery_is_present() == BP_YES); +#endif + + /* + * Clear this flag to cover case where a TrySrc + * mode went from enabled to disabled and trying_source + * was active at that time. + */ + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) + pd[i].flags &= ~PD_FLAGS_TRY_SRC; + +} +DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pd_update_try_source, HOOK_PRIO_DEFAULT); +#endif + +void pd_set_dual_role(enum pd_dual_role_states state) +{ + int i; + drp_state = state; + +#ifdef CONFIG_USB_PD_TRY_SRC + pd_update_try_source(); +#endif + + /* Inform PD tasks of dual role change. */ + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) + // getting rid of task stuff + //task_set_event(PD_PORT_TO_TASK_ID(i), + // PD_EVENT_UPDATE_DUAL_ROLE, 0); + ; +} + +void pd_update_dual_role_config(int port) +{ + /* + * Change to sink if port is currently a source AND (new DRP + * state is force sink OR new DRP state is either toggle off + * or debug accessory toggle only and we are in the source + * disconnected state). + */ + if (pd[port].power_role == PD_ROLE_SOURCE && + ((drp_state == PD_DRP_FORCE_SINK && !pd_ts_dts_plugged(port)) || + (drp_state == PD_DRP_TOGGLE_OFF + && pd[port].task_state == PD_STATE_SRC_DISCONNECTED))) { + pd[port].power_role = PD_ROLE_SINK; + set_state(port, PD_STATE_SNK_DISCONNECTED); + tcpm_set_cc(port, TYPEC_CC_RD); + /* Make sure we're not sourcing VBUS. */ + pd_power_supply_reset(port); + } + + /* + * Change to source if port is currently a sink and the + * new DRP state is force source. + */ + if (pd[port].power_role == PD_ROLE_SINK && + drp_state == PD_DRP_FORCE_SOURCE) { + pd[port].power_role = PD_ROLE_SOURCE; + set_state(port, PD_STATE_SRC_DISCONNECTED); + tcpm_set_cc(port, TYPEC_CC_RP); + } + +#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \ + defined(CONFIG_USB_PD_TCPC_LOW_POWER) + /* When switching drp mode, make sure tcpc is out of standby mode */ + tcpm_set_drp_toggle(port, 0); +#endif +} + +int pd_get_role(int port) +{ + return pd[port].power_role; +} + +static int pd_is_power_swapping(int port) +{ + /* return true if in the act of swapping power roles */ + return pd[port].task_state == PD_STATE_SNK_SWAP_SNK_DISABLE || + pd[port].task_state == PD_STATE_SNK_SWAP_SRC_DISABLE || + pd[port].task_state == PD_STATE_SNK_SWAP_STANDBY || + pd[port].task_state == PD_STATE_SNK_SWAP_COMPLETE || + pd[port].task_state == PD_STATE_SRC_SWAP_SNK_DISABLE || + pd[port].task_state == PD_STATE_SRC_SWAP_SRC_DISABLE || + pd[port].task_state == PD_STATE_SRC_SWAP_STANDBY; +} + +/* + * Provide Rp to ensure the partner port is in a known state (eg. not + * PD negotiated, not sourcing 20V). + */ +static void pd_partner_port_reset(int port) +{ + uint64_t timeout; + +#ifdef CONFIG_BBRAM + /* + * Check our battery-backed previous port state. If PD comms were + * active, and we didn't just lose power, make sure we + * don't boot into RO with a pre-existing power contract. + */ + if (!pd_get_saved_active(port) || + system_get_image_copy() != SYSTEM_IMAGE_RO || + system_get_reset_flags() & + (RESET_FLAG_BROWNOUT | RESET_FLAG_POWER_ON)) + return; +#endif // CONFIG_BBRAM + /* Provide Rp for 100 msec. or until we no longer have VBUS. */ + tcpm_set_cc(port, TYPEC_CC_RP); + timeout = get_time().val + 100 * MSEC_US; + + while (get_time().val < timeout && pd_is_vbus_present(port)) + msleep(10); +#ifdef CONFIG_BBRAM + pd_set_saved_active(port, 0); +#endif +} +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + +int pd_get_polarity(int port) +{ + return pd[port].polarity; +} + +int pd_get_partner_data_swap_capable(int port) +{ + /* return data swap capable status of port partner */ + return pd[port].flags & PD_FLAGS_PARTNER_DR_DATA; +} + +#ifdef CONFIG_COMMON_RUNTIME +void pd_comm_enable(int port, int enable) +{ + /* We don't check port >= CONFIG_USB_PD_PORT_COUNT deliberately */ + pd_comm_enabled[port] = enable; + + /* If type-C connection, then update the TCPC RX enable */ + if (pd_is_connected(port)) + tcpm_set_rx_enable(port, enable); + +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* + * If communications are enabled, start hard reset timer for + * any port in PD_SNK_DISCOVERY. + */ + if (enable && pd[port].task_state == PD_STATE_SNK_DISCOVERY) + set_state_timeout(port, + get_time().val + PD_T_SINK_WAIT_CAP, + PD_STATE_HARD_RESET_SEND); +#endif +} +#endif + +void pd_ping_enable(int port, int enable) +{ + if (enable) + pd[port].flags |= PD_FLAGS_PING_ENABLED; + else + pd[port].flags &= ~PD_FLAGS_PING_ENABLED; +} + +/** + * Returns whether the sink has detected a Rp resistor on the other side. + */ +static inline int cc_is_rp(int cc) +{ + return (cc == TYPEC_CC_VOLT_SNK_DEF) || (cc == TYPEC_CC_VOLT_SNK_1_5) || + (cc == TYPEC_CC_VOLT_SNK_3_0); +} + +/* + * CC values for regular sources and Debug sources (aka DTS) + * + * Source type Mode of Operation CC1 CC2 + * --------------------------------------------- + * Regular Default USB Power RpUSB Open + * Regular USB-C @ 1.5 A Rp1A5 Open + * Regular USB-C @ 3 A Rp3A0 Open + * DTS Default USB Power Rp3A0 Rp1A5 + * DTS USB-C @ 1.5 A Rp1A5 RpUSB + * DTS USB-C @ 3 A Rp3A0 RpUSB +*/ + +/** + * Returns the polarity of a Sink. + */ +static inline int get_snk_polarity(int cc1, int cc2) +{ + /* the following assumes: + * TYPEC_CC_VOLT_SNK_3_0 > TYPEC_CC_VOLT_SNK_1_5 + * TYPEC_CC_VOLT_SNK_1_5 > TYPEC_CC_VOLT_SNK_DEF + * TYPEC_CC_VOLT_SNK_DEF > TYPEC_CC_VOLT_OPEN + */ + return (cc2 > cc1); +} + +#if defined(CONFIG_CHARGE_MANAGER) +/** + * Returns type C current limit (mA) based upon cc_voltage (mV). + */ +static typec_current_t get_typec_current_limit(int polarity, int cc1, int cc2) +{ + typec_current_t charge; + int cc = polarity ? cc2 : cc1; + int cc_alt = polarity ? cc1 : cc2; + + if (cc == TYPEC_CC_VOLT_SNK_3_0 && cc_alt != TYPEC_CC_VOLT_SNK_1_5) + charge = 3000; + else if (cc == TYPEC_CC_VOLT_SNK_1_5) + charge = 1500; + else + charge = 0; + + if (cc_is_rp(cc_alt)) + charge |= TYPEC_CURRENT_DTS_MASK; + + return charge; +} + +/** + * Signal power request to indicate a charger update that affects the port. + */ +void pd_set_new_power_request(int port) +{ + pd[port].new_power_request = 1; + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); +} +#endif /* CONFIG_CHARGE_MANAGER */ + +#if defined(CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP) && defined(CONFIG_USBC_SS_MUX) +/* + * Backwards compatible DFP does not support USB SS because it applies VBUS + * before debouncing CC and setting USB SS muxes, but SS detection will fail + * before we are done debouncing CC. + */ +#error "Backwards compatible DFP does not support USB" +#endif + +#ifdef CONFIG_COMMON_RUNTIME + +/* Initialize globals based on system state. */ +static void pd_init_tasks(void) +{ + static int initialized; + int enable = 1; + int i; + + /* Initialize globals once, for all PD tasks. */ + if (initialized) + return; + +#if defined(HAS_TASK_CHIPSET) && defined(CONFIG_USB_PD_DUAL_ROLE) + /* Set dual-role state based on chipset power state */ + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) + drp_state = PD_DRP_FORCE_SINK; + else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) + drp_state = PD_DRP_TOGGLE_OFF; + else /* CHIPSET_STATE_ON */ + drp_state = PD_DRP_TOGGLE_ON; +#endif + +#if defined(CONFIG_USB_PD_COMM_DISABLED) + enable = 0; +#elif defined(CONFIG_USB_PD_COMM_LOCKED) + /* Disable PD communication at init if we're in RO and locked. */ + if (!system_is_in_rw() && system_is_locked()) + enable = 0; +#endif + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) + pd_comm_enabled[i] = enable; + CPRINTS("PD comm %sabled", enable ? "en" : "dis"); + + initialized = 1; +} +#endif /* CONFIG_COMMON_RUNTIME */ + +#ifndef CONFIG_USB_PD_TCPC +static int pd_restart_tcpc(int port) +{ + if (board_set_tcpc_power_mode) { + /* force chip reset */ + board_set_tcpc_power_mode(port, 0); + } + return tcpm_init(port); +} +#endif + +void pd_init(int port) +{ +#ifdef CONFIG_COMMON_RUNTIME + pd_init_tasks(); +#endif + + /* Ensure the power supply is in the default state */ + pd_power_supply_reset(port); + +#ifdef CONFIG_USB_PD_TCPC_BOARD_INIT + /* Board specific TCPC init */ + board_tcpc_init(); +#endif + + /* Initialize TCPM driver and wait for TCPC to be ready */ + res = tcpm_init(port); + +#ifdef CONFIG_USB_PD_DUAL_ROLE + //pd_partner_port_reset(port); +#endif + + CPRINTS("TCPC p%d init %s", port, res ? "failed" : "ready"); + this_state = res ? PD_STATE_SUSPENDED : PD_DEFAULT_STATE(port); + +#ifdef CONFIG_USB_PD_REV30 + /* Set Revision to highest */ + pd[port].rev = PD_REV30; + pd_ca_reset(port); +#endif + +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* + * If VBUS is high, then initialize flag for VBUS has always been + * present. This flag is used to maintain a PD connection after a + * reset by sending a soft reset. + */ + //pd[port].flags = pd_is_vbus_present(port) ? PD_FLAGS_VBUS_NEVER_LOW : 0; + pd[port].flags = 0; +#endif + + /* Disable TCPC RX until connection is established */ + tcpm_set_rx_enable(port, 0); + +#ifdef CONFIG_USBC_SS_MUX + /* Initialize USB mux to its default state */ + usb_mux_init(port); +#endif + + /* Initialize PD protocol state variables for each port. */ + pd[port].power_role = PD_ROLE_DEFAULT(port); + pd[port].vdm_state = VDM_STATE_DONE; + set_state(port, this_state); +#ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT + ASSERT(PD_ROLE_DEFAULT(port) == PD_ROLE_SINK); + tcpm_select_rp_value(port, CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT); +#else + tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); +#endif + tcpm_set_cc(port, PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE ? + TYPEC_CC_RP : TYPEC_CC_RD); + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP + /* Initialize PD Policy engine */ + pd_dfp_pe_init(port); +#endif + +#ifdef CONFIG_CHARGE_MANAGER + /* Initialize PD and type-C supplier current limits to 0 */ + pd_set_input_current_limit(port, 0, 0); + //typec_set_input_current_limit(port, 0, 0); + //charge_manager_update_dualrole(port, CAP_UNKNOWN); +#endif +} + +void pd_run_state_machine(int port) +{ +#ifdef CONFIG_USB_PD_REV30 + /* send any pending messages */ + pd_ca_send_pending(port); +#endif + /* process VDM messages last */ + pd_vdm_send_state_machine(port); + + /* Verify board specific health status : current, voltages... */ + res = pd_board_checks(); + if (res != EC_SUCCESS) { + /* cut the power */ + pd_execute_hard_reset(port); + /* notify the other side of the issue */ + pd_transmit(port, TCPC_TX_HARD_RESET, 0, NULL); + } + + /* wait for next event/packet or timeout expiration */ + // getting rid of task stuff + //evt = task_wait_event(timeout); + +#ifdef CONFIG_USB_PD_DUAL_ROLE + if (evt & PD_EVENT_UPDATE_DUAL_ROLE) + pd_update_dual_role_config(port); +#endif + +#ifdef CONFIG_USB_PD_TCPC + /* + * run port controller task to check CC and/or read incoming + * messages + */ + tcpc_run(port, evt); +#else + /* if TCPC has reset, then need to initialize it again */ + if (evt & PD_EVENT_TCPC_RESET) { + CPRINTS("TCPC p%d reset!", port); + if (tcpm_init(port) != EC_SUCCESS) + CPRINTS("TCPC p%d init failed", port); +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + } + + if ((evt & PD_EVENT_TCPC_RESET) && + (pd[port].task_state != PD_STATE_DRP_AUTO_TOGGLE)) { +#endif + /* Ensure CC termination is default */ + tcpm_set_cc(port, PD_ROLE_DEFAULT(port) == + PD_ROLE_SOURCE ? TYPEC_CC_RP : TYPEC_CC_RD); + + /* + * If we have a stable contract in the default role, + * then simply update TCPC with some missing info + * so that we can continue without resetting PD comms. + * Otherwise, go to the default disconnected state + * and force renegotiation. + */ + if (pd[port].vdm_state == VDM_STATE_DONE && ( +#ifdef CONFIG_USB_PD_DUAL_ROLE + (PD_ROLE_DEFAULT(port) == PD_ROLE_SINK && + pd[port].task_state == PD_STATE_SNK_READY) || +#endif + (PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE && + pd[port].task_state == PD_STATE_SRC_READY))) { + tcpm_set_polarity(port, pd[port].polarity); + tcpm_set_msg_header(port, pd[port].power_role, + pd[port].data_role); + tcpm_set_rx_enable(port, 1); + } else { + /* Ensure state variables are at default */ + pd[port].power_role = PD_ROLE_DEFAULT(port); + pd[port].vdm_state = VDM_STATE_DONE; + set_state(port, PD_DEFAULT_STATE(port)); + } + } +#endif + + /* process any potential incoming message */ + incoming_packet = evt & PD_EVENT_RX; + //if (incoming_packet) { + if (!tcpm_get_message(port, payload, &head)) + handle_request(port, head, payload); + //} + + if (pd[port].req_suspend_state) + set_state(port, PD_STATE_SUSPENDED); + + /* if nothing to do, verify the state of the world in 500ms */ + this_state = pd[port].task_state; + timeout = 500*MSEC_US; + switch (this_state) { + case PD_STATE_DISABLED: + /* Nothing to do */ + break; + case PD_STATE_SRC_DISCONNECTED: + timeout = 10*MSEC_US; + tcpm_get_cc(port, &cc1, &cc2); +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + /* + * Attempt TCPC auto DRP toggle if it is + * not already auto toggling and not try.src + */ + if (auto_toggle_supported && + !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && + !(pd[port].flags & PD_FLAGS_TRY_SRC) && + (cc1 == TYPEC_CC_VOLT_OPEN && + cc2 == TYPEC_CC_VOLT_OPEN)) { + set_state(port, PD_STATE_DRP_AUTO_TOGGLE); + timeout = 2*MSEC_US; + break; + } +#endif + + /* Vnc monitoring */ + if ((cc1 == TYPEC_CC_VOLT_RD || + cc2 == TYPEC_CC_VOLT_RD) || + (cc1 == TYPEC_CC_VOLT_RA && + cc2 == TYPEC_CC_VOLT_RA)) { +#ifdef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP + /* Enable VBUS */ + if (pd_set_power_supply_ready(port)) + break; +#endif + pd[port].cc_state = PD_CC_NONE; + set_state(port, + PD_STATE_SRC_DISCONNECTED_DEBOUNCE); + } +#if defined(CONFIG_USB_PD_DUAL_ROLE) + /* + * Try.SRC state is embedded here. Wait for SNK + * detect, or if timer expires, transition to + * SNK_DISCONNETED. + * + * If Try.SRC state is not active, then this block + * handles the normal DRP toggle from SRC->SNK + */ + else if ((pd[port].flags & PD_FLAGS_TRY_SRC && + get_time().val >= pd[port].try_src_marker) || + (!(pd[port].flags & PD_FLAGS_TRY_SRC) && + drp_state != PD_DRP_FORCE_SOURCE && + drp_state != PD_DRP_FREEZE && + get_time().val >= next_role_swap)) { + pd[port].power_role = PD_ROLE_SINK; + set_state(port, PD_STATE_SNK_DISCONNECTED); + tcpm_set_cc(port, TYPEC_CC_RD); + next_role_swap = get_time().val + PD_T_DRP_SNK; + pd[port].try_src_marker = get_time().val + + PD_T_TRY_WAIT; + + /* Swap states quickly */ + timeout = 2*MSEC_US; + } +#endif + break; + case PD_STATE_SRC_DISCONNECTED_DEBOUNCE: + timeout = 20*MSEC_US; + tcpm_get_cc(port, &cc1, &cc2); + + if (cc1 == TYPEC_CC_VOLT_RD && + cc2 == TYPEC_CC_VOLT_RD) { + /* Debug accessory */ + new_cc_state = PD_CC_DEBUG_ACC; + } else if (cc1 == TYPEC_CC_VOLT_RD || + cc2 == TYPEC_CC_VOLT_RD) { + /* UFP attached */ + new_cc_state = PD_CC_UFP_ATTACHED; + } else if (cc1 == TYPEC_CC_VOLT_RA && + cc2 == TYPEC_CC_VOLT_RA) { + /* Audio accessory */ + new_cc_state = PD_CC_AUDIO_ACC; + } else { + /* No UFP */ + set_state(port, PD_STATE_SRC_DISCONNECTED); + timeout = 5*MSEC_US; + break; + } + /* If in Try.SRC state, then don't need to debounce */ + if (!(pd[port].flags & PD_FLAGS_TRY_SRC)) { + /* Debounce the cc state */ + if (new_cc_state != pd[port].cc_state) { + pd[port].cc_debounce = get_time().val + + PD_T_CC_DEBOUNCE; + pd[port].cc_state = new_cc_state; + break; + } else if (get_time().val < + pd[port].cc_debounce) { + break; + } + } + + /* Debounce complete */ + /* UFP is attached */ + if (new_cc_state == PD_CC_UFP_ATTACHED || + new_cc_state == PD_CC_DEBUG_ACC) { + pd[port].polarity = (cc1 != TYPEC_CC_VOLT_RD); + tcpm_set_polarity(port, pd[port].polarity); + + /* initial data role for source is DFP */ + pd_set_data_role(port, PD_ROLE_DFP); + + if (new_cc_state == PD_CC_DEBUG_ACC) + pd[port].flags |= + PD_FLAGS_TS_DTS_PARTNER; + +#ifndef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP + /* Enable VBUS */ + if (pd_set_power_supply_ready(port)) { +#ifdef CONFIG_USBC_SS_MUX + usb_mux_set(port, TYPEC_MUX_NONE, + USB_SWITCH_DISCONNECT, + pd[port].polarity); +#endif + break; + } +#endif + /* If PD comm is enabled, enable TCPC RX */ + if (pd_comm_is_enabled(port)) + tcpm_set_rx_enable(port, 1); + +#ifdef CONFIG_USBC_VCONN + tcpm_set_vconn(port, 1); + pd[port].flags |= PD_FLAGS_VCONN_ON; +#endif + + pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | + PD_FLAGS_CHECK_DR_ROLE; + hard_reset_count = 0; + timeout = 5*MSEC_US; + set_state(port, PD_STATE_SRC_STARTUP); + } + /* + * AUDIO_ACC will remain in this state indefinitely + * until disconnect. + */ + break; + case PD_STATE_SRC_HARD_RESET_RECOVER: + /* Do not continue until hard reset recovery time */ + if (get_time().val < pd[port].src_recover) { + timeout = 50*MSEC_US; + break; + } + + /* Enable VBUS */ + timeout = 10*MSEC_US; + if (pd_set_power_supply_ready(port)) { + set_state(port, PD_STATE_SRC_DISCONNECTED); + break; + } +#ifdef CONFIG_USB_PD_TCPM_TCPCI + /* + * After transmitting hard reset, TCPM writes + * to RECEIVE_DETECT register to enable + * PD message passing. + */ + if (pd_comm_is_enabled(port)) + tcpm_set_rx_enable(port, 1); +#endif /* CONFIG_USB_PD_TCPM_TCPCI */ + + set_state(port, PD_STATE_SRC_STARTUP); + break; + case PD_STATE_SRC_STARTUP: + /* Wait for power source to enable */ + if (pd[port].last_state != pd[port].task_state) { + pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; + /* reset various counters */ + caps_count = 0; + pd[port].msg_id = 0; + snk_cap_count = 0; + set_state_timeout( + port, +#ifdef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP + /* + * delay for power supply to start up. + * subtract out debounce time if coming + * from debounce state since vbus is + * on during debounce. + */ + get_time().val + + PD_POWER_SUPPLY_TURN_ON_DELAY - + (pd[port].last_state == + PD_STATE_SRC_DISCONNECTED_DEBOUNCE + ? PD_T_CC_DEBOUNCE : 0), +#else + get_time().val + + PD_POWER_SUPPLY_TURN_ON_DELAY, +#endif + PD_STATE_SRC_DISCOVERY); + } + break; + case PD_STATE_SRC_DISCOVERY: + if (pd[port].last_state != pd[port].task_state) { + /* + * If we have had PD connection with this port + * partner, then start NoResponseTimer. + */ + if (pd[port].flags & PD_FLAGS_PREVIOUS_PD_CONN) + set_state_timeout(port, + get_time().val + + PD_T_NO_RESPONSE, + hard_reset_count < + PD_HARD_RESET_COUNT ? + PD_STATE_HARD_RESET_SEND : + PD_STATE_SRC_DISCONNECTED); + } + + /* Send source cap some minimum number of times */ + if (caps_count < PD_CAPS_COUNT) { + /* Query capabilities of the other side */ + res = send_source_cap(port); + /* packet was acked => PD capable device) */ + if (res >= 0) { + set_state(port, + PD_STATE_SRC_NEGOCIATE); + timeout = 10*MSEC_US; + hard_reset_count = 0; + caps_count = 0; + /* Port partner is PD capable */ + pd[port].flags |= + PD_FLAGS_PREVIOUS_PD_CONN; + } else { /* failed, retry later */ + timeout = PD_T_SEND_SOURCE_CAP; + caps_count++; + } + } + break; + case PD_STATE_SRC_NEGOCIATE: + /* wait for a "Request" message */ + if (pd[port].last_state != pd[port].task_state) + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + PD_STATE_HARD_RESET_SEND); + break; + case PD_STATE_SRC_ACCEPTED: + /* Accept sent, wait for enabling the new voltage */ + if (pd[port].last_state != pd[port].task_state) + set_state_timeout( + port, + get_time().val + + PD_T_SINK_TRANSITION, + PD_STATE_SRC_POWERED); + break; + case PD_STATE_SRC_POWERED: + /* Switch to the new requested voltage */ + if (pd[port].last_state != pd[port].task_state) { + pd_transition_voltage(pd[port].requested_idx); + set_state_timeout( + port, + get_time().val + + PD_POWER_SUPPLY_TURN_ON_DELAY, + PD_STATE_SRC_TRANSITION); + } + break; + case PD_STATE_SRC_TRANSITION: + /* the voltage output is good, notify the source */ + res = send_control(port, PD_CTRL_PS_RDY); + if (res >= 0) { + timeout = 10*MSEC_US; + /* it'a time to ping regularly the sink */ + set_state(port, PD_STATE_SRC_READY); + } else { + /* The sink did not ack, cut the power... */ + set_state(port, PD_STATE_SRC_DISCONNECTED); + } + break; + case PD_STATE_SRC_READY: + timeout = PD_T_SOURCE_ACTIVITY; + + /* + * Don't send any PD traffic if we woke up due to + * incoming packet or if VDO response pending to avoid + * collisions. + */ + if (incoming_packet || + (pd[port].vdm_state == VDM_STATE_BUSY)) + break; + + /* Send updated source capabilities to our partner */ + if (pd[port].flags & PD_FLAGS_UPDATE_SRC_CAPS) { + res = send_source_cap(port); + if (res >= 0) { + set_state(port, + PD_STATE_SRC_NEGOCIATE); + pd[port].flags &= + ~PD_FLAGS_UPDATE_SRC_CAPS; + } + break; + } + + /* Send get sink cap if haven't received it yet */ + if (!(pd[port].flags & PD_FLAGS_SNK_CAP_RECVD)) { + if (++snk_cap_count <= PD_SNK_CAP_RETRIES) { + /* Get sink cap to know if dual-role device */ + send_control(port, PD_CTRL_GET_SINK_CAP); + set_state(port, PD_STATE_SRC_GET_SINK_CAP); + break; + } else if (debug_level >= 2 && + snk_cap_count == PD_SNK_CAP_RETRIES+1) { + CPRINTF("ERR SNK_CAP\n"); + } + } + + /* Check power role policy, which may trigger a swap */ + if (pd[port].flags & PD_FLAGS_CHECK_PR_ROLE) { + pd_check_pr_role(port, PD_ROLE_SOURCE, + pd[port].flags); + pd[port].flags &= ~PD_FLAGS_CHECK_PR_ROLE; + break; + } + + /* Check data role policy, which may trigger a swap */ + if (pd[port].flags & PD_FLAGS_CHECK_DR_ROLE) { + pd_check_dr_role(port, pd[port].data_role, + pd[port].flags); + pd[port].flags &= ~PD_FLAGS_CHECK_DR_ROLE; + break; + } + + /* Send discovery SVDMs last */ + if (pd[port].data_role == PD_ROLE_DFP && + (pd[port].flags & PD_FLAGS_CHECK_IDENTITY)) { +#ifndef CONFIG_USB_PD_SIMPLE_DFP + pd_send_vdm(port, USB_SID_PD, + CMD_DISCOVER_IDENT, NULL, 0); +#endif + pd[port].flags &= ~PD_FLAGS_CHECK_IDENTITY; + break; + } + + if (!(pd[port].flags & PD_FLAGS_PING_ENABLED)) + break; + + /* Verify that the sink is alive */ + res = send_control(port, PD_CTRL_PING); + if (res >= 0) + break; + + /* Ping dropped. Try soft reset. */ + set_state(port, PD_STATE_SOFT_RESET); + timeout = 10 * MSEC_US; + break; + case PD_STATE_SRC_GET_SINK_CAP: + if (pd[port].last_state != pd[port].task_state) + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + PD_STATE_SRC_READY); + break; + case PD_STATE_DR_SWAP: + if (pd[port].last_state != pd[port].task_state) { + res = send_control(port, PD_CTRL_DR_SWAP); + if (res < 0) { + timeout = 10*MSEC_US; + /* + * If failed to get goodCRC, send + * soft reset, otherwise ignore + * failure. + */ + set_state(port, res == -1 ? + PD_STATE_SOFT_RESET : + READY_RETURN_STATE(port)); + break; + } + /* Wait for accept or reject */ + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + READY_RETURN_STATE(port)); + } + break; +#ifdef CONFIG_USB_PD_DUAL_ROLE + case PD_STATE_SRC_SWAP_INIT: + if (pd[port].last_state != pd[port].task_state) { + res = send_control(port, PD_CTRL_PR_SWAP); + if (res < 0) { + timeout = 10*MSEC_US; + /* + * If failed to get goodCRC, send + * soft reset, otherwise ignore + * failure. + */ + set_state(port, res == -1 ? + PD_STATE_SOFT_RESET : + PD_STATE_SRC_READY); + break; + } + /* Wait for accept or reject */ + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + PD_STATE_SRC_READY); + } + break; + case PD_STATE_SRC_SWAP_SNK_DISABLE: + /* Give time for sink to stop drawing current */ + if (pd[port].last_state != pd[port].task_state) + set_state_timeout(port, + get_time().val + + PD_T_SINK_TRANSITION, + PD_STATE_SRC_SWAP_SRC_DISABLE); + break; + case PD_STATE_SRC_SWAP_SRC_DISABLE: + /* Turn power off */ + if (pd[port].last_state != pd[port].task_state) { + pd_power_supply_reset(port); + set_state_timeout(port, + get_time().val + + PD_POWER_SUPPLY_TURN_OFF_DELAY, + PD_STATE_SRC_SWAP_STANDBY); + } + break; + case PD_STATE_SRC_SWAP_STANDBY: + /* Send PS_RDY to let sink know our power is off */ + if (pd[port].last_state != pd[port].task_state) { + /* Send PS_RDY */ + res = send_control(port, PD_CTRL_PS_RDY); + if (res < 0) { + timeout = 10*MSEC_US; + set_state(port, + PD_STATE_SRC_DISCONNECTED); + break; + } + /* Switch to Rd and swap roles to sink */ + tcpm_set_cc(port, TYPEC_CC_RD); + pd[port].power_role = PD_ROLE_SINK; + /* Wait for PS_RDY from new source */ + set_state_timeout(port, + get_time().val + + PD_T_PS_SOURCE_ON, + PD_STATE_SNK_DISCONNECTED); + } + break; + case PD_STATE_SUSPENDED: { +#ifndef CONFIG_USB_PD_TCPC + int rstatus; +#endif + CPRINTS("TCPC p%d suspended!", port); + pd[port].req_suspend_state = 0; +#ifdef CONFIG_USB_PD_TCPC + pd_rx_disable_monitoring(port); + pd_hw_release(port); + pd_power_supply_reset(port); +#else + rstatus = tcpm_release(port); + if (rstatus != 0 && rstatus != EC_ERROR_UNIMPLEMENTED) + CPRINTS("TCPC p%d release failed!", port); +#endif + /* Wait for resume */ + // getting rid of task stuff + //while (pd[port].task_state == PD_STATE_SUSPENDED) + // task_wait_event(-1); +#ifdef CONFIG_USB_PD_TCPC + pd_hw_init(port, PD_ROLE_DEFAULT(port)); + CPRINTS("TCPC p%d resumed!", port); +#else + if (rstatus != EC_ERROR_UNIMPLEMENTED && + pd_restart_tcpc(port) != 0) { + /* stay in PD_STATE_SUSPENDED */ + CPRINTS("TCPC p%d restart failed!", port); + break; + } + set_state(port, PD_DEFAULT_STATE(port)); + CPRINTS("TCPC p%d resumed!", port); +#endif + break; + } + case PD_STATE_SNK_DISCONNECTED: +#ifdef CONFIG_USB_PD_LOW_POWER + timeout = drp_state != PD_DRP_TOGGLE_ON ? SECOND_US + : 10*MSEC_US; +#else + timeout = 10*MSEC_US; +#endif + tcpm_get_cc(port, &cc1, &cc2); + +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + /* + * Attempt TCPC auto DRP toggle if it is + * not already auto toggling and not try.src + */ + if (auto_toggle_supported && + !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && + !(pd[port].flags & PD_FLAGS_TRY_SRC) && + (cc1 == TYPEC_CC_VOLT_OPEN && + cc2 == TYPEC_CC_VOLT_OPEN)) { + set_state(port, PD_STATE_DRP_AUTO_TOGGLE); + timeout = 2*MSEC_US; + break; + } +#endif + + /* Source connection monitoring */ + if (cc1 != TYPEC_CC_VOLT_OPEN || + cc2 != TYPEC_CC_VOLT_OPEN) { + pd[port].cc_state = PD_CC_NONE; + hard_reset_count = 0; + new_cc_state = PD_CC_NONE; + pd[port].cc_debounce = get_time().val + + PD_T_CC_DEBOUNCE; + set_state(port, + PD_STATE_SNK_DISCONNECTED_DEBOUNCE); + timeout = 10*MSEC_US; + break; + } + + /* + * If Try.SRC is active and failed to detect a SNK, + * then it transitions to TryWait.SNK. Need to prevent + * normal dual role toggle until tDRPTryWait timer + * expires. + */ + if (pd[port].flags & PD_FLAGS_TRY_SRC) { + if (get_time().val > pd[port].try_src_marker) + pd[port].flags &= ~PD_FLAGS_TRY_SRC; + break; + } + + /* If no source detected, check for role toggle. */ + if (drp_state == PD_DRP_TOGGLE_ON && + get_time().val >= next_role_swap) { + /* Swap roles to source */ + pd[port].power_role = PD_ROLE_SOURCE; + set_state(port, PD_STATE_SRC_DISCONNECTED); + tcpm_set_cc(port, TYPEC_CC_RP); + next_role_swap = get_time().val + PD_T_DRP_SRC; + + /* Swap states quickly */ + timeout = 2*MSEC_US; + } + break; + case PD_STATE_SNK_DISCONNECTED_DEBOUNCE: + tcpm_get_cc(port, &cc1, &cc2); + + if (cc_is_rp(cc1) && cc_is_rp(cc2)) { + /* Debug accessory */ + new_cc_state = PD_CC_DEBUG_ACC; + } else if (cc_is_rp(cc1) || cc_is_rp(cc2)) { + new_cc_state = PD_CC_DFP_ATTACHED; + } else { + /* No connection any more */ + set_state(port, PD_STATE_SNK_DISCONNECTED); + timeout = 5*MSEC_US; + break; + } + + timeout = 20*MSEC_US; + + /* Debounce the cc state */ + if (new_cc_state != pd[port].cc_state) { + pd[port].cc_debounce = get_time().val + + PD_T_CC_DEBOUNCE; + pd[port].cc_state = new_cc_state; + break; + } + /* Wait for CC debounce and VBUS present */ + if (get_time().val < pd[port].cc_debounce || + !pd_is_vbus_present(port)) + break; + + if (pd_try_src_enable && + !(pd[port].flags & PD_FLAGS_TRY_SRC)) { + /* + * If TRY_SRC is enabled, but not active, + * then force attempt to connect as source. + */ + pd[port].try_src_marker = get_time().val + + PD_T_TRY_SRC; + /* Swap roles to source */ + pd[port].power_role = PD_ROLE_SOURCE; + tcpm_set_cc(port, TYPEC_CC_RP); + timeout = 2*MSEC_US; + set_state(port, PD_STATE_SRC_DISCONNECTED); + /* Set flag after the state change */ + pd[port].flags |= PD_FLAGS_TRY_SRC; + break; + } + + /* We are attached */ + pd[port].polarity = get_snk_polarity(cc1, cc2); + tcpm_set_polarity(port, pd[port].polarity); + /* reset message ID on connection */ + pd[port].msg_id = 0; + /* initial data role for sink is UFP */ + pd_set_data_role(port, PD_ROLE_UFP); +#if defined(CONFIG_CHARGE_MANAGER) + typec_curr = get_typec_current_limit(pd[port].polarity, + cc1, cc2); + //typec_set_input_current_limit( + // port, typec_curr, TYPE_C_VOLTAGE); +#endif + /* If PD comm is enabled, enable TCPC RX */ + if (pd_comm_is_enabled(port)) + tcpm_set_rx_enable(port, 1); + + /* DFP is attached */ + if (new_cc_state == PD_CC_DFP_ATTACHED || + new_cc_state == PD_CC_DEBUG_ACC) { + pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | + PD_FLAGS_CHECK_DR_ROLE | + PD_FLAGS_CHECK_IDENTITY; + if (new_cc_state == PD_CC_DEBUG_ACC) + pd[port].flags |= + PD_FLAGS_TS_DTS_PARTNER; + send_control(port, PD_CTRL_GET_SOURCE_CAP); + set_state(port, PD_STATE_SNK_DISCOVERY); + timeout = 10*MSEC_US; + //hook_call_deferred( + // &pd_usb_billboard_deferred_data, + // PD_T_AME); + } + break; + case PD_STATE_SNK_HARD_RESET_RECOVER: + if (pd[port].last_state != pd[port].task_state) + pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; +#ifdef CONFIG_USB_PD_VBUS_DETECT_NONE + /* + * Can't measure vbus state so this is the maximum + * recovery time for the source. + */ + if (pd[port].last_state != pd[port].task_state) + set_state_timeout(port, get_time().val + + PD_T_SAFE_0V + + PD_T_SRC_RECOVER_MAX + + PD_T_SRC_TURN_ON, + PD_STATE_SNK_DISCONNECTED); +#else + /* Wait for VBUS to go low and then high*/ + if (pd[port].last_state != pd[port].task_state) { + snk_hard_reset_vbus_off = 0; + set_state_timeout(port, + get_time().val + + PD_T_SAFE_0V, + hard_reset_count < + PD_HARD_RESET_COUNT ? + PD_STATE_HARD_RESET_SEND : + PD_STATE_SNK_DISCOVERY); + } + + if (!pd_is_vbus_present(port) && + !snk_hard_reset_vbus_off) { + /* VBUS has gone low, reset timeout */ + snk_hard_reset_vbus_off = 1; + set_state_timeout(port, + get_time().val + + PD_T_SRC_RECOVER_MAX + + PD_T_SRC_TURN_ON, + PD_STATE_SNK_DISCONNECTED); + } + if (pd_is_vbus_present(port) && + snk_hard_reset_vbus_off) { +#ifdef CONFIG_USB_PD_TCPM_TCPCI + /* + * After transmitting hard reset, TCPM writes + * to RECEIVE_MESSAGE register to enable + * PD message passing. + */ + if (pd_comm_is_enabled(port)) + tcpm_set_rx_enable(port, 1); +#endif /* CONFIG_USB_PD_TCPM_TCPCI */ + + /* VBUS went high again */ + set_state(port, PD_STATE_SNK_DISCOVERY); + timeout = 10*MSEC_US; + } + + /* + * Don't need to set timeout because VBUS changing + * will trigger an interrupt and wake us up. + */ +#endif + break; + case PD_STATE_SNK_DISCOVERY: + /* Wait for source cap expired only if we are enabled */ + if ((pd[port].last_state != pd[port].task_state) + && pd_comm_is_enabled(port)) { + /* + * If VBUS has never been low, and we timeout + * waiting for source cap, try a soft reset + * first, in case we were already in a stable + * contract before this boot. + */ + if (pd[port].flags & PD_FLAGS_VBUS_NEVER_LOW) + set_state_timeout(port, + get_time().val + + PD_T_SINK_WAIT_CAP, + PD_STATE_SOFT_RESET); + /* + * If we haven't passed hard reset counter, + * start SinkWaitCapTimer, otherwise start + * NoResponseTimer. + */ + else if (hard_reset_count < PD_HARD_RESET_COUNT) + set_state_timeout(port, + get_time().val + + PD_T_SINK_WAIT_CAP, + PD_STATE_HARD_RESET_SEND); + else if (pd[port].flags & + PD_FLAGS_PREVIOUS_PD_CONN) + /* ErrorRecovery */ + set_state_timeout(port, + get_time().val + + PD_T_NO_RESPONSE, + PD_STATE_SNK_DISCONNECTED); +#if defined(CONFIG_CHARGE_MANAGER) + /* + * If we didn't come from disconnected, must + * have come from some path that did not set + * typec current limit. So, set to 0 so that + * we guarantee this is revised below. + */ + if (pd[port].last_state != + PD_STATE_SNK_DISCONNECTED_DEBOUNCE) + typec_curr = 0; +#endif + } + +#if defined(CONFIG_CHARGE_MANAGER) + timeout = PD_T_SINK_ADJ - PD_T_DEBOUNCE; + + /* Check if CC pull-up has changed */ + tcpm_get_cc(port, &cc1, &cc2); + if (typec_curr != get_typec_current_limit( + pd[port].polarity, cc1, cc2)) { + /* debounce signal by requiring two reads */ + if (typec_curr_change) { + /* set new input current limit */ + typec_curr = get_typec_current_limit( + pd[port].polarity, cc1, cc2); + //typec_set_input_current_limit( + // port, typec_curr, TYPE_C_VOLTAGE); + } else { + /* delay for debounce */ + timeout = PD_T_DEBOUNCE; + } + typec_curr_change = !typec_curr_change; + } else { + typec_curr_change = 0; + } +#endif + break; + case PD_STATE_SNK_REQUESTED: + /* Wait for ACCEPT or REJECT */ + if (pd[port].last_state != pd[port].task_state) { + hard_reset_count = 0; + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + PD_STATE_HARD_RESET_SEND); + } + break; + case PD_STATE_SNK_TRANSITION: + /* Wait for PS_RDY */ + if (pd[port].last_state != pd[port].task_state) + set_state_timeout(port, + get_time().val + + PD_T_PS_TRANSITION, + PD_STATE_HARD_RESET_SEND); + break; + case PD_STATE_SNK_READY: + timeout = 20*MSEC_US; + + /* + * Don't send any PD traffic if we woke up due to + * incoming packet or if VDO response pending to avoid + * collisions. + */ + if (incoming_packet || + (pd[port].vdm_state == VDM_STATE_BUSY)) + break; + + /* Check for new power to request */ + if (pd[port].new_power_request) { + if (pd_send_request_msg(port, 0) != EC_SUCCESS) + set_state(port, PD_STATE_SOFT_RESET); + break; + } + + /* Check power role policy, which may trigger a swap */ + if (pd[port].flags & PD_FLAGS_CHECK_PR_ROLE) { + pd_check_pr_role(port, PD_ROLE_SINK, + pd[port].flags); + pd[port].flags &= ~PD_FLAGS_CHECK_PR_ROLE; + break; + } + + /* Check data role policy, which may trigger a swap */ + if (pd[port].flags & PD_FLAGS_CHECK_DR_ROLE) { + pd_check_dr_role(port, pd[port].data_role, + pd[port].flags); + pd[port].flags &= ~PD_FLAGS_CHECK_DR_ROLE; + break; + } + + /* If DFP, send discovery SVDMs */ + if (pd[port].data_role == PD_ROLE_DFP && + (pd[port].flags & PD_FLAGS_CHECK_IDENTITY)) { + pd_send_vdm(port, USB_SID_PD, + CMD_DISCOVER_IDENT, NULL, 0); + pd[port].flags &= ~PD_FLAGS_CHECK_IDENTITY; + break; + } + + /* Sent all messages, don't need to wake very often */ + timeout = 200*MSEC_US; + break; + case PD_STATE_SNK_SWAP_INIT: + if (pd[port].last_state != pd[port].task_state) { + res = send_control(port, PD_CTRL_PR_SWAP); + if (res < 0) { + timeout = 10*MSEC_US; + /* + * If failed to get goodCRC, send + * soft reset, otherwise ignore + * failure. + */ + set_state(port, res == -1 ? + PD_STATE_SOFT_RESET : + PD_STATE_SNK_READY); + break; + } + /* Wait for accept or reject */ + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + PD_STATE_SNK_READY); + } + break; + case PD_STATE_SNK_SWAP_SNK_DISABLE: + /* Stop drawing power */ + pd_set_input_current_limit(port, 0, 0); +#ifdef CONFIG_CHARGE_MANAGER + //typec_set_input_current_limit(port, 0, 0); + //charge_manager_set_ceil(port, + // CEIL_REQUESTOR_PD, + // CHARGE_CEIL_NONE); +#endif + set_state(port, PD_STATE_SNK_SWAP_SRC_DISABLE); + timeout = 10*MSEC_US; + break; + case PD_STATE_SNK_SWAP_SRC_DISABLE: + /* Wait for PS_RDY */ + if (pd[port].last_state != pd[port].task_state) + set_state_timeout(port, + get_time().val + + PD_T_PS_SOURCE_OFF, + PD_STATE_HARD_RESET_SEND); + break; + case PD_STATE_SNK_SWAP_STANDBY: + if (pd[port].last_state != pd[port].task_state) { + /* Switch to Rp and enable power supply */ + tcpm_set_cc(port, TYPEC_CC_RP); + if (pd_set_power_supply_ready(port)) { + /* Restore Rd */ + tcpm_set_cc(port, TYPEC_CC_RD); + timeout = 10*MSEC_US; + set_state(port, + PD_STATE_SNK_DISCONNECTED); + break; + } + /* Wait for power supply to turn on */ + set_state_timeout( + port, + get_time().val + + PD_POWER_SUPPLY_TURN_ON_DELAY, + PD_STATE_SNK_SWAP_COMPLETE); + } + break; + case PD_STATE_SNK_SWAP_COMPLETE: + /* Send PS_RDY and change to source role */ + res = send_control(port, PD_CTRL_PS_RDY); + if (res < 0) { + /* Restore Rd */ + tcpm_set_cc(port, TYPEC_CC_RD); + pd_power_supply_reset(port); + timeout = 10 * MSEC_US; + set_state(port, PD_STATE_SNK_DISCONNECTED); + break; + } + + /* Don't send GET_SINK_CAP on swap */ + snk_cap_count = PD_SNK_CAP_RETRIES+1; + caps_count = 0; + pd[port].msg_id = 0; + pd[port].power_role = PD_ROLE_SOURCE; + pd_update_roles(port); + set_state(port, PD_STATE_SRC_DISCOVERY); + timeout = 10*MSEC_US; + break; +#ifdef CONFIG_USBC_VCONN_SWAP + case PD_STATE_VCONN_SWAP_SEND: + if (pd[port].last_state != pd[port].task_state) { + res = send_control(port, PD_CTRL_VCONN_SWAP); + if (res < 0) { + timeout = 10*MSEC_US; + /* + * If failed to get goodCRC, send + * soft reset, otherwise ignore + * failure. + */ + set_state(port, res == -1 ? + PD_STATE_SOFT_RESET : + READY_RETURN_STATE(port)); + break; + } + /* Wait for accept or reject */ + set_state_timeout(port, + get_time().val + + PD_T_SENDER_RESPONSE, + READY_RETURN_STATE(port)); + } + break; + case PD_STATE_VCONN_SWAP_INIT: + if (pd[port].last_state != pd[port].task_state) { + if (!(pd[port].flags & PD_FLAGS_VCONN_ON)) { + /* Turn VCONN on and wait for it */ + tcpm_set_vconn(port, 1); + set_state_timeout(port, + get_time().val + PD_VCONN_SWAP_DELAY, + PD_STATE_VCONN_SWAP_READY); + } else { + set_state_timeout(port, + get_time().val + PD_T_VCONN_SOURCE_ON, + READY_RETURN_STATE(port)); + } + } + break; + case PD_STATE_VCONN_SWAP_READY: + if (pd[port].last_state != pd[port].task_state) { + if (!(pd[port].flags & PD_FLAGS_VCONN_ON)) { + /* VCONN is now on, send PS_RDY */ + pd[port].flags |= PD_FLAGS_VCONN_ON; + res = send_control(port, + PD_CTRL_PS_RDY); + if (res == -1) { + timeout = 10*MSEC_US; + /* + * If failed to get goodCRC, + * send soft reset + */ + set_state(port, + PD_STATE_SOFT_RESET); + break; + } + set_state(port, + READY_RETURN_STATE(port)); + } else { + /* Turn VCONN off and wait for it */ + tcpm_set_vconn(port, 0); + pd[port].flags &= ~PD_FLAGS_VCONN_ON; + set_state_timeout(port, + get_time().val + PD_VCONN_SWAP_DELAY, + READY_RETURN_STATE(port)); + } + } + break; +#endif /* CONFIG_USBC_VCONN_SWAP */ +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + case PD_STATE_SOFT_RESET: + if (pd[port].last_state != pd[port].task_state) { + /* Message ID of soft reset is always 0 */ + pd[port].msg_id = 0; + res = send_control(port, PD_CTRL_SOFT_RESET); + + /* if soft reset failed, try hard reset. */ + if (res < 0) { + set_state(port, + PD_STATE_HARD_RESET_SEND); + timeout = 5*MSEC_US; + break; + } + + set_state_timeout( + port, + get_time().val + PD_T_SENDER_RESPONSE, + PD_STATE_HARD_RESET_SEND); + } + break; + case PD_STATE_HARD_RESET_SEND: + hard_reset_count++; + if (pd[port].last_state != pd[port].task_state) + hard_reset_sent = 0; +#ifdef CONFIG_CHARGE_MANAGER + if (pd[port].last_state == PD_STATE_SNK_DISCOVERY || + (pd[port].last_state == PD_STATE_SOFT_RESET && + (pd[port].flags & PD_FLAGS_VBUS_NEVER_LOW))) { + pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; + /* + * If discovery timed out, assume that we + * have a dedicated charger attached. This + * may not be a correct assumption according + * to the specification, but it generally + * works in practice and the harmful + * effects of a wrong assumption here + * are minimal. + */ + //charge_manager_update_dualrole(port, + // CAP_DEDICATED); + } +#endif + + /* try sending hard reset until it succeeds */ + if (!hard_reset_sent) { + if (pd_transmit(port, TCPC_TX_HARD_RESET, + 0, NULL) < 0) { + timeout = 10*MSEC_US; + break; + } + + /* successfully sent hard reset */ + hard_reset_sent = 1; + /* + * If we are source, delay before cutting power + * to allow sink time to get hard reset. + */ + if (pd[port].power_role == PD_ROLE_SOURCE) { + set_state_timeout(port, + get_time().val + PD_T_PS_HARD_RESET, + PD_STATE_HARD_RESET_EXECUTE); + } else { + set_state(port, + PD_STATE_HARD_RESET_EXECUTE); + timeout = 10*MSEC_US; + } + } + break; + case PD_STATE_HARD_RESET_EXECUTE: +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* + * If hard reset while in the last stages of power + * swap, then we need to restore our CC resistor. + */ + if (pd[port].last_state == PD_STATE_SNK_SWAP_STANDBY) + tcpm_set_cc(port, TYPEC_CC_RD); +#endif + + /* reset our own state machine */ + pd_execute_hard_reset(port); + timeout = 10*MSEC_US; + break; +#ifdef CONFIG_COMMON_RUNTIME + case PD_STATE_BIST_RX: + send_bist_cmd(port); + /* Delay at least enough for partner to finish BIST */ + timeout = PD_T_BIST_RECEIVE + 20*MSEC_US; + /* Set to appropriate port disconnected state */ + set_state(port, DUAL_ROLE_IF_ELSE(port, + PD_STATE_SNK_DISCONNECTED, + PD_STATE_SRC_DISCONNECTED)); + break; + case PD_STATE_BIST_TX: + pd_transmit(port, TCPC_TX_BIST_MODE_2, 0, NULL); + /* Delay at least enough to finish sending BIST */ + timeout = PD_T_BIST_TRANSMIT + 20*MSEC_US; + /* Set to appropriate port disconnected state */ + set_state(port, DUAL_ROLE_IF_ELSE(port, + PD_STATE_SNK_DISCONNECTED, + PD_STATE_SRC_DISCONNECTED)); + break; +#endif +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + case PD_STATE_DRP_AUTO_TOGGLE: + { + enum pd_states next_state; + + assert(auto_toggle_supported); + + /* Check for connection */ + tcpm_get_cc(port, &cc1, &cc2); + + /* Set to appropriate port state */ + if (cc1 == TYPEC_CC_VOLT_OPEN && + cc2 == TYPEC_CC_VOLT_OPEN) + /* nothing connected, keep toggling*/ + next_state = PD_STATE_DRP_AUTO_TOGGLE; + else if ((cc_is_rp(cc1) || cc_is_rp(cc2)) && + drp_state != PD_DRP_FORCE_SOURCE) + /* SNK allowed unless ForceSRC */ + next_state = PD_STATE_SNK_DISCONNECTED; + else if (((cc1 == TYPEC_CC_VOLT_RD || + cc2 == TYPEC_CC_VOLT_RD) || + (cc1 == TYPEC_CC_VOLT_RA && + cc2 == TYPEC_CC_VOLT_RA)) && + (drp_state != PD_DRP_TOGGLE_OFF && + drp_state != PD_DRP_FORCE_SINK)) + /* SRC allowed unless ForceSNK or Toggle Off */ + next_state = PD_STATE_SRC_DISCONNECTED; + else + /* Anything else, keep toggling */ + next_state = PD_STATE_DRP_AUTO_TOGGLE; + + if (next_state != PD_STATE_DRP_AUTO_TOGGLE) { + tcpm_set_drp_toggle(port, 0); +#ifdef CONFIG_USB_PD_TCPC_LOW_POWER + CPRINTS("TCPC p%d Exit Low Power Mode", port); +#endif + } + + if (next_state == PD_STATE_SNK_DISCONNECTED) { + tcpm_set_cc(port, TYPEC_CC_RD); + pd[port].power_role = PD_ROLE_SINK; + timeout = 2*MSEC_US; + } else if (next_state == PD_STATE_SRC_DISCONNECTED) { + tcpm_set_cc(port, TYPEC_CC_RP); + pd[port].power_role = PD_ROLE_SOURCE; + timeout = 2*MSEC_US; + } else { + tcpm_set_drp_toggle(port, 1); + pd[port].flags |= PD_FLAGS_TCPC_DRP_TOGGLE; + timeout = -1; +#ifdef CONFIG_USB_PD_TCPC_LOW_POWER + CPRINTS("TCPC p%d Low Power Mode", port); +#endif + } + set_state(port, next_state); + + break; + } +#endif + default: + break; + } + + pd[port].last_state = this_state; + + /* + * Check for state timeout, and if not check if need to adjust + * timeout value to wake up on the next state timeout. + */ + now = get_time(); + if (pd[port].timeout) { + if (now.val >= pd[port].timeout) { + set_state(port, pd[port].timeout_state); + /* On a state timeout, run next state soon */ + timeout = timeout < 10*MSEC_US ? timeout : 10*MSEC_US; + } else if (pd[port].timeout - now.val < timeout) { + timeout = pd[port].timeout - now.val; + } + } + + /* Check for disconnection if we're connected */ + if (!pd_is_connected(port)) + return; +#ifdef CONFIG_USB_PD_DUAL_ROLE + if (pd_is_power_swapping(port)) + return; +#endif + if (pd[port].power_role == PD_ROLE_SOURCE) { + /* Source: detect disconnect by monitoring CC */ + tcpm_get_cc(port, &cc1, &cc2); + if (pd[port].polarity) + cc1 = cc2; + if (cc1 == TYPEC_CC_VOLT_OPEN) { + set_state(port, PD_STATE_SRC_DISCONNECTED); + /* Debouncing */ + timeout = 10*MSEC_US; +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* + * If Try.SRC is configured, then ATTACHED_SRC + * needs to transition to TryWait.SNK. Change + * power role to SNK and start state timer. + */ + if (pd_try_src_enable) { + /* Swap roles to sink */ + pd[port].power_role = PD_ROLE_SINK; + tcpm_set_cc(port, TYPEC_CC_RD); + /* Set timer for TryWait.SNK state */ + pd[port].try_src_marker = get_time().val + + PD_T_TRY_WAIT; + /* Advance to TryWait.SNK state */ + set_state(port, + PD_STATE_SNK_DISCONNECTED); + /* Mark state as TryWait.SNK */ + pd[port].flags |= PD_FLAGS_TRY_SRC; + } +#endif + } + } +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* + * Sink disconnect if VBUS is low and we are not recovering + * a hard reset. + */ + if (pd[port].power_role == PD_ROLE_SINK && + !pd_is_vbus_present(port) && + pd[port].task_state != PD_STATE_SNK_HARD_RESET_RECOVER && + pd[port].task_state != PD_STATE_HARD_RESET_EXECUTE) { + /* Sink: detect disconnect by monitoring VBUS */ + set_state(port, PD_STATE_SNK_DISCONNECTED); + /* set timeout small to reconnect fast */ + timeout = 5*MSEC_US; + } +#endif /* CONFIG_USB_PD_DUAL_ROLE */ +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +static void dual_role_on(void) +{ + int i; + + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { +#ifdef CONFIG_CHARGE_MANAGER + //if (charge_manager_get_active_charge_port() != i) +#endif + pd[i].flags |= PD_FLAGS_CHECK_PR_ROLE | + PD_FLAGS_CHECK_DR_ROLE; + + pd[i].flags |= PD_FLAGS_CHECK_IDENTITY; + } + + pd_set_dual_role(PD_DRP_TOGGLE_ON); + CPRINTS("chipset -> S0"); +} +DECLARE_HOOK(HOOK_CHIPSET_RESUME, dual_role_on, HOOK_PRIO_DEFAULT); + +static void dual_role_off(void) +{ + pd_set_dual_role(PD_DRP_TOGGLE_OFF); + CPRINTS("chipset -> S3"); +} +DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, dual_role_off, HOOK_PRIO_DEFAULT); +DECLARE_HOOK(HOOK_CHIPSET_STARTUP, dual_role_off, HOOK_PRIO_DEFAULT); + +static void dual_role_force_sink(void) +{ + pd_set_dual_role(PD_DRP_FORCE_SINK); + CPRINTS("chipset -> S5"); +} +DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, dual_role_force_sink, HOOK_PRIO_DEFAULT); + +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + +#ifdef CONFIG_COMMON_RUNTIME + +/* + * (enable=1) request pd_task transition to the suspended state. hang + * around for a while until we observe the state change. this can + * take a while (like 300ms) on startup when pd_task is sleeping in + * tcpci_tcpm_init. + * + * (enable=0) force pd_task out of the suspended state and into the + * port's default state. + */ + +void pd_set_suspend(int port, int enable) +{ + int tries = 300; + + if (enable) { + pd[port].req_suspend_state = 1; + do { + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + if (pd[port].task_state == PD_STATE_SUSPENDED) + break; + msleep(1); + } while (--tries != 0); + if (!tries) + CPRINTS("TCPC p%d set_suspend failed!", port); + } else { + if (pd[port].task_state != PD_STATE_SUSPENDED) + CPRINTS("TCPC p%d suspend disable request " + "while not suspended!", port); + set_state(port, PD_DEFAULT_STATE(port)); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } +} + +int pd_is_port_enabled(int port) +{ + switch (pd[port].task_state) { + case PD_STATE_DISABLED: + case PD_STATE_SUSPENDED: + return 0; + default: + return 1; + } +} + +#if defined(CONFIG_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) +static int hex8tou32(char *str, uint32_t *val) +{ + char *ptr = str; + uint32_t tmp = 0; + + while (*ptr) { + char c = *ptr++; + if (c >= '0' && c <= '9') + tmp = (tmp << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + tmp = (tmp << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + tmp = (tmp << 4) + (c - 'a' + 10); + else + return EC_ERROR_INVAL; + } + if (ptr != str + 8) + return EC_ERROR_INVAL; + *val = tmp; + return EC_SUCCESS; +} + +static int remote_flashing(int argc, char **argv) +{ + int port, cnt, cmd; + uint32_t data[VDO_MAX_SIZE-1]; + char *e; + static int flash_offset[CONFIG_USB_PD_PORT_COUNT]; + + if (argc < 4 || argc > (VDO_MAX_SIZE + 4 - 1)) + return EC_ERROR_PARAM_COUNT; + + port = strtoi(argv[1], &e, 10); + if (*e || port >= CONFIG_USB_PD_PORT_COUNT) + return EC_ERROR_PARAM2; + + cnt = 0; + if (!strcasecmp(argv[3], "erase")) { + cmd = VDO_CMD_FLASH_ERASE; + flash_offset[port] = 0; + ccprintf("ERASE ..."); + } else if (!strcasecmp(argv[3], "reboot")) { + cmd = VDO_CMD_REBOOT; + ccprintf("REBOOT ..."); + } else if (!strcasecmp(argv[3], "signature")) { + cmd = VDO_CMD_ERASE_SIG; + ccprintf("ERASE SIG ..."); + } else if (!strcasecmp(argv[3], "info")) { + cmd = VDO_CMD_READ_INFO; + ccprintf("INFO..."); + } else if (!strcasecmp(argv[3], "version")) { + cmd = VDO_CMD_VERSION; + ccprintf("VERSION..."); + } else { + int i; + argc -= 3; + for (i = 0; i < argc; i++) + if (hex8tou32(argv[i+3], data + i)) + return EC_ERROR_INVAL; + cmd = VDO_CMD_FLASH_WRITE; + cnt = argc; + ccprintf("WRITE %d @%04x ...", argc * 4, + flash_offset[port]); + flash_offset[port] += argc * 4; + } + + pd_send_vdm(port, USB_VID_GOOGLE, cmd, data, cnt); + + /* Wait until VDM is done */ + while (pd[port].vdm_state > 0) + task_wait_event(100*MSEC_US); + + ccprintf("DONE %d\n", pd[port].vdm_state); + return EC_SUCCESS; +} +#endif /* defined(CONFIG_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) */ + +int pd_fetch_acc_log_entry(int port) +{ + timestamp_t timeout; + + /* Cannot send a VDM now, the host should retry */ + if (pd[port].vdm_state > 0) + return pd[port].vdm_state == VDM_STATE_BUSY ? + EC_RES_BUSY : EC_RES_UNAVAILABLE; + + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_GET_LOG, NULL, 0); + timeout.val = get_time().val + 75*MSEC_US; + + /* Wait until VDM is done */ + while ((pd[port].vdm_state > 0) && + (get_time().val < timeout.val)) + task_wait_event(10*MSEC_US); + + if (pd[port].vdm_state > 0) + return EC_RES_TIMEOUT; + else if (pd[port].vdm_state < 0) + return EC_RES_ERROR; + + return EC_RES_SUCCESS; +} + +#ifdef CONFIG_USB_PD_DUAL_ROLE +void pd_request_source_voltage(int port, int mv) +{ + pd_set_max_voltage(mv); + + if (pd[port].task_state == PD_STATE_SNK_READY || + pd[port].task_state == PD_STATE_SNK_TRANSITION) { + /* Set flag to send new power request in pd_task */ + pd[port].new_power_request = 1; + } else { + pd[port].power_role = PD_ROLE_SINK; + tcpm_set_cc(port, TYPEC_CC_RD); + set_state(port, PD_STATE_SNK_DISCONNECTED); + } + + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); +} + +void pd_set_external_voltage_limit(int port, int mv) +{ + pd_set_max_voltage(mv); + + if (pd[port].task_state == PD_STATE_SNK_READY || + pd[port].task_state == PD_STATE_SNK_TRANSITION) { + /* Set flag to send new power request in pd_task */ + pd[port].new_power_request = 1; + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } +} + +void pd_update_contract(int port) +{ + if ((pd[port].task_state >= PD_STATE_SRC_NEGOCIATE) && + (pd[port].task_state <= PD_STATE_SRC_GET_SINK_CAP)) { + pd[port].flags |= PD_FLAGS_UPDATE_SRC_CAPS; + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } +} + +#endif /* CONFIG_USB_PD_DUAL_ROLE */ + +static int command_pd(int argc, char **argv) +{ + int port; + char *e; + + if (argc < 2) + return EC_ERROR_PARAM_COUNT; + +#if defined(CONFIG_CMD_PD) && defined(CONFIG_USB_PD_DUAL_ROLE) + /* command: pd */ + if (!strcasecmp(argv[1], "dualrole")) { + if (argc < 3) { + ccprintf("dual-role toggling: "); + switch (drp_state) { + case PD_DRP_TOGGLE_ON: + ccprintf("on\n"); + break; + case PD_DRP_TOGGLE_OFF: + ccprintf("off\n"); + break; + case PD_DRP_FREEZE: + ccprintf("freeze\n"); + break; + case PD_DRP_FORCE_SINK: + ccprintf("force sink\n"); + break; + case PD_DRP_FORCE_SOURCE: + ccprintf("force source\n"); + break; + } + } else { + if (!strcasecmp(argv[2], "on")) + pd_set_dual_role(PD_DRP_TOGGLE_ON); + else if (!strcasecmp(argv[2], "off")) + pd_set_dual_role(PD_DRP_TOGGLE_OFF); + else if (!strcasecmp(argv[2], "freeze")) + pd_set_dual_role(PD_DRP_FREEZE); + else if (!strcasecmp(argv[2], "sink")) + pd_set_dual_role(PD_DRP_FORCE_SINK); + else if (!strcasecmp(argv[2], "source")) + pd_set_dual_role(PD_DRP_FORCE_SOURCE); + else + return EC_ERROR_PARAM3; + } + return EC_SUCCESS; + } else +#endif + if (!strcasecmp(argv[1], "dump")) { +#ifndef CONFIG_USB_PD_DEBUG_LEVEL + int level; + + if (argc >= 3) { + level = strtoi(argv[2], &e, 10); + if (*e) + return EC_ERROR_PARAM2; + debug_level = level; + } else +#endif + ccprintf("dump level: %d\n", debug_level); + + return EC_SUCCESS; + } +#ifdef CONFIG_CMD_PD +#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO + else if (!strncasecmp(argv[1], "rwhashtable", 3)) { + int i; + struct ec_params_usb_pd_rw_hash_entry *p; + for (i = 0; i < RW_HASH_ENTRIES; i++) { + p = &rw_hash_table[i]; + pd_dev_dump_info(p->dev_id, p->dev_rw_hash); + } + return EC_SUCCESS; + } +#endif /* CONFIG_CMD_PD_DEV_DUMP_INFO */ +#ifdef CONFIG_USB_PD_TRY_SRC + else if (!strncasecmp(argv[1], "trysrc", 6)) { + int enable; + + if (argc < 2) { + return EC_ERROR_PARAM_COUNT; + } else if (argc >= 3) { + enable = strtoi(argv[2], &e, 10); + if (*e) + return EC_ERROR_PARAM3; + pd_try_src_enable = enable ? 1 : 0; + } + + ccprintf("Try.SRC %s\n", pd_try_src_enable ? "on" : "off"); + return EC_SUCCESS; + } +#endif +#endif + /* command: pd [args] */ + port = strtoi(argv[1], &e, 10); + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + if (*e || port >= CONFIG_USB_PD_PORT_COUNT) + return EC_ERROR_PARAM2; +#if defined(CONFIG_CMD_PD) && defined(CONFIG_USB_PD_DUAL_ROLE) + + if (!strcasecmp(argv[2], "tx")) { + set_state(port, PD_STATE_SNK_DISCOVERY); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } else if (!strcasecmp(argv[2], "bist_rx")) { + set_state(port, PD_STATE_BIST_RX); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } else if (!strcasecmp(argv[2], "bist_tx")) { + if (*e) + return EC_ERROR_PARAM3; + set_state(port, PD_STATE_BIST_TX); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } else if (!strcasecmp(argv[2], "charger")) { + pd[port].power_role = PD_ROLE_SOURCE; + tcpm_set_cc(port, TYPEC_CC_RP); + set_state(port, PD_STATE_SRC_DISCONNECTED); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } else if (!strncasecmp(argv[2], "dev", 3)) { + int max_volt; + if (argc >= 4) + max_volt = strtoi(argv[3], &e, 10) * 1000; + else + max_volt = pd_get_max_voltage(); + + pd_request_source_voltage(port, max_volt); + ccprintf("max req: %dmV\n", max_volt); + } else if (!strcasecmp(argv[2], "disable")) { + pd_comm_enable(port, 0); + ccprintf("Port C%d disable\n", port); + return EC_SUCCESS; + } else if (!strcasecmp(argv[2], "enable")) { + pd_comm_enable(port, 1); + ccprintf("Port C%d enabled\n", port); + return EC_SUCCESS; + } else if (!strncasecmp(argv[2], "hard", 4)) { + set_state(port, PD_STATE_HARD_RESET_SEND); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } else if (!strncasecmp(argv[2], "info", 4)) { + int i; + ccprintf("Hash "); + for (i = 0; i < PD_RW_HASH_SIZE / 4; i++) + ccprintf("%08x ", pd[port].dev_rw_hash[i]); + ccprintf("\nImage %s\n", system_image_copy_t_to_string( + pd[port].current_image)); + } else if (!strncasecmp(argv[2], "soft", 4)) { + set_state(port, PD_STATE_SOFT_RESET); + // getting rid of task stuff + //task_wake(PD_PORT_TO_TASK_ID(port)); + } else if (!strncasecmp(argv[2], "swap", 4)) { + if (argc < 4) + return EC_ERROR_PARAM_COUNT; + + if (!strncasecmp(argv[3], "power", 5)) + pd_request_power_swap(port); + else if (!strncasecmp(argv[3], "data", 4)) + pd_request_data_swap(port); +#ifdef CONFIG_USBC_VCONN_SWAP + else if (!strncasecmp(argv[3], "vconn", 5)) + pd_request_vconn_swap(port); +#endif + else + return EC_ERROR_PARAM3; + } else if (!strncasecmp(argv[2], "ping", 4)) { + int enable; + + if (argc > 3) { + enable = strtoi(argv[3], &e, 10); + if (*e) + return EC_ERROR_PARAM3; + pd_ping_enable(port, enable); + } + + ccprintf("Pings %s\n", + (pd[port].flags & PD_FLAGS_PING_ENABLED) ? + "on" : "off"); + } else if (!strncasecmp(argv[2], "vdm", 3)) { + if (argc < 4) + return EC_ERROR_PARAM_COUNT; + + if (!strncasecmp(argv[3], "ping", 4)) { + uint32_t enable; + if (argc < 5) + return EC_ERROR_PARAM_COUNT; + enable = strtoi(argv[4], &e, 10); + if (*e) + return EC_ERROR_PARAM4; + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_PING_ENABLE, + &enable, 1); + } else if (!strncasecmp(argv[3], "curr", 4)) { + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_CURRENT, + NULL, 0); + } else if (!strncasecmp(argv[3], "vers", 4)) { + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_VERSION, + NULL, 0); + } else { + return EC_ERROR_PARAM_COUNT; + } +#if defined(CONFIG_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) + } else if (!strncasecmp(argv[2], "flash", 4)) { + return remote_flashing(argc, argv); +#endif + } else +#endif + if (!strncasecmp(argv[2], "state", 5)) { + ccprintf("Port C%d CC%d, %s - Role: %s-%s%s " + "State: %s, Flags: 0x%04x\n", + port, pd[port].polarity + 1, + pd_comm_is_enabled(port) ? "Ena" : "Dis", + pd[port].power_role == PD_ROLE_SOURCE ? "SRC" : "SNK", + pd[port].data_role == PD_ROLE_DFP ? "DFP" : "UFP", + (pd[port].flags & PD_FLAGS_VCONN_ON) ? "-VC" : "", + pd_state_names[pd[port].task_state], + pd[port].flags); + } else { + return EC_ERROR_PARAM1; + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(pd, command_pd, + "dualrole|dump|rwhashtable" + "|trysrc [0|1]\n\t " + "[tx|bist_rx|bist_tx|charger|clock|dev|disable|enable" + "|soft|hash|hard|ping|state|swap [power|data]|" + "vdm [ping | curr | vers]]", + "USB PD"); + +#ifdef HAS_TASK_HOSTCMD + +static int hc_pd_ports(struct host_cmd_handler_args *args) +{ + struct ec_response_usb_pd_ports *r = args->response; + r->num_ports = CONFIG_USB_PD_PORT_COUNT; + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_PORTS, + hc_pd_ports, + EC_VER_MASK(0)); + +static const enum pd_dual_role_states dual_role_map[USB_PD_CTRL_ROLE_COUNT] = { + [USB_PD_CTRL_ROLE_TOGGLE_ON] = PD_DRP_TOGGLE_ON, + [USB_PD_CTRL_ROLE_TOGGLE_OFF] = PD_DRP_TOGGLE_OFF, + [USB_PD_CTRL_ROLE_FORCE_SINK] = PD_DRP_FORCE_SINK, + [USB_PD_CTRL_ROLE_FORCE_SOURCE] = PD_DRP_FORCE_SOURCE, + [USB_PD_CTRL_ROLE_FREEZE] = PD_DRP_FREEZE, +}; + +#ifdef CONFIG_USBC_SS_MUX +static const enum typec_mux typec_mux_map[USB_PD_CTRL_MUX_COUNT] = { + [USB_PD_CTRL_MUX_NONE] = TYPEC_MUX_NONE, + [USB_PD_CTRL_MUX_USB] = TYPEC_MUX_USB, + [USB_PD_CTRL_MUX_AUTO] = TYPEC_MUX_DP, + [USB_PD_CTRL_MUX_DP] = TYPEC_MUX_DP, + [USB_PD_CTRL_MUX_DOCK] = TYPEC_MUX_DOCK, +}; +#endif + +static int hc_usb_pd_control(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_pd_control *p = args->params; + struct ec_response_usb_pd_control_v1 *r_v1 = args->response; + struct ec_response_usb_pd_control *r = args->response; + + if (p->port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + if (p->role >= USB_PD_CTRL_ROLE_COUNT || + p->mux >= USB_PD_CTRL_MUX_COUNT) + return EC_RES_INVALID_PARAM; + + if (p->role != USB_PD_CTRL_ROLE_NO_CHANGE) + pd_set_dual_role(dual_role_map[p->role]); + +#ifdef CONFIG_USBC_SS_MUX + if (p->mux != USB_PD_CTRL_MUX_NO_CHANGE) + usb_mux_set(p->port, typec_mux_map[p->mux], + typec_mux_map[p->mux] == TYPEC_MUX_NONE ? + USB_SWITCH_DISCONNECT : + USB_SWITCH_CONNECT, + pd_get_polarity(p->port)); +#endif /* CONFIG_USBC_SS_MUX */ + + if (p->swap == USB_PD_CTRL_SWAP_DATA) + pd_request_data_swap(p->port); +#ifdef CONFIG_USB_PD_DUAL_ROLE + else if (p->swap == USB_PD_CTRL_SWAP_POWER) + pd_request_power_swap(p->port); +#ifdef CONFIG_USBC_VCONN_SWAP + else if (p->swap == USB_PD_CTRL_SWAP_VCONN) + pd_request_vconn_swap(p->port); +#endif +#endif + + if (args->version == 0) { + r->enabled = pd_comm_is_enabled(p->port); + r->role = pd[p->port].power_role; + r->polarity = pd[p->port].polarity; + r->state = pd[p->port].task_state; + args->response_size = sizeof(*r); + } else { + r_v1->enabled = + (pd_comm_is_enabled(p->port) ? + PD_CTRL_RESP_ENABLED_COMMS : 0) | + (pd_is_connected(p->port) ? + PD_CTRL_RESP_ENABLED_CONNECTED : 0) | + ((pd[p->port].flags & PD_FLAGS_PREVIOUS_PD_CONN) ? + PD_CTRL_RESP_ENABLED_PD_CAPABLE : 0); + r_v1->role = + (pd[p->port].power_role ? PD_CTRL_RESP_ROLE_POWER : 0) | + (pd[p->port].data_role ? PD_CTRL_RESP_ROLE_DATA : 0) | + ((pd[p->port].flags & PD_FLAGS_VCONN_ON) ? + PD_CTRL_RESP_ROLE_VCONN : 0) | + ((pd[p->port].flags & PD_FLAGS_PARTNER_DR_POWER) ? + PD_CTRL_RESP_ROLE_DR_POWER : 0) | + ((pd[p->port].flags & PD_FLAGS_PARTNER_DR_DATA) ? + PD_CTRL_RESP_ROLE_DR_DATA : 0) | + ((pd[p->port].flags & PD_FLAGS_PARTNER_USB_COMM) ? + PD_CTRL_RESP_ROLE_USB_COMM : 0) | + ((pd[p->port].flags & PD_FLAGS_PARTNER_EXTPOWER) ? + PD_CTRL_RESP_ROLE_EXT_POWERED : 0); + r_v1->polarity = pd[p->port].polarity; + strzcpy(r_v1->state, + pd_state_names[pd[p->port].task_state], + sizeof(r_v1->state)); + args->response_size = sizeof(*r_v1); + } + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_CONTROL, + hc_usb_pd_control, + EC_VER_MASK(0) | EC_VER_MASK(1)); + +static int hc_remote_flash(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_pd_fw_update *p = args->params; + int port = p->port; + const uint32_t *data = &(p->size) + 1; + int i, size, rv = EC_RES_SUCCESS; + timestamp_t timeout; + + if (port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + if (p->size + sizeof(*p) > args->params_size) + return EC_RES_INVALID_PARAM; + +#if defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \ + defined(CONFIG_BATTERY_PRESENT_GPIO) + /* + * Do not allow PD firmware update if no battery and this port + * is sinking power, because we will lose power. + */ + if (battery_is_present() != BP_YES && + charge_manager_get_active_charge_port() == port) + return EC_RES_UNAVAILABLE; +#endif + + /* + * Busy still with a VDM that host likely generated. 1 deep VDM queue + * so just return for retry logic on host side to deal with. + */ + if (pd[port].vdm_state > 0) + return EC_RES_BUSY; + + switch (p->cmd) { + case USB_PD_FW_REBOOT: + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_REBOOT, NULL, 0); + + /* + * Return immediately to free pending i2c bus. Host needs to + * manage this delay. + */ + return EC_RES_SUCCESS; + + case USB_PD_FW_FLASH_ERASE: + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_ERASE, NULL, 0); + + /* + * Return immediately. Host needs to manage delays here which + * can be as long as 1.2 seconds on 64KB RW flash. + */ + return EC_RES_SUCCESS; + + case USB_PD_FW_ERASE_SIG: + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_ERASE_SIG, NULL, 0); + timeout.val = get_time().val + 500*MSEC_US; + break; + + case USB_PD_FW_FLASH_WRITE: + /* Data size must be a multiple of 4 */ + if (!p->size || p->size % 4) + return EC_RES_INVALID_PARAM; + + size = p->size / 4; + for (i = 0; i < size; i += VDO_MAX_SIZE - 1) { + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_WRITE, + data + i, MIN(size - i, VDO_MAX_SIZE - 1)); + timeout.val = get_time().val + 500*MSEC_US; + + /* Wait until VDM is done */ + while ((pd[port].vdm_state > 0) && + (get_time().val < timeout.val)) + task_wait_event(10*MSEC_US); + + if (pd[port].vdm_state > 0) + return EC_RES_TIMEOUT; + } + return EC_RES_SUCCESS; + + default: + return EC_RES_INVALID_PARAM; + break; + } + + /* Wait until VDM is done or timeout */ + while ((pd[port].vdm_state > 0) && (get_time().val < timeout.val)) + task_wait_event(50*MSEC_US); + + if ((pd[port].vdm_state > 0) || + (pd[port].vdm_state == VDM_STATE_ERR_TMOUT)) + rv = EC_RES_TIMEOUT; + else if (pd[port].vdm_state < 0) + rv = EC_RES_ERROR; + + return rv; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_FW_UPDATE, + hc_remote_flash, + EC_VER_MASK(0)); + +static int hc_remote_rw_hash_entry(struct host_cmd_handler_args *args) +{ + int i, idx = 0, found = 0; + const struct ec_params_usb_pd_rw_hash_entry *p = args->params; + static int rw_hash_next_idx; + + if (!p->dev_id) + return EC_RES_INVALID_PARAM; + + for (i = 0; i < RW_HASH_ENTRIES; i++) { + if (p->dev_id == rw_hash_table[i].dev_id) { + idx = i; + found = 1; + break; + } + } + if (!found) { + idx = rw_hash_next_idx; + rw_hash_next_idx = rw_hash_next_idx + 1; + if (rw_hash_next_idx == RW_HASH_ENTRIES) + rw_hash_next_idx = 0; + } + memcpy(&rw_hash_table[idx], p, sizeof(*p)); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_RW_HASH_ENTRY, + hc_remote_rw_hash_entry, + EC_VER_MASK(0)); + +static int hc_remote_pd_dev_info(struct host_cmd_handler_args *args) +{ + const uint8_t *port = args->params; + struct ec_params_usb_pd_rw_hash_entry *r = args->response; + + if (*port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + r->dev_id = pd[*port].dev_id; + + if (r->dev_id) { + memcpy(r->dev_rw_hash, pd[*port].dev_rw_hash, + PD_RW_HASH_SIZE); + } + + r->current_image = pd[*port].current_image; + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} + +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DEV_INFO, + hc_remote_pd_dev_info, + EC_VER_MASK(0)); + +#ifndef CONFIG_USB_PD_TCPC +#ifdef CONFIG_EC_CMD_PD_CHIP_INFO +static int hc_remote_pd_chip_info(struct host_cmd_handler_args *args) +{ + const struct ec_params_pd_chip_info *p = args->params; + struct ec_response_pd_chip_info *r = args->response, *info; + + if (p->port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + if (tcpm_get_chip_info(p->port, p->renew, &info)) + return EC_RES_ERROR; + + memcpy(r, info, sizeof(*r)); + args->response_size = sizeof(*r); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_PD_CHIP_INFO, + hc_remote_pd_chip_info, + EC_VER_MASK(0)); +#endif +#endif + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +static int hc_remote_pd_set_amode(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_pd_set_mode_request *p = args->params; + + if ((p->port >= CONFIG_USB_PD_PORT_COUNT) || (!p->svid) || (!p->opos)) + return EC_RES_INVALID_PARAM; + + switch (p->cmd) { + case PD_EXIT_MODE: + if (pd_dfp_exit_mode(p->port, p->svid, p->opos)) + pd_send_vdm(p->port, p->svid, + CMD_EXIT_MODE | VDO_OPOS(p->opos), NULL, 0); + else { + CPRINTF("Failed exit mode\n"); + return EC_RES_ERROR; + } + break; + case PD_ENTER_MODE: + if (pd_dfp_enter_mode(p->port, p->svid, p->opos)) + pd_send_vdm(p->port, p->svid, CMD_ENTER_MODE | + VDO_OPOS(p->opos), NULL, 0); + break; + default: + return EC_RES_INVALID_PARAM; + } + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_SET_AMODE, + hc_remote_pd_set_amode, + EC_VER_MASK(0)); +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ + +#endif /* HAS_TASK_HOSTCMD */ + +#ifdef CONFIG_CMD_PD_CONTROL + +static int pd_control(struct host_cmd_handler_args *args) +{ + static int pd_control_disabled[CONFIG_USB_PD_PORT_COUNT]; + const struct ec_params_pd_control *cmd = args->params; + int enable = 0; + + if (cmd->chip >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + /* Always allow disable command */ + if (cmd->subcmd == PD_CONTROL_DISABLE) { + pd_control_disabled[cmd->chip] = 1; + return EC_RES_SUCCESS; + } + + if (pd_control_disabled[cmd->chip]) + return EC_RES_ACCESS_DENIED; + + if (cmd->subcmd == PD_SUSPEND) { + enable = 0; + } else if (cmd->subcmd == PD_RESUME) { + enable = 1; + } else if (cmd->subcmd == PD_RESET) { +#ifdef HAS_TASK_PDCMD + board_reset_pd_mcu(); +#else + return EC_RES_INVALID_COMMAND; +#endif + } else if (cmd->subcmd == PD_CHIP_ON && board_set_tcpc_power_mode) { + board_set_tcpc_power_mode(cmd->chip, 1); + return EC_RES_SUCCESS; + } else { + return EC_RES_INVALID_COMMAND; + } + + pd_comm_enable(cmd->chip, enable); + pd_set_suspend(cmd->chip, !enable); + + return EC_RES_SUCCESS; +} + +DECLARE_HOST_COMMAND(EC_CMD_PD_CONTROL, pd_control, EC_VER_MASK(0)); +#endif /* CONFIG_CMD_PD_CONTROL */ + +#endif /* CONFIG_COMMON_RUNTIME */ + +#if defined(CONFIG_USB_PD_ALT_MODE) && !defined(CONFIG_USB_PD_ALT_MODE_DFP) +void pd_send_hpd(int port, enum hpd_event hpd) +{ + uint32_t data[1]; + int opos = pd_alt_mode(port, USB_SID_DISPLAYPORT); + if (!opos) + return; + + data[0] = VDO_DP_STATUS((hpd == hpd_irq), /* IRQ_HPD */ + (hpd != hpd_low), /* HPD_HI|LOW */ + 0, /* request exit DP */ + 0, /* request exit USB */ + 0, /* MF pref */ + 1, /* enabled */ + 0, /* power low */ + 0x2); + pd_send_vdm(port, USB_SID_DISPLAYPORT, + VDO_OPOS(opos) | CMD_ATTENTION, data, 1); + /* Wait until VDM is done. */ + //while (pd[0].vdm_state > 0) + // task_wait_event(USB_PD_RX_TMOUT_US * (PD_RETRY_COUNT + 1)); +} + +bool pd_is_vdm_busy(int port) +{ + return ((pd[port].vdm_state != 0) && (pd[port].vdm_state != -1)); +} +#endif diff --git a/fw/usb_pd_tcpm.h b/fw/usb_pd_tcpm.h new file mode 100644 index 0000000..2a3f5ad --- /dev/null +++ b/fw/usb_pd_tcpm.h @@ -0,0 +1,351 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* USB Power delivery port management */ + +#ifndef __CROS_EC_USB_PD_TCPM_H +#define __CROS_EC_USB_PD_TCPM_H + +#ifdef __cplusplus +extern "C" { +#endif + + +/* List of common error codes that can be returned */ +enum ec_error_list { + /* Success - no error */ + EC_SUCCESS = 0, + /* Unknown error */ + EC_ERROR_UNKNOWN = 1, + /* Function not implemented yet */ + EC_ERROR_UNIMPLEMENTED = 2, + /* Overflow error; too much input provided. */ + EC_ERROR_OVERFLOW = 3, + /* Timeout */ + EC_ERROR_TIMEOUT = 4, + /* Invalid argument */ + EC_ERROR_INVAL = 5, + /* Already in use, or not ready yet */ + EC_ERROR_BUSY = 6, + /* Access denied */ + EC_ERROR_ACCESS_DENIED = 7, + /* Failed because component does not have power */ + EC_ERROR_NOT_POWERED = 8, + /* Failed because component is not calibrated */ + EC_ERROR_NOT_CALIBRATED = 9, + /* Failed because CRC error */ + EC_ERROR_CRC = 10, + /* Invalid console command param (PARAMn means parameter n is bad) */ + EC_ERROR_PARAM1 = 11, + EC_ERROR_PARAM2 = 12, + EC_ERROR_PARAM3 = 13, + EC_ERROR_PARAM4 = 14, + EC_ERROR_PARAM5 = 15, + EC_ERROR_PARAM6 = 16, + EC_ERROR_PARAM7 = 17, + EC_ERROR_PARAM8 = 18, + EC_ERROR_PARAM9 = 19, + /* Wrong number of params */ + EC_ERROR_PARAM_COUNT = 20, + /* Interrupt event not handled */ + EC_ERROR_NOT_HANDLED = 21, + /* Data has not changed */ + EC_ERROR_UNCHANGED = 22, + /* Memory allocation */ + EC_ERROR_MEMORY_ALLOCATION = 23, + + /* Verified boot errors */ + EC_ERROR_VBOOT_SIGNATURE = 0x1000, /* 4096 */ + EC_ERROR_VBOOT_SIG_MAGIC = 0x1001, + EC_ERROR_VBOOT_SIG_SIZE = 0x1002, + EC_ERROR_VBOOT_SIG_ALGORITHM = 0x1003, + EC_ERROR_VBOOT_HASH_ALGORITHM = 0x1004, + EC_ERROR_VBOOT_SIG_OFFSET = 0x1005, + EC_ERROR_VBOOT_DATA_SIZE = 0x1006, + + /* Verified boot key errors */ + EC_ERROR_VBOOT_KEY = 0x1100, + EC_ERROR_VBOOT_KEY_MAGIC = 0x1101, + EC_ERROR_VBOOT_KEY_SIZE = 0x1102, + + /* Verified boot data errors */ + EC_ERROR_VBOOT_DATA = 0x1200, + EC_ERROR_VBOOT_DATA_VERIFY = 0x1201, + + /* Module-internal error codes may use this range. */ + EC_ERROR_INTERNAL_FIRST = 0x10000, + EC_ERROR_INTERNAL_LAST = 0x1FFFF +}; + +/* Flags for i2c_xfer() */ +#define I2C_XFER_START (1 << 0) /* Start smbus session from idle state */ +#define I2C_XFER_STOP (1 << 1) /* Terminate smbus session with stop bit */ +#define I2C_XFER_SINGLE (I2C_XFER_START | I2C_XFER_STOP) /* One transaction */ + +/* Default retry count for transmitting */ +#define PD_RETRY_COUNT 3 + +/* Time to wait for TCPC to complete transmit */ +#define PD_T_TCPC_TX_TIMEOUT (100*MSEC_US) + +enum tcpc_cc_voltage_status { + TYPEC_CC_VOLT_OPEN = 0, + TYPEC_CC_VOLT_RA = 1, + TYPEC_CC_VOLT_RD = 2, + TYPEC_CC_VOLT_SNK_DEF = 5, + TYPEC_CC_VOLT_SNK_1_5 = 6, + TYPEC_CC_VOLT_SNK_3_0 = 7, +}; + +enum tcpc_cc_pull { + TYPEC_CC_RA = 0, + TYPEC_CC_RP = 1, + TYPEC_CC_RD = 2, + TYPEC_CC_OPEN = 3, +}; + +enum tcpc_rp_value { + TYPEC_RP_USB = 0, + TYPEC_RP_1A5 = 1, + TYPEC_RP_3A0 = 2, + TYPEC_RP_RESERVED = 3, +}; + +enum tcpm_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 tcpc_transmit_complete { + TCPC_TX_COMPLETE_SUCCESS = 0, + TCPC_TX_COMPLETE_DISCARDED = 1, + TCPC_TX_COMPLETE_FAILED = 2, +}; + +struct tcpm_drv { + /** + * Initialize TCPM driver and wait for TCPC readiness. + * + * @param port Type-C port number + * + * @return EC_SUCCESS or error + */ + int (*init)(int port); + + /** + * Release the TCPM hardware and disconnect the driver. + * Only .init() can be called after .release(). + * + * @param port Type-C port number + * + * @return EC_SUCCESS or error + */ + int (*release)(int port); + + /** + * Read the CC line status. + * + * @param port Type-C port number + * @param cc1 pointer to CC status for CC1 + * @param cc2 pointer to CC status for CC2 + * + * @return EC_SUCCESS or error + */ + int (*get_cc)(int port, int *cc1, int *cc2); + + /** + * Read VBUS + * + * @param port Type-C port number + * + * @return 0 => VBUS not detected, 1 => VBUS detected + */ + int (*get_vbus_level)(int port); + + /** + * Set the value of the CC pull-up used when we are a source. + * + * @param port Type-C port number + * @param rp One of enum tcpc_rp_value + * + * @return EC_SUCCESS or error + */ + int (*select_rp_value)(int port, int rp); + + /** + * Set the CC pull resistor. This sets our role as either source or sink. + * + * @param port Type-C port number + * @param pull One of enum tcpc_cc_pull + * + * @return EC_SUCCESS or error + */ + int (*set_cc)(int port, int pull); + + /** + * Set polarity + * + * @param port Type-C port number + * @param polarity 0=> transmit on CC1, 1=> transmit on CC2 + * + * @return EC_SUCCESS or error + */ + int (*set_polarity)(int port, int polarity); + + /** + * Set Vconn. + * + * @param port Type-C port number + * @param polarity Polarity of the CC line to read + * + * @return EC_SUCCESS or error + */ + int (*set_vconn)(int port, int enable); + + /** + * Set PD message header to use for goodCRC + * + * @param port Type-C port number + * @param power_role Power role to use in header + * @param data_role Data role to use in header + * + * @return EC_SUCCESS or error + */ + int (*set_msg_header)(int port, int power_role, int data_role); + + /** + * Set RX enable flag + * + * @param port Type-C port number + * @enable true for enable, false for disable + * + * @return EC_SUCCESS or error + */ + int (*set_rx_enable)(int port, int enable); + + /** + * Read last received PD message. + * + * @param port Type-C port number + * @param payload Pointer to location to copy payload of message + * @param header of message + * + * @return EC_SUCCESS or error + */ + int (*get_message)(int port, uint32_t *payload, int *head); + + /** + * Transmit PD message + * + * @param port Type-C port number + * @param type Transmit type + * @param header Packet header + * @param cnt Number of bytes in payload + * @param data Payload + * + * @return EC_SUCCESS or error + */ + int (*transmit)(int port, enum tcpm_transmit_type type, uint16_t header, + const uint32_t *data); + + /** + * TCPC is asserting alert + * + * @param port Type-C port number + */ + void (*tcpc_alert)(int port); + + /** + * Discharge PD VBUS on src/sink disconnect & power role swap + * + * @param port Type-C port number + * @param enable Discharge enable or disable + */ + void (*tcpc_discharge_vbus)(int port, int enable); + +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + /** + * Enable TCPC auto DRP toggling. + * + * @param port Type-C port number + * @param enable 1: Enable 0: Disable + * + * @return EC_SUCCESS or error + */ + int (*drp_toggle)(int port, int enable); +#endif +}; + +enum tcpc_alert_polarity { + TCPC_ALERT_ACTIVE_LOW, + TCPC_ALERT_ACTIVE_HIGH, +}; + +struct tcpc_config_t { + int i2c_host_port; + int i2c_slave_addr; + const struct tcpm_drv *drv; + enum tcpc_alert_polarity pol; +}; + +/** + * Returns the PD_STATUS_TCPC_ALERT_* mask corresponding to the TCPC ports + * that are currently asserting ALERT. + * + * @return PD_STATUS_TCPC_ALERT_* mask. + */ +uint16_t tcpc_get_alert_status(void); + +/** + * Optional, set the TCPC power mode. + * + * @param port Type-C port number + * @param mode 0: off/sleep, 1: on/awake + */ +void board_set_tcpc_power_mode(int port, int mode) __attribute__((weak)); + +/** + * Initialize TCPC. + * + * @param port Type-C port number + */ +void tcpc_init(int port); + +/** + * TCPC is asserting alert + * + * @param port Type-C port number + */ +void tcpc_alert_clear(int port); + +/** + * Run TCPC task once. This checks for incoming messages, processes + * any outgoing messages, and reads CC lines. + * + * @param port Type-C port number + * @param evt Event type that woke up this task + */ +int tcpc_run(int port, int evt); + +/** + * Initialize board specific TCPC functions post TCPC initialization. + * + * @param port Type-C port number + * + * @return EC_SUCCESS or error + */ +int board_tcpc_post_init(int port) __attribute__((weak)); + +#ifdef __cplusplus +} +#endif + +#endif /* __CROS_EC_USB_PD_TCPM_H */ diff --git a/fw/utils.c b/fw/utils.c new file mode 100644 index 0000000..49dbbf1 --- /dev/null +++ b/fw/utils.c @@ -0,0 +1,39 @@ +// +// Copyright 2022 Wenting Zhang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include +#include +#include +#include +#include "utils.h" + +void fatal(const char *msg, ...) { + va_list params; + + va_start(params, msg); + + printf("[FATAL] "); + vprintf(msg, params); + + va_end(params); + + while(1); +} \ No newline at end of file diff --git a/fw/utils.h b/fw/utils.h new file mode 100644 index 0000000..9a86f9e --- /dev/null +++ b/fw/utils.h @@ -0,0 +1,24 @@ +// +// Copyright 2022 Wenting Zhang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +void fatal(const char *msg, ...);