/* Copyright 2016 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 : Analogix Semiconductor. */ /* Type-C port manager for Analogix's anx74xx chips */ #include "anx74xx.h" #include "task.h" #include "tcpci.h" #include "tcpm.h" #include "timer.h" #include "usb_charge.h" #include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_tcpc.h" #include "util.h" #if !defined(CONFIG_USB_PD_TCPM_TCPCI) #error "ANX74xx is using part of standard TCPCI control" #error "Please upgrade your board configuration" #endif struct anx_state { int polarity; int vconn_en; int mux_state; #ifdef CONFIG_USB_PD_TCPC_LOW_POWER int prev_mode; #endif }; #define clear_recvd_msg_int(port) do {\ int reg, rv; \ rv = tcpc_read(port, ANX74XX_REG_RECVD_MSG_INT, ®); \ if (!rv) \ tcpc_write(port, ANX74XX_REG_RECVD_MSG_INT, \ reg | 0x01); \ } while (0) static struct anx_state anx[CONFIG_USB_PD_PORT_COUNT]; /* Save the selected rp value */ static int selected_rp[CONFIG_USB_PD_PORT_COUNT]; static int anx74xx_tcpm_init(int port); static void anx74xx_tcpm_set_auto_good_crc(int port, int enable) { tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_2, enable ? ANX74XX_REG_REPLY_SOP_EN : 0); } static void anx74xx_update_cable_det(int port, int mode) { #ifdef CONFIG_USB_PD_TCPC_LOW_POWER int reg; if (anx[port].prev_mode == mode) return; /* Update power mode */ anx[port].prev_mode = mode; /* Get ANALOG_CTRL_0 for cable det bit */ if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_0, ®)) return; if (mode == ANX74XX_STANDBY_MODE) { int cc_reg; /* * The ANX4329 enters standby mode by setting PWR_EN signal * low. In addition, RESET_L must be set low to keep the ANX3429 * in standby mode. * * Clearing bit 7 of ANX74XX_REG_ANALOG_CTRL_0 will cause the * ANX3429 to clear the cable_det signal that goes from the * ANX3429 to the EC. If this bit is cleared when a cable is * attached then cable_det will go high once standby is entered. * * In some cases, such as when the chipset power state is * S3/S5/G3 and a sink only adapter is connected to the port, * this behavior is undesirable. The constant toggling between * standby and normal mode means that effectively the ANX3429 is * not in standby mode only consumes ~1 mW less than just * remaining in normal mode. However when an E mark cable is * connected, clearing bit 7 is required so that while the E * mark cable configuration happens, the USB PD state machine * will continue to wake up until the USB PD attach event can be * regtistered. * * Therefore, the decision to clear bit 7 is based on the * current CC status of the port. If the CC status is open for * both CC lines OR if either CC line is showing Ra, then clear * bit 7. Not clearing bit 7 has no impact for normal cables and * prevents the constant toggle of standby<->normal when an * adapter is connected that isn't allowed to attach. Clearing * bit 7 when CC status reads Ra for either CC line allows the * USB PD state machine to be woken until the attach event can * happen. Note that in the case an E mark cable is connected * and can't attach (i.e. sink only port <- Emark cable -> sink * only adapter), then the ANX3429 will toggle indefinitely, * until either the cable is removed, or the port drp status * changes so the attach event can occur. * . */ /* Read CC status to see if cable_det bit should be cleared */ if (tcpc_read(port, ANX74XX_REG_CC_STATUS, &cc_reg)) return; /* If open or either CC line is Ra, then clear cable_det */ if (!cc_reg || (cc_reg & ANX74XX_CC_RA_MASK && !(cc_reg & ANX74XX_CC_RD_MASK))) reg &= ~ANX74XX_REG_R_PIN_CABLE_DET; } else { reg |= ANX74XX_REG_R_PIN_CABLE_DET; } tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_0, reg); #endif } static void anx74xx_set_power_mode(int port, int mode) { /* * Update PWR_EN and RESET_N signals to the correct level. High for * Normal mode and low for Standby mode. When transitioning from standby * to normal mode, must set the PWR_EN and RESET_N before attempting to * modify cable_det bit of analog_ctrl_0. If going from Normal to * Standby, updating analog_ctrl_0 must happen before setting PWR_EN and * RESET_N low. */ if (mode == ANX74XX_NORMAL_MODE) { /* Take chip out of standby mode */ board_set_tcpc_power_mode(port, mode); /* Update the cable det signal */ anx74xx_update_cable_det(port, mode); } else { /* Update cable cable det signal */ anx74xx_update_cable_det(port, mode); /* * Delay between setting cable_det low and setting RESET_L low * as recommended the ANX3429 datasheet. */ msleep(1); /* Put chip into standby mode */ board_set_tcpc_power_mode(port, mode); } } void anx74xx_tcpc_set_vbus(int port, int enable) { int reg; tcpc_read(port, ANX74XX_REG_GPIO_CTRL_4_5, ®); if (enable) reg |= ANX74XX_REG_SET_VBUS; else reg &= ~ANX74XX_REG_SET_VBUS; tcpc_write(port, ANX74XX_REG_GPIO_CTRL_4_5, reg); } #ifdef CONFIG_USB_PD_DISCHARGE_TCPC static void anx74xx_tcpc_discharge_vbus(int port, int enable) { int reg; tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, ®); if (enable) reg |= ANX74XX_REG_DISCHARGE_CTRL; else reg &= ~ANX74XX_REG_DISCHARGE_CTRL; tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); } #endif /* * timestamp of the next possible toggle to ensure the 2-ms spacing * between IRQ_HPD. */ static uint64_t hpd_deadline[CONFIG_USB_PD_PORT_COUNT]; void anx74xx_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq) { int reg; tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, ®); if (hpd_lvl) reg |= ANX74XX_REG_HPD_OUT_DATA; else reg &= ~ANX74XX_REG_HPD_OUT_DATA; tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); if (hpd_irq) { uint64_t now = get_time().val; /* wait for the minimum spacing between IRQ_HPD if needed */ if (now < hpd_deadline[port]) usleep(hpd_deadline[port] - now); tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, ®); reg &= ~ANX74XX_REG_HPD_OUT_DATA; tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); usleep(HPD_DSTREAM_DEBOUNCE_IRQ); reg |= ANX74XX_REG_HPD_OUT_DATA; tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); } /* enforce 2-ms delay between HPD pulses */ hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL; } void anx74xx_tcpc_clear_hpd_status(int port) { int reg; tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, ®); reg &= 0xcf; tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); } #ifdef CONFIG_USB_PD_TCPM_MUX static int anx74xx_tcpm_mux_init(int i2c_addr) { int port = i2c_addr; /* Nothing to do here, ANX initializes its muxes * as (MUX_USB_ENABLED | MUX_DP_ENABLED) */ anx[port].mux_state = MUX_USB_ENABLED | MUX_DP_ENABLED; return EC_SUCCESS; } static int anx74xx_tcpm_mux_enter_safe_mode(int port) { int reg; if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, ®)) return EC_ERROR_UNKNOWN; if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg | ANX74XX_REG_MODE_TRANS)) return EC_ERROR_UNKNOWN; return EC_SUCCESS; } static int anx74xx_tcpm_mux_exit_safe_mode(int port) { int reg; if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, ®)) return EC_ERROR_UNKNOWN; if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg & ~ANX74XX_REG_MODE_TRANS)) return EC_ERROR_UNKNOWN; return EC_SUCCESS; } static int anx74xx_tcpm_mux_exit(int port) { int reg; /* * Safe mode must be entered before any changes are made to the mux * settings used to enable ALT_DP mode. This funciton is called either * from anx74xx_tcpm_mux_set when TYPEC_MUX_NONE is selected as the new * mux state, or when both cc lines are determined to be * TYPEC_CC_VOLT_OPEN. Therefore, safe mode must be entered and exited * here so that both entry paths are handled. */ if (anx74xx_tcpm_mux_enter_safe_mode(port)) return EC_ERROR_UNKNOWN; /* Disconnect aux from sbu */ if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, ®)) return EC_ERROR_UNKNOWN; if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg & 0xf)) return EC_ERROR_UNKNOWN; /* Clear Bit[7:0] R_SWITCH */ if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_1, 0x0)) return EC_ERROR_UNKNOWN; /* Clear Bit[7:4] R_SWITCH_H */ if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_5, ®)) return EC_ERROR_UNKNOWN; if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, reg & 0x0f)) return EC_ERROR_UNKNOWN; /* Exit safe mode */ if (anx74xx_tcpm_mux_exit_safe_mode(port)) return EC_ERROR_UNKNOWN; return EC_SUCCESS; } static int anx74xx_mux_aux_to_sbu(int port, int polarity, int enabled) { int reg; const int aux_mask = ANX74XX_REG_AUX_SWAP_SET_CC2 | ANX74XX_REG_AUX_SWAP_SET_CC1; /* * Get the current value of analog_ctrl_2 register. Note, that safe mode * is enabled and exited by the calling function, so only have to worry * about setting the correct value for the upper 4 bits of analog_ctrl_2 * here. */ if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, ®)) return EC_ERROR_UNKNOWN; /* Assume aux_p/n lines are not connected */ reg &= ~aux_mask; if (enabled) { /* If enabled, connect aux to sbu based on desired polarity */ if (polarity) reg |= ANX74XX_REG_AUX_SWAP_SET_CC2; else reg |= ANX74XX_REG_AUX_SWAP_SET_CC1; } /* Write new aux <-> sbu settings */ if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg)) return EC_ERROR_UNKNOWN; return EC_SUCCESS; } static int anx74xx_tcpm_mux_set(int i2c_addr, mux_state_t mux_state) { int reg; int pin_cfg = 0; int rv; int port = i2c_addr; if (!(mux_state & ~MUX_POLARITY_INVERTED)) { anx[port].mux_state = mux_state; return anx74xx_tcpm_mux_exit(port); } rv = tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_5, ®); if (rv) return EC_ERROR_UNKNOWN; reg &= 0x0f; if (mux_state & MUX_USB_ENABLED) { /* Set pin assignment D */ if (mux_state & MUX_POLARITY_INVERTED) { pin_cfg = ANX74XX_REG_MUX_DP_MODE_BDF_CC2; reg |= ANX74XX_REG_MUX_SSTX_B; } else { pin_cfg = ANX74XX_REG_MUX_DP_MODE_BDF_CC1; reg |= ANX74XX_REG_MUX_SSTX_A; } } else if (mux_state & MUX_DP_ENABLED) { /* Set pin assignment C */ if (mux_state & MUX_POLARITY_INVERTED) { pin_cfg = ANX74XX_REG_MUX_DP_MODE_ACE_CC2; reg |= ANX74XX_REG_MUX_ML2_B; } else { pin_cfg = ANX74XX_REG_MUX_DP_MODE_ACE_CC1; reg |= ANX74XX_REG_MUX_ML2_A; } } else if (!mux_state) { return anx74xx_tcpm_mux_exit(port); } else { return EC_ERROR_UNIMPLEMENTED; } /* * Safe mode must be entererd prior to any changes to the mux related to * ALT_DP mode. Therefore, first enable safe mode prior to updating the * values for analog_ctrl_1, analog_ctrl_5, and analog_ctrl_2. */ if (anx74xx_tcpm_mux_enter_safe_mode(port)) return EC_ERROR_UNKNOWN; /* Write updated pin assignment */ rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_1, pin_cfg); /* Write Rswitch config bits */ rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, reg); if (rv) return EC_ERROR_UNKNOWN; /* Configure DP aux to sbu settings */ if (anx74xx_mux_aux_to_sbu(port, mux_state & MUX_POLARITY_INVERTED, mux_state & MUX_DP_ENABLED)) return EC_ERROR_UNKNOWN; /* Exit safe mode */ if (anx74xx_tcpm_mux_exit_safe_mode(port)) return EC_ERROR_UNKNOWN; anx[port].mux_state = mux_state; return EC_SUCCESS; } /* current mux state */ static int anx74xx_tcpm_mux_get(int i2c_addr, mux_state_t *mux_state) { int port = i2c_addr; *mux_state = anx[port].mux_state; return EC_SUCCESS; } const struct usb_mux_driver anx74xx_tcpm_usb_mux_driver = { .init = anx74xx_tcpm_mux_init, .set = anx74xx_tcpm_mux_set, .get = anx74xx_tcpm_mux_get, }; #endif /* CONFIG_USB_PD_TCPM_MUX */ static int anx74xx_init_analog(int port) { int reg, rv = EC_SUCCESS; /* Analog settings for chip */ rv |= tcpc_write(port, ANX74XX_REG_HPD_CONTROL, ANX74XX_REG_HPD_OP_MODE); rv |= tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, ANX74XX_REG_HPD_DEFAULT); if (rv) return rv; rv = tcpc_read(port, ANX74XX_REG_GPIO_CTRL_4_5, ®); if (rv) return rv; reg &= ANX74XX_REG_VBUS_GPIO_MODE; reg |= ANX74XX_REG_VBUS_OP_ENABLE; rv = tcpc_write(port, ANX74XX_REG_GPIO_CTRL_4_5, reg); if (rv) return rv; rv = tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, ®); if (rv) return rv; reg |= ANX74XX_REG_TX_MODE_ENABLE; rv = tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg); return rv; } static int anx74xx_send_message(int port, uint16_t header, const uint32_t *payload, int type, uint8_t len) { int reg, rv = EC_SUCCESS; uint8_t *buf = NULL; int num_retry = 0, i = 0; /* If sending Soft_reset, clear received message */ /* Soft Reset Message type = 1101 and Number of Data Object = 0 */ if ((header & 0x700f) == 0x000d) { /* * When sending soft reset, * the Rx buffer of ANX3429 shall be clear */ rv = tcpc_read(port, ANX74XX_REG_CTRL_FW, ®); rv |= tcpc_write( port, ANX74XX_REG_CTRL_FW, reg | CLEAR_RX_BUFFER); if (rv) return EC_ERROR_UNKNOWN; tcpc_write(port, ANX74XX_REG_RECVD_MSG_INT, 0xFF); } /* Inform chip about message length and TX type * type->bit-0..2, len->bit-3..7 */ reg = (len << 3) & 0xf8; reg |= type & 0x07; rv |= tcpc_write(port, ANX74XX_REG_TX_CTRL_2, reg); if (rv) return EC_ERROR_UNKNOWN; /* Enqueue Header */ rv = tcpc_write(port, ANX74XX_REG_TX_HEADER_L, (header & 0xff)); if (rv) return EC_ERROR_UNKNOWN; rv = tcpc_write(port, ANX74XX_REG_TX_HEADER_H, (header >> 8)); if (rv) return EC_ERROR_UNKNOWN; /* Enqueue payload */ if (len > 2) { len -= 2; buf = (uint8_t *)payload; while (1) { if (i < 18) rv = tcpc_write(port, ANX74XX_REG_TX_START_ADDR_0 + i, *buf); else rv = tcpc_write(port, ANX74XX_REG_TX_START_ADDR_1 + i - 18, *buf); if (rv) { num_retry++; } else { buf++; len--; num_retry = 0; i++; } if (len == 0 || num_retry >= 3) break; } /* If enqueue failed, do not request anx to transmit * messages, FIFO will get cleared in next call * before enqueue. * num_retry = 0, refer to success */ if (num_retry) return EC_ERROR_UNKNOWN; } /* Request a data transmission * This bit will be cleared by ANX after TX success */ rv = tcpc_read(port, ANX74XX_REG_CTRL_COMMAND, ®); if (rv) return EC_ERROR_UNKNOWN; reg |= ANX74XX_REG_TX_SEND_DATA_REQ; rv |= tcpc_write(port, ANX74XX_REG_CTRL_COMMAND, reg); return rv; } static int anx74xx_read_pd_obj(int port, uint8_t *buf, int plen) { int rv = EC_SUCCESS, i; int reg, addr = ANX74XX_REG_PD_RX_DATA_OBJ; /* Read PD data objects from ANX */ for (i = 0; i < plen ; i++) { /* Register sequence changes for last two bytes, if * plen is greater than 26 */ if (i == 26) addr = ANX74XX_REG_PD_RX_DATA_OBJ_M; rv = tcpc_read(port, addr + i, ®); if (rv) break; buf[i] = reg; } clear_recvd_msg_int(port); return rv; } static int anx74xx_check_cc_type(int cc_reg) { int cc; switch (cc_reg & ANX74XX_REG_CC_STATUS_MASK) { case BIT_VALUE_OF_SRC_CC_RD: cc = TYPEC_CC_VOLT_RD; break; case BIT_VALUE_OF_SRC_CC_RA: cc = TYPEC_CC_VOLT_RA; break; case BIT_VALUE_OF_SNK_CC_DEFAULT: cc = TYPEC_CC_VOLT_SNK_DEF; break; case BIT_VALUE_OF_SNK_CC_1_P_5: cc = TYPEC_CC_VOLT_SNK_1_5; break; case BIT_VALUE_OF_SNK_CC_3_P_0: cc = TYPEC_CC_VOLT_SNK_3_0; break; default: /* If no bits are set, then nothing is attached */ cc = TYPEC_CC_VOLT_OPEN; } return cc; } static int anx74xx_tcpm_get_cc(int port, int *cc1, int *cc2) { int rv = EC_SUCCESS; int reg = 0; /* Read tcpc cc status register */ rv |= tcpc_read(port, ANX74XX_REG_CC_STATUS, ®); /* Check for cc1 type */ *cc1 = anx74xx_check_cc_type(reg); /* * Check for cc2 type (note cc2 bits are upper 4 of cc status * register. */ *cc2 = anx74xx_check_cc_type(reg >> 4); /* clear HPD status*/ if (!(*cc1) && !(*cc2)) { anx74xx_tcpc_clear_hpd_status(port); #ifdef CONFIG_USB_PD_TCPM_MUX anx74xx_tcpm_mux_exit(port); #endif } return EC_SUCCESS; } static int anx74xx_rp_control(int port, int rp) { int reg; int rv; rv = tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_6, ®); if (rv) return EC_ERROR_UNKNOWN; /* clear Bit[0,1] R_RP to default Rp's value */ reg &= ~0x03; switch (rp) { case TYPEC_RP_1A5: /* Set Rp strength to 12K for presenting 1.5A */ reg |= ANX74XX_REG_CC_PULL_RP_12K; break; case TYPEC_RP_3A0: /* Set Rp strength to 4K for presenting 3A */ reg |= ANX74XX_REG_CC_PULL_RP_4K; break; case TYPEC_RP_USB: default: /* default: Set Rp strength to 36K */ break; } return tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_6, reg); } static int anx74xx_tcpm_select_rp_value(int port, int rp) { /* For ANX3429 cannot get cc correctly when Rp != USB_Default */ selected_rp[port] = rp; return EC_SUCCESS; } static int anx74xx_cc_software_ctrl(int port, int enable) { int rv; int reg; rv = tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, ®); if (rv) return EC_ERROR_UNKNOWN; if (enable) reg |= ANX74XX_REG_CC_SW_CTRL_ENABLE; else reg &= ~ANX74XX_REG_CC_SW_CTRL_ENABLE; rv |= tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg); return rv; } static int anx74xx_tcpm_set_cc(int port, int pull) { int rv = EC_SUCCESS; int reg; /* Enable CC software Control */ rv = anx74xx_cc_software_ctrl(port, 1); if (rv) return EC_ERROR_UNKNOWN; switch (pull) { case TYPEC_CC_RP: /* Enable Rp */ rv |= tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, ®); if (rv) return EC_ERROR_UNKNOWN; reg |= ANX74XX_REG_CC_PULL_RP; rv |= tcpc_write(port, ANX74XX_REG_ANALOG_STATUS, reg); break; case TYPEC_CC_RD: /* Enable Rd */ rv |= tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, ®); if (rv) return EC_ERROR_UNKNOWN; reg &= ANX74XX_REG_CC_PULL_RD; rv |= tcpc_write(port, ANX74XX_REG_ANALOG_STATUS, reg); break; default: rv = EC_ERROR_UNKNOWN; break; } return rv; } #if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \ defined(CONFIG_USB_PD_TCPC_LOW_POWER) static void anx74xx_handle_power_mode(int port, int mode) { if (mode == ANX74XX_STANDBY_MODE) { anx74xx_set_power_mode(port, mode); } else if (anx[port].prev_mode != ANX74XX_NORMAL_MODE) { /* * TODO: Interrupt high follows CC line hence ignore multiple * interrupts. */ anx74xx_tcpm_init(port); } } static int anx74xx_tcpc_drp_toggle(int port, int enable) { /* * When using low power mode, this function is an entry to point to * bring the ANX3429 in to or out of standby mode. DRP toggle is * associated with the chip being in standby mode. */ anx74xx_handle_power_mode(port, enable ? ANX74XX_STANDBY_MODE : ANX74XX_NORMAL_MODE); return EC_SUCCESS; } #endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE && CONFIG_USB_PD_TCPC_LOW_POWER */ static int anx74xx_tcpm_set_polarity(int port, int polarity) { int reg, mux_state, rv = EC_SUCCESS; rv |= tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, ®); if (polarity) /* Inform ANX to use CC2 */ reg &= ~ANX74XX_REG_SELECT_CC1; else /* Inform ANX to use CC1 */ reg |= ANX74XX_REG_SELECT_CC1; rv |= tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg); anx[port].polarity = polarity; /* Update mux polarity */ #ifdef CONFIG_USB_PD_TCPM_MUX mux_state = anx[port].mux_state & ~MUX_POLARITY_INVERTED; if (polarity) mux_state |= MUX_POLARITY_INVERTED; anx74xx_tcpm_mux_set(port, mux_state); #endif return rv; } int anx74xx_tcpc_get_fw_version(int port, int *version) { return tcpc_read(port, ANX74XX_REG_FW_VERSION, version); } static int anx74xx_tcpm_set_vconn(int port, int enable) { int reg, rv = EC_SUCCESS; /* switch VCONN to Non CC line */ rv |= tcpc_read(port, ANX74XX_REG_INTP_VCONN_CTRL, ®); if (rv) return EC_ERROR_UNKNOWN; if (enable) { if (anx[port].polarity) reg |= ANX74XX_REG_VCONN_1_ENABLE; else reg |= ANX74XX_REG_VCONN_2_ENABLE; } else { reg &= ANX74XX_REG_VCONN_DISABLE; } rv |= tcpc_write(port, ANX74XX_REG_INTP_VCONN_CTRL, reg); anx[port].vconn_en = enable; return rv; } static int anx74xx_tcpm_set_msg_header(int port, int power_role, int data_role) { return tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_1, ANX74XX_REG_AUTO_GOODCRC_SET(!!data_role, !!power_role)); } static int anx74xx_alert_status(int port, int *alert) { int reg, rv = EC_SUCCESS; /* Clear soft irq bit */ rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_SOURCE_3, ANX74XX_REG_CLEAR_SOFT_IRQ); *alert = 0; rv = tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, ®); if (rv) return EC_ERROR_UNKNOWN; /*Clear msg received bit, until read it by TCPM*/ rv |= tcpc_write(port, ANX74XX_REG_RECVD_MSG_INT, (reg & 0xFE)); if (reg & ANX74XX_REG_IRQ_CC_MSG_INT) *alert |= ANX74XX_REG_ALERT_MSG_RECV; if (reg & ANX74XX_REG_IRQ_CC_STATUS_INT) *alert |= ANX74XX_REG_ALERT_CC_CHANGE; if (reg & ANX74XX_REG_IRQ_GOOD_CRC_INT) *alert |= ANX74XX_REG_ALERT_TX_ACK_RECV; if (reg & ANX74XX_REG_IRQ_TX_FAIL_INT) *alert |= ANX74XX_REG_ALERT_TX_MSG_ERROR; rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_SOURCE_1, ®); if (rv) return EC_ERROR_UNKNOWN; /* Clears interrupt bits */ rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_SOURCE_1, reg); /* Check for Hard Reset done bit */ if (reg & ANX74XX_REG_ALERT_TX_HARD_RESETOK) *alert |= ANX74XX_REG_ALERT_TX_HARD_RESETOK; /* Read TCPC Alert register2 */ rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_SOURCE_2, ®); /* Clears interrupt bits */ rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_SOURCE_2, reg); if (reg & ANX74XX_REG_EXT_HARD_RST) *alert |= ANX74XX_REG_ALERT_HARD_RST_RECV; return rv; } static int anx74xx_tcpm_set_rx_enable(int port, int enable) { int reg, rv; rv = tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK, ®); if (rv) return rv; if (enable) { reg &= ~(ANX74XX_REG_IRQ_CC_MSG_INT); anx74xx_tcpm_set_auto_good_crc(port, 1); anx74xx_rp_control(port, selected_rp[port]); } else { /* Disable RX message by masking interrupt */ reg |= (ANX74XX_REG_IRQ_CC_MSG_INT); anx74xx_tcpm_set_auto_good_crc(port, 0); anx74xx_rp_control(port, TYPEC_RP_USB); } /*When this function was call, the interrupt status shall be cleared*/ tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, 0); return tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK, reg); } #ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC static int anx74xx_tcpm_get_vbus_level(int port) { int reg = 0; tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, ®); return ((reg & ANX74XX_REG_VBUS_STATUS) ? 1 : 0); } #endif static int anx74xx_tcpm_get_message(int port, uint32_t *payload, int *head) { int reg = 0, rv = EC_SUCCESS; int len = 0; /* Fetch the header */ rv |= tcpc_read16(port, ANX74XX_REG_PD_HEADER, ®); if (rv) { clear_recvd_msg_int(port); return EC_ERROR_UNKNOWN; } *head = reg; len = PD_HEADER_CNT(*head) * 4; if (!len) { clear_recvd_msg_int(port); return EC_SUCCESS; } /* Receive message : assuming payload have enough * memory allocated */ rv |= anx74xx_read_pd_obj(port, (uint8_t *)payload, len); if (rv) return EC_ERROR_UNKNOWN; return rv; } static int anx74xx_tcpm_transmit(int port, enum tcpm_transmit_type type, uint16_t header, const uint32_t *data) { uint8_t len = 0; int ret = 0, reg = 0; switch (type) { /* ANX is aware of type */ case TCPC_TX_SOP: case TCPC_TX_SOP_PRIME: case TCPC_TX_SOP_PRIME_PRIME: len = PD_HEADER_CNT(header) * 4 + 2; ret = anx74xx_send_message(port, header, data, type, len); break; case TCPC_TX_HARD_RESET: /* Request HARD RESET */ tcpc_read(port, ANX74XX_REG_TX_CTRL_1, ®); reg |= ANX74XX_REG_TX_HARD_RESET_REQ; ret = tcpc_write(port, ANX74XX_REG_TX_CTRL_1, reg); /*After Hard Reset, TCPM shall disable goodCRC*/ anx74xx_tcpm_set_auto_good_crc(port, 0); break; case TCPC_TX_CABLE_RESET: /* Request CABLE RESET */ tcpc_read(port, ANX74XX_REG_TX_CTRL_1, ®); reg |= ANX74XX_REG_TX_CABLE_RESET_REQ; ret = tcpc_write(port, ANX74XX_REG_TX_CTRL_1, reg); break; case TCPC_TX_BIST_MODE_2: /* Request BIST MODE 2 */ reg = ANX74XX_REG_TX_BIST_START | ANX74XX_REG_TX_BIXT_FOREVER | (0x02 << 4); ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, reg); msleep(1); ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, reg | ANX74XX_REG_TX_BIST_ENABLE); msleep(30); tcpc_read(port, ANX74XX_REG_TX_BIST_CTRL, ®); ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, reg | ANX74XX_REG_TX_BIST_STOP); ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, reg & (~ANX74XX_REG_TX_BIST_STOP)); ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, 0); break; default: return EC_ERROR_UNIMPLEMENTED; } return ret; } void anx74xx_tcpc_alert(int port) { int status; /* Check the alert status from anx74xx */ if (anx74xx_alert_status(port, &status)) status = 0; if (status) { if (status & ANX74XX_REG_ALERT_CC_CHANGE) { /* CC status changed, wake task */ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0); } /* If alert is to receive a message */ if (status & ANX74XX_REG_ALERT_MSG_RECV) { /* Set a PD_EVENT_RX */ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_RX, 0); } if (status & ANX74XX_REG_ALERT_TX_ACK_RECV) { /* Inform PD about this TX success */ pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); } if (status & ANX74XX_REG_ALERT_TX_MSG_ERROR) { /* let PD does not wait for this */ pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); } if (status & ANX74XX_REG_ALERT_TX_CABLE_RESETOK) { /* ANX hardware clears the request bit */ pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); } if (status & ANX74XX_REG_ALERT_TX_HARD_RESETOK) { /* ANX hardware clears the request bit */ pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); } if (status & ANX74XX_REG_ALERT_HARD_RST_RECV) { /* hard reset received */ pd_execute_hard_reset(port); task_wake(PD_PORT_TO_TASK_ID(port)); } } } static int anx74xx_tcpm_init(int port) { int rv = 0, reg; memset(&anx[port], 0, sizeof(struct anx_state)); /* Bring chip in normal mode to work */ anx74xx_set_power_mode(port, ANX74XX_NORMAL_MODE); /* Initialize analog section of ANX */ rv |= anx74xx_init_analog(port); /* disable all interrupts */ rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_MASK_1, ANX74XX_REG_CLEAR_SET_BITS); /* Initialize interrupt polarity */ rv |= tcpc_write(port, ANX74XX_REG_IRQ_STATUS, tcpc_config[port].pol == TCPC_ALERT_ACTIVE_LOW ? ANX74XX_REG_IRQ_POL_LOW : ANX74XX_REG_IRQ_POL_HIGH); /* unmask interrupts */ rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_MASK_1, ®); reg &= (~ANX74XX_REG_ALERT_TX_MSG_ERROR); reg &= (~ANX74XX_REG_ALERT_TX_CABLE_RESETOK); reg &= (~ANX74XX_REG_ALERT_TX_HARD_RESETOK); rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_MASK_1, reg); rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_MASK_2, ®); reg &= (~ANX74XX_REG_EXT_HARD_RST); rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_MASK_2, reg); /* HPD pin output enable*/ rv |= tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, ANX74XX_REG_HPD_DEFAULT); if (rv) return EC_ERROR_UNKNOWN; /* Set AVDD10_BMC to 1.08 */ rv |= tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_5, ®); if (rv) return EC_ERROR_UNKNOWN; rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, (reg & 0xf3)); if (rv) return EC_ERROR_UNKNOWN; /* Decrease BMC TX lowest swing voltage */ rv |= tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_11, ®); if (rv) return EC_ERROR_UNKNOWN; rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_11, (reg & 0x3f) | 0x40); if (rv) return EC_ERROR_UNKNOWN; /* Set BMC TX cap slew rate to 400ns */ rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_12, 0x4); if (rv) return EC_ERROR_UNKNOWN; tcpm_get_chip_info(port, 1, NULL); return EC_SUCCESS; } /* * Dissociate from the TCPC. */ static int anx74xx_tcpm_release(int port) { return EC_SUCCESS; } const struct tcpm_drv anx74xx_tcpm_drv = { .init = &anx74xx_tcpm_init, .release = &anx74xx_tcpm_release, .get_cc = &anx74xx_tcpm_get_cc, #ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC .get_vbus_level = &anx74xx_tcpm_get_vbus_level, #endif .select_rp_value = &anx74xx_tcpm_select_rp_value, .set_cc = &anx74xx_tcpm_set_cc, .set_polarity = &anx74xx_tcpm_set_polarity, .set_vconn = &anx74xx_tcpm_set_vconn, .set_msg_header = &anx74xx_tcpm_set_msg_header, .set_rx_enable = &anx74xx_tcpm_set_rx_enable, .get_message = &anx74xx_tcpm_get_message, .transmit = &anx74xx_tcpm_transmit, .tcpc_alert = &anx74xx_tcpc_alert, #ifdef CONFIG_USB_PD_DISCHARGE_TCPC .tcpc_discharge_vbus = &anx74xx_tcpc_discharge_vbus, #endif .get_chip_info = &tcpci_get_chip_info, #if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \ defined(CONFIG_USB_PD_TCPC_LOW_POWER) .drp_toggle = &anx74xx_tcpc_drp_toggle, #endif }; #ifdef CONFIG_CMD_I2C_STRESS_TEST_TCPC struct i2c_stress_test_dev anx74xx_i2c_stress_test_dev = { .reg_info = { .read_reg = ANX74XX_REG_VENDOR_ID_L, .read_val = ANX74XX_VENDOR_ID & 0xFF, .write_reg = ANX74XX_REG_CC_SOFTWARE_CTRL, }, .i2c_read = &tcpc_i2c_read, .i2c_write = &tcpc_i2c_write, }; #endif /* CONFIG_CMD_I2C_STRESS_TEST_TCPC */