diff options
Diffstat (limited to 'driver/tcpm/fusb302.c')
-rw-r--r-- | driver/tcpm/fusb302.c | 1205 |
1 files changed, 0 insertions, 1205 deletions
diff --git a/driver/tcpm/fusb302.c b/driver/tcpm/fusb302.c deleted file mode 100644 index 0098906d32..0000000000 --- a/driver/tcpm/fusb302.c +++ /dev/null @@ -1,1205 +0,0 @@ -/* 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. - * - * Author: Gabe Noblesmith - */ - -/* Type-C port manager for Fairchild's FUSB302 */ - -#include "console.h" -#include "fusb302.h" -#include "task.h" -#include "hooks.h" -#include "tcpm/tcpci.h" -#include "tcpm/tcpm.h" -#include "timer.h" -#include "usb_charge.h" -#include "usb_pd.h" -#include "usb_pd_tcpc.h" -#include "usb_pd_tcpm.h" -#include "util.h" - -#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) || \ - defined(CONFIG_USB_PD_DISCHARGE_TCPC) -#error "Unsupported config options of fusb302 PD driver" -#endif - -#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_MAX_COUNT]; - -static struct mutex measure_lock; - -/* - * 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_RP_DEF; - else if (bc_lvl == 0x2) - ret = TYPEC_CC_VOLT_RP_1_5; - else if (bc_lvl == 0x3) - ret = TYPEC_CC_VOLT_RP_3_0; - } - - return ret; -} - -static int measure_cc_pin_source(int port, int cc_measure) -{ - int switches0_reg; - int reg; - int cc_lvl; - - mutex_lock(&measure_lock); - - /* 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 */ - usleep(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 */ - usleep(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); - - mutex_unlock(&measure_lock); - - return cc_lvl; -} - -/* Determine cc pin state for source when in manual detect mode */ -static void detect_cc_pin_source_manual(int port, - enum tcpc_cc_voltage_status *cc1_lvl, - enum tcpc_cc_voltage_status *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, enum tcpc_cc_voltage_status *cc1, - enum tcpc_cc_voltage_status *cc2) -{ - int reg; - int orig_meas_cc1; - int orig_meas_cc2; - int bc_lvl_cc1; - int bc_lvl_cc2; - - mutex_lock(&measure_lock); - - /* - * 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 */ - usleep(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 */ - usleep(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); - - mutex_unlock(&measure_lock); -} - -/* 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); - - return rv; -} - -static int fusb302_tcpm_select_rp_value(int port, int rp) -{ - int reg; - int rv; - uint8_t vnc, rd; - - /* Keep track of current RP value */ - tcpci_set_cached_rp(port, rp); - - 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; - return tcpc_write(port, TCPC_REG_CONTROL0, reg); -} - -static int fusb302_tcpm_init(int port) -{ - int reg; - - /* 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 |= (CONFIG_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; -#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC - /* TODO(crbug.com/791109): Clean up VBUS notification. */ - - /* VBUS threshold crossed (~4.0V) */ - reg &= ~TCPC_REG_MASK_VBUSOK; -#endif - 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); - - /* TODO: Reduce power consumption */ - tcpc_write(port, TCPC_REG_POWER, TCPC_REG_POWER_PWR_ALL); - -#if defined(CONFIG_USB_PD_VBUS_DETECT_TCPC) && defined(CONFIG_USB_CHARGER) - /* Wait for the reference voltage to stablize */ - usleep(250); - /* - * Initialize VBUS supplier when VBUS is already present before - * init (e.g. Cold reboot with charger plugged). - */ - tcpc_read(port, TCPC_REG_STATUS0, ®); - if (reg & TCPC_REG_STATUS0_VBUSOK) - usb_charger_vbus_change(port, 1); -#endif - - return 0; -} - -static int fusb302_tcpm_release(int port) -{ - return EC_ERROR_UNIMPLEMENTED; -} - -static int fusb302_tcpm_get_cc(int port, enum tcpc_cc_voltage_status *cc1, - enum tcpc_cc_voltage_status *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, enum tcpc_cc_polarity 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_rm_dts(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_rm_dts(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_rm_dts(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; -} - -__maybe_unused static int fusb302_tcpm_decode_sop_prime_enable(int port, - bool enable) -{ - int reg; - - if (tcpc_read(port, TCPC_REG_CONTROL1, ®)) - return EC_ERROR_UNKNOWN; - - if (enable) - reg |= (TCPC_REG_CONTROL1_ENSOP1 | - TCPC_REG_CONTROL1_ENSOP2); - else - reg &= ~(TCPC_REG_CONTROL1_ENSOP1 | - TCPC_REG_CONTROL1_ENSOP2); - - return tcpc_write(port, TCPC_REG_CONTROL1, reg); -} - -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); - - if (IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) { - if (state[port].rx_enable) { - if (fusb302_tcpm_decode_sop_prime_enable(port, - true)) - return EC_ERROR_UNKNOWN; - } - } - } 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); - - if (IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) { - if (state[port].rx_enable) { - if (fusb302_tcpm_decode_sop_prime_enable(port, - false)) - return EC_ERROR_UNKNOWN; - } - } - } - - 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); - } - -#ifdef CONFIG_USB_PD_DECODE_SOP - /* - * Only the VCONN Source is allowed to communicate - * with the Cable Plugs. - */ - if (state[port].vconn_enabled) { - if (tcpc_read(port, TCPC_REG_CONTROL1, ®)) - return EC_ERROR_UNKNOWN; - - reg |= (TCPC_REG_CONTROL1_ENSOP1 | TCPC_REG_CONTROL1_ENSOP2); - tcpc_write(port, TCPC_REG_CONTROL1, reg); - } -#endif - - 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; - - return (!tcpc_read(port, TCPC_REG_STATUS1, ®)) && - (reg & TCPC_REG_STATUS1_RX_EMPTY); -} - -static int fusb302_tcpm_get_message_raw(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; - - /* Read until we have a non-GoodCRC packet or an empty FIFO */ - do { - buf[0] = TCPC_REG_FIFOS; - tcpc_lock(port, 1); - - /* - * PART 1 OF BURST READ: Write in register address. - * Issue a START, no STOP. - */ - rv = tcpc_xfer_unlocked(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_unlocked(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_unlocked(port, 0, 0, buf, len+4, I2C_XFER_STOP); - - tcpc_lock(port, 0); - } 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); - } - -#ifdef CONFIG_USB_PD_DECODE_SOP - { - int reg; - - if (tcpc_read(port, TCPC_REG_STATUS1, ®)) - return EC_ERROR_UNKNOWN; - - if (reg & TCPC_REG_STATUS1_RXSOP1) - *head |= PD_HEADER_SOP(TCPCI_MSG_SOP_PRIME); - else if (reg & TCPC_REG_STATUS1_RXSOP2) - *head |= PD_HEADER_SOP(TCPCI_MSG_SOP_PRIME_PRIME); - } -#endif - - return rv; -} - -static int fusb302_tcpm_transmit(int port, enum tcpci_msg_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 TCPCI_MSG_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; - - return fusb302_send_message(port, header, data, buf, buf_pos); - case TCPCI_MSG_SOP_PRIME: - - /* 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_SYNC3; - buf[buf_pos++] = FUSB302_TKN_SYNC3; - - return fusb302_send_message(port, header, data, buf, buf_pos); - case TCPCI_MSG_SOP_PRIME_PRIME: - - /* 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_SYNC3; - buf[buf_pos++] = FUSB302_TKN_SYNC1; - buf[buf_pos++] = FUSB302_TKN_SYNC3; - - return fusb302_send_message(port, header, data, buf, buf_pos); - case TCPCI_MSG_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 TCPCI_MSG_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 bool fusb302_tcpm_check_vbus_level(int port, enum vbus_level level) -{ - int reg; - - /* Read status register */ - tcpc_read(port, TCPC_REG_STATUS0, ®); - - if (level == VBUS_PRESENT) - return (reg & TCPC_REG_STATUS0_VBUSOK) ? 1 : 0; - else - return (reg & TCPC_REG_STATUS0_VBUSOK) ? 0 : 1; -} -#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); - } - - if (interrupt & TCPC_REG_INTERRUPT_COLLISION) { - /* packet sending collided */ - pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); - } - -#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC - if (interrupt & TCPC_REG_INTERRUPT_VBUSOK) { - /* VBUS crossed threshold */ -#ifdef CONFIG_USB_CHARGER - usb_charger_vbus_change(port, - fusb302_tcpm_check_vbus_level(port, - VBUS_PRESENT)); -#else - if (!fusb302_tcpm_check_vbus_level(port, VBUS_PRESENT)) - pd_vbus_low(port); -#endif - task_wake(PD_PORT_TO_TASK_ID(port)); - hook_notify(HOOK_AC_CHANGE); - } -#endif - - /* GoodCRC was received, our FIFO is now non-empty */ - if (interrupta & TCPC_REG_INTERRUPTA_TX_SUCCESS) { - 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); - task_set_event(PD_PORT_TO_TASK_ID(port), - PD_EVENT_RX_HARD_RESET); - } - - if (interruptb & TCPC_REG_INTERRUPTB_GCRCSENT) { - /* Packet received and GoodCRC sent */ - /* (this interrupt fires after the GoodCRC finishes) */ - if (state[port].rx_enable) { - /* Pull all RX messages from TCPC into EC memory */ - while (!fusb302_rx_fifo_is_empty(port)) - tcpm_enqueue_message(port); - } 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); -} - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER -static int fusb302_set_toggle_mode(int port, int mode) -{ - int reg, rv; - - rv = i2c_read8(tcpc_config[port].i2c_info.port, - tcpc_config[port].i2c_info.addr_flags, - TCPC_REG_CONTROL2, ®); - if (rv) - return rv; - - reg &= ~TCPC_REG_CONTROL2_MODE_MASK; - reg |= mode << TCPC_REG_CONTROL2_MODE_POS; - return i2c_write8(tcpc_config[port].i2c_info.port, - tcpc_config[port].i2c_info.addr_flags, - TCPC_REG_CONTROL2, reg); -} - -static int fusb302_tcpm_enter_low_power_mode(int port) -{ - int reg, rv, mode = TCPC_REG_CONTROL2_MODE_DRP; - - /** - * vendor's suggested LPM flow: - * - enable low power mode and set up other things - * - sleep 250 us - * - start toggling - */ - rv = i2c_write8(tcpc_config[port].i2c_info.port, - tcpc_config[port].i2c_info.addr_flags, - TCPC_REG_POWER, TCPC_REG_POWER_PWR_LOW); - if (rv) - return rv; - - switch (pd_get_dual_role(port)) { - case PD_DRP_TOGGLE_ON: - mode = TCPC_REG_CONTROL2_MODE_DRP; - break; - case PD_DRP_TOGGLE_OFF: - mode = TCPC_REG_CONTROL2_MODE_UFP; - break; - case PD_DRP_FREEZE: - mode = pd_get_power_role(port) == PD_ROLE_SINK ? - TCPC_REG_CONTROL2_MODE_UFP : - TCPC_REG_CONTROL2_MODE_DFP; - break; - case PD_DRP_FORCE_SINK: - mode = TCPC_REG_CONTROL2_MODE_UFP; - break; - case PD_DRP_FORCE_SOURCE: - mode = TCPC_REG_CONTROL2_MODE_DFP; - break; - } - rv = fusb302_set_toggle_mode(port, mode); - if (rv) - return rv; - - usleep(250); - - rv = i2c_read8(tcpc_config[port].i2c_info.port, - tcpc_config[port].i2c_info.addr_flags, - TCPC_REG_CONTROL2, ®); - if (rv) - return rv; - reg |= TCPC_REG_CONTROL2_TOGGLE; - return i2c_write8(tcpc_config[port].i2c_info.port, - tcpc_config[port].i2c_info.addr_flags, - TCPC_REG_CONTROL2, reg); -} -#endif - -/* - * Compare VBUS voltage with given mdac reference voltage. - * returns non-zero if VBUS voltage >= (mdac + 1) * 420 mV - */ -static int fusb302_compare_mdac(int port, int mdac) -{ - int orig_reg, status0; - - mutex_lock(&measure_lock); - - /* backup REG_MEASURE */ - tcpc_read(port, TCPC_REG_MEASURE, &orig_reg); - /* set reg_measure bit 0~5 to mdac, and bit6 to 1(measure vbus) */ - tcpc_write(port, TCPC_REG_MEASURE, - (mdac & TCPC_REG_MEASURE_MDAC_MASK) | TCPC_REG_MEASURE_VBUS); - - /* Wait on measurement */ - usleep(350); - - /* - * Read status register, if STATUS0_COMP=1 then vbus is higher than - * (mdac + 1) * 0.42V - */ - tcpc_read(port, TCPC_REG_STATUS0, &status0); - /* write back original value */ - tcpc_write(port, TCPC_REG_MEASURE, orig_reg); - - mutex_unlock(&measure_lock); - - return status0 & TCPC_REG_STATUS0_COMP; -} - -int tcpc_get_vbus_voltage(int port) -{ - int mdac = 0, i; - - /* - * Implement by comparing VBUS with MDAC reference voltage, and binary - * search the value of MDAC. - * - * MDAC register has 6 bits, so we can simply search 1 bit per - * iteration, from MSB to LSB. - */ - for (i = 5; i >= 0; i--) { - if (fusb302_compare_mdac(port, mdac | BIT(i))) - mdac |= BIT(i); - } - - return (mdac + 1) * 420; -} - -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 - .check_vbus_level = &fusb302_tcpm_check_vbus_level, -#endif - .select_rp_value = &fusb302_tcpm_select_rp_value, - .set_cc = &fusb302_tcpm_set_cc, - .set_polarity = &fusb302_tcpm_set_polarity, -#ifdef CONFIG_USB_PD_DECODE_SOP - .sop_prime_enable = &fusb302_tcpm_decode_sop_prime_enable, -#endif - .set_vconn = &fusb302_tcpm_set_vconn, - .set_msg_header = &fusb302_tcpm_set_msg_header, - .set_rx_enable = &fusb302_tcpm_set_rx_enable, - .get_message_raw = &fusb302_tcpm_get_message_raw, - .transmit = &fusb302_tcpm_transmit, - .tcpc_alert = &fusb302_tcpc_alert, -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - .enter_low_power_mode = &fusb302_tcpm_enter_low_power_mode, -#endif -}; |