diff options
Diffstat (limited to 'driver/ppc/syv682x.c')
-rw-r--r-- | driver/ppc/syv682x.c | 837 |
1 files changed, 0 insertions, 837 deletions
diff --git a/driver/ppc/syv682x.c b/driver/ppc/syv682x.c deleted file mode 100644 index 0e49abdf6a..0000000000 --- a/driver/ppc/syv682x.c +++ /dev/null @@ -1,837 +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. - */ - -/* Silergy SYV682x USB-C Power Path Controller */ -#include "atomic.h" -#include "common.h" -#include "config.h" -#include "console.h" -#include "syv682x.h" -#include "hooks.h" -#include "i2c.h" -#include "system.h" -#include "timer.h" -#include "usb_charge.h" -#include "usb_pd_tcpm.h" -#include "usbc_ppc.h" -#include "usb_pd.h" -#include "util.h" - -#define SYV682X_FLAGS_SOURCE_ENABLED BIT(0) -#define SYV682X_FLAGS_SINK_ENABLED BIT(1) -/* 0 -> CC1, 1 -> CC2 */ -#define SYV682X_FLAGS_CC_POLARITY BIT(2) -#define SYV682X_FLAGS_VBUS_PRESENT BIT(3) -#define SYV682X_FLAGS_TSD BIT(4) -#define SYV682X_FLAGS_OVP BIT(5) -#define SYV682X_FLAGS_5V_OC BIT(6) -#define SYV682X_FLAGS_FRS BIT(7) -#define SYV682X_FLAGS_VCONN_OCP BIT(8) - -static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */ -static uint32_t flags[CONFIG_USB_PD_PORT_MAX_COUNT]; -/* Running count of sink ocp events */ -static uint32_t sink_ocp_count[CONFIG_USB_PD_PORT_MAX_COUNT]; -static timestamp_t vbus_oc_timer[CONFIG_USB_PD_PORT_MAX_COUNT]; -static timestamp_t vconn_oc_timer[CONFIG_USB_PD_PORT_MAX_COUNT]; - -#define SYV682X_VBUS_DET_THRESH_MV 4000 -/* Longest time that can be programmed in DSG_TIME field */ -#define SYV682X_MAX_VBUS_DISCHARGE_TIME_MS 400 -/* - * Delay between checks when polling the interrupt registers. Must be longer - * than the HW deglitch on OC (10ms) - */ -#define INTERRUPT_DELAY_MS 15 -/* Deglitch in ms of sourcing overcurrent detection */ -#define SOURCE_OC_DEGLITCH_MS 100 -#define VCONN_OC_DEGLITCH_MS 100 -/* Max. number of OC events allowed before disabling port */ -#define OCP_COUNT_LIMIT 3 - -#if INTERRUPT_DELAY_MS <= SYV682X_HW_OC_DEGLITCH_MS -#error "INTERRUPT_DELAY_MS should be greater than SYV682X_HW_OC_DEGLITCH_MS" -#endif - -#if SOURCE_OC_DEGLITCH_MS < INTERRUPT_DELAY_MS -#error "SOURCE_OC_DEGLITCH_MS should be at least INTERRUPT_DELAY_MS" -#endif - -/* When FRS is enabled, the VCONN line isn't passed through to the TCPC */ -#if defined(CONFIG_USB_PD_FRS_PPC) && defined(CONFIG_USBC_VCONN) && \ - !defined(CONFIG_USBC_PPC_VCONN) -#error "if FRS is enabled on the SYV682X, VCONN must be supplied by the PPC " -"instead of the TCPC" -#endif - -#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) - -static int syv682x_vbus_sink_enable(int port, int enable); - -static int syv682x_init(int port); - -static void syv682x_interrupt_delayed(int port, int delay); - -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); -} - -#ifdef CONFIG_USBC_PPC_SYV682C -__overridable int syv682x_board_is_syv682c(int port) -{ - return true; -} -#endif - -/* - * During channel transition or discharge, the SYV682X silently ignores I2C - * writes. Poll the BUSY bit until the SYV682A is ready. - */ -static int syv682x_wait_for_ready(int port, int reg) -{ - int regval; - int rv; - timestamp_t deadline; - -#ifdef CONFIG_USBC_PPC_SYV682C - /* On SYV682C, busy bit is not applied to CONTROL_4 */ - if (syv682x_board_is_syv682c(port) && reg == SYV682X_CONTROL_4_REG) - return EC_SUCCESS; -#endif - - deadline.val = get_time().val - + (SYV682X_MAX_VBUS_DISCHARGE_TIME_MS * MSEC); - - do { - rv = read_reg(port, SYV682X_CONTROL_3_REG, ®val); - if (rv) - return rv; - - if (!(regval & SYV682X_BUSY)) - break; - - if (timestamp_expired(deadline, NULL)) { - ppc_prints("busy timeout", port); - return EC_ERROR_TIMEOUT; - } - - msleep(1); - } while (1); - - return EC_SUCCESS; -} - -static int write_reg(uint8_t port, int reg, int regval) -{ - int rv; - - rv = syv682x_wait_for_ready(port, reg); - if (rv) - return rv; - - return i2c_write8(ppc_chips[port].i2c_port, - ppc_chips[port].i2c_addr_flags, - reg, - regval); -} - -static int syv682x_is_sourcing_vbus(int port) -{ - return !!(flags[port] & SYV682X_FLAGS_SOURCE_ENABLED); -} - -static int syv682x_discharge_vbus(int port, int enable) -{ -#ifndef CONFIG_USBC_PPC_SYV682X_SMART_DISCHARGE - int regval; - int rv; - - rv = read_reg(port, SYV682X_CONTROL_2_REG, ®val); - if (rv) - return rv; - - if (enable) - regval |= SYV682X_CONTROL_2_FDSG; - else - regval &= ~SYV682X_CONTROL_2_FDSG; - - return write_reg(port, SYV682X_CONTROL_2_REG, regval); -#else - /* - * Smart discharge mode is enabled, nothing to do - */ - return EC_SUCCESS; -#endif -} - -static int syv682x_vbus_source_enable(int port, int enable) -{ - int regval; - int rv; - /* - * For source mode need to make sure 5V power path is connected - * and source mode is selected. - */ - rv = read_reg(port, SYV682X_CONTROL_1_REG, ®val); - if (rv) - return rv; - - if (enable) { - /* Select 5V path and turn on channel */ - regval &= ~(SYV682X_CONTROL_1_CH_SEL | - SYV682X_CONTROL_1_PWR_ENB); - /* Disable HV Sink path */ - regval |= SYV682X_CONTROL_1_HV_DR; - } else if (flags[port] & SYV682X_FLAGS_SOURCE_ENABLED) { - /* - * For the disable case, make sure that VBUS was being sourced - * prior to disabling the source path. Because the source/sink - * paths can't be independently disabled, and this function will - * get called as part of USB PD initialization, setting the - * PWR_ENB always can lead to broken dead battery behavior. - * - * No need to change the voltage path or channel direction. But, - * turn both paths off. - * - * De-assert the FRS GPIO, which will be asserted if we got to - * be a source via an FRS. - */ - regval |= SYV682X_CONTROL_1_PWR_ENB; - if (IS_ENABLED(CONFIG_USB_PD_FRS_PPC)) - gpio_set_level(ppc_chips[port].frs_en, 0); - } - - rv = write_reg(port, SYV682X_CONTROL_1_REG, regval); - if (rv) - return rv; - - if (enable) { - atomic_or(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED); - atomic_clear_bits(&flags[port], SYV682X_FLAGS_SINK_ENABLED); - } else { - atomic_clear_bits(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED); - } - -#if defined(CONFIG_USB_CHARGER) && defined(CONFIG_USB_PD_VBUS_DETECT_PPC) - /* - * Since the VBUS state could be changing here, need to wake the - * USB_CHG_N task so that BC 1.2 detection will be triggered. - */ - usb_charger_vbus_change(port, enable); -#endif - - return EC_SUCCESS; -} - -/* Filter interrupts with rising edge trigger */ -static bool syv682x_interrupt_filter(int port, int regval, int regmask, - int flagmask) -{ - if (regval & regmask) { - if (!(flags[port] & flagmask)) { - atomic_or(&flags[port], flagmask); - return true; - } - } else { - atomic_clear_bits(&flags[port], flagmask); - } - return false; -} - -/* - * Two status registers can trigger the ALERT_L pin, STATUS and CONTROL_4 - * These registers are clear on read if the condition has been cleared. - * The ALERT_L pin will not de-assert if the alert condition has not been - * cleared. Since they are clear on read, we should check the alerts whenever we - * read these registers to avoid race conditions. - */ -static void syv682x_handle_status_interrupt(int port, int regval) -{ - /* - * An FRS will automatically disable sinking immediately, and enable the - * source path if VBUS is <5V. The FRS GPIO must remain asserted until - * VBUS falls below 5V. SYV682X_FLAGS_FRS signals that the SRC state was - * entered via an FRS. - * - * Note the FRS Alert will remain asserted until VBUS has fallen below - * 5V or the frs_en gpio is de-asserted. So use the rising edge trigger. - */ - if (IS_ENABLED(CONFIG_USB_PD_FRS_PPC)) { - if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_FRS, - SYV682X_FLAGS_FRS)) { - atomic_or(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED); - atomic_clear_bits(&flags[port], - SYV682X_FLAGS_SINK_ENABLED); - if (!IS_ENABLED(CONFIG_USB_PD_FRS_TCPC)) - pd_got_frs_signal(port); - } - } - - /* - * 5V OC is actually notifying that it is current limiting - * to 3.3A. If this happens for a long time, we will trip TSD - * which will disable the channel. We should disable the sourcing path - * before that happens for safety reasons. - * - * On first check, set the flag and set the timer. This also clears the - * flag if the OC is gone. - */ - if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_OC_5V, - SYV682X_FLAGS_5V_OC)) { - vbus_oc_timer[port].val = - get_time().val + SOURCE_OC_DEGLITCH_MS * MSEC; - } else if ((regval & SYV682X_STATUS_OC_5V) && - (get_time().val > vbus_oc_timer[port].val)) { - vbus_oc_timer[port].val = UINT64_MAX; - atomic_clear_bits(&flags[port], SYV682X_FLAGS_5V_OC); - syv682x_vbus_source_enable(port, 0); - pd_handle_overcurrent(port); - } - - /* - * No PD handling for VBUS OVP or TSD events. - * For TSD, this means we are in danger of burning the device so turn - * everything off and leave it off. The power paths will be - * automatically disabled. - * In the case of OVP, the channels will be - * disabled but don't unset the sink flag, since a sink OCP can - * inadvertently cause an OVP, and we'd want to re-enable the sink - * path in that situation. - */ - if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_TSD, - SYV682X_FLAGS_TSD)) { - ppc_prints("TSD!", port); - atomic_clear_bits(&flags[port], - SYV682X_FLAGS_SOURCE_ENABLED | - SYV682X_FLAGS_SINK_ENABLED); - } - if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_OVP, - SYV682X_FLAGS_OVP)) { - ppc_prints("VBUS OVP!", port); - atomic_clear_bits(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED); - } - - /* - * HV OC is a hard limit that will disable the sink path (automatically - * removing this alert condition), so try re-enabling if we hit an OCP. - * If we get multiple OCPs, don't re-enable. The OCP counter is reset on - * the sink path being explicitly disabled or on a PPC init. - */ - if (regval & SYV682X_STATUS_OC_HV) { - ppc_prints("Sink OCP!", port); - atomic_add(&sink_ocp_count[port], 1); - if ((sink_ocp_count[port] < OCP_COUNT_LIMIT) && - (flags[port] & SYV682X_FLAGS_SINK_ENABLED)) { - syv682x_vbus_sink_enable(port, 1); - } else { - ppc_prints("Disable sink", port); - atomic_clear_bits(&flags[port], - SYV682X_FLAGS_SINK_ENABLED); - } - } -} - -static int syv682x_handle_control_4_interrupt(int port, int regval) -{ - /* - * VCONN OC is actually notifying that it is current limiting - * to 600mA. If this happens for a long time, we will trip TSD - * which will disable the channel. We should disable the sourcing path - * before that happens for safety reasons. - * - * On first check, set the flag and set the timer. This also clears the - * flag if the OC is gone. - */ - if (syv682x_interrupt_filter(port, regval, SYV682X_CONTROL_4_VCONN_OCP, - SYV682X_FLAGS_VCONN_OCP)) { - vconn_oc_timer[port].val = - get_time().val + VCONN_OC_DEGLITCH_MS * MSEC; - } else if ((regval & SYV682X_CONTROL_4_VCONN_OCP) && - (get_time().val > vconn_oc_timer[port].val)) { - vconn_oc_timer[port].val = UINT64_MAX; - atomic_clear_bits(&flags[port], SYV682X_FLAGS_VCONN_OCP); - - /* Disable VCONN */ - regval &= - ~(SYV682X_CONTROL_4_VCONN2 | SYV682X_CONTROL_4_VCONN1); - write_reg(port, SYV682X_CONTROL_4_REG, regval); - - ppc_prints("VCONN OC!", port); - } - - /* - * On VBAT OVP, CC/VCONN are cut. Re-enable before sending the hard - * reset using a PPC re-init. We could reconfigure CC based on flags, - * but these will be updated anyway due to a hard reset so just re-init - * for simplicity. If this happens return an error since this isn't - * recoverable. - */ - if (regval & SYV682X_CONTROL_4_VBAT_OVP) { - ppc_prints("VBAT or CC OVP!", port); - syv682x_init(port); - pd_handle_cc_overvoltage(port); - return EC_ERROR_UNKNOWN; - } - return EC_SUCCESS; -} - -static int syv682x_vbus_sink_enable(int port, int enable) -{ - int regval; - int rv; - - if (!enable) { - atomic_clear(&sink_ocp_count[port]); - atomic_clear_bits(&flags[port], SYV682X_FLAGS_SINK_ENABLED); - /* - * We're currently a source, so nothing more to do - */ - if (syv682x_is_sourcing_vbus(port)) - return EC_SUCCESS; - } else if (sink_ocp_count[port] > OCP_COUNT_LIMIT) { - /* - * Don't re-enable the channel until an explicit sink disable - * resets the ocp counter. - */ - return EC_ERROR_UNKNOWN; - } - - /* - * For sink mode need to make sure high voltage power path is connected - * and sink mode is selected. - */ - rv = read_reg(port, SYV682X_CONTROL_1_REG, ®val); - if (rv) - return rv; - - if (enable) { - /* Select high voltage path */ - regval |= SYV682X_CONTROL_1_CH_SEL; - /* Select Sink mode and turn on the channel */ - regval &= ~(SYV682X_CONTROL_1_HV_DR | - SYV682X_CONTROL_1_PWR_ENB); - /* Set sink current limit to the configured value */ - regval |= CONFIG_SYV682X_HV_ILIM << SYV682X_HV_ILIM_BIT_SHIFT; - atomic_clear_bits(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED); - atomic_or(&flags[port], SYV682X_FLAGS_SINK_ENABLED); - } else { - /* - * No need to change the voltage path or channel direction. But, - * turn both paths off because we are currently a sink. - */ - regval |= SYV682X_CONTROL_1_PWR_ENB; - } - - return write_reg(port, SYV682X_CONTROL_1_REG, regval); -} - -#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC -static int syv682x_is_vbus_present(int port) -{ - int val; - int vbus = 0; - - if (read_reg(port, SYV682X_STATUS_REG, &val)) - return vbus; - /* - * The status register interrupt bits are clear on read, check - * register value to see if there are interrupts to avoid race - * conditions with the interrupt handler - */ - syv682x_handle_status_interrupt(port, val); - - /* - * VBUS is considered present if VSafe5V is detected or neither VSafe5V - * or VSafe0V is detected, which implies VBUS > 5V. - */ - if ((val & SYV682X_STATUS_VSAFE_5V) || - !(val & (SYV682X_STATUS_VSAFE_5V | SYV682X_STATUS_VSAFE_0V))) - vbus = 1; -#ifdef CONFIG_USB_CHARGER - if (!!(flags[port] & SYV682X_FLAGS_VBUS_PRESENT) != vbus) - usb_charger_vbus_change(port, vbus); - - if (vbus) - atomic_or(&flags[port], SYV682X_FLAGS_VBUS_PRESENT); - else - atomic_clear_bits(&flags[port], SYV682X_FLAGS_VBUS_PRESENT); -#endif - - return vbus; -} -#endif - -static int syv682x_set_vbus_source_current_limit(int port, - enum tcpc_rp_value rp) -{ - int rv; - int limit; - int regval; - - rv = read_reg(port, SYV682X_CONTROL_1_REG, ®val); - if (rv) - return rv; - - /* We need buffer room for all current values. */ - switch (rp) { - case TYPEC_RP_3A0: - limit = SYV682X_5V_ILIM_3_30; - break; - - case TYPEC_RP_1A5: - limit = SYV682X_5V_ILIM_1_75; - break; - - case TYPEC_RP_USB: - default: - /* 1.25 A is lowest current limit setting for SVY682 */ - limit = SYV682X_5V_ILIM_1_25; - break; - }; - - regval &= ~SYV682X_5V_ILIM_MASK; - regval |= (limit << SYV682X_5V_ILIM_BIT_SHIFT); - return write_reg(port, SYV682X_CONTROL_1_REG, regval); -} - -#ifdef CONFIG_USBC_PPC_POLARITY -static int syv682x_set_polarity(int port, int polarity) -{ - /* - * The SYV682x does not explicitly set CC polarity. However, if VCONN is - * being used then the polarity is required to connect 5V to the correct - * CC line. So this function saves the CC polarity as a bit in the flags - * variable so VCONN is connected the correct CC line. The flag bit - * being set means polarity = CC2, the flag bit clear means - * polarity = CC1. - */ - if (polarity) - atomic_or(&flags[port], SYV682X_FLAGS_CC_POLARITY); - else - atomic_clear_bits(&flags[port], SYV682X_FLAGS_CC_POLARITY); - - return EC_SUCCESS; -} -#endif - -#ifdef CONFIG_USBC_PPC_VCONN -static int syv682x_set_vconn(int port, int enable) -{ - int regval; - int rv; - - rv = read_reg(port, SYV682X_CONTROL_4_REG, ®val); - if (rv) - return rv; - /* - * The control4 register interrupt bits are clear on read, check - * register value to see if there are interrupts to avoid race - * conditions with the interrupt handler - */ - rv = syv682x_handle_control_4_interrupt(port, regval); - if (rv) - return rv; - - regval &= ~(SYV682X_CONTROL_4_VCONN2 | SYV682X_CONTROL_4_VCONN1); - if (enable) { - regval |= flags[port] & SYV682X_FLAGS_CC_POLARITY ? - SYV682X_CONTROL_4_VCONN1 : - SYV682X_CONTROL_4_VCONN2; - } - - return write_reg(port, SYV682X_CONTROL_4_REG, regval); -} -#endif - -#ifdef CONFIG_CMD_PPC_DUMP -static int syv682x_dump(int port) -{ - int reg_addr; - int data; - int rv; - const int i2c_port = ppc_chips[port].i2c_port; - const int i2c_addr_flags = ppc_chips[port].i2c_addr_flags; - - for (reg_addr = SYV682X_STATUS_REG; reg_addr <= SYV682X_CONTROL_4_REG; - reg_addr++) { - rv = i2c_read8(i2c_port, i2c_addr_flags, reg_addr, &data); - if (rv) - ccprintf("ppc_syv682[p%d]: Failed to read reg 0x%02x\n", - port, reg_addr); - else - ccprintf("ppc_syv682[p%d]: reg 0x%02x = 0x%02x\n", - port, reg_addr, data); - } - - cflush(); - - return EC_SUCCESS; -} -#endif /* defined(CONFIG_CMD_PPC_DUMP) */ - -static void syv682x_handle_interrupt(int port) -{ - int control4; - int status; - - /* Both interrupt registers are clear on read */ - read_reg(port, SYV682X_CONTROL_4_REG, &control4); - syv682x_handle_control_4_interrupt(port, control4); - - read_reg(port, SYV682X_STATUS_REG, &status); - syv682x_handle_status_interrupt(port, status); - - /* - * Since ALERT_L is level-triggered, check the alert status and repeat - * until all interrupts are cleared. The SYV682B and later have a 10ms - * deglitch on OC, so make sure not to check the status register again - * for at least 10ms to give it time to re-trigger. This will not spam - * indefinitely on OCP, but may on OVP, RVS, or TSD. - */ - - if (status & SYV682X_STATUS_INT_MASK || - control4 & SYV682X_CONTROL_4_INT_MASK) { - syv682x_interrupt_delayed(port, INTERRUPT_DELAY_MS); - } -} - -static void syv682x_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) - syv682x_handle_interrupt(i); -} -DECLARE_DEFERRED(syv682x_irq_deferred); - -static void syv682x_interrupt_delayed(int port, int delay) -{ - atomic_or(&irq_pending, BIT(port)); - hook_call_deferred(&syv682x_irq_deferred_data, delay * MSEC); -} - -void syv682x_interrupt(int port) -{ - /* FRS timings require <15ms response to an FRS event */ - syv682x_interrupt_delayed(port, 0); -} - -/* - * The frs_en signal can be driven from the TCPC as well (preferred). - * In that case, no PPC configuration needs to be done to enable FRS - */ -#ifdef CONFIG_USB_PD_FRS_PPC -static int syv682x_set_frs_enable(int port, int enable) -{ - int regval; - - read_reg(port, SYV682X_CONTROL_4_REG, ®val); - syv682x_handle_control_4_interrupt(port, regval); - - if (enable) { - /* - * The CC line is the FRS trigger, and VCONN should - * be ignored. The SYV682 uses the CCx_BPS fields to - * determine if CC1 or CC2 is CC and should be used for - * FRS. This CCx is also connected through to the TCPC. - * The other CCx signal (VCONN) is isolated from the - * TCPC with this write (VCONN must be provided by PPC). - * - * It is not a valid state to have both or neither - * CC_BPS bits set and the CC_FRS enabled, exactly 1 - * should be set. - */ - regval &= ~(SYV682X_CONTROL_4_CC1_BPS | - SYV682X_CONTROL_4_CC2_BPS); - regval |= flags[port] & SYV682X_FLAGS_CC_POLARITY ? - SYV682X_CONTROL_4_CC2_BPS : - SYV682X_CONTROL_4_CC1_BPS; - /* set GPIO after configuring */ - write_reg(port, SYV682X_CONTROL_4_REG, regval); - gpio_set_level(ppc_chips[port].frs_en, 1); - } else { - /* - * Reconnect CC lines to TCPC. Since the FRS GPIO needs to be - * asserted until VBUS falls below 5V during an FRS, if - * SYV682X_FLAGS_FRS is set then don't deassert it, instead - * disable when sourcing is disabled. - */ - regval |= SYV682X_CONTROL_4_CC1_BPS | SYV682X_CONTROL_4_CC2_BPS; - write_reg(port, SYV682X_CONTROL_4_REG, regval); - if (!(flags[port] & SYV682X_FLAGS_FRS)) - gpio_set_level(ppc_chips[port].frs_en, 0); - } - return EC_SUCCESS; -} -#endif /*CONFIG_USB_PD_FRS_PPC*/ - -#ifndef CONFIG_USBC_PPC_SYV682X_SMART_DISCHARGE -static int syv682x_dev_is_connected(int port, enum ppc_device_role dev) -{ - /* - * (b:160548079) We disable the smart discharge(SDSG), so we should - * turn off the discharge FET if a source is connected. - */ - if (dev == PPC_DEV_SRC) - return syv682x_discharge_vbus(port, 0); - else if (dev == PPC_DEV_DISCONNECTED) - return syv682x_discharge_vbus(port, 1); - - return EC_SUCCESS; -} -#endif - -static bool syv682x_is_sink(uint8_t control_1) -{ - /* - * The SYV682 integrates power paths: 5V and HV (high voltage). - * The SYV682 can source either 5V or HV, but only sinks on the HV path. - * - * PD analyzer without a device connected confirms the SYV682 acts as - * a source under these conditions: - * HV_DR && !CH_SEL: source 5V - * HV_DR && CH_SEL: source 15V - * !HV_DR && !CH_SEL: source 5V - * - * The SYV682 is only a sink when !HV_DR && CH_SEL - */ - if (!(control_1 & SYV682X_CONTROL_1_PWR_ENB) - && !(control_1 & SYV682X_CONTROL_1_HV_DR) - && (control_1 & SYV682X_CONTROL_1_CH_SEL)) - return true; - - return false; -} - -static int syv682x_init(int port) -{ - int rv; - int regval; - int status, control_1; - enum tcpc_rp_value initial_current_limit; - - rv = read_reg(port, SYV682X_STATUS_REG, &status); - if (rv) - return rv; - - rv = read_reg(port, SYV682X_CONTROL_1_REG, &control_1); - if (rv) - return rv; - atomic_clear(&sink_ocp_count[port]); - - /* - * Disable FRS prior to configuring the power paths - */ - if (IS_ENABLED(CONFIG_USB_PD_FRS_PPC)) - gpio_set_level(ppc_chips[port].frs_en, 0); - - if (!syv682x_is_sink(control_1) - || (status & SYV682X_STATUS_VSAFE_0V)) { - /* - * Disable both power paths, - * set HV_ILIM to 3.3A, - * set 5V_ILIM to 3.3A, - * set HV direction to sink, - * select HV channel. - */ - regval = SYV682X_CONTROL_1_PWR_ENB | - (CONFIG_SYV682X_HV_ILIM << SYV682X_HV_ILIM_BIT_SHIFT) | - /* !SYV682X_CONTROL_1_HV_DR */ - SYV682X_CONTROL_1_CH_SEL; - rv = write_reg(port, SYV682X_CONTROL_1_REG, regval); - if (rv) - return rv; - } else { - /* Dead battery mode, or an existing PD contract is in place */ - rv = syv682x_vbus_sink_enable(port, 1); - if (rv) - return rv; - } - -#ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT - initial_current_limit = CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT; -#else - initial_current_limit = CONFIG_USB_PD_PULLUP; -#endif - rv = syv682x_set_vbus_source_current_limit(port, initial_current_limit); - if (rv) - return rv; - - /* - * Set Control Reg 2 to defaults except 50ms smart discharge time. - * Note: On SYV682A/B, enable smart discharge would block i2c - * transactions for 50ms (discharge time) and this - * prevents us from disabling Vconn when stop sourcing Vbus and has - * tVconnOff (35ms) timeout. - * On SYV682C, we are allowed to access CONTROL4 while the i2c busy. - */ - regval = (SYV682X_OC_DELAY_10MS << SYV682X_OC_DELAY_SHIFT) - | (SYV682X_DSG_RON_200_OHM << SYV682X_DSG_RON_SHIFT) - | (SYV682X_DSG_TIME_50MS << SYV682X_DSG_TIME_SHIFT); - - if (IS_ENABLED(CONFIG_USBC_PPC_SYV682X_SMART_DISCHARGE)) - regval |= SYV682X_CONTROL_2_SDSG; - - rv = write_reg(port, SYV682X_CONTROL_2_REG, regval); - if (rv) - return rv; - - /* - * Always set the over voltage setting to the maximum to support - * sinking from a 20V PD charger. The common PPC code doesn't provide - * any hooks for indicating what the currently negotiated voltage is. - * - * Mask Alerts due to Reverse Voltage. - */ - regval = (SYV682X_OVP_23_7 << SYV682X_OVP_BIT_SHIFT) | SYV682X_RVS_MASK; - rv = write_reg(port, SYV682X_CONTROL_3_REG, regval); - if (rv) - return rv; - - /* - * Remove Rd and connect CC1/CC2 lines to TCPC - * Disable Vconn - * Enable CC detection of Fast Role Swap (FRS) - */ - regval = SYV682X_CONTROL_4_CC1_BPS | SYV682X_CONTROL_4_CC2_BPS; - rv = write_reg(port, SYV682X_CONTROL_4_REG, regval); - if (rv) - return rv; - - return EC_SUCCESS; -} - -const struct ppc_drv syv682x_drv = { - .init = &syv682x_init, - .is_sourcing_vbus = &syv682x_is_sourcing_vbus, - .vbus_sink_enable = &syv682x_vbus_sink_enable, - .vbus_source_enable = &syv682x_vbus_source_enable, -#ifdef CONFIG_CMD_PPC_DUMP - .reg_dump = &syv682x_dump, -#endif /* defined(CONFIG_CMD_PPC_DUMP) */ -#ifdef CONFIG_USB_PD_FRS_PPC - .set_frs_enable = &syv682x_set_frs_enable, -#endif -#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC - .is_vbus_present = &syv682x_is_vbus_present, -#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */ - .set_vbus_source_current_limit = &syv682x_set_vbus_source_current_limit, - .discharge_vbus = &syv682x_discharge_vbus, -#ifndef CONFIG_USBC_PPC_SYV682X_SMART_DISCHARGE - .dev_is_connected = &syv682x_dev_is_connected, -#endif /* defined(CONFIG_USBC_PPC_SYV682X_SMART_DISCHARGE) */ -#ifdef CONFIG_USBC_PPC_POLARITY - .set_polarity = &syv682x_set_polarity, -#endif -#ifdef CONFIG_USBC_PPC_VCONN - .set_vconn = &syv682x_set_vconn, -#endif -}; |