From e3554f39d752c39df2fb8d70b8a6dc0fdce441f6 Mon Sep 17 00:00:00 2001 From: Gabe Noblesmith Date: Fri, 28 Aug 2015 10:34:47 -0400 Subject: Initial commit of TCPM driver for FUSB302. BUG=none BRANCH=none TEST=PD contract established with various devices Change-Id: I4b452befe9ccd9d67bd6ad5c8cf77ae58320f6af Signed-off-by: Gabe Noblesmith Reviewed-on: https://chromium-review.googlesource.com/294924 Commit-Ready: Shawn N Tested-by: Shawn N Reviewed-by: Alec Berg --- driver/build.mk | 1 + driver/tcpm/fusb302.c | 952 ++++++++++++++++++++++++++++++++++++++++++++++++++ driver/tcpm/fusb302.h | 198 +++++++++++ 3 files changed, 1151 insertions(+) create mode 100644 driver/tcpm/fusb302.c create mode 100644 driver/tcpm/fusb302.h (limited to 'driver') diff --git a/driver/build.mk b/driver/build.mk index 48e8c47825..1a33ee714e 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -74,6 +74,7 @@ driver-$(CONFIG_THERMISTOR_NCP15WB)+=temp_sensor/thermistor_ncp15wb.o # Type-C port controller (TCPC) drivers driver-$(CONFIG_USB_PD_TCPM_STUB)+=tcpm/stub.o driver-$(CONFIG_USB_PD_TCPM_TCPCI)+=tcpm/tcpci.o +driver-$(CONFIG_USB_PD_TCPM_FUSB302)+=tcpm/fusb302.o # USB switches driver-$(CONFIG_USB_SWITCH_PI3USB9281)+=usb_switch_pi3usb9281.o diff --git a/driver/tcpm/fusb302.c b/driver/tcpm/fusb302.c new file mode 100644 index 0000000000..1b5d26f233 --- /dev/null +++ b/driver/tcpm/fusb302.c @@ -0,0 +1,952 @@ +/* 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 "i2c.h" +#include "task.h" +#include "hooks.h" +#include "timer.h" +#include "usb_pd.h" +#include "usb_pd_tcpc.h" +#include "usb_pd_tcpm.h" +#include "util.h" + +/* Convert port number to tcpc i2c address */ +#define I2C_ADDR_TCPC(p) (CONFIG_TCPC_I2C_BASE_ADDR + 2*(p)) + +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; + int dfp_toggling_on; + int previous_pull; + int togdone_pullup_cc1; + int togdone_pullup_cc2; + int tx_hard_reset_req; +} state[CONFIG_USB_PD_PORT_COUNT]; + +static int fusb302_i2c_write8(int port, int reg, int val) +{ + return i2c_write8(I2C_PORT_TCPC, I2C_ADDR_TCPC(port), reg, val); +} + +static int fusb302_i2c_read8(int port, int reg, int *val) +{ + return i2c_read8(I2C_PORT_TCPC, I2C_ADDR_TCPC(port), reg, val); +} + +/* bring the FUSB302 out of reset after Hard Reset signaling */ +static void fusb302_pd_reset(int port) +{ + fusb302_i2c_write8(port, TCPC_REG_RESET, TCPC_REG_RESET_PD_RESET); +} + +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! + */ + fusb302_i2c_write8(port, TCPC_REG_CONTROL1, TCPC_REG_CONTROL1_RX_FLUSH); +} + +static void fusb302_flush_tx_fifo(int port) +{ + int reg; + + fusb302_i2c_read8(port, TCPC_REG_CONTROL0, ®); + reg |= TCPC_REG_CONTROL0_TX_FLUSH; + fusb302_i2c_write8(port, TCPC_REG_CONTROL0, reg); +} + +static void fusb302_auto_goodcrc_enable(int port, int enable) +{ + int reg; + + fusb302_i2c_read8(port, TCPC_REG_SWITCHES1, ®); + + if (enable) + reg |= TCPC_REG_SWITCHES1_AUTO_GCRC; + else + reg &= ~TCPC_REG_SWITCHES1_AUTO_GCRC; + + fusb302_i2c_write8(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; +} + +/* Determine cc pin state for source */ +static void detect_cc_pin_source(int port, int *cc1_lvl, int *cc2_lvl) +{ + int reg; + + *cc1_lvl = TYPEC_CC_VOLT_OPEN; + *cc2_lvl = TYPEC_CC_VOLT_OPEN; + + if (state[port].togdone_pullup_cc1 == 1) { + /* Measure CC1 */ + + /* Enable CC1 measurement switch and pullup */ + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, + TCPC_REG_SWITCHES0_CC1_PU_EN | + TCPC_REG_SWITCHES0_MEAS_CC1); + + /* Set MDAC Value to High. MDAC Reg is 7:2 */ + fusb302_i2c_write8(port, TCPC_REG_MEASURE, 0x26 << 2); + + /* CC1 is now being measured by FUSB302. */ + + /* Wait on measurement */ + usleep(250); + + /* Read status register */ + fusb302_i2c_read8(port, TCPC_REG_STATUS0, ®); + + if (reg & TCPC_REG_STATUS0_COMP) { + *cc1_lvl = TYPEC_CC_VOLT_OPEN; + } else { + + /* Set MDAC Value to Low. MDAC Reg is 7:2 */ + fusb302_i2c_write8(port, TCPC_REG_MEASURE, 0x05 << 2); + + /* Wait on measurement */ + usleep(250); + + /* Read status register */ + fusb302_i2c_read8(port, TCPC_REG_STATUS0, ®); + + if (reg & TCPC_REG_STATUS0_COMP) + *cc1_lvl = TYPEC_CC_VOLT_RA; + else + *cc1_lvl = TYPEC_CC_VOLT_RD; + } + } else if (state[port].togdone_pullup_cc2 == 1) { + /* Measure CC2 */ + + /* Enable CC2 measurement switch and pullup */ + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, + TCPC_REG_SWITCHES0_CC2_PU_EN | + TCPC_REG_SWITCHES0_MEAS_CC2); + + /* Set MDAC Value to High. MDAC Reg is 7:2 */ + fusb302_i2c_write8(port, TCPC_REG_MEASURE, 0x26 << 2); + + /* CC2 is now being measured by FUSB302. */ + + /* Wait on measurement */ + usleep(250); + + /* Read status register */ + fusb302_i2c_read8(port, TCPC_REG_STATUS0, ®); + + if (reg & TCPC_REG_STATUS0_COMP) { + *cc2_lvl = TYPEC_CC_VOLT_OPEN; + } else { + + /* Set MDAC Value to Low. MDAC Reg is 7:2 */ + fusb302_i2c_write8(port, TCPC_REG_MEASURE, 0x05 << 2); + + /* Wait on measurement */ + usleep(250); + + /* Read status register */ + fusb302_i2c_read8(port, TCPC_REG_STATUS0, ®); + + if (reg & TCPC_REG_STATUS0_COMP) + *cc2_lvl = TYPEC_CC_VOLT_RA; + else + *cc2_lvl = TYPEC_CC_VOLT_RD; + } + } +} + +/* 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. + */ + fusb302_i2c_read8(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; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + /* + * CC1 is now being measured by FUSB302. + */ + fusb302_i2c_read8(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. + */ + + fusb302_i2c_read8(port, TCPC_REG_SWITCHES0, ®); + + /* Disable CC1 measurement switch, enable CC2 measurement switch */ + reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; + reg |= TCPC_REG_SWITCHES0_MEAS_CC2; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + /* + * CC2 is now being measured by FUSB302. + */ + fusb302_i2c_read8(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 */ + fusb302_i2c_read8(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; + + fusb302_i2c_write8(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! */ + i2c_lock(I2C_PORT_TCPC, 1); + rv = i2c_xfer(I2C_PORT_TCPC, I2C_ADDR_TCPC(port), + buf, buf_pos, 0, 0, I2C_XFER_SINGLE); + i2c_lock(I2C_PORT_TCPC, 0); + + return rv; +} + +void tcpm_init_ports(void) +{ + int i; + + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) + tcpm_init(i); +} +DECLARE_HOOK(HOOK_INIT, tcpm_init_ports, HOOK_PRIO_DEFAULT); + +int tcpm_init(int port) +{ + int reg; + + /* set default */ + state[port].cc_polarity = -1; + + state[port].previous_pull = TYPEC_CC_RD; + + /* all other variables assumed to default to 0 */ + + /* Restore default settings */ + fusb302_i2c_write8(port, TCPC_REG_RESET, TCPC_REG_RESET_SW_RESET); + + /* Turn on retries and set number of retries */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL3, ®); + reg |= TCPC_REG_CONTROL3_AUTO_RETRY; + reg |= (PD_RETRY_COUNT & 0x3) << + TCPC_REG_CONTROL3_N_RETRIES_POS; + fusb302_i2c_write8(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; + fusb302_i2c_write8(port, TCPC_REG_MASK, reg); + + reg = 0xFF; + /* informs of attaches */ + reg &= ~TCPC_REG_MASKA_TOGDONE; + /* 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; + fusb302_i2c_write8(port, TCPC_REG_MASKA, reg); + + reg = 0xFF; + /* when fusb302 sends GoodCRC to ack a pd message */ + reg &= ~TCPC_REG_MASKB_GCRCSENT; + fusb302_i2c_write8(port, TCPC_REG_MASKB, reg); + + /* Interrupt Enable */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL0, ®); + reg &= ~TCPC_REG_CONTROL0_INT_MASK; + fusb302_i2c_write8(port, TCPC_REG_CONTROL0, reg); + + /* Set VCONN switch defaults */ + tcpm_set_polarity(port, 0); + tcpm_set_vconn(port, 0); + + /* Turn on the power! */ + /* TODO: Reduce power consumption */ + fusb302_i2c_write8(port, TCPC_REG_POWER, TCPC_REG_POWER_PWR_ALL); + + return 0; +} + +int tcpm_get_cc(int port, int *cc1, int *cc2) +{ + /* + * can't measure while doing DFP toggling - + * FUSB302 takes control of the switches. + * During this time, tell software that CCs are open - + * at least until we get the TOGDONE interrupt... + * which signals that the hardware found something. + */ + if (state[port].dfp_toggling_on) { + *cc1 = TYPEC_CC_VOLT_OPEN; + *cc2 = TYPEC_CC_VOLT_OPEN; + return 0; + } + + if (state[port].pulling_up) { + /* Source mode? */ + detect_cc_pin_source(port, cc1, cc2); + } else { + /* Sink mode? */ + detect_cc_pin_sink(port, cc1, cc2); + } + + return 0; +} + +int tcpm_set_cc(int port, int pull) +{ + int reg; + + state[port].previous_pull = pull; + + /* NOTE: FUSB302 toggles a single pull-up between CC1 and CC2 */ + /* NOTE: FUSB302 Does not support Ra. */ + switch (pull) { + case TYPEC_CC_RP: + + /* if fusb302 hasn't figured anything out yet */ + if (!state[port].togdone_pullup_cc1 && + !state[port].togdone_pullup_cc2) { + + /* Enable DFP Toggle Mode */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL2, ®); + + /* turn on toggle */ + reg |= (TCPC_REG_CONTROL2_MODE_DFP << + TCPC_REG_CONTROL2_MODE_POS); + reg |= TCPC_REG_CONTROL2_TOGGLE; + fusb302_i2c_write8(port, TCPC_REG_CONTROL2, reg); + + state[port].pulling_up = 1; + state[port].dfp_toggling_on = 1; + } else { + + /* enable the pull-up we know to be necessary */ + fusb302_i2c_read8(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; + + if (state[port].togdone_pullup_cc1) + reg |= TCPC_REG_SWITCHES0_CC1_PU_EN; + else + reg |= TCPC_REG_SWITCHES0_CC2_PU_EN; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + state[port].pulling_up = 1; + state[port].dfp_toggling_on = 0; + } + + break; + case TYPEC_CC_RD: + /* Enable UFP Mode */ + + /* turn off toggle */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL2, ®); + reg &= ~TCPC_REG_CONTROL2_TOGGLE; + fusb302_i2c_write8(port, TCPC_REG_CONTROL2, reg); + + /* enable pull-downs, disable pullups */ + fusb302_i2c_read8(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); + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + state[port].pulling_up = 0; + state[port].dfp_toggling_on = 0; + break; + case TYPEC_CC_OPEN: + /* Disable toggling */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL2, ®); + reg &= ~TCPC_REG_CONTROL2_TOGGLE; + fusb302_i2c_write8(port, TCPC_REG_CONTROL2, reg); + + /* Ensure manual switches are opened */ + fusb302_i2c_read8(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; + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + state[port].pulling_up = 0; + state[port].dfp_toggling_on = 0; + break; + default: + /* Unsupported... */ + return EC_ERROR_UNIMPLEMENTED; + } + return 0; +} + +int tcpm_set_polarity(int port, int polarity) +{ + /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ + int reg; + + fusb302_i2c_read8(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; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + fusb302_i2c_read8(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; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES1, reg); + + /* Save the polarity for later */ + state[port].cc_polarity = polarity; + + return 0; +} + +int 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; + + if (enable) { + /* set to saved polarity */ + tcpm_set_polarity(port, state[port].cc_polarity); + } else { + + fusb302_i2c_read8(port, TCPC_REG_SWITCHES0, ®); + + /* clear VCONN switch bits */ + reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1; + reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + } + + /* save enable state for later use */ + state[port].vconn_enabled = enable; + return 0; +} + +int tcpm_set_msg_header(int port, int power_role, int data_role) +{ + int reg; + + fusb302_i2c_read8(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; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES1, reg); + + return 0; +} + +int tcpm_set_rx_enable(int port, int enable) +{ + int reg; + + state[port].rx_enable = enable; + + /* Get current switch state */ + fusb302_i2c_read8(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; + } + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + + /* flush rx fifo in case messages have been coming our way */ + fusb302_flush_rx_fifo(port); + + + } else { + /* + * bit of a hack here. + * when this function is called to disable rx (enable=0) + * using it as an indication of detach (gulp!) + * to reset our knowledge of where + * the toggle state machine landed. + */ + state[port].togdone_pullup_cc1 = 0; + state[port].togdone_pullup_cc2 = 0; + + tcpm_set_cc(port, state[port].previous_pull); + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + } + + fusb302_auto_goodcrc_enable(port, enable); + + return 0; +} + +int 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 = 0; + int len; + + /* NOTE: Assuming enough memory has been allocated for payload. */ + + /* + * PART 1 OF BURST READ: Write in register address. + * Issue a START, no STOP. + */ + i2c_lock(I2C_PORT_TCPC, 1); + buf[0] = TCPC_REG_FIFOS; + rv |= i2c_xfer(I2C_PORT_TCPC, I2C_ADDR_TCPC(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. + */ + rv |= i2c_xfer(I2C_PORT_TCPC, I2C_ADDR_TCPC(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 |= i2c_xfer(I2C_PORT_TCPC, I2C_ADDR_TCPC(port), + 0, 0, buf, len+4, I2C_XFER_STOP); + + i2c_lock(I2C_PORT_TCPC, 0); + + /* return the data */ + memcpy(payload, buf, len); + + return rv; +} + +int 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 i2c 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 TCPC_TX_HARD_RESET: + state[port].tx_hard_reset_req = 1; + + /* Simply hit the SEND_HARD_RESET bit */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL3, ®); + reg |= TCPC_REG_CONTROL3_SEND_HARDRESET; + fusb302_i2c_write8(port, TCPC_REG_CONTROL3, reg); + + break; + case TCPC_TX_BIST_MODE_2: + /* Simply hit the BIST_MODE2 bit */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL1, ®); + reg |= TCPC_REG_CONTROL1_BIST_MODE2; + fusb302_i2c_write8(port, TCPC_REG_CONTROL1, reg); + break; + default: + return EC_ERROR_UNIMPLEMENTED; + } + + return 0; +} + +int tcpm_get_vbus_level(int port) +{ + int reg; + + /* Read status register */ + fusb302_i2c_read8(port, TCPC_REG_STATUS0, ®); + + return (reg & TCPC_REG_STATUS0_VBUSOK) ? 1 : 0; +} + +void tcpc_alert(int port) +{ + /* interrupt has been received */ + int interrupt; + int interrupta; + int interruptb; + int reg; + int toggle_answer; + + /* reading interrupt registers clears them */ + + fusb302_i2c_read8(port, TCPC_REG_INTERRUPT, &interrupt); + fusb302_i2c_read8(port, TCPC_REG_INTERRUPTA, &interrupta); + fusb302_i2c_read8(port, TCPC_REG_INTERRUPTB, &interruptb); + + 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 */ + state[port].tx_hard_reset_req = 0; + pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); + } + + if (interrupta & TCPC_REG_INTERRUPTA_TX_SUCCESS) { + /* sent packet was acknowledged with a GoodCRC */ + + /* flush out the GoodCRC message*/ + fusb302_flush_rx_fifo(port); + + pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); + } + + if (interrupta & TCPC_REG_INTERRUPTA_TOGDONE) { + /* toggle done */ + state[port].dfp_toggling_on = 0; + + /* read what 302 settled on for an answer...*/ + fusb302_i2c_read8(port, TCPC_REG_STATUS1A, ®); + reg = reg >> TCPC_REG_STATUS1A_TOGSS_POS; + reg = reg & TCPC_REG_STATUS1A_TOGSS_MASK; + + toggle_answer = reg; + + /* Turn off toggle so we can take over the switches again */ + fusb302_i2c_read8(port, TCPC_REG_CONTROL2, ®); + reg &= ~TCPC_REG_CONTROL2_TOGGLE; + fusb302_i2c_write8(port, TCPC_REG_CONTROL2, reg); + + switch (toggle_answer) { + case TCPC_REG_STATUS1A_TOGSS_SRC1: + state[port].togdone_pullup_cc1 = 1; + state[port].togdone_pullup_cc2 = 0; + break; + case TCPC_REG_STATUS1A_TOGSS_SRC2: + state[port].togdone_pullup_cc1 = 0; + state[port].togdone_pullup_cc2 = 1; + break; + case TCPC_REG_STATUS1A_TOGSS_SNK1: + state[port].togdone_pullup_cc1 = 0; + state[port].togdone_pullup_cc2 = 0; + break; + case TCPC_REG_STATUS1A_TOGSS_SNK2: + state[port].togdone_pullup_cc1 = 0; + state[port].togdone_pullup_cc2 = 0; + break; + case TCPC_REG_STATUS1A_TOGSS_AA: + state[port].togdone_pullup_cc1 = 0; + state[port].togdone_pullup_cc2 = 0; + break; + default: + /* TODO: should never get here, but? */ + break; + } + + /* enable the pull-up we know to be necessary */ + fusb302_i2c_read8(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; + + if (state[port].togdone_pullup_cc1) + reg |= TCPC_REG_SWITCHES0_CC1_PU_EN; + else + reg |= TCPC_REG_SWITCHES0_CC2_PU_EN; + + fusb302_i2c_write8(port, TCPC_REG_SWITCHES0, reg); + } + + 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 */ + + if (state[port].tx_hard_reset_req) { + state[port].tx_hard_reset_req = 0; + /* 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); + } + } + +} diff --git a/driver/tcpm/fusb302.h b/driver/tcpm/fusb302.h new file mode 100644 index 0000000000..ce8bd20af9 --- /dev/null +++ b/driver/tcpm/fusb302.h @@ -0,0 +1,198 @@ +/* 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 + */ + +/* USB Power delivery port management */ +/* For Fairchild FUSB302 */ +#ifndef __CROS_EC_DRIVER_TCPM_FUSB302_H +#define __CROS_EC_DRIVER_TCPM_FUSB302_H + +/* FUSB302 I2C Address */ +#define CONFIG_TCPC_I2C_BASE_ADDR 0x44 + +/* Default retry count for transmitting */ +#define PD_RETRY_COUNT 3 + +#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_MDAC5 (1<<7) +#define TCPC_REG_MEASURE_MDAC4 (1<<6) +#define TCPC_REG_MEASURE_MDAC3 (1<<5) +#define TCPC_REG_MEASURE_MDAC2 (1<<4) +#define TCPC_REG_MEASURE_MDAC1 (1<<3) +#define TCPC_REG_MEASURE_MDAC0 (1<<2) +#define TCPC_REG_MEASURE_VBUS (1<<1) +#define TCPC_REG_MEASURE_XXXX5 (1<<0) + +#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_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_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, +}; + + +#endif /* __CROS_EC_DRIVER_TCPM_FUSB302_H */ + -- cgit v1.2.1