From 82a357a385f9eee75bfbca9911e404d54b0c3217 Mon Sep 17 00:00:00 2001 From: Dylan Lai Date: Fri, 9 Mar 2018 13:54:08 +0800 Subject: TCPM: Add TCPM driver for Analogix anx7447 chip Driver implements TCPC for ANX7447 chip. Enable Type C port for USB and DP alt mode. BUG=b:73793947 BRANCH=NONE TEST=tested compiled binary for pdeval-stm32f072 board with this patch. Power contract establishment, port role swap, DP alt mode works fine. Change-Id: Ic11e499fc5fb4aba7732c75e4cb2fee54828c616 Reviewed-on: https://chromium-review.googlesource.com/956790 Commit-Ready: Scott Collyer Tested-by: Scott Collyer Reviewed-by: Scott Collyer --- board/pdeval-stm32f072/board.c | 11 +- board/pdeval-stm32f072/board.h | 22 +- board/pdeval-stm32f072/ec.tasklist | 3 +- board/pdeval-stm32f072/gpio.inc | 3 + board/pdeval-stm32f072/usb_pd_policy.c | 128 ++++++++++- driver/build.mk | 1 + driver/tcpm/anx7447.c | 393 +++++++++++++++++++++++++++++++++ driver/tcpm/anx7447.h | 61 +++++ 8 files changed, 599 insertions(+), 23 deletions(-) create mode 100644 driver/tcpm/anx7447.c create mode 100644 driver/tcpm/anx7447.h diff --git a/board/pdeval-stm32f072/board.c b/board/pdeval-stm32f072/board.c index f5aa26d10b..a306d853ac 100644 --- a/board/pdeval-stm32f072/board.c +++ b/board/pdeval-stm32f072/board.c @@ -4,6 +4,7 @@ */ /* STM32F072-discovery board based USB PD evaluation configuration */ +#include "anx7447.h" #include "common.h" #include "ec_version.h" #include "gpio.h" @@ -52,15 +53,12 @@ void board_reset_pd_mcu(void) /* I2C ports */ const struct i2c_port_t i2c_ports[] = { - {"tcpc", I2C_PORT_TCPC, 100 /* kHz */, GPIO_I2C0_SCL, GPIO_I2C0_SDA} + {"tcpc", I2C_PORT_TCPC, 400 /* kHz */, GPIO_I2C0_SCL, GPIO_I2C0_SDA} }; const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); const struct tcpc_config_t tcpc_config[CONFIG_USB_PD_PORT_COUNT] = { - {I2C_PORT_TCPC, TCPC1_I2C_ADDR, &tcpci_tcpm_drv}, -#if CONFIG_USB_PD_PORT_COUNT >= 2 - {I2C_PORT_TCPC, TCPC2_I2C_ADDR, &tcpci_tcpm_drv}, -#endif + {I2C_PORT_TCPC, AN7447_TCPC3_I2C_ADDR, &anx7447_tcpm_drv} }; uint16_t tcpc_get_alert_status(void) @@ -69,9 +67,6 @@ uint16_t tcpc_get_alert_status(void) if (!gpio_get_level(GPIO_PD_MCU_INT)) { status = PD_STATUS_TCPC_ALERT_0; -#if CONFIG_USB_PD_PORT_COUNT >= 2 - status |= PD_STATUS_TCPC_ALERT_1; -#endif } return status; diff --git a/board/pdeval-stm32f072/board.h b/board/pdeval-stm32f072/board.h index df32ec2683..9100ab1a94 100644 --- a/board/pdeval-stm32f072/board.h +++ b/board/pdeval-stm32f072/board.h @@ -26,12 +26,21 @@ #define CONFIG_USB_PD_ALT_MODE_DFP #define CONFIG_USB_PD_CUSTOM_VDM #define CONFIG_USB_PD_DUAL_ROLE -#define CONFIG_USB_PD_PORT_COUNT 2 +#define CONFIG_USB_PD_PORT_COUNT 1 #define CONFIG_USB_PD_TCPM_TCPCI +#define CONFIG_USB_PD_VBUS_DETECT_TCPC +#define CONFIG_USB_PD_TCPM_ANX7447 +#define CONFIG_USB_PD_TCPM_MUX + +#undef CONFIG_USB_PD_INITIAL_DRP_STATE +#define CONFIG_USB_PD_INITIAL_DRP_STATE PD_DRP_TOGGLE_ON + +#undef CONFIG_USB_PD_PULLUP +#define CONFIG_USB_PD_PULLUP TYPEC_RP_USB /* fake board specific type-C power constants */ #define PD_POWER_SUPPLY_TURN_ON_DELAY 30000 /* us */ -#define PD_POWER_SUPPLY_TURN_OFF_DELAY 250000 /* us */ +#define PD_POWER_SUPPLY_TURN_OFF_DELAY 650000 /* us */ /* Define typical operating power and max power */ #define PD_OPERATING_POWER_MW 15000 @@ -43,12 +52,13 @@ #define I2C_PORT_TCPC 0 #define I2C_PORT_PD_MCU 0 -/* TCPC I2C slave addresses */ -#define TCPC1_I2C_ADDR 0x9c -#define TCPC2_I2C_ADDR 0x9e - /* Timer selection */ +#define CONFIG_USBC_VCONN +#define CONFIG_USBC_VCONN_SWAP +/* delay to turn on/off vconn */ +#define PD_VCONN_SWAP_DELAY 5000 /* us */ + /* USB Configuration */ #define CONFIG_USB #define CONFIG_USB_PID 0x500f diff --git a/board/pdeval-stm32f072/ec.tasklist b/board/pdeval-stm32f072/ec.tasklist index fd87fc0237..f58f5e3a7c 100644 --- a/board/pdeval-stm32f072/ec.tasklist +++ b/board/pdeval-stm32f072/ec.tasklist @@ -20,5 +20,4 @@ TASK_ALWAYS(HOOKS, hook_task, NULL, TASK_STACK_SIZE) \ TASK_NOTEST(PDCMD, pd_command_task,NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, TASK_STACK_SIZE) \ - TASK_ALWAYS(PD_C0, pd_task, NULL, LARGER_TASK_STACK_SIZE) \ - TASK_ALWAYS(PD_C1, pd_task, NULL, LARGER_TASK_STACK_SIZE) + TASK_ALWAYS(PD_C0, pd_task, NULL, LARGER_TASK_STACK_SIZE) diff --git a/board/pdeval-stm32f072/gpio.inc b/board/pdeval-stm32f072/gpio.inc index cd3c601a0a..f71d69441e 100644 --- a/board/pdeval-stm32f072/gpio.inc +++ b/board/pdeval-stm32f072/gpio.inc @@ -16,6 +16,9 @@ GPIO(LED_U, PIN(C, 6), GPIO_OUT_LOW) GPIO(LED_D, PIN(C, 7), GPIO_OUT_LOW) GPIO(LED_L, PIN(C, 8), GPIO_OUT_LOW) GPIO(LED_R, PIN(C, 9), GPIO_OUT_LOW) +GPIO(USB_C0_DVDDIO, PIN(C, 14), GPIO_OUT_HIGH) +GPIO(USB_C0_AVDD33, PIN(C, 15), GPIO_OUT_HIGH) +GPIO(VBUS_PMIC_CTRL, PIN(A, 4), GPIO_OUT_LOW) /* * I2C pins should be configured as inputs until I2C module is diff --git a/board/pdeval-stm32f072/usb_pd_policy.c b/board/pdeval-stm32f072/usb_pd_policy.c index 73add50ca4..9b81222d58 100644 --- a/board/pdeval-stm32f072/usb_pd_policy.c +++ b/board/pdeval-stm32f072/usb_pd_policy.c @@ -3,6 +3,7 @@ * found in the LICENSE file. */ +#include "anx7447.h" #include "common.h" #include "console.h" #include "gpio.h" @@ -12,6 +13,7 @@ #include "task.h" #include "timer.h" #include "util.h" +#include "usb_mux.h" #include "usb_pd.h" #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) @@ -23,15 +25,25 @@ static int vbus_present; const uint32_t pd_src_pdo[] = { - PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), + PDO_FIXED(5000, 3000, PDO_FIXED_FLAGS), }; const int pd_src_pdo_cnt = ARRAY_SIZE(pd_src_pdo); const uint32_t pd_snk_pdo[] = { - PDO_FIXED(5000, 500, PDO_FIXED_FLAGS), + PDO_FIXED(5000, 900, PDO_FIXED_FLAGS), + PDO_BATT(5000, 21000, 30000), }; const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo); +#if defined(CONFIG_USB_PD_TCPM_MUX) && defined(CONFIG_USB_PD_TCPM_ANX7447) +struct usb_mux usb_muxes[CONFIG_USB_PD_PORT_COUNT] = { + { + .port_addr = 0, + .driver = &anx7447_usb_mux_driver, + }, +}; +#endif + int pd_is_valid_input_voltage(int mv) { return 1; @@ -42,6 +54,34 @@ void pd_transition_voltage(int idx) /* No-operation: we are always 5V */ } +#ifdef CONFIG_USB_PD_TCPM_ANX7447 +int pd_set_power_supply_ready(int port) +{ + /* Disable charging */ + anx7447_board_charging_enable(port, 0); + + /* Provide VBUS */ + gpio_set_level(GPIO_VBUS_PMIC_CTRL, 1); + anx7447_set_power_supply_ready(port); + + /* notify host of power info change */ + + CPRINTS("Enable VBUS, port%d", port); + + return EC_SUCCESS; +} + +void pd_power_supply_reset(int port) +{ + /* Disable VBUS */ + anx7447_power_supply_reset(port); + gpio_set_level(GPIO_VBUS_PMIC_CTRL, 0); + CPRINTS("Disable VBUS, port%d", port); + + /* Enable charging */ + anx7447_board_charging_enable(port, 1); +} +#else int pd_set_power_supply_ready(int port) { /* Turn on the "up" LED when we output VBUS */ @@ -57,6 +97,7 @@ void pd_power_supply_reset(int port) /* Disable VBUS */ CPRINTS("Disable VBUS", port); } +#endif /* CONFIG_USB_PD_TCPM_ANX7447 */ void pd_set_input_current_limit(int port, uint32_t max_ma, uint32_t supply_voltage) @@ -117,7 +158,7 @@ int pd_check_power_swap(int port) * otherwise assume our role is fixed (not in S0 or console command * to fix our role). */ - return pd_get_dual_role() == PD_DRP_TOGGLE_ON ? 1 : 0; + return pd_get_dual_role() == PD_DRP_TOGGLE_ON; } int pd_check_data_swap(int port, int data_role) @@ -126,6 +167,18 @@ int pd_check_data_swap(int port, int data_role) return 1; } +#ifdef CONFIG_USBC_VCONN_SWAP +int pd_check_vconn_swap(int port) +{ + /* + * Allow vconn swap as long as we are acting as a dual role device, + * otherwise assume our role is fixed (not in S0 or console command + * to fix our role). + */ + return pd_get_dual_role() == PD_DRP_TOGGLE_ON; +} +#endif + void pd_execute_data_swap(int port, int data_role) { } @@ -138,8 +191,24 @@ void pd_check_dr_role(int port, int dr_role, int flags) { } /* ----------------- Vendor Defined Messages ------------------ */ +const uint32_t vdo_idh = VDO_IDH(1, /* data caps as USB host */ + 0, /* data caps as USB device */ + IDH_PTYPE_PERIPH, + 0, /* supports alt modes */ + 0x0000); + +const uint32_t vdo_product = VDO_PRODUCT(0x0000, 0x0000); + +static int svdm_response_identity(int port, uint32_t *payload) +{ + payload[VDO_I(IDH)] = vdo_idh; + payload[VDO_I(CSTAT)] = VDO_CSTAT(0); + payload[VDO_I(PRODUCT)] = vdo_product; + return VDO_I(PRODUCT) + 1; +} + const struct svdm_response svdm_rsp = { - .identity = NULL, + .identity = &svdm_response_identity, .svids = NULL, .modes = NULL, }; @@ -186,6 +255,7 @@ int pd_custom_vdm(int port, int cnt, uint32_t *payload, #ifdef CONFIG_USB_PD_ALT_MODE_DFP static int dp_flags[CONFIG_USB_PD_PORT_COUNT]; +static uint32_t dp_status[CONFIG_USB_PD_PORT_COUNT]; static void svdm_safe_dp_mode(int port) { @@ -224,24 +294,66 @@ static int svdm_dp_status(int port, uint32_t *payload) static int svdm_dp_config(int port, uint32_t *payload) { int opos = pd_alt_mode(port, USB_SID_DISPLAYPORT); + int pin_mode = pd_dfp_dp_get_pin_mode(port, dp_status[port]); + +#ifdef CONFIG_USB_PD_TCPM_ANX7447 + mux_state_t mux_state = TYPEC_MUX_NONE; + if (pd_get_polarity(port)) + mux_state |= MUX_POLARITY_INVERTED; +#endif + + CPRINTS("pin_mode = %d\n", pin_mode); + if (!pin_mode) + return 0; + +#if defined(CONFIG_USB_PD_TCPM_MUX) && defined(CONFIG_USB_PD_TCPM_ANX7447) + switch (pin_mode) { + case MODE_DP_PIN_A: + case MODE_DP_PIN_C: + case MODE_DP_PIN_E: + mux_state |= TYPEC_MUX_DP; + usb_muxes[port].driver->set(port, mux_state); + break; + case MODE_DP_PIN_B: + case MODE_DP_PIN_D: + case MODE_DP_PIN_F: + mux_state |= TYPEC_MUX_DOCK; + usb_muxes[port].driver->set(port, mux_state); + break; + } +#endif + /* board_set_usb_mux(port, TYPEC_MUX_DP, pd_get_polarity(port)); */ payload[0] = VDO(USB_SID_DISPLAYPORT, 1, CMD_DP_CONFIG | VDO_OPOS(opos)); - payload[1] = VDO_DP_CFG(MODE_DP_PIN_E, /* pin mode */ + payload[1] = VDO_DP_CFG(pin_mode, /* pin mode */ 1, /* DPv1.3 signaling */ 2); /* UFP connected */ return 2; -}; +} static void svdm_dp_post_config(int port) { dp_flags[port] |= DP_FLAGS_DP_ON; if (!(dp_flags[port] & DP_FLAGS_HPD_HI_PENDING)) return; + +#ifdef CONFIG_USB_PD_TCPM_ANX7447 + anx7447_tcpc_update_hpd_status(port, 1, 0); +#endif } static int svdm_dp_attention(int port, uint32_t *payload) { +#ifdef CONFIG_USB_PD_TCPM_ANX7447 + int lvl = PD_VDO_DPSTS_HPD_LVL(payload[1]); + int irq = PD_VDO_DPSTS_HPD_IRQ(payload[1]); + + CPRINTS("Attention: 0x%x\n", payload[1]); + anx7447_tcpc_update_hpd_status(port, lvl, irq); +#endif + dp_status[port] = payload[1]; + /* ack */ return 1; } @@ -249,7 +361,9 @@ static int svdm_dp_attention(int port, uint32_t *payload) static void svdm_exit_dp_mode(int port) { svdm_safe_dp_mode(port); - /* gpio_set_level(PORT_TO_HPD(port), 0); */ +#ifdef CONFIG_USB_PD_TCPM_ANX7447 + anx7447_tcpc_clear_hpd_status(port); +#endif } static int svdm_enter_gfu_mode(int port, uint32_t mode_caps) diff --git a/driver/build.mk b/driver/build.mk index 541b8df235..ca04d48e60 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -104,6 +104,7 @@ driver-$(CONFIG_USB_PD_TCPM_ANX3429)+=tcpm/anx74xx.o driver-$(CONFIG_USB_PD_TCPM_ANX740X)+=tcpm/anx74xx.o driver-$(CONFIG_USB_PD_TCPM_ANX741X)+=tcpm/anx74xx.o driver-$(CONFIG_USB_PD_TCPM_ANX7688)+=tcpm/anx7688.o +driver-$(CONFIG_USB_PD_TCPM_ANX7447)+=tcpm/anx7447.o driver-$(CONFIG_USB_PD_TCPM_PS8751)+=tcpm/ps8xxx.o driver-$(CONFIG_USB_PD_TCPM_PS8805)+=tcpm/ps8xxx.o diff --git a/driver/tcpm/anx7447.c b/driver/tcpm/anx7447.c new file mode 100644 index 0000000000..30266a3b6b --- /dev/null +++ b/driver/tcpm/anx7447.c @@ -0,0 +1,393 @@ +/* Copyright 2018 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. + */ + +/* ANX7447 port manager */ + +#include "anx7447.h" +#include "console.h" +#include "ec_version.h" +#include "hooks.h" +#include "tcpci.h" +#include "tcpm.h" +#include "timer.h" +#include "usb_mux.h" +#include "usb_pd.h" +#include "util.h" + +#define ANX7447_VENDOR_ALERT (1 << 15) + +#define ANX7447_REG_STATUS 0x82 +#define ANX7447_REG_STATUS_LINK (1 << 0) + +#define ANX7447_REG_HPD 0x83 +#define ANX7447_REG_HPD_HIGH (1 << 0) +#define ANX7447_REG_HPD_IRQ (1 << 1) +#define ANX7447_REG_HPD_ENABLE (1 << 2) + +#define vsafe5v_min (3800/25) +#define vsafe0v_max (800/25) +#define is_equal_greater_safe5v(port) \ + (((anx7447_get_vbus_voltage(port))) > vsafe5v_min) +#define is_equal_greater_safe0v(port) \ + (((anx7447_get_vbus_voltage(port))) > vsafe0v_max) + +struct anx_state { + int i2c_slave_addr; + int mux_state; +}; + +static struct anx_state anx[CONFIG_USB_PD_PORT_COUNT]; + +/* + * ANX7447 has two co-existence I2C slave addresses, TCPC slave address and + * SPI slave address. The registers of TCPC slave address are partly compliant + * with standard USB TCPC specification, and the registers in SPI slave + * address controls the other functions (ex, hpd_level, mux_switch, and + * so on). It can't use tcpc_read() and tcpc_write() to access SPI slave + * address because its slave address has been set as TCPC in the structure + * tcpc_config_t. + * anx7447_reg_write() and anx7447_reg_read() are implemented here to access + * ANX7447 SPI slave address. + */ +const struct anx7447_i2c_addr anx7447_i2c_addrs[] = { + {AN7447_TCPC0_I2C_ADDR, AN7447_SPI0_I2C_ADDR}, + {AN7447_TCPC1_I2C_ADDR, AN7447_SPI1_I2C_ADDR}, + {AN7447_TCPC2_I2C_ADDR, AN7447_SPI2_I2C_ADDR}, + {AN7447_TCPC3_I2C_ADDR, AN7447_SPI3_I2C_ADDR} +}; + +static inline int anx7447_reg_write(int port, int reg, int val) +{ + return i2c_write8(tcpc_config[port].i2c_host_port, + anx[port].i2c_slave_addr, + reg, val); +} + +static inline int anx7447_reg_read(int port, int reg, int *val) +{ + return i2c_read8(tcpc_config[port].i2c_host_port, + anx[port].i2c_slave_addr, + reg, val); +} + +void anx7447_hpd_mode_en(int port) +{ + int reg, rv; + + rv = anx7447_reg_read(port, ANX7447_REG_HPD_CTRL_0, ®); + if (rv) + return; + + reg |= ANX7447_REG_HPD_MODE; + anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg); +} + +void anx7447_hpd_output_en(int port) +{ + int reg, rv; + + rv = anx7447_reg_read(port, ANX7447_REG_HPD_DEGLITCH_H, ®); + if (rv) + return; + + reg |= ANX7447_REG_HPD_OEN; + anx7447_reg_write(port, ANX7447_REG_HPD_DEGLITCH_H, reg); +} + +void anx7447_set_hpd_level(int port, int hpd_lvl) +{ + int reg, rv; + + rv = anx7447_reg_read(port, ANX7447_REG_HPD_CTRL_0, ®); + if (rv) + return; + + if (hpd_lvl) + reg |= ANX7447_REG_HPD_OUT; + else + reg &= ~ANX7447_REG_HPD_OUT; + anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg); +} + +static int anx7447_init(int port) +{ + int rv, reg, i; + + memset(&anx[port], 0, sizeof(struct anx_state)); + + /* + * find corresponding anx7447 SPI slave address according to + * specified TCPC slave address + */ + for (i = 0; i < ARRAY_SIZE(anx7447_i2c_addrs); i++) { + if (tcpc_config[port].i2c_slave_addr == + anx7447_i2c_addrs[i].tcpc_slave_addr) { + anx[port].i2c_slave_addr = + anx7447_i2c_addrs[i].spi_slave_addr; + break; + } + } + if (!anx[port].i2c_slave_addr) { + ccprintf("TCPC I2C slave addr 0x%x is invalid for ANX7447\n", + tcpc_config[port].i2c_slave_addr); + return EC_ERROR_UNKNOWN; + } + + rv = tcpci_tcpm_init(port); + if (rv) + return rv; + + /* + * 7447 has a physical pin to detect the presence of VBUS, VBUS_SENSE + * , and 7447 has a VBUS current protection mechanism through another + * pin input VBUS_OCP. To enable VBUS OCP, OVP protection, driver needs + * to set the threshold to the registers VBUS_VOLTAGE_ALARM_HI_CFG + * (0x76 & 0x77) and VBUS_OCP_HI_THRESHOLD (0xDD &0xDE). These values + * could be customized based on different platform design. + * Disable VBUS protection here since the default values of + * VBUS_VOLTAGE_ALARM_HI_CFG and VBUS_OCP_HI_THRESHOLD are zero. + */ + rv = tcpc_read(port, ANX7447_REG_TCPC_CTRL_2, ®); + if (rv) + return rv; + reg &= ~ANX7447_REG_ENABLE_VBUS_PROTECT; + rv = tcpc_write(port, ANX7447_REG_TCPC_CTRL_2, reg); + if (rv) + return rv; + + /* ADC enable, use to monitor VBUS voltage */ + rv = tcpc_read(port, ANX7447_REG_ADC_CTRL_1, ®); + if (rv) + return rv; + reg |= ANX7447_REG_ADCFSM_EN; + rv = tcpc_write(port, ANX7447_REG_ADC_CTRL_1, reg); + if (rv) + return rv; + + /* init hpd status */ + anx7447_hpd_mode_en(port); + anx7447_set_hpd_level(port, 0); + anx7447_hpd_output_en(port); + + return rv; +} + +static int anx7447_release(int port) +{ + return EC_SUCCESS; +} + +static void anx7447_update_hpd_enable(int port) +{ + int status, reg, rv; + + rv = tcpc_read(port, ANX7447_REG_STATUS, &status); + rv |= tcpc_read(port, ANX7447_REG_HPD, ®); + if (rv) + return; + + if (!(reg & ANX7447_REG_HPD_ENABLE) || + !(status & ANX7447_REG_STATUS_LINK)) { + reg &= ~ANX7447_REG_HPD_IRQ; + tcpc_write(port, ANX7447_REG_HPD, + (status & ANX7447_REG_STATUS_LINK) + ? reg | ANX7447_REG_HPD_ENABLE + : reg & ~ANX7447_REG_HPD_ENABLE); + } +} + +#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC +static int anx7447_get_vbus_voltage(int port) +{ + int vbus_volt = 0; + + tcpc_read16(port, TCPC_REG_VBUS_VOLTAGE, &vbus_volt); + + return vbus_volt; +} + +static int anx7447_tcpm_get_vbus_level(int port) +{ + return is_equal_greater_safe5v(port); +} + +int anx7447_set_power_supply_ready(int port) +{ + int count = 0; + + while (is_equal_greater_safe0v(port)) { + if (count >= 10) + break; + msleep(100); + count++; + } + + return tcpc_write(port, TCPC_REG_COMMAND, 0x77); +} +#endif /* CONFIG_USB_PD_VBUS_DETECT_TCPC */ + +int anx7447_power_supply_reset(int port) +{ + return tcpc_write(port, TCPC_REG_COMMAND, 0x66); +} + +int anx7447_board_charging_enable(int port, int enable) +{ + return tcpc_write(port, TCPC_REG_COMMAND, enable ? 0x55 : 0x44); +} + +static void anx7447_tcpc_alert(int port) +{ + int alert, rv; + + rv = tcpc_read16(port, TCPC_REG_ALERT, &alert); + /* process and clear alert status */ + tcpci_tcpc_alert(port); + + if (!rv && (alert & ANX7447_VENDOR_ALERT)) + anx7447_update_hpd_enable(port); +} + +/* + * 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 anx7447_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq) +{ + int reg = 0; + + anx7447_set_hpd_level(port, hpd_lvl); + + 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); + + anx7447_reg_read(port, ANX7447_REG_HPD_CTRL_0, ®); + reg &= ~ANX7447_REG_HPD_OUT; + anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg); + usleep(HPD_DSTREAM_DEBOUNCE_IRQ); + reg |= ANX7447_REG_HPD_OUT; + anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg); + } + /* enforce 2-ms delay between HPD pulses */ + hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL; +} + +void anx7447_tcpc_clear_hpd_status(int port) +{ + anx7447_hpd_output_en(port); + anx7447_set_hpd_level(port, 0); +} + +#ifdef CONFIG_USB_PD_TCPM_MUX +static int anx7447_mux_init(int port) +{ + /* 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 anx7447_mux_set(int port, mux_state_t mux_state) +{ + int cc_direction; + mux_state_t mux_type; + int sw_sel = 0x30, aux_sw = 0x00; + int rv = EC_SUCCESS; + + cc_direction = mux_state & MUX_POLARITY_INVERTED; + mux_type = mux_state & TYPEC_MUX_DOCK; + ccprintf("mux_state = 0x%x, mux_type = 0x%x\n", mux_state, mux_type); + + if (mux_type == TYPEC_MUX_NONE) { + /* set MUX control as no connection */ + sw_sel = 0x00; + } + + /* type-C interface detect cable plug direction + * is positive orientation + */ + /* CC1_CONNECTED */ + if (cc_direction == 0) { + /* cc1 connection */ + if (mux_type == TYPEC_MUX_DOCK) { + /* L0-a10/11,L1-b2/b3, sstx-a2/a3, ssrx-b10/11 */ + sw_sel = 0x21; + aux_sw = 0x03; + } else if (mux_type == TYPEC_MUX_DP) { + /* L0-a10/11,L1-b2/b3, L2-a2/a3, L3-b10/11 */ + sw_sel = 0x09; + aux_sw = 0x03; + } + } else { + /* cc2 connection */ + if (mux_type == TYPEC_MUX_DOCK) { + /* L0-b10/11,L1-a2/b3, sstx-b2/a3, ssrx-a10/11 */ + sw_sel = 0x12; + aux_sw = 0x0C; + } else if (mux_type == TYPEC_MUX_DP) { + /* L0-b10/11,L1-a2/b3, L2-b2/a3, L3-a10/11 */ + sw_sel = 0x06; + aux_sw = 0x0C; + } + } + rv = tcpc_write(port, ANX7447_REG_TCPC_SWITCH_0, sw_sel); + rv |= tcpc_write(port, ANX7447_REG_TCPC_SWITCH_1, sw_sel); + rv |= tcpc_write(port, ANX7447_REG_TCPC_AUX_SWITCH, aux_sw); + + anx[port].mux_state = mux_state; + + return rv; +} + +/* current mux state */ +static int anx7447_mux_get(int port, mux_state_t *mux_state) +{ + *mux_state = anx[port].mux_state; + + return EC_SUCCESS; +} +#endif /* CONFIG_USB_PD_TCPM_MUX */ + +/* ANX7447 is a TCPCI compatible port controller */ +const struct tcpm_drv anx7447_tcpm_drv = { + .init = &anx7447_init, + .release = &anx7447_release, + .get_cc = &tcpci_tcpm_get_cc, +#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC + .get_vbus_level = &anx7447_tcpm_get_vbus_level, +#endif + .select_rp_value = &tcpci_tcpm_select_rp_value, + .set_cc = &tcpci_tcpm_set_cc, + .set_polarity = &tcpci_tcpm_set_polarity, + .set_vconn = &tcpci_tcpm_set_vconn, + .set_msg_header = &tcpci_tcpm_set_msg_header, + .set_rx_enable = &tcpci_tcpm_set_rx_enable, + .get_message = &tcpci_tcpm_get_message, + .transmit = &tcpci_tcpm_transmit, + .tcpc_alert = &anx7447_tcpc_alert, +#ifdef CONFIG_USB_PD_DISCHARGE_TCPC + .tcpc_discharge_vbus = &tcpci_tcpc_discharge_vbus, +#endif +#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE + .drp_toggle = &tcpci_tcpc_drp_toggle, +#endif + .get_chip_info = &tcpci_get_chip_info, +}; + +#ifdef CONFIG_USB_PD_TCPM_MUX +const struct usb_mux_driver anx7447_usb_mux_driver = { + .init = anx7447_mux_init, + .set = anx7447_mux_set, + .get = anx7447_mux_get, +}; +#endif /* CONFIG_USB_PD_TCPM_MUX */ + diff --git a/driver/tcpm/anx7447.h b/driver/tcpm/anx7447.h new file mode 100644 index 0000000000..a2fe128f77 --- /dev/null +++ b/driver/tcpm/anx7447.h @@ -0,0 +1,61 @@ +/* Copyright 2018 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* USB Power delivery port management */ + +#ifndef __CROS_EC_USB_PD_TCPM_ANX7447_H +#define __CROS_EC_USB_PD_TCPM_ANX7447_H + +/* Registers: TCPC slave address used */ +#define ANX7447_REG_TCPC_SWITCH_0 0xB4 +#define ANX7447_REG_TCPC_SWITCH_1 0xB5 +#define ANX7447_REG_TCPC_AUX_SWITCH 0xB6 + +#define ANX7447_REG_INTR_ALERT_MASK_0 0xC9 + +#define ANX7447_REG_TCPC_CTRL_2 0xCD +#define ANX7447_REG_ENABLE_VBUS_PROTECT 0x20 + +#define ANX7447_REG_ADC_CTRL_1 0xBF +#define ANX7447_REG_ADCFSM_EN 0x20 + +/* Registers: SPI slave address used */ +#define ANX7447_REG_HPD_CTRL_0 0x7E +#define ANX7447_REG_HPD_MODE 0x01 +#define ANX7447_REG_HPD_OUT 0x02 + +#define ANX7447_REG_HPD_DEGLITCH_H 0x80 +#define ANX7447_REG_HPD_OEN 0x40 + +#define ANX7447_REG_INTP_CTRL_0 0x9E + +struct anx7447_i2c_addr { + int tcpc_slave_addr; + int spi_slave_addr; +}; + +#define AN7447_TCPC0_I2C_ADDR 0x58 +#define AN7447_TCPC1_I2C_ADDR 0x56 +#define AN7447_TCPC2_I2C_ADDR 0x54 +#define AN7447_TCPC3_I2C_ADDR 0x52 + +#define AN7447_SPI0_I2C_ADDR 0x7E +#define AN7447_SPI1_I2C_ADDR 0x6E +#define AN7447_SPI2_I2C_ADDR 0x64 +#define AN7447_SPI3_I2C_ADDR 0x62 + +int anx7447_set_power_supply_ready(int port); +int anx7447_power_supply_reset(int port); +int anx7447_board_charging_enable(int port, int enable); + +void anx7447_hpd_mode_en(int port); +void anx7447_hpd_output_en(int port); + +extern const struct tcpm_drv anx7447_tcpm_drv; +extern const struct usb_mux_driver anx7447_usb_mux_driver; +void anx7447_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq); +void anx7447_tcpc_clear_hpd_status(int port); + +#endif /* __CROS_EC_USB_PD_TCPM_ANX7688_H */ -- cgit v1.2.1