/* 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, &regval);
		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, &regval);
	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, &regval);
	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, &regval);
	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, &regval);
	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, &regval);
	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, &regval);
	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
};
