diff options
Diffstat (limited to 'driver/tcpm/ps8xxx.c')
-rw-r--r-- | driver/tcpm/ps8xxx.c | 1037 |
1 files changed, 0 insertions, 1037 deletions
diff --git a/driver/tcpm/ps8xxx.c b/driver/tcpm/ps8xxx.c deleted file mode 100644 index 4643c669c0..0000000000 --- a/driver/tcpm/ps8xxx.c +++ /dev/null @@ -1,1037 +0,0 @@ -/* Copyright 2017 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. - */ - -/* - * Type-C port manager for Parade PS8XXX with integrated superspeed muxes. - * - * Supported TCPCs: - * - PS8705 - * - PS8751 - * - PS8755 - * - PS8805 - * - PS8815 - */ - -#include "common.h" -#include "console.h" -#include "ps8xxx.h" -#include "tcpm/tcpci.h" -#include "tcpm/tcpm.h" -#include "timer.h" -#include "usb_mux.h" -#include "usb_pd.h" - -#if !defined(CONFIG_USB_PD_TCPM_PS8705) && \ - !defined(CONFIG_USB_PD_TCPM_PS8751) && \ - !defined(CONFIG_USB_PD_TCPM_PS8755) && \ - !defined(CONFIG_USB_PD_TCPM_PS8805) && \ - !defined(CONFIG_USB_PD_TCPM_PS8815) -#error "Unsupported PS8xxx TCPC." -#endif - -#if !defined(CONFIG_USB_PD_TCPM_TCPCI) || \ - !defined(CONFIG_USB_PD_TCPM_MUX) || \ - !defined(CONFIG_USBC_SS_MUX) - -#error "PS8XXX is using a standard TCPCI interface with integrated mux control" -#error "Please upgrade your board configuration" - -#endif - -#ifdef CONFIG_USB_PD_TCPM_PS8751 -/* PS8751 cannot run with PD 3.0 (see b/148554997 for details) */ -#if defined(CONFIG_USB_PD_REV30) -#error "PS8751 cannot run with PD 3.0. Fall back to using PD 2.0" -#endif - -#endif /* CONFIG_USB_PD_TCPM_PS8751 */ - -#ifdef CONFIG_USB_PD_TCPM_PS8751_CUSTOM_MUX_DRIVER -#if !defined(CONFIG_USB_PD_TCPM_PS8751) -#error "Custom MUX driver is available only for PS8751" -#endif - -#endif /* CONFIG_USB_PD_TCPM_PS8751_CUSTOM_MUX_DRIVER */ - -#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) -#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) - -#define PS8XXX_I2C_RECOVERY_DELAY_MS 10 - -/* - * The product_id per ports here is expected to be set in callback function - - * .init of tcpm_drv by calling board_get_ps8xxx_product_id(). - * - * In case of CONFIG_USB_PD_TCPM_MULTI_PS8XXX enabled, board code should - * override the board_get_ps8xxx_product_id() for getting the correct id. - */ -static uint16_t product_id[CONFIG_USB_PD_PORT_MAX_COUNT]; - -/* - * Revisions A1 and A0 of the PS8815 can corrupt the transmit buffer when - * updating the transmit buffer within 1ms of writing the ROLE_CONTROL - * register. When this version of silicon is detected, add a 1ms delay before - * all writes to the transmit buffer. - * - * See b/171430855 for details. - */ -static uint8_t ps8xxx_role_control_delay_ms[CONFIG_USB_PD_PORT_MAX_COUNT]; - -/* - * b/178664884, on PS8815, firmware revision 0x10 and older can report an - * incorrect value on the the CC lines. This flag controls when to apply - * the workaround. - */ -static bool ps8815_disable_rp_detect[CONFIG_USB_PD_PORT_MAX_COUNT]; -static bool ps8815_disconnected[CONFIG_USB_PD_PORT_MAX_COUNT]; -/* - * timestamp of the next possible toggle to ensure the 2-ms spacing - * between IRQ_HPD. - */ -static uint64_t hpd_deadline[CONFIG_USB_PD_PORT_MAX_COUNT]; - -void ps8xxx_wake_from_standby(const struct usb_mux *me); - -#if defined(CONFIG_USB_PD_TCPM_PS8705) || \ - defined(CONFIG_USB_PD_TCPM_PS8751) || \ - defined(CONFIG_USB_PD_TCPM_PS8755) || \ - defined(CONFIG_USB_PD_TCPM_PS8805) -/* - * DCI is enabled by default and burns about 40 mW when the port is in - * USB2 mode or when a C-to-A dongle is attached, so force it off. - */ - -static int ps8xxx_addr_dci_disable(int port, int i2c_addr, int i2c_reg) -{ - int status; - int dci; - - status = tcpc_addr_read(port, i2c_addr, i2c_reg, &dci); - if (status != EC_SUCCESS) - return status; - if ((dci & PS8XXX_REG_MUX_USB_DCI_CFG_MODE_MASK) != - PS8XXX_REG_MUX_USB_DCI_CFG_MODE_OFF) { - dci &= ~PS8XXX_REG_MUX_USB_DCI_CFG_MODE_MASK; - dci |= PS8XXX_REG_MUX_USB_DCI_CFG_MODE_OFF; - if (tcpc_addr_write(port, i2c_addr, i2c_reg, dci) != EC_SUCCESS) - return status; - } - return EC_SUCCESS; -} -#endif /* CONFIG_USB_PD_TCPM_PS875[15] || CONFIG_USB_PD_TCPM_PS8[78]05 */ - -#if defined(CONFIG_USB_PD_TCPM_PS8705) || \ - defined(CONFIG_USB_PD_TCPM_PS8755) || \ - defined(CONFIG_USB_PD_TCPM_PS8805) -static int ps8705_dci_disable(int port) -{ - int p1_addr; - int p3_addr; - int regval; - int rv; - - /* Enable access to debug pages. */ - p3_addr = tcpc_config[port].i2c_info.addr_flags; - rv = tcpc_addr_read(port, p3_addr, PS8XXX_REG_I2C_DEBUGGING_ENABLE, - ®val); - if (rv) - return rv; - - rv = tcpc_addr_write(port, p3_addr, PS8XXX_REG_I2C_DEBUGGING_ENABLE, - PS8XXX_REG_I2C_DEBUGGING_ENABLE_ON); - - /* Disable Auto DCI */ - p1_addr = PS8751_P3_TO_P1_FLAGS(p3_addr); - rv = ps8xxx_addr_dci_disable(port, p1_addr, - PS8XXX_P1_REG_MUX_USB_DCI_CFG); - - /* - * PS8705/PS8755/PS8805 will automatically re-assert bit:0 on the - * PS8XXX_REG_I2C_DEBUGGING_ENABLE register. - */ - return rv; -} -#endif /* CONFIG_USB_PD_TCPM_PS8755 || CONFIG_USB_PD_TCPM_PS8[78]05 */ - -#ifdef CONFIG_USB_PD_TCPM_PS8751 -static int ps8751_dci_disable(int port) -{ - int p3_addr; - - p3_addr = tcpc_config[port].i2c_info.addr_flags; - return ps8xxx_addr_dci_disable(port, p3_addr, - PS8751_REG_MUX_USB_DCI_CFG); -} -#endif /* CONFIG_USB_PD_TCPM_PS8751 */ - -#ifdef CONFIG_USB_PD_TCPM_PS8815 -static int ps8815_dci_disable(int port) -{ - /* DCI is disabled on the ps8815 */ - return EC_SUCCESS; -} -#endif /* CONFIG_USB_PD_TCPM_PS8815 */ - -#ifdef CONFIG_USB_PD_TCPM_PS8805 -static int ps8805_gpio_mask[] = { - PS8805_REG_GPIO_0, - PS8805_REG_GPIO_1, - PS8805_REG_GPIO_2, -}; - -int ps8805_gpio_set_level(int port, enum ps8805_gpio signal, int level) -{ - int rv; - int regval; - int mask; - - if (signal >= PS8805_GPIO_NUM) - return EC_ERROR_INVAL; - - rv = i2c_read8(tcpc_config[port].i2c_info.port, - PS8805_VENDOR_DEFINED_I2C_ADDR, - PS8805_REG_GPIO_CONTROL, ®val); - if (rv) - return rv; - - mask = ps8805_gpio_mask[signal]; - if (level) - regval |= mask; - else - regval &= ~mask; - - return i2c_write8(tcpc_config[port].i2c_info.port, - PS8805_VENDOR_DEFINED_I2C_ADDR, - PS8805_REG_GPIO_CONTROL, regval); -} - -int ps8805_gpio_get_level(int port, enum ps8805_gpio signal, int *level) -{ - int regval; - int rv; - - if (signal >= PS8805_GPIO_NUM) - return EC_ERROR_INVAL; - - rv = i2c_read8(tcpc_config[port].i2c_info.port, - PS8805_VENDOR_DEFINED_I2C_ADDR, - PS8805_REG_GPIO_CONTROL, ®val); - if (rv) - return rv; - *level = !!(regval & ps8805_gpio_mask[signal]); - - return EC_SUCCESS; -} -#endif /* CONFIG_USB_PD_TCPM_PS8805 */ - -enum ps8xxx_variant_regs { - REG_FIRST_INDEX = 0, - /* NOTE: The rev will read as 0x00 if the FW has malfunctioned. */ - REG_FW_VER = REG_FIRST_INDEX, - REG_MAX_COUNT, -}; - -struct ps8xxx_variant_map { - int product_id; - int (*dci_disable_ptr)(int port); - int reg_map[REG_MAX_COUNT]; -}; - -/* - * variant_map here is leveraged to lookup from ps8xxx_variant_regs to i2c - * register and corresponding dci_disable function based on product_id. - */ -static struct ps8xxx_variant_map variant_map[] = { -#ifdef CONFIG_USB_PD_TCPM_PS8705 - { - PS8705_PRODUCT_ID, - ps8705_dci_disable, - { - [REG_FW_VER] = 0x82, - } - }, -#endif -#ifdef CONFIG_USB_PD_TCPM_PS8751 - { - PS8751_PRODUCT_ID, - ps8751_dci_disable, - { - [REG_FW_VER] = 0x90, - } - }, -#endif -#ifdef CONFIG_USB_PD_TCPM_PS8755 - { - PS8755_PRODUCT_ID, - ps8705_dci_disable, - { - [REG_FW_VER] = 0x82, - } - }, -#endif -#ifdef CONFIG_USB_PD_TCPM_PS8805 - { - PS8805_PRODUCT_ID, - ps8705_dci_disable, - { - [REG_FW_VER] = 0x82, - } - }, -#endif -#ifdef CONFIG_USB_PD_TCPM_PS8815 - { - PS8815_PRODUCT_ID, - ps8815_dci_disable, - { - [REG_FW_VER] = 0x82, - } - }, -#endif -}; - -static int get_reg_by_product(const int port, - const enum ps8xxx_variant_regs reg) -{ - int i; - - if (reg < REG_FIRST_INDEX || reg >= REG_MAX_COUNT) - return INT32_MAX; - - for (i = 0; i < ARRAY_SIZE(variant_map); i++) { - if (product_id[port] == - variant_map[i].product_id) { - return variant_map[i].reg_map[reg]; - } - } - - CPRINTS("%s: failed to get register number by product_id.", __func__); - return INT32_MAX; -} - -static int dp_set_hpd(const struct usb_mux *me, int enable) -{ - int reg; - int rv; - - rv = mux_read(me, MUX_IN_HPD_ASSERTION_REG, ®); - if (rv) - return rv; - if (enable) - reg |= IN_HPD; - else - reg &= ~IN_HPD; - return mux_write(me, MUX_IN_HPD_ASSERTION_REG, reg); -} - -static int dp_set_irq(const struct usb_mux *me, int enable) -{ - int reg; - int rv; - - rv = mux_read(me, MUX_IN_HPD_ASSERTION_REG, ®); - if (rv) - return rv; - if (enable) - reg |= HPD_IRQ; - else - reg &= ~HPD_IRQ; - return mux_write(me, MUX_IN_HPD_ASSERTION_REG, reg); -} - -__overridable -uint16_t board_get_ps8xxx_product_id(int port) -{ - /* Board supporting multiple chip sources in ps8xxx.c MUST override this - * function to judge the real chip source for this board. For example, - * SKU ID / strappings / provisioning in the factory can be the ways. - */ - - if (IS_ENABLED(CONFIG_USB_PD_TCPM_MULTI_PS8XXX)) { - CPRINTS("%s: board should override this function.", __func__); - return 0; - } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8705)) { - return PS8705_PRODUCT_ID; - } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8751)) { - return PS8751_PRODUCT_ID; - } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8755)) { - return PS8755_PRODUCT_ID; - } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8805)) { - return PS8805_PRODUCT_ID; - } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8815)) { - return PS8815_PRODUCT_ID; - } - - CPRINTS("%s: Any new product id is not defined here?", __func__); - return 0; -} - -bool check_ps8755_chip(int port) -{ - int val; - int p0_addr; - int status; - bool is_ps8755 = false; - - p0_addr = PS8751_P3_TO_P0_FLAGS(tcpc_config[port].i2c_info.addr_flags); - status = tcpc_addr_read(port, p0_addr, PS8755_P0_REG_SM, &val); - if (status == EC_SUCCESS && val == PS8755_P0_REG_SM_VALUE) - is_ps8755 = true; - - return is_ps8755; -} - -void ps8xxx_tcpc_update_hpd_status(const struct usb_mux *me, - mux_state_t mux_state) -{ - int port = me->usb_port; - int hpd_lvl = (mux_state & USB_PD_MUX_HPD_LVL) ? 1 : 0; - int hpd_irq = (mux_state & USB_PD_MUX_HPD_IRQ) ? 1 : 0; - - if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8751_CUSTOM_MUX_DRIVER) && - product_id[me->usb_port] == PS8751_PRODUCT_ID && - me->flags & USB_MUX_FLAG_NOT_TCPC) - ps8xxx_wake_from_standby(me); - - dp_set_hpd(me, 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); - - dp_set_irq(me, 0); - usleep(HPD_DSTREAM_DEBOUNCE_IRQ); - dp_set_irq(me, hpd_irq); - } - /* enforce 2-ms delay between HPD pulses */ - hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL; -} - -static int ps8xxx_tcpc_bist_mode_2(int port) -{ - int rv; - - /* Generate BIST for 50ms. */ - rv = tcpc_write(port, - PS8XXX_REG_BIST_CONT_MODE_BYTE0, PS8751_BIST_COUNTER_BYTE0); - rv |= tcpc_write(port, - PS8XXX_REG_BIST_CONT_MODE_BYTE1, PS8751_BIST_COUNTER_BYTE1); - rv |= tcpc_write(port, - PS8XXX_REG_BIST_CONT_MODE_BYTE2, PS8751_BIST_COUNTER_BYTE2); - - /* Auto stop */ - rv |= tcpc_write(port, PS8XXX_REG_BIST_CONT_MODE_CTR, 0); - - /* Start BIST MODE 2 */ - rv |= tcpc_write(port, TCPC_REG_TRANSMIT, TCPCI_MSG_TX_BIST_MODE_2); - - return rv; -} - -static int ps8xxx_tcpm_transmit(int port, enum tcpci_msg_type type, - uint16_t header, const uint32_t *data) -{ - if (type == TCPCI_MSG_TX_BIST_MODE_2) - return ps8xxx_tcpc_bist_mode_2(port); - else - return tcpci_tcpm_transmit(port, type, header, data); -} - -static int ps8xxx_tcpm_release(int port) -{ - int version; - int status; - int reg = get_reg_by_product(port, REG_FW_VER); - - status = tcpc_read(port, reg, &version); - if (status != 0) { - /* wait for chip to wake up */ - msleep(10); - } - - return tcpci_tcpm_release(port); -} - -static void ps8xxx_role_control_delay(int port) -{ - int delay; - - delay = ps8xxx_role_control_delay_ms[port]; - if (delay) - msleep(delay); -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE -static int ps8xxx_set_role_ctrl(int port, enum tcpc_drp drp, - enum tcpc_rp_value rp, enum tcpc_cc_pull pull) -{ - int rv; - - rv = tcpci_set_role_ctrl(port, drp, rp, pull); - - /* - * b/171430855 delay 1 ms after ROLE_CONTROL updates to prevent - * transmit buffer corruption - */ - ps8xxx_role_control_delay(port); - - return rv; -} - -static int ps8xxx_tcpc_drp_toggle(int port) -{ - int rv; - int status; - int opposite_pull; - - /* - * Workaround for PS8805/PS8815, which can't restart Connection - * Detection if the partner already presents pull. Now starts with - * the opposite pull. Check b/149570002. - */ - if (product_id[port] == PS8805_PRODUCT_ID || - product_id[port] == PS8815_PRODUCT_ID) { - if (ps8815_disable_rp_detect[port]) { - CPRINTS("TCPC%d: rearm Rp disable detect on connect", - port); - ps8815_disconnected[port] = true; - } - - /* Check CC_STATUS for the current pull */ - rv = tcpc_read(port, TCPC_REG_CC_STATUS, &status); - if (status & TCPC_REG_CC_STATUS_CONNECT_RESULT_MASK) { - /* Current pull: Rd */ - opposite_pull = TYPEC_CC_RP; - } else { - /* Current pull: Rp */ - opposite_pull = TYPEC_CC_RD; - } - - /* Set auto drp toggle, starting with the opposite pull */ - rv |= ps8xxx_set_role_ctrl(port, TYPEC_DRP, TYPEC_RP_USB, - opposite_pull); - - /* Set Look4Connection command */ - rv |= tcpc_write(port, TCPC_REG_COMMAND, - TCPC_REG_COMMAND_LOOK4CONNECTION); - - return rv; - } else { - return tcpci_tcpc_drp_toggle(port); - } -} -#endif - -#ifdef CONFIG_USB_PD_TCPM_PS8805_FORCE_DID -static int ps8805_make_device_id(int port, int *id) -{ - int p0_addr; - int val; - int status; - - p0_addr = PS8751_P3_TO_P0_FLAGS(tcpc_config[port].i2c_info.addr_flags); - - status = tcpc_addr_read(port, p0_addr, PS8805_P0_REG_CHIP_REVISION, - &val); - if (status != EC_SUCCESS) - return status; - switch (val & 0xF0) { - case 0x00: /* A2 chip */ - *id = 1; - break; - case 0xa0: /* A3 chip */ - *id = 2; - break; - default: - return EC_ERROR_UNKNOWN; - } - return EC_SUCCESS; -} -#endif - -#ifdef CONFIG_USB_PD_TCPM_PS8815_FORCE_DID -/* - * Early ps8815 A1 firmware reports 0x0001 in the TCPCI Device ID - * registers which makes it indistinguishable from A0. This - * overrides the Device ID based if vendor specific registers - * identify the chip as A1. - * - * See b/159289062. - * - * The ps8815 A2 reports device ID 0x0001 instead of 0x0003 when the - * firmware is bad (mis-programmed). - */ -static int ps8815_make_device_id(int port, int *id) -{ - int p1_addr; - int val; - int status; - - /* P1 registers are always accessible on PS8815 */ - p1_addr = PS8751_P3_TO_P1_FLAGS(tcpc_config[port].i2c_info.addr_flags); - - status = tcpc_addr_read16(port, p1_addr, PS8815_P1_REG_HW_REVISION, - &val); - if (status != EC_SUCCESS) - return status; - - switch (val) { - case 0x0a00: - *id = 1; - break; - case 0x0a01: - *id = 2; - break; - case 0x0a02: - *id = 3; - break; - default: - return EC_ERROR_UNKNOWN; - } - return EC_SUCCESS; -} -#endif - -/* - * The ps8815 can take up to 50ms (FW_INIT_DELAY_MS) to fully wake up - * from sleep/low power mode - specially when it contains an application - * block firmware update. When the chip is asleep, the 1st I2C - * transaction will fail but the chip will begin to wake up within 10ms - * (I2C_RECOVERY_DELAY_MS). After this delay, I2C transactions succeed, - * but the firmware is still not fully operational. The way to check if - * the firmware is ready, is to poll the firmware register for a - * non-zero value. This logic applies to all ps8xxx family members - * supported by this driver. - */ - -static int ps8xxx_lpm_recovery_delay(int port) -{ - int val; - int status; - int fw_reg; - timestamp_t deadline; - - fw_reg = get_reg_by_product(port, REG_FW_VER); - - deadline = get_time(); - deadline.val += PS8815_FW_INIT_DELAY_MS * 1000; - - val = 0; - for (;;) { - if (timestamp_expired(deadline, NULL)) - return EC_ERROR_TIMEOUT; - - status = tcpc_read(port, fw_reg, &val); - if (status != EC_SUCCESS) { - /* wait for chip to wake up */ - msleep(PS8XXX_I2C_RECOVERY_DELAY_MS); - continue; - } - if (val != 0) - break; - msleep(1); - } - - return EC_SUCCESS; -} - -static int ps8xxx_get_chip_info(int port, int live, - struct ec_response_pd_chip_info_v1 *chip_info) -{ - int val; - int reg; - int rv = tcpci_get_chip_info(port, live, chip_info); - - if (rv != EC_SUCCESS) - return rv; - - if (chip_info == NULL) - return EC_SUCCESS; - - if (!live) { - uint16_t pid; - - pid = board_get_ps8xxx_product_id(port); - if (pid == 0) - return EC_ERROR_UNKNOWN; - product_id[port] = pid; - chip_info->vendor_id = PS8XXX_VENDOR_ID; - chip_info->product_id = product_id[port]; - } - -#ifdef CONFIG_USB_PD_TCPM_PS8805_FORCE_DID - if (chip_info->product_id == PS8805_PRODUCT_ID && - chip_info->device_id == 0x0001) { - rv = ps8805_make_device_id(port, &val); - if (rv != EC_SUCCESS) - return rv; - chip_info->device_id = val; - } -#endif -#ifdef CONFIG_USB_PD_TCPM_PS8815_FORCE_DID - if (chip_info->product_id == PS8815_PRODUCT_ID && - chip_info->device_id == 0x0001) { - rv = ps8815_make_device_id(port, &val); - if (rv != EC_SUCCESS) - return rv; - chip_info->device_id = val; - } -#endif - reg = get_reg_by_product(port, REG_FW_VER); - rv = tcpc_read(port, reg, &val); - if (rv != EC_SUCCESS) - return rv; - - chip_info->fw_version_number = val; - - /* Treat unexpected values as error (FW not initiated from reset) */ - if (live && ( - chip_info->vendor_id != PS8XXX_VENDOR_ID || - chip_info->product_id != board_get_ps8xxx_product_id(port) || - chip_info->fw_version_number == 0)) - return EC_ERROR_UNKNOWN; - -#if defined(CONFIG_USB_PD_TCPM_PS8751) && \ - defined(CONFIG_USB_PD_VBUS_DETECT_TCPC) - /* - * Min firmware version of PS8751 to ensure that it can detect Vbus - * properly. See b/109769787#comment7 - */ - chip_info->min_req_fw_version_number = 0x39; -#endif - - return rv; -} - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER -static int ps8xxx_enter_low_power_mode(int port) -{ - /* - * PS8751 has the auto sleep function that enters low power mode on - * its own in ~2 seconds. Other chips don't have it. Stub it out for - * PS8751. - */ - if (product_id[port] == PS8751_PRODUCT_ID) - return EC_SUCCESS; - - return tcpci_enter_low_power_mode(port); -} -#endif - -static int ps8xxx_dci_disable(int port) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(variant_map); i++) { - if (product_id[port] == variant_map[i].product_id) - return variant_map[i].dci_disable_ptr(port); - } - - CPRINTS("%s: failed to get dci_disable function pointers.", __func__); - return EC_ERROR_INVAL; -} - -__maybe_unused static int ps8815_transmit_buffer_workaround_check(int port) -{ - int p1_addr; - int val; - int status; - - if (product_id[port] != PS8815_PRODUCT_ID) - return EC_SUCCESS; - - /* P1 registers are always accessible on PS8815 */ - p1_addr = PS8751_P3_TO_P1_FLAGS(tcpc_config[port].i2c_info.addr_flags); - - status = tcpc_addr_read16(port, p1_addr, PS8815_P1_REG_HW_REVISION, - &val); - if (status != EC_SUCCESS) - return status; - - switch (val) { - case 0x0a00: - case 0x0a01: - ps8xxx_role_control_delay_ms[port] = 1; - break; - default: - break; - } - - return EC_SUCCESS; -} - -__maybe_unused static int ps8815_disable_rp_detect_workaround_check(int port) -{ - int val; - int rv; - int reg; - - ps8815_disable_rp_detect[port] = false; - ps8815_disconnected[port] = true; - - reg = get_reg_by_product(port, REG_FW_VER); - rv = tcpc_read(port, reg, &val); - if (rv != EC_SUCCESS) - return rv; - - /* - * RP detect is a problem in firmware version 0x10 and older. - */ - if (val <= 0x10) - ps8815_disable_rp_detect[port] = true; - - return EC_SUCCESS; -} - -__overridable void board_ps8xxx_tcpc_init(int port) -{} - -static int ps8xxx_tcpm_init(int port) -{ - int status; - - product_id[port] = board_get_ps8xxx_product_id(port); - - status = ps8xxx_lpm_recovery_delay(port); - if (status != EC_SUCCESS) { - CPRINTS("C%d: init: LPM recovery failed", port); - return status; - } - - if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8815)) { - status = ps8815_transmit_buffer_workaround_check(port); - if (status != EC_SUCCESS) - return status; - status = ps8815_disable_rp_detect_workaround_check(port); - if (status != EC_SUCCESS) - return status; - } - - board_ps8xxx_tcpc_init(port); - - status = tcpci_tcpm_init(port); - if (status != EC_SUCCESS) - return status; - - return ps8xxx_dci_disable(port); -} - -#ifdef CONFIG_USB_PD_TCPM_PS8751 -/* - * TODO(twawrzynczak): Remove this workaround when no - * longer needed. See: https://issuetracker.google.com/147684491 - * - * This is a workaround for what appears to be a bug in PS8751 firmware - * version 0x44. (Does the bug exist in other PS8751 firmware versions? - * Should this workaround be limited to only 0x44?) - * - * With nothing connected to the port, sometimes after DRP is disabled, - * the CC_STATUS register reads the CC state incorrectly (reading it - * as though a port partner is detected), which ends up confusing - * our TCPM. The workaround for this seems to be a short sleep and - * then re-reading the CC state. In other words, the issue shows up - * as a short glitch or transient, which an extra read and then a short - * delay will allow the transient to disappear. - */ -static int ps8751_get_gcc(int port, enum tcpc_cc_voltage_status *cc1, - enum tcpc_cc_voltage_status *cc2) -{ - int rv; - int status; - rv = tcpc_read(port, TCPC_REG_CC_STATUS, &status); - if (rv) - return rv; - - /* Derived empirically */ - usleep(300); - - return tcpci_tcpm_get_cc(port, cc1, cc2); -} -#endif - -static int ps8xxx_tcpm_set_cc(int port, int pull) -{ - int rv; - - /* - * b/178664884: Before presenting Rp on initial connect, disable - * internal function that checks Rp value. This is a workaround - * in the PS8815 firmware that reports an incorrect value on the CC - * lines. - * - * The PS8815 self-clears these bits. - */ - if (ps8815_disable_rp_detect[port] && ps8815_disconnected[port] && - pull == TYPEC_CC_RP) { - CPRINTS("TCPC%d: disable chip based Rp detect on connection", - port); - tcpc_write(port, PS8XXX_REG_RP_DETECT_CONTROL, - RP_DETECT_DISABLE); - ps8815_disconnected[port] = false; - } - - rv = tcpci_tcpm_set_cc(port, pull); - - /* - * b/171430855 delay 1 ms after ROLE_CONTROL updates to prevent - * transmit buffer corruption - */ - ps8xxx_role_control_delay(port); - - return rv; -} - -static int ps8xxx_tcpm_get_cc(int port, enum tcpc_cc_voltage_status *cc1, - enum tcpc_cc_voltage_status *cc2) -{ -#ifdef CONFIG_USB_PD_TCPM_PS8751 - if (product_id[port] == PS8751_PRODUCT_ID) - return ps8751_get_gcc(port, cc1, cc2); -#endif - - return tcpci_tcpm_get_cc(port, cc1, cc2); -} - -const struct tcpm_drv ps8xxx_tcpm_drv = { - .init = ps8xxx_tcpm_init, - .release = ps8xxx_tcpm_release, - .get_cc = ps8xxx_tcpm_get_cc, -#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC - .check_vbus_level = tcpci_tcpm_check_vbus_level, -#endif - .select_rp_value = tcpci_tcpm_select_rp_value, - .set_cc = ps8xxx_tcpm_set_cc, - .set_polarity = tcpci_tcpm_set_polarity, -#ifdef CONFIG_USB_PD_DECODE_SOP - .sop_prime_enable = tcpci_tcpm_sop_prime_enable, -#endif - .set_vconn = tcpci_tcpm_set_vconn, - .set_msg_header = tcpci_tcpm_set_msg_header, - .set_rx_enable = tcpci_tcpm_set_rx_enable, - .get_message_raw = tcpci_tcpm_get_message_raw, - .transmit = ps8xxx_tcpm_transmit, - .tcpc_alert = tcpci_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 = ps8xxx_tcpc_drp_toggle, -#endif -#ifdef CONFIG_USB_PD_PPC - .set_snk_ctrl = tcpci_tcpm_set_snk_ctrl, - .set_src_ctrl = tcpci_tcpm_set_src_ctrl, -#endif - .get_chip_info = ps8xxx_get_chip_info, -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - .enter_low_power_mode = ps8xxx_enter_low_power_mode, -#endif - .set_bist_test_mode = tcpci_set_bist_test_mode, -}; - -#ifdef CONFIG_CMD_I2C_STRESS_TEST_TCPC -struct i2c_stress_test_dev ps8xxx_i2c_stress_test_dev = { - .reg_info = { - .read_reg = PS8XXX_REG_VENDOR_ID_L, - .read_val = PS8XXX_VENDOR_ID & 0xFF, - .write_reg = MUX_IN_HPD_ASSERTION_REG, - }, - .i2c_read = tcpc_i2c_read, - .i2c_write = tcpc_i2c_write, -}; -#endif /* CONFIG_CMD_I2C_STRESS_TEST_TCPC */ - -#ifdef CONFIG_USB_PD_TCPM_PS8751_CUSTOM_MUX_DRIVER - -static int ps8xxx_mux_init(const struct usb_mux *me) -{ - RETURN_ERROR(tcpci_tcpm_mux_init(me)); - - /* If this MUX is also the TCPC, then skip init */ - if (!(me->flags & USB_MUX_FLAG_NOT_TCPC)) - return EC_SUCCESS; - - product_id[me->usb_port] = board_get_ps8xxx_product_id(me->usb_port); - - return EC_SUCCESS; -} - -/* - * PS8751 goes to standby mode automatically when both CC lines are set to RP. - * In standby mode it doesn't respond to first I2C access, but next - * transactions are working fine (until it goes to sleep again). - * - * To wake device documentation recommends read content of 0xA0 register. - */ -void ps8xxx_wake_from_standby(const struct usb_mux *me) -{ - int reg; - - /* Since we are waking up device, this call will most likely fail */ - mux_read(me, PS8XXX_REG_I2C_DEBUGGING_ENABLE, ®); - msleep(10); -} - -static int ps8xxx_mux_set(const struct usb_mux *me, mux_state_t mux_state, - bool *ack_required) -{ - if (product_id[me->usb_port] == PS8751_PRODUCT_ID && - me->flags & USB_MUX_FLAG_NOT_TCPC) { - ps8xxx_wake_from_standby(me); - - /* - * To operate properly, when working as mux only, PS8751 CC - * lines needs to be RD all the time. Changing to RP after - * setting mux breaks SuperSpeed connection. - */ - if (mux_state != USB_PD_MUX_NONE) - RETURN_ERROR(mux_write(me, TCPC_REG_ROLE_CTRL, - TCPC_REG_ROLE_CTRL_SET(TYPEC_NO_DRP, - TYPEC_RP_USB, - TYPEC_CC_RD, - TYPEC_CC_RD))); - } - - return tcpci_tcpm_mux_set(me, mux_state, ack_required); -} - -static int ps8xxx_mux_get(const struct usb_mux *me, mux_state_t *mux_state) -{ - if (product_id[me->usb_port] == PS8751_PRODUCT_ID && - me->flags & USB_MUX_FLAG_NOT_TCPC) - ps8xxx_wake_from_standby(me); - - return tcpci_tcpm_mux_get(me, mux_state); -} - -static int ps8xxx_mux_enter_low_power(const struct usb_mux *me) -{ - /* - * Set PS8751 lines to RP. This allows device to standby - * automatically after ~2 seconds - */ - if (product_id[me->usb_port] == PS8751_PRODUCT_ID && - me->flags & USB_MUX_FLAG_NOT_TCPC) { - /* - * It may happen that this write will fail, but - * RP seems to be set correctly - */ - mux_write(me, TCPC_REG_ROLE_CTRL, - TCPC_REG_ROLE_CTRL_SET(TYPEC_NO_DRP, TYPEC_RP_USB, - TYPEC_CC_RP, TYPEC_CC_RP)); - return EC_SUCCESS; - } - - return tcpci_tcpm_mux_enter_low_power(me); -} - -const struct usb_mux_driver ps8xxx_usb_mux_driver = { - .init = ps8xxx_mux_init, - .set = ps8xxx_mux_set, - .get = ps8xxx_mux_get, - .enter_low_power_mode = ps8xxx_mux_enter_low_power, -}; - -#endif /* CONFIG_USB_PD_TCPM_PS8751_CUSTOM_MUX_DRIVER */ |