diff options
Diffstat (limited to 'driver/ppc/nx20p348x.c')
-rw-r--r-- | driver/ppc/nx20p348x.c | 566 |
1 files changed, 0 insertions, 566 deletions
diff --git a/driver/ppc/nx20p348x.c b/driver/ppc/nx20p348x.c deleted file mode 100644 index a5136bbf23..0000000000 --- a/driver/ppc/nx20p348x.c +++ /dev/null @@ -1,566 +0,0 @@ -/* 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. - */ - -/* NX20P348x USB-C Power Path Controller */ - -#include "common.h" -#include "console.h" -#include "nx20p348x.h" -#include "gpio.h" -#include "hooks.h" -#include "i2c.h" -#include "system.h" -#include "tcpm/tcpm.h" -#include "usb_charge.h" -#include "usb_pd_tcpm.h" -#include "usb_pd.h" -#include "usbc_ppc.h" -#include "util.h" - -#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) -#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) - -static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */ - -#define NX20P348X_DB_EXIT_FAIL_THRESHOLD 10 -static int db_exit_fail_count[CONFIG_USB_PD_PORT_MAX_COUNT]; - -#define NX20P348X_FLAGS_SOURCE_ENABLED BIT(0) -static uint8_t flags[CONFIG_USB_PD_PORT_MAX_COUNT]; - -#if !defined(CONFIG_USBC_PPC_NX20P3481) && !defined(CONFIG_USBC_PPC_NX20P3483) -#error "Either the NX20P3481 or NX20P3483 must be selected" -#endif - -static int read_reg(uint8_t port, int reg, int *regval) -{ - return i2c_read8(ppc_chips[port].i2c_port, - ppc_chips[port].i2c_addr_flags, - reg, - regval); -} - -static int write_reg(uint8_t port, int reg, int regval) -{ - return i2c_write8(ppc_chips[port].i2c_port, - ppc_chips[port].i2c_addr_flags, - reg, - regval); -} - -static int nx20p348x_set_ovp_limit(int port) -{ - int rv; - int reg; - - /* Set VBUS over voltage threshold (OVLO) */ - rv = read_reg(port, NX20P348X_OVLO_THRESHOLD_REG, ®); - if (rv) - return rv; - /* OVLO threshold is 3 bit field */ - reg &= ~NX20P348X_OVLO_THRESHOLD_MASK; - /* Set SNK OVP to 23.0 V */ - reg |= NX20P348X_OVLO_23_0; - rv = write_reg(port, NX20P348X_OVLO_THRESHOLD_REG, reg); - if (rv) - return rv; - - return EC_SUCCESS; -} - -static int nx20p348x_is_sourcing_vbus(int port) -{ - return flags[port] & NX20P348X_FLAGS_SOURCE_ENABLED; -} - -static int nx20p348x_set_vbus_source_current_limit(int port, - enum tcpc_rp_value rp) -{ - int regval; - int status; - - status = read_reg(port, NX20P348X_5V_SRC_OCP_THRESHOLD_REG, ®val); - if (status) - return status; - - regval &= ~NX20P348X_ILIM_MASK; - - /* We need buffer room for all current values. */ - switch (rp) { - case TYPEC_RP_3A0: - regval |= NX20P348X_ILIM_3_200; - break; - - case TYPEC_RP_1A5: - regval |= NX20P348X_ILIM_1_600; - break; - - case TYPEC_RP_USB: - default: - regval |= NX20P348X_ILIM_0_600; - break; - }; - - - return write_reg(port, NX20P348X_5V_SRC_OCP_THRESHOLD_REG, regval); -} - -static int nx20p348x_discharge_vbus(int port, int enable) -{ - int regval; - int newval; - int status; - - status = read_reg(port, NX20P348X_DEVICE_CONTROL_REG, ®val); - if (status) - return status; - - if (enable) - newval = regval | NX20P348X_CTRL_VBUSDIS_EN; - else - newval = regval & ~NX20P348X_CTRL_VBUSDIS_EN; - - if (newval == regval) - return EC_SUCCESS; - - status = write_reg(port, NX20P348X_DEVICE_CONTROL_REG, newval); - if (status) { - CPRINTS("Failed to %s VBUS discharge", - enable ? "enable" : "disable"); - return status; - } - - return EC_SUCCESS; -} - -__maybe_unused static int nx20p3481_vbus_sink_enable(int port, int enable) -{ - int status; - int rv; - int control = enable ? NX20P3481_SWITCH_CONTROL_HVSNK : 0; - - if (enable) { - /* - * VBUS Discharge must be off in sink mode. - */ - rv = nx20p348x_discharge_vbus(port, 0); - if (rv) - return rv; - } - - rv = write_reg(port, NX20P348X_SWITCH_CONTROL_REG, control); - if (rv) - return rv; - - /* - * Read switch status register. The bit definitions for switch control - * and switch status resister are identical, so the control value can be - * compared against the status value. The control switch has a debounce - * (15 msec) before the status will reflect the control command. - */ - msleep(NX20P348X_SWITCH_STATUS_DEBOUNCE_MSEC); - rv = read_reg(port, NX20P348X_SWITCH_STATUS_REG, &status); - if (rv) - return rv; - - return (status & NX20P348X_SWITCH_STATUS_HVSNK) == control ? - EC_SUCCESS : EC_ERROR_UNKNOWN; -} - -__maybe_unused static int nx20p3481_vbus_source_enable(int port, int enable) -{ - int status; - int rv; - uint8_t previous_flags = flags[port]; - int control = enable ? NX20P3481_SWITCH_CONTROL_5VSRC : 0; - - rv = write_reg(port, NX20P348X_SWITCH_CONTROL_REG, control); - if (rv) - return rv; - - /* Cache the anticipated Vbus state */ - if (enable) - flags[port] |= NX20P348X_FLAGS_SOURCE_ENABLED; - else - flags[port] &= ~NX20P348X_FLAGS_SOURCE_ENABLED; - - /* - * Read switch status register. The bit definitions for switch control - * and switch status resister are identical, so the control value can be - * compared against the status value. The control switch has a debounce - * (15 msec) before the status will reflect the control command. - */ - msleep(NX20P348X_SWITCH_STATUS_DEBOUNCE_MSEC); - - if (IS_ENABLED(CONFIG_USBC_PPC_NX20P3481)) { - rv = read_reg(port, NX20P348X_SWITCH_STATUS_REG, &status); - if (rv) { - flags[port] = previous_flags; - return rv; - } - if ((status & NX20P348X_SWITCH_STATUS_MASK) != control) { - flags[port] = previous_flags; - return EC_ERROR_UNKNOWN; - } - } - - return EC_SUCCESS; -} - -__maybe_unused static int nx20p3483_vbus_sink_enable(int port, int enable) -{ - int rv; - - enable = !!enable; - - if (enable) { - /* - * VBUS Discharge must be off in sink mode. - */ - rv = nx20p348x_discharge_vbus(port, 0); - if (rv) - return rv; - } - - /* - * We cannot use an EC GPIO for EN_SNK since an EC reset - * will float the GPIO thus browning out the board (without - * a battery). - */ - rv = tcpm_set_snk_ctrl(port, enable); - if (rv) - return rv; - - for (int i = 0; i < NX20P348X_SWITCH_STATUS_DEBOUNCE_MSEC; ++i) { - int ds; - bool is_sink; - - rv = read_reg(port, NX20P348X_DEVICE_STATUS_REG, &ds); - if (rv != EC_SUCCESS) - return rv; - - is_sink = (ds & NX20P3483_DEVICE_MODE_MASK) == - NX20P3483_MODE_HV_SNK; - if (enable == is_sink) - return EC_SUCCESS; - - msleep(1); - } - - return EC_ERROR_TIMEOUT; -} - -__maybe_unused static int nx20p3483_vbus_source_enable(int port, int enable) -{ - int rv; - - enable = !!enable; - - /* - * For parity's sake, we should not use an EC GPIO for - * EN_SRC since we cannot use it for EN_SNK (for brown - * out reason listed above). - */ - rv = tcpm_set_src_ctrl(port, enable); - if (rv) - return rv; - - /* - * Wait up to NX20P348X_SWITCH_STATUS_DEBOUNCE_MSEC for the status - * to reflect the control command. - */ - - for (int i = 0; i < NX20P348X_SWITCH_STATUS_DEBOUNCE_MSEC; ++i) { - int s; - - rv = read_reg(port, NX20P348X_SWITCH_STATUS_REG, &s); - if (rv != EC_SUCCESS) - return rv; - - if (!!(s & (NX20P348X_SWITCH_STATUS_5VSRC | - NX20P348X_SWITCH_STATUS_HVSRC)) == enable) { - if (enable) - flags[port] |= NX20P348X_FLAGS_SOURCE_ENABLED; - else - flags[port] &= ~NX20P348X_FLAGS_SOURCE_ENABLED; - return EC_SUCCESS; - } - msleep(1); - } - - return EC_ERROR_TIMEOUT; -} - -static int nx20p348x_init(int port) -{ - int reg; - int mask; - int mode; - int rv; - enum tcpc_rp_value initial_current_limit; - - /* Mask interrupts for interrupt 2 register */ - mask = ~NX20P348X_INT2_EN_ERR; - rv = write_reg(port, NX20P348X_INTERRUPT2_MASK_REG, mask); - if (rv) - return rv; - - /* Mask interrupts for interrupt 1 register */ - mask = ~(NX20P348X_INT1_OC_5VSRC | NX20P348X_INT1_SC_5VSRC | - NX20P348X_INT1_RCP_5VSRC | NX20P348X_INT1_DBEXIT_ERR); - if (IS_ENABLED(CONFIG_USBC_PPC_NX20P3481)) { - /* Unmask Fast Role Swap detect interrupt */ - mask &= ~NX20P3481_INT1_FRS_DET; - } - rv = write_reg(port, NX20P348X_INTERRUPT1_MASK_REG, mask); - if (rv) - return rv; - - /* Clear any pending interrupts by reading interrupt registers */ - read_reg(port, NX20P348X_INTERRUPT1_REG, ®); - read_reg(port, NX20P348X_INTERRUPT2_REG, ®); - - /* Get device mode */ - rv = read_reg(port, NX20P348X_DEVICE_STATUS_REG, &mode); - if (rv) - return rv; - - if (IS_ENABLED(CONFIG_USBC_PPC_NX20P3481)) - mode &= NX20P3481_DEVICE_MODE_MASK; - else if (IS_ENABLED(CONFIG_USBC_PPC_NX20P3483)) - mode &= NX20P3483_DEVICE_MODE_MASK; - - /* Check if dead battery mode is active. */ - if (mode == NX20P348X_MODE_DEAD_BATTERY) { - /* - * If in dead battery mode, must enable HV SNK mode prior to - * exiting dead battery mode or VBUS path will get cut off and - * system will lose power. Before DB mode is exited, the device - * mode will not reflect the correct value and therefore the - * return value isn't useful here. - */ - nx20p348x_drv.vbus_sink_enable(port, 1); - - /* Exit dead battery mode. */ - rv = read_reg(port, NX20P348X_DEVICE_CONTROL_REG, ®); - if (rv) - return rv; - reg |= NX20P348X_CTRL_DB_EXIT; - rv = write_reg(port, NX20P348X_DEVICE_CONTROL_REG, reg); - if (rv) - return rv; - } - - /* - * Set VBUS over voltage threshold (OVLO). Note that while the PPC is in - * dead battery mode, OVLO is forced to 6.8V, so this setting must be - * done after dead battery mode is exited. - */ - nx20p348x_set_ovp_limit(port); - - /* Set the Vbus current limit after dead battery mode exit */ -#ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT - initial_current_limit = CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT; -#else - initial_current_limit = TYPEC_RP_1A5; -#endif - nx20p348x_set_vbus_source_current_limit(port, initial_current_limit); - - /* Restore power-on reset value */ - rv = write_reg(port, NX20P348X_DEVICE_CONTROL_REG, 0); - if (rv) - return rv; - - return EC_SUCCESS; -} - -static void nx20p348x_handle_interrupt(int port) -{ - int reg; - int control_reg; - - /* - * Read interrupt 1 status register. Note, interrupt register is - * automatically cleared by reading. - */ - read_reg(port, NX20P348X_INTERRUPT1_REG, ®); - - /* Check for DBEXIT error */ - if (reg & NX20P348X_INT1_DBEXIT_ERR) { - int mask_reg; - - /* - * This failure is not expected. If for some reason, this - * keeps happening, then log an error and mask the interrupt to - * prevent interrupt floods. - */ - if (++db_exit_fail_count[port] >= - NX20P348X_DB_EXIT_FAIL_THRESHOLD) { - ppc_prints("failed to exit DB mode", port); - if (read_reg(port, NX20P348X_INTERRUPT1_MASK_REG, - &mask_reg)) { - mask_reg |= NX20P348X_INT1_DBEXIT_ERR; - write_reg(port, NX20P348X_INTERRUPT1_MASK_REG, - mask_reg); - } - } - read_reg(port, NX20P348X_DEVICE_CONTROL_REG, &control_reg); - control_reg |= NX20P348X_CTRL_DB_EXIT; - write_reg(port, NX20P348X_DEVICE_CONTROL_REG, control_reg); - /* - * If DB exit mode failed, then the OVP limit setting done in - * the init routine will not be successful. Set the OVP limit - * again here. - */ - nx20p348x_set_ovp_limit(port); - } - - /* Check for 5V OC interrupt */ - if (reg & NX20P348X_INT1_OC_5VSRC) { - ppc_prints("detected Vbus overcurrent!", port); - pd_handle_overcurrent(port); - } - - /* Check for Vbus reverse current protection */ - if (reg & NX20P348X_INT1_RCP_5VSRC) - ppc_prints("detected Vbus reverse current!", port); - - /* Check for Vbus short protection */ - if (reg & NX20P348X_INT1_SC_5VSRC) - ppc_prints("Vbus short detected!", port); - - /* Check for FRS detection */ - if (IS_ENABLED(CONFIG_USBC_PPC_NX20P3481) && - (reg & NX20P3481_INT1_FRS_DET)) { - /* - * TODO(b/113069469): Need to check for CC status and verifiy - * that a sink is attached to continue with FRS. If a sink is - * not attached, then this FRS detect is a false detect which is - * triggered when removing an external charger. If FRS was - * detected by the PPC, then it has automatically enabled the - * 5V SRC mode and this must be undone for a proper detach. - */ - /* Check CC status */ - - /* - * False detect, disable SRC mode which was enabled by - * NX20P3481. - */ - ppc_prints("FRS false detect, disabling SRC mode!", port); - nx20p3481_vbus_source_enable(port, 0); - } - - /* - * Read interrupt 2 status register. Note, interrupt register is - * automatically cleared by reading. - */ - /* - * TODO (b/75272421): Not sure if any of these interrupts - * will be used. Might want to use EN_ERR which tracks when both - * SNK_EN and SRC_EN are set. However, since for the Analogix TCPC - * these values aren't controlled by the EC directly, not sure what - * action if any can be taken. - */ - read_reg(port, NX20P348X_INTERRUPT2_REG, ®); -} - -static void nx20p348x_irq_deferred(void) -{ - int i; - uint32_t pending = atomic_clear(&irq_pending); - - for (i = 0; i < board_get_usb_pd_port_count(); i++) - if (BIT(i) & pending) - nx20p348x_handle_interrupt(i); -} -DECLARE_DEFERRED(nx20p348x_irq_deferred); - -void nx20p348x_interrupt(int port) -{ - atomic_or(&irq_pending, BIT(port)); - hook_call_deferred(&nx20p348x_irq_deferred_data, 0); -} - -#ifdef CONFIG_CMD_PPC_DUMP -static int nx20p348x_dump(int port) -{ - int reg_addr; - int reg; - int rv; - - ccprintf("Port %d NX20P348X registers\n", port); - for (reg_addr = NX20P348X_DEVICE_ID_REG; reg_addr <= - NX20P348X_DEVICE_CONTROL_REG; reg_addr++) { - rv = read_reg(port, reg_addr, ®); - if (rv) { - ccprintf("nx20p: Failed to read register 0x%x\n", - reg_addr); - return rv; - } - ccprintf("[0x%02x]: 0x%02x\n", reg_addr, reg); - - /* Flush every call otherwise buffer may get full */ - cflush(); - } - - return EC_SUCCESS; -} -#endif /* defined(CONFIG_CMD_PPC_DUMP) */ - -/* - * TODO (b/112697473): The NX20P348x PPCs do not support vbus detection or vconn - * generation. However, if a different PPC does support these features and needs - * these config options, then these functions do need to exist. The - * configuration for what each PPC supports should be converted to bits within - * a flags variable that is part of the ppc_config_t struct. - */ -#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC -static int nx20p348x_is_vbus_present(int port) -{ - return EC_ERROR_UNIMPLEMENTED; -} -#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */ - -#ifdef CONFIG_USBC_PPC_POLARITY -static int nx20p348x_set_polarity(int port, int polarity) -{ - return EC_ERROR_UNIMPLEMENTED; -} -#endif - -#ifdef CONFIG_USBC_PPC_VCONN -static int nx20p348x_set_vconn(int port, int enable) -{ - return EC_ERROR_UNIMPLEMENTED; -} -#endif - -const struct ppc_drv nx20p348x_drv = { - .init = &nx20p348x_init, - .is_sourcing_vbus = &nx20p348x_is_sourcing_vbus, -#ifdef CONFIG_USBC_PPC_NX20P3481 - .vbus_sink_enable = &nx20p3481_vbus_sink_enable, - .vbus_source_enable = &nx20p3481_vbus_source_enable, -#endif /* CONFIG_USBC_PPC_NX20P3481 */ -#ifdef CONFIG_USBC_PPC_NX20P3483 - .vbus_sink_enable = &nx20p3483_vbus_sink_enable, - .vbus_source_enable = &nx20p3483_vbus_source_enable, -#endif /* CONFIG_USBC_PPC_NX20P3483 */ -#ifdef CONFIG_CMD_PPC_DUMP - .reg_dump = &nx20p348x_dump, -#endif /* defined(CONFIG_CMD_PPC_DUMP) */ - .set_vbus_source_current_limit = - &nx20p348x_set_vbus_source_current_limit, - .discharge_vbus = &nx20p348x_discharge_vbus, -#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC - .is_vbus_present = &nx20p348x_is_vbus_present, -#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */ -#ifdef CONFIG_USBC_PPC_POLARITY - .set_polarity = &nx20p348x_set_polarity, -#endif /* defined(CONFIG_USBC_PPC_POLARITY) */ -#ifdef CONFIG_USBC_PPC_VCONN - .set_vconn = &nx20p348x_set_vconn, -#endif /* defined(CONFIG_USBC_PPC_VCONN) */ -}; |