/* 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 "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) /* 0 -> CC1, 1 -> CC2 */ #define SYV682X_FLAGS_CC_POLARITY BIT(1) #define SYV682X_FLAGS_VBUS_PRESENT BIT(2) #define SYV682X_FLAGS_OCP BIT(3) #define SYV682X_FLAGS_OVP BIT(4) #define SYV682X_FLAGS_5V_OC BIT(5) #define SYV682X_FLAGS_RVS BIT(6) #define SYV682X_FLAGS_VCONN_OCP BIT(7) static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */ static uint8_t flags[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 */ #define INTERRUPT_DELAY_MS 10 /* Deglitch in ms of sourcing overcurrent detection */ #define SOURCE_OC_DEGLITCH_MS 100 #define VCONN_OC_DEGLITCH_MS 100 #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_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); } /* * During channel transition or discharge, the SYV682A silently ignores I2C * writes. Poll the BUSY bit until the SYV682A is ready. */ static int syv682x_wait_for_ready(int port) { int regval; int rv; timestamp_t deadline; 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); 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) { 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); } 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. */ regval |= SYV682X_CONTROL_1_PWR_ENB; } rv = write_reg(port, SYV682X_CONTROL_1_REG, regval); if (rv) return rv; if (enable) flags[port] |= SYV682X_FLAGS_SOURCE_ENABLED; else 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)) { flags[port] |= flagmask; return true; } } else { 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 enable the source path */ if (IS_ENABLED(CONFIG_USB_PD_FRS_PPC) && (regval & SYV682X_STATUS_FRS)) { flags[port] |= SYV682X_FLAGS_SOURCE_ENABLED; /* * Workaround for bug in SYV692. * * The SYV682X has an FRS trigger but it is broken in some * versions of the part. The old parts require VBUS to fall to * generate the interrupt, it needs to be generated on CC alone. * * The workaround is to use to the TCPC trigger if * available. When the part is fixed, the TCPC trigger is no * longer needed. If the TCPC doesn't have a trigger, FRS timing * may be violated for slow-vbus discharge cases. */ if (!IS_ENABLED(CONFIG_USB_PD_FRS_TCPC)) pd_got_frs_signal(port); } /* These conditions automatically turn off VBUS sourcing */ if (regval & (SYV682X_STATUS_OVP | SYV682X_STATUS_TSD)) flags[port] &= ~SYV682X_FLAGS_SOURCE_ENABLED; /* * 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; flags[port] &= ~SYV682X_FLAGS_5V_OC; syv682x_vbus_source_enable(port, 0); pd_handle_overcurrent(port); } if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_OC_HV | SYV682X_STATUS_TSD, SYV682X_FLAGS_OCP)) pd_handle_overcurrent(port); /* No PD handler for VBUS OVP/RVS events */ if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_OVP, SYV682X_FLAGS_OVP)) { ppc_prints("VBUS OVP!", port); } if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_RVS, SYV682X_FLAGS_RVS)) { ppc_prints("VBUS Reverse Voltage!", port); } } 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; 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 && syv682x_is_sourcing_vbus(port)) { /* * We're currently a source, so nothing more to do */ return EC_SUCCESS; } /* * 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; flags[port] &= ~SYV682X_FLAGS_SOURCE_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) flags[port] |= SYV682X_FLAGS_VBUS_PRESENT; else 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) flags[port] |= SYV682X_FLAGS_CC_POLARITY; else 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. This will not spam indefinitely on * OCP, but may on OVP, RVS, or TSD */ if (IS_ENABLED(CONFIG_USBC_PPC_DEDICATED_INT) && ppc_get_alert_status(port)) { syv682x_interrupt_delayed(port, INTERRUPT_DELAY_MS); } else { read_reg(port, SYV682X_CONTROL_4_REG, &control4); read_reg(port, SYV682X_STATUS_REG, &status); 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 status; int regval; read_reg(port, SYV682X_CONTROL_4_REG, ®val); syv682x_handle_control_4_interrupt(port, regval); if (enable) { read_reg(port, SYV682X_STATUS_REG, &status); syv682x_handle_status_interrupt(port, status); /* * Workaround for a bug in SYV682A * * The bug is that VBUS needs to be below VBAT when CC is pulled * low to trigger FRS. This is fine when charging at 5V usually, * but often not when charging at higher voltages. At higher * voltages, the CC trigger needs to be disabled for the broken * parts. * * TODO (b/161372139): When this is fixed in SYV682B, always use * the CC trigger. */ if (status & SYV682X_STATUS_VSAFE_5V) { /* Inverted register, clear to enable CC detection */ regval &= ~SYV682X_CONTROL_4_CC_FRS; /* * 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; } else { regval |= SYV682X_CONTROL_4_CC_FRS; } } else { /* * Disabling FRS is part of the disconnect sequence, reconnect * CC lines to TCPC. */ regval |= SYV682X_CONTROL_4_CC1_BPS | SYV682X_CONTROL_4_CC2_BPS; } write_reg(port, SYV682X_CONTROL_4_REG, regval); gpio_set_level(ppc_chips[port].frs_en, enable); return EC_SUCCESS; } #endif /*CONFIG_USB_PD_FRS_PPC*/ 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; } 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; 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. * Note: do not enable smart discharge since it would block * i2c transactions for 50ms (discharge time) and this prevents * us from disabling Vconn when stop sourcing Vbus and has tVconnOff * (35ms) timeout. */ regval = (SYV682X_OC_DELAY_10MS << SYV682X_OC_DELAY_SHIFT) | (SYV682X_DSG_TIME_200MS << SYV682X_DSG_TIME_SHIFT) | (SYV682X_DSG_RON_200_OHM << SYV682X_DSG_RON_SHIFT); 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. */ regval = (SYV682X_OVP_23_7 << SYV682X_OVP_BIT_SHIFT); rv = write_reg(port, SYV682X_CONTROL_3_REG, regval); if (rv) return rv; /* * Remove Rd and connect CC1/CC2 lines to TCPC * Disable Vconn * Disable CC detection of Fast Role Swap (FRS) */ regval = SYV682X_CONTROL_4_CC1_BPS | SYV682X_CONTROL_4_CC2_BPS | SYV682X_CONTROL_4_CC_FRS; 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, .dev_is_connected = &syv682x_dev_is_connected, #ifdef CONFIG_USBC_PPC_POLARITY .set_polarity = &syv682x_set_polarity, #endif #ifdef CONFIG_USBC_PPC_VCONN .set_vconn = &syv682x_set_vconn, #endif };