diff options
-rw-r--r-- | driver/build.mk | 1 | ||||
-rw-r--r-- | driver/tcpm/anx74xx.c | 799 | ||||
-rw-r--r-- | driver/tcpm/anx74xx.h | 154 | ||||
-rw-r--r-- | include/config.h | 1 |
4 files changed, 955 insertions, 0 deletions
diff --git a/driver/build.mk b/driver/build.mk index 6f3f9b82fd..fc571af018 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -78,6 +78,7 @@ 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 driver-$(CONFIG_USB_PD_TCPM_ITE83XX)+=tcpm/it83xx.o +driver-$(CONFIG_USB_PD_TCPM_ANX74XX)+=tcpm/anx74xx.o # USB switches driver-$(CONFIG_USB_SWITCH_PI3USB9281)+=usb_switch_pi3usb9281.o diff --git a/driver/tcpm/anx74xx.c b/driver/tcpm/anx74xx.c new file mode 100644 index 0000000000..b2b33e8576 --- /dev/null +++ b/driver/tcpm/anx74xx.c @@ -0,0 +1,799 @@ +/* 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 "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" + +struct anx_state { + int polarity; + int pull; + int vconn_en; + int mux_state; +}; +static struct anx_state anx[CONFIG_USB_PD_PORT_COUNT]; + +static void anx74xx_tcpm_set_auto_good_crc(int port, int enable) +{ + int reg; + + if (enable) { + /* Set default header for Good CRC auto reply */ + tcpc_read(port, ANX74XX_REG_TX_MSG_HEADER, ®); + reg |= (PD_REV20 << ANX74XX_REG_SPEC_REV_BIT_POS); + reg |= ANX74XX_REG_AUTO_GOODCRC_EN; + tcpc_write(port, ANX74XX_REG_TX_MSG_HEADER, reg); + + reg = ANX74XX_REG_ENABLE_GOODCRC; + tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_2, reg); + /* Set bit-0 if enable, reset bit-0 if disable */ + tcpc_read(port, ANX74XX_REG_TX_AUTO_GOODCRC_1, ®); + reg |= ANX74XX_REG_AUTO_GOODCRC_EN; + } else { + reg &= ~ANX74XX_REG_AUTO_GOODCRC_EN; + tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_2, 0); + } + tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_1, reg); +} + +static void anx74xx_set_power_mode(int port, int mode) +{ + switch (mode) { + case ANX74XX_NORMAL_MODE: + /* Set PWR_EN and RST_N GPIO pins high */ + board_set_tcpc_power_mode(port, 1); + break; + case ANX74XX_STANDBY_MODE: + /* Disable PWR_EN, keep Digital and analog block + * ON for cable detection + */ + board_set_tcpc_power_mode(port, 0); + break; + default: + break; + } +} + +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); +} + +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); +} + +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) { + tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, ®); + reg &= ~ANX74XX_REG_HPD_OUT_DATA; + tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); + msleep(1); + reg |= ANX74XX_REG_HPD_OUT_DATA; + tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg); + } +} + +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_set(int i2c_addr, mux_state_t mux_state) +{ + int reg = 0, val = 0; + int rv; + int port = i2c_addr; + + 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) { + val = ANX74XX_REG_MUX_DP_MODE_BDF_CC2; + reg |= ANX74XX_REG_MUX_SSTX_B; + } else { + val = 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) { + val = ANX74XX_REG_MUX_DP_MODE_ACE_CC2; + reg |= ANX74XX_REG_MUX_ML2_B; + } else { + val = ANX74XX_REG_MUX_DP_MODE_ACE_CC1; + reg |= ANX74XX_REG_MUX_ML2_A; + } + } else { + return EC_ERROR_UNIMPLEMENTED; + } + + rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_1, val); + rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, reg); + anx[port].mux_state = mux_state; + + return rv; +} + +/* 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; +} + +static int anx74xx_tcpm_mux_exit(int port) +{ + int rv = EC_SUCCESS; + + rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, + ANX74XX_REG_MODE_TRANS); + rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_1, + 0x0); + rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, + 0x04); + rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, + 0x0); + if (rv) + return EC_ERROR_UNKNOWN; + return rv; +} + +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_set_mux(int port, int polarity) +{ + int reg, rv = EC_SUCCESS; + + rv = tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, ®); + if (rv) + return EC_ERROR_UNKNOWN; + if (polarity) { + reg |= ANX74XX_REG_AUX_SWAP_SET_CC2; + reg &= ~ANX74XX_REG_AUX_SWAP_SET_CC1; + } else { + reg |= ANX74XX_REG_AUX_SWAP_SET_CC1; + reg &= ~ANX74XX_REG_AUX_SWAP_SET_CC2; + } + rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, 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; + + + /* 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 receive message interrupt bit(bit-0) */ + rv |= tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, ®); + rv |= tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, + reg & (~0x01)); + + return rv; +} + +static int anx74xx_tcpm_get_cc(int port, int *cc1, int *cc2) +{ + int rv = EC_SUCCESS; + int reg = 0; + int status = 0; + + rv |= tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, ®); + + if (!anx[port].pull) {/* get CC in sink mode */ + rv |= tcpc_read(port, ANX74XX_REG_POWER_DOWN_CTRL, ®); + if (rv) + return EC_ERROR_UNKNOWN; + /* CC1 */ + if (reg & ANX74XX_REG_STATUS_CC1_VRD_USB) + *cc1 = TYPEC_CC_VOLT_SNK_DEF; + else if (reg & ANX74XX_REG_STATUS_CC1_VRD_1P5) + *cc1 = TYPEC_CC_VOLT_SNK_1_5; + else if (reg & ANX74XX_REG_STATUS_CC1_VRD_3P0) + *cc1 = TYPEC_CC_VOLT_SNK_3_0; + else + *cc1 = TYPEC_CC_VOLT_OPEN; + /* CC2 */ + if (reg & ANX74XX_REG_STATUS_CC2_VRD_USB) + *cc2 = TYPEC_CC_VOLT_SNK_DEF; + else if (reg & ANX74XX_REG_STATUS_CC2_VRD_1P5) + *cc2 = TYPEC_CC_VOLT_SNK_1_5; + else if (reg & ANX74XX_REG_STATUS_CC2_VRD_3P0) + *cc2 = TYPEC_CC_VOLT_SNK_3_0; + else + *cc2 = TYPEC_CC_VOLT_OPEN; + } else {/* get CC in source mode */ + rv |= tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_7, ®); + if (rv) + return EC_ERROR_UNKNOWN; + /* CC1 */ + status = ANX74XX_REG_STATUS_CC1(reg); + if (status) { + if ((status & ANX74XX_REG_STATUS_CC_RA) == + ANX74XX_REG_STATUS_CC_RA) + *cc1 = TYPEC_CC_VOLT_RA; + else if (status & ANX74XX_REG_STATUS_CC_RD) + *cc1 = TYPEC_CC_VOLT_RD; + } else { + *cc1 = TYPEC_CC_VOLT_OPEN; + } + /* CC2 */ + status = ANX74XX_REG_STATUS_CC2(reg); + if (status) { + if ((status & ANX74XX_REG_STATUS_CC_RA) == + ANX74XX_REG_STATUS_CC_RA) + *cc2 = TYPEC_CC_VOLT_RA; + else if (status & ANX74XX_REG_STATUS_CC_RD) + *cc2 = TYPEC_CC_VOLT_RD; + } else { + *cc2 = TYPEC_CC_VOLT_OPEN; + } + } + /* 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_tcpm_set_cc(int port, int pull) +{ + int rv = EC_SUCCESS; + int reg; + + /* Enable CC software Control */ + rv |= tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, ®); + if (rv) + return EC_ERROR_UNKNOWN; + reg |= ANX74XX_REG_CC_SW_CTRL_ENABLE; + rv |= tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg); + 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); + anx[port].pull = 1; + 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); + anx[port].pull = 0; + break; + default: + rv = EC_ERROR_UNKNOWN; + break; + } + + return rv; +} + +static int anx74xx_tcpm_set_polarity(int port, int polarity) +{ + int reg, 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; + rv |= anx74xx_set_mux(port, polarity); + + /* Default DP pin mux D */ +#ifdef CONFIG_USB_PD_TCPM_MUX + anx74xx_tcpm_mux_set(port, TYPEC_MUX_DOCK); +#endif + return rv; +} + +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) +{ + int rv = 0, reg; + + rv |= tcpc_read(port, ANX74XX_REG_TX_MSG_HEADER, ®); + reg |= ((!!power_role) << ANX74XX_REG_PWR_ROLE_BIT_POS); + reg |= ((!!data_role) << ANX74XX_REG_DATA_ROLE_BIT_POS); + rv |= tcpc_write(port, ANX74XX_REG_TX_MSG_HEADER, reg); + + return rv; +} + +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); + + /* Read TCPC Alert register1 */ + 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); + + *alert = reg; + rv = tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, ®); + if (rv) + return EC_ERROR_UNKNOWN; + + if (reg & ANX74XX_REG_IRQ_CC_MSG_INT) + *alert |= ANX74XX_REG_ALERT_MSG_RECV; + else + *alert &= (~ANX74XX_REG_ALERT_MSG_RECV); + + if (reg & ANX74XX_REG_IRQ_CC_STATUS_INT) { + *alert |= ANX74XX_REG_ALERT_CC_CHANGE; + rv |= tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, + reg & 0xfd); + } else { + *alert &= (~ANX74XX_REG_ALERT_CC_CHANGE); + } + + if (reg & ANX74XX_REG_IRQ_GOOD_CRC_INT) { + *alert |= ANX74XX_REG_ALERT_TX_ACK_RECV; + rv |= tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, + reg & 0xfb); + } else { + *alert &= (~ANX74XX_REG_ALERT_TX_ACK_RECV); + } + + if (reg & ANX74XX_REG_IRQ_TX_FAIL_INT) { + *alert |= ANX74XX_REG_ALERT_TX_MSG_ERROR; + rv |= tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, + reg & 0xf7); + } + /* 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; + else + *alert &= (~ANX74XX_REG_ALERT_HARD_RST_RECV); + + return rv; +} + +static int anx74xx_tcpm_set_rx_enable(int port, int enable) +{ + int reg, rv = 0; + + rv |= tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK, ®); + if (enable) + reg &= ~(ANX74XX_REG_IRQ_CC_MSG_INT); + else/* Disable RX message by masking interrupt */ + reg |= (ANX74XX_REG_IRQ_CC_MSG_INT); + anx74xx_tcpm_set_auto_good_crc(port, enable); + rv |= tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK, reg); + + return rv; +} + +#ifdef CONFIG_USB_PD_TCPM_VBUS +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) { + *head = 0; + /* Clear receive message interrupt bit(bit-0) */ + tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, ®); + tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, + reg & (~0x01)); + + return EC_ERROR_UNKNOWN; + } + *head = reg; + len = PD_HEADER_CNT(*head) * 4; + if (!len) { + /* Clear receive message interrupt bit(bit-0) */ + tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, ®); + tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, + reg & (~0x01)); + return EC_SUCCESS; + } + + /* Receive message : assuming payload have enough + * memory allocated + */ + rv |= anx74xx_read_pd_obj(port, (uint8_t *)payload, len); + if (rv) { + *head = 0; + 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); + 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 */ + 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)); + } + } +} + +int anx74xx_tcpm_init(int port) +{ + int rv = 0, reg; + + memset(anx, 0, CONFIG_USB_PD_PORT_COUNT*sizeof(struct anx_state)); + /* Bring chip in normal mode to work */ + anx74xx_set_power_mode(port, ANX74XX_NORMAL_MODE); + + /* Set Pd dual role mode */ + pd_set_dual_role(PD_DRP_TOGGLE_ON); + + /* 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, + ANX74XX_INT_ACTIVE_POLARITY); + + /* 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; + + return EC_SUCCESS; +} + +const struct tcpm_drv anx74xx_tcpm_drv = { + .init = &anx74xx_tcpm_init, + .get_cc = &anx74xx_tcpm_get_cc, +#ifdef CONFIG_USB_PD_TCPM_VBUS + .get_vbus_level = &anx74xx_tcpm_get_vbus_level, +#endif + .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, +}; diff --git a/driver/tcpm/anx74xx.h b/driver/tcpm/anx74xx.h new file mode 100644 index 0000000000..b25de49bf4 --- /dev/null +++ b/driver/tcpm/anx74xx.h @@ -0,0 +1,154 @@ +/* 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. + */ + +/* USB Power delivery port management */ + +#ifndef __CROS_EC_USB_PD_TCPM_ANX74XX_H +#define __CROS_EC_USB_PD_TCPM_ANX74XX_H + +#define ANX74XX_REG_IRQ_POL_LOW 0x00 +#define ANX74XX_REG_IRQ_POL_HIGH 0x02 +#define ANX74XX_INT_ACTIVE_POLARITY ANX74XX_REG_IRQ_POL_HIGH + +#define ANX74XX_REG_IRQ_STATUS 0x53 + +#define ANX74XX_REG_INTP_VCONN_CTRL 0x33 +#define ANX74XX_REG_VCONN_DISABLE 0x0f +#define ANX74XX_REG_VCONN_1_ENABLE (1 << 4) +#define ANX74XX_REG_VCONN_2_ENABLE (1 << 5) + +#define ANX74XX_STANDBY_MODE (0) +#define ANX74XX_NORMAL_MODE (1) + +#define ANX74XX_REG_TX_CTRL_1 0x81 +#define ANX74XX_REG_TX_HARD_RESET_REQ (1 << 1) +#define ANX74XX_REG_TX_CABLE_RESET_REQ (1 << 2) + +#define ANX74XX_REG_TX_CTRL_2 0x82 +#define ANX74XX_REG_TX_WR_FIFO 0x83 +#define ANX74XX_REG_TX_FIFO_CTRL 0x9a +#define ANX74XX_REG_TX_HEADER_L 0x2c +#define ANX74XX_REG_TX_HEADER_H 0x2d +#define ANX74XX_REG_TX_START_ADDR_0 0x6d +#define ANX74XX_REG_TX_START_ADDR_1 0xd0 + +#define ANX74XX_REG_CTRL_COMMAND 0xdb +#define ANX74XX_REG_TX_SEND_DATA_REQ (1 << 0) +#define ANX74XX_REG_TX_HARD_RST_REQ (1 << 1) + +#define ANX74XX_REG_TX_BIST_CTRL 0x9D +#define ANX74XX_REG_TX_BIST_MODE (1 << 4) +#define ANX74XX_REG_TX_BIST_STOP (1 << 3) +#define ANX74XX_REG_TX_BIXT_FOREVER (1 << 2) +#define ANX74XX_REG_TX_BIST_ENABLE (1 << 1) +#define ANX74XX_REG_TX_BIST_START (1 << 0) + +#define ANX74XX_REG_PD_HEADER 0x69 +#define ANX74XX_REG_PD_RX_DATA_OBJ 0x11 +#define ANX74XX_REG_PD_RX_DATA_OBJ_M 0x4d + +#define ANX74XX_REG_ANALOG_STATUS 0x40 +#define ANX74XX_REG_VBUS_STATUS (1 << 4) +#define ANX74XX_REG_CC_PULL_RD 0xfd +#define ANX74XX_REG_CC_PULL_RP 0x02 + +#define ANX74XX_REG_TX_AUTO_GOODCRC_1 0x9c +#define ANX74XX_REG_TX_AUTO_GOODCRC_2 0x94 +#define ANX74XX_REG_AUTO_GOODCRC_EN 0x01 +#define ANX74XX_REG_ENABLE_GOODCRC 0x38 + +#define ANX74XX_REG_ANALOG_CTRL_1 0x42 +#define ANX74XX_REG_ANALOG_CTRL_5 0x46 +#define ANX74XX_REG_R_SWITCH_CC_CLR 0x0f +#define ANX74XX_REG_R_SWITCH_CC2_SET 0x10 +#define ANX74XX_REG_R_SWITCH_CC1_SET 0x20 +#define ANX74XX_REG_AUX_SWAP_SET_CC1 0x30 +#define ANX74XX_REG_AUX_SWAP_SET_CC2 0xc0 + +#define ANX74XX_REG_MUX_DP_MODE_ACE_CC1 0x49 +#define ANX74XX_REG_MUX_DP_MODE_ACE_CC2 0x86 +#define ANX74XX_REG_MUX_DP_MODE_BDF_CC1 0x61 +#define ANX74XX_REG_MUX_DP_MODE_BDF_CC2 0x92 + +#define ANX74XX_REG_MUX_ML2_B (1 << 4) +#define ANX74XX_REG_MUX_ML2_A (1 << 5) +#define ANX74XX_REG_MUX_SSTX_B (1 << 6) +#define ANX74XX_REG_MUX_SSTX_A (1 << 7) + +#define ANX74XX_REG_CC_SOFTWARE_CTRL 0x4a +#define ANX74XX_REG_CC_SW_CTRL_ENABLE 0x01 +#define ANX74XX_REG_TX_MODE_ENABLE 0x04 + +#define ANX74XX_REG_SELECT_CC1 0x02 + +#define ANX74XX_REG_GPIO_CTRL_4_5 0x3f +#define ANX74XX_REG_VBUS_OP_ENABLE 0x04 +#define ANX74XX_REG_VBUS_GPIO_MODE 0xfe + +#define ANX74XX_REG_IRQ_EXT_MASK_1 0x3b +#define ANX74XX_REG_IRQ_EXT_MASK_2 0x3c +#define ANX74XX_REG_IRQ_EXT_SOURCE_1 0x3e +#define ANX74XX_REG_IRQ_EXT_SOURCE_2 0x4e +#define ANX74XX_REG_EXT_HARD_RST (1 << 2) +#define ANX74XX_REG_IRQ_EXT_SOURCE_3 0x4f +#define ANX74XX_REG_CLEAR_SOFT_IRQ (1 << 2) + +#define ANX74XX_REG_IRQ_SOURCE_RECV_MSG 0x6b +#define ANX74XX_REG_IRQ_CC_MSG_INT (1 << 0) +#define ANX74XX_REG_IRQ_CC_STATUS_INT (1 << 1) +#define ANX74XX_REG_IRQ_GOOD_CRC_INT (1 << 2) +#define ANX74XX_REG_IRQ_TX_FAIL_INT (1 << 3) +#define ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK 0x6c + +#define ANX74XX_REG_CLEAR_SET_BITS 0xff +#define ANX74XX_REG_ALERT_HARD_RST_RECV (1 << 6) +#define ANX74XX_REG_ALERT_MSG_RECV (1 << 5) +#define ANX74XX_REG_ALERT_TX_MSG_ERROR (1 << 4) +#define ANX74XX_REG_ALERT_TX_ACK_RECV (1 << 3) +#define ANX74XX_REG_ALERT_TX_CABLE_RESETOK (1 << 2) +#define ANX74XX_REG_ALERT_TX_HARD_RESETOK (1 << 1) +#define ANX74XX_REG_ALERT_CC_CHANGE (1 << 0) + +#define ANX74XX_REG_ANALOG_CTRL_2 0x43 +#define ANX74XX_REG_MODE_TRANS 0x01 + +#define ANX74XX_REG_SET_VBUS 0x20 + +#define ANX74XX_REG_ANALOG_CTRL_7 0x48 +#define ANX74XX_REG_STATUS_CC_RD 0x01 +#define ANX74XX_REG_STATUS_CC_RA 0x03 +#define ANX74XX_REG_STATUS_CC1(reg) ((reg & 0x0C) >> 2) +#define ANX74XX_REG_STATUS_CC2(reg) ((reg & 0x03) >> 0) + +#define ANX74XX_REG_HPD_CONTROL 0xfd + +#define ANX74XX_REG_HPD_CTRL_0 0x36 +#define ANX74XX_REG_DISCHARGE_CTRL 0x80 +#define ANX74XX_REG_HPD_OP_MODE 0x08 +#define ANX74XX_REG_HPD_DEFAULT 0x00 +#define ANX74XX_REG_HPD_OUT_DATA 0x10 + +#define ANX74XX_REG_TX_MSG_HEADER 0x9c +#define ANX74XX_REG_SPEC_REV_BIT_POS (3) +#define ANX74XX_REG_PWR_ROLE_BIT_POS (2) +#define ANX74XX_REG_DATA_ROLE_BIT_POS (1) + +#define ANX74XX_REG_POWER_DOWN_CTRL 0x0d +#define ANX74XX_REG_STATUS_CC1_VRD_USB (1 << 7) +#define ANX74XX_REG_STATUS_CC1_VRD_1P5 (1 << 6) +#define ANX74XX_REG_STATUS_CC1_VRD_3P0 (1 << 5) +#define ANX74XX_REG_STATUS_CC2_VRD_USB (1 << 4) +#define ANX74XX_REG_STATUS_CC2_VRD_1P5 (1 << 3) +#define ANX74XX_REG_STATUS_CC2_VRD_3P0 (1 << 2) + +extern const struct tcpm_drv anx74xx_tcpm_drv; +extern const struct usb_mux_driver anx74xx_tcpm_usb_mux_driver; +void anx74xx_tcpc_set_vbus(int port, int enable); +void anx74xx_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq); +void anx74xx_tcpc_clear_hpd_status(int port); + +#endif /* __CROS_EC_USB_PD_TCPM_ANX74XX_H */ diff --git a/include/config.h b/include/config.h index 709ee2e41e..eed5b34302 100644 --- a/include/config.h +++ b/include/config.h @@ -1875,6 +1875,7 @@ #undef CONFIG_USB_PD_TCPM_TCPCI #undef CONFIG_USB_PD_TCPM_FUSB302 #undef CONFIG_USB_PD_TCPM_ITE83XX +#undef CONFIG_USB_PD_TCPM_ANX74XX /* * Use this option if the TCPC port controller supports the optional register |