/* 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: * - PS8751 * - PS8805 */ #include "common.h" #include "ps8xxx.h" #include "tcpci.h" #include "tcpm.h" #include "timer.h" #include "usb_pd.h" #if !defined(CONFIG_USB_PD_TCPM_PS8751) && \ !defined(CONFIG_USB_PD_TCPM_PS8805) #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 /* * 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]; static int dp_set_hpd(int port, int enable) { int reg; int rv; rv = tcpc_read(port, MUX_IN_HPD_ASSERTION_REG, ®); if (rv) return rv; if (enable) reg |= IN_HPD; else reg &= ~IN_HPD; return tcpc_write(port, MUX_IN_HPD_ASSERTION_REG, reg); } static int dp_set_irq(int port, int enable) { int reg; int rv; rv = tcpc_read(port, MUX_IN_HPD_ASSERTION_REG, ®); if (rv) return rv; if (enable) reg |= HPD_IRQ; else reg &= ~HPD_IRQ; return tcpc_write(port, MUX_IN_HPD_ASSERTION_REG, reg); } void ps8xxx_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq) { dp_set_hpd(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); dp_set_irq(port, 0); usleep(HPD_DSTREAM_DEBOUNCE_IRQ); dp_set_irq(port, hpd_irq); } /* enforce 2-ms delay between HPD pulses */ hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL; } int ps8xxx_tcpc_get_fw_version(int port, int *version) { return tcpc_read(port, FW_VER_REG, version); } #ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC /* * Read Vbus level directly instead of using the cached version because some * TCPCs do not fire the Vbus level change interrupt correctly after resuming * from low-power mode. * * TODO(b/77639399): Remove this method once PS8751 firmware has updated to * support better handling of vbus detection */ int ps8xxx_tcpm_get_vbus_level(int port) { int reg; /* Read Power Status register */ if (tcpc_read(port, TCPC_REG_POWER_STATUS, ®) == EC_SUCCESS) return reg & TCPC_REG_POWER_STATUS_VBUS_PRES ? 1 : 0; /* If read failed, report that Vbus is off */ return 0; } #endif 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, TCPC_TX_BIST_MODE_2); return rv; } static int ps8xxx_tcpm_transmit(int port, enum tcpm_transmit_type type, uint16_t header, const uint32_t *data) { if (type == TCPC_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; status = tcpc_read(port, FW_VER_REG, &version); if (status != 0) { /* wait for chip to wake up */ msleep(10); } return tcpci_tcpm_release(port); } const struct tcpm_drv ps8xxx_tcpm_drv = { .init = &tcpci_tcpm_init, .release = &ps8xxx_tcpm_release, .get_cc = &tcpci_tcpm_get_cc, #ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC /* * TODO(b/77639399): Replace with tcpci_tcpm_get_vbus_level() after firmware * upgrade. */ .get_vbus_level = &ps8xxx_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 = &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 = &tcpci_tcpc_drp_toggle, #endif .get_chip_info = &tcpci_get_chip_info, }; #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 */