/* 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.
 */

/* ANX7447 port manager */

#include "common.h"
#include "anx7447.h"
#include "console.h"
#include "hooks.h"
#include "tcpm/tcpci.h"
#include "tcpm/tcpm.h"
#include "timer.h"
#include "usb_mux.h"
#include "usb_pd.h"
#include "util.h"

#define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args)

#define vsafe5v_min (3800/25)
#define vsafe0v_max (800/25)
/*
 * These interface are workable while ADC is enabled, before
 * calling them should make sure ec driver finished chip initilization.
 */
#define is_equal_greater_safe5v(port) \
		(((anx7447_get_vbus_voltage(port))) > vsafe5v_min)
#define is_equal_greater_safe0v(port) \
		(((anx7447_get_vbus_voltage(port))) > vsafe0v_max)

struct anx_state {
	uint16_t i2c_addr_flags;
};

struct anx_usb_mux {
	int state;
};

static int anx7447_mux_set(const struct usb_mux *me, mux_state_t mux_state,
			   bool *ack_required);

static struct anx_state anx[CONFIG_USB_PD_PORT_MAX_COUNT];
static struct anx_usb_mux mux[CONFIG_USB_PD_PORT_MAX_COUNT];

/*
 * ANX7447 has two co-existence I2C addresses, TCPC address and
 * SPI address. The registers of TCPC address are partly compliant
 * with standard USB TCPC specification, and the registers in SPI
 * address controls the other functions (ex, hpd_level, mux_switch, and
 * so on). It can't use tcpc_read() and tcpc_write() to access SPI
 * address because its address has been set as TCPC in the structure
 * tcpc_config_t.
 * anx7447_reg_write() and anx7447_reg_read() are implemented here to access
 * ANX7447 SPI address.
 */
const struct anx7447_i2c_addr anx7447_i2c_addrs_flags[] = {
	{AN7447_TCPC0_I2C_ADDR_FLAGS, AN7447_SPI0_I2C_ADDR_FLAGS},
	{AN7447_TCPC1_I2C_ADDR_FLAGS, AN7447_SPI1_I2C_ADDR_FLAGS},
	{AN7447_TCPC2_I2C_ADDR_FLAGS, AN7447_SPI2_I2C_ADDR_FLAGS},
	{AN7447_TCPC3_I2C_ADDR_FLAGS, AN7447_SPI3_I2C_ADDR_FLAGS}
};

static inline int anx7447_reg_write(int port, int reg, int val)
{
	int rv = i2c_write8(tcpc_config[port].i2c_info.port,
			    anx[port].i2c_addr_flags,
			    reg, val);
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
	pd_device_accessed(port);
#endif
	return rv;
}

static inline int anx7447_reg_read(int port, int reg, int *val)
{
	int rv = i2c_read8(tcpc_config[port].i2c_info.port,
			   anx[port].i2c_addr_flags,
			   reg, val);
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
	pd_device_accessed(port);
#endif
	return rv;
}

void anx7447_hpd_mode_init(int port)
{
	int reg, rv;

	rv = anx7447_reg_read(port, ANX7447_REG_HPD_CTRL_0, &reg);
	if (rv)
		return;

	/*
	 * Set ANX7447_REG_HPD_MODE bit as 0, then the TCPC will generate the
	 * HPD pulse from internal timer (by using ANX7447_REG_HPD_IRQ0)
	 * instead of using the ANX7447_REG_HPD_OUT to set the HPD IRQ signal.
	 */
	reg &= ~(ANX7447_REG_HPD_MODE | ANX7447_REG_HPD_PLUG |
		 ANX7447_REG_HPD_UNPLUG);
	anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg);
}

void anx7447_hpd_output_en(int port)
{
	int reg, rv;

	rv = anx7447_reg_read(port, ANX7447_REG_HPD_DEGLITCH_H, &reg);
	if (rv)
		return;

	reg |= ANX7447_REG_HPD_OEN;
	anx7447_reg_write(port, ANX7447_REG_HPD_DEGLITCH_H, reg);
}

void anx7447_set_hpd_level(int port, int hpd_lvl)
{
	int reg, rv;

	rv = anx7447_reg_read(port, ANX7447_REG_HPD_CTRL_0, &reg);
	if (rv)
		return;

	/*
	 * When ANX7447_REG_HPD_MODE is 1, use ANX7447_REG_HPD_OUT
	 * to generate HPD event, otherwise use ANX7447_REG_HPD_UNPLUG
	 * and ANX7447_REG_HPD_PLUG.
	 */
	if (hpd_lvl) {
		reg &= ~ANX7447_REG_HPD_UNPLUG;
		reg |= ANX7447_REG_HPD_PLUG;
	} else {
		reg &= ~ANX7447_REG_HPD_PLUG;
		reg |= ANX7447_REG_HPD_UNPLUG;
	}
	anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg);
}

#ifdef CONFIG_USB_PD_TCPM_ANX7447_OCM_ERASE_COMMAND
static inline void anx7447_reg_write_and(int port, int reg, int v_and)
{
	int val;

	if (!anx7447_reg_read(port, reg, &val))
		anx7447_reg_write(port, reg, (val & v_and));
}

static inline void anx7447_reg_write_or(int port, int reg, int v_or)
{
	int val;

	if (!anx7447_reg_read(port, reg, &val))
		anx7447_reg_write(port, reg, (val | v_or));
}

#define ANX7447_FLASH_DONE_TIMEOUT_US	(100 * MSEC)

static int anx7447_wait_for_flash_done(int port)
{
	timestamp_t deadline;
	int rv;
	int r;

	deadline.val = get_time().val + ANX7447_FLASH_DONE_TIMEOUT_US;
	do {
		if (timestamp_expired(deadline, NULL))
			return EC_ERROR_TIMEOUT;
		rv = anx7447_reg_read(port, ANX7447_REG_R_RAM_CTRL, &r);
		if (rv)
			return rv;
	} while (!(r & ANX7447_R_RAM_CTRL_FLASH_DONE));

	return EC_SUCCESS;
}

static int anx7447_flash_write_en(int port)
{
	anx7447_reg_write(port, ANX7447_REG_FLASH_INST_TYPE,
			  ANX7447_FLASH_INST_TYPE_WRITEENABLE);
	anx7447_reg_write_or(port, ANX7447_REG_R_FLASH_RW_CTRL,
			     ANX7447_R_FLASH_RW_CTRL_GENERAL_INST_EN);
	return anx7447_wait_for_flash_done(port);
}

static int anx7447_flash_op_init(int port)
{
	int rv;

	anx7447_reg_write_or(port, ANX7447_REG_OCM_CTRL_0,
			     ANX7447_OCM_CTRL_OCM_RESET);
	anx7447_reg_write_or(port, ANX7447_REG_ADDR_GPIO_CTRL_0,
			     ANX7447_ADDR_GPIO_CTRL_0_SPI_WP);

	rv = anx7447_flash_write_en(port);
	if (rv)
		return rv;

	anx7447_reg_write_and(port, ANX7447_REG_R_FLASH_STATUS_0,
			      ANX7447_FLASH_STATUS_SPI_STATUS_0);
	anx7447_reg_write_or(port, ANX7447_REG_R_FLASH_RW_CTRL,
			     ANX7447_R_FLASH_RW_CTRL_WRITE_STATUS_EN);

	return anx7447_wait_for_flash_done(port);
}

static int anx7447_flash_is_empty(int port)
{
	int r;

	anx7447_reg_read(port, ANX7447_REG_OCM_VERSION, &r);

	return ((r == 0) ? 1 : 0);
}

static int anx7447_flash_erase_internal(int port, int write_console_if_empty)
{
	int rv;
	int r;

	tcpc_read(port, TCPC_REG_COMMAND, &r);
	usleep(ANX7447_DELAY_IN_US);

	if (anx7447_flash_is_empty(port) == 1) {
		if (write_console_if_empty)
			CPRINTS("C%d: Nothing to erase!", port);
		return EC_SUCCESS;
	}
	CPRINTS("C%d: Erasing OCM flash...", port);

	rv = anx7447_flash_op_init(port);
	if (rv)
		return rv;

	usleep(ANX7447_DELAY_IN_US);

	rv = anx7447_flash_write_en(port);
	if (rv)
		return rv;

	anx7447_reg_write(port, ANX7447_REG_FLASH_ERASE_TYPE,
			  ANX7447_FLASH_ERASE_TYPE_CHIPERASE);
	anx7447_reg_write_or(port, ANX7447_REG_R_FLASH_RW_CTRL,
			     ANX7447_R_FLASH_RW_CTRL_FLASH_ERASE_EN);

	return anx7447_wait_for_flash_done(port);
}

int anx7447_flash_erase(int port)
{
	return anx7447_flash_erase_internal(port,
					    0 /* suppress console if empty */);
}

/* Add console command to erase OCM flash if needed. */
static int command_anx_ocm(int argc, char **argv)
{
	char *e = NULL;
	int port;

	if (argc < 2)
		return EC_ERROR_PARAM_COUNT;

	/* Get port number from first parameter */
	port = strtoi(argv[1], &e, 0);
	if (*e)
		return EC_ERROR_PARAM1;

	if (argc > 2) {
		int rv;
		if (strcasecmp(argv[2], "erase"))
			return EC_ERROR_PARAM2;
		rv = anx7447_flash_erase_internal(
			port, 1 /* write to console if empty */);
		if (rv)
			ccprintf("C%d: Failed to erase OCM flash (%d)\n",
				 port, rv);
	}

	ccprintf("C%d: OCM flash is %sempty.\n",
		port, anx7447_flash_is_empty(port) ? "" : "not ");

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(anx_ocm, command_anx_ocm,
			"port [erase]",
			"Print OCM status or erases OCM for a given port.");
#endif

static int anx7447_init(int port)
{
	int rv, reg, i;
	const struct usb_mux *me = &usb_muxes[port];
	bool unused;

	ASSERT(port < CONFIG_USB_PD_PORT_MAX_COUNT);

	memset(&anx[port], 0, sizeof(struct anx_state));

	/*
	 * find corresponding anx7447 SPI address according to
	 * specified TCPC address
	 */
	for (i = 0; i < ARRAY_SIZE(anx7447_i2c_addrs_flags); i++) {
		if (I2C_STRIP_FLAGS(tcpc_config[port].i2c_info.addr_flags) ==
		    I2C_STRIP_FLAGS(
			    anx7447_i2c_addrs_flags[i].tcpc_addr_flags)) {
			anx[port].i2c_addr_flags =
				anx7447_i2c_addrs_flags[i].spi_addr_flags;
			break;
		}
	}
	if (!I2C_STRIP_FLAGS(anx[port].i2c_addr_flags)) {
		ccprintf("TCPC I2C addr 0x%x is invalid for ANX7447\n",
			 I2C_STRIP_FLAGS(tcpc_config[port]
				      .i2c_info.addr_flags));
		return EC_ERROR_UNKNOWN;
	}


	rv = tcpci_tcpm_init(port);
	if (rv)
		return rv;

#ifdef CONFIG_USB_PD_TCPM_ANX7447_OCM_ERASE_COMMAND
	/* Check and print OCM status to console. */
	CPRINTS("C%d: OCM flash is %sempty",
		port, anx7447_flash_is_empty(port) ? "" : "not ");
#endif

	/*
	 * 7447 has a physical pin to detect the presence of VBUS, VBUS_SENSE
	 * , and 7447 has a VBUS current protection mechanism through another
	 * pin input VBUS_OCP. To enable VBUS OCP, OVP protection, driver needs
	 * to set the threshold to the registers VBUS_VOLTAGE_ALARM_HI_CFG
	 * (0x76 & 0x77) and VBUS_OCP_HI_THRESHOLD (0xDD &0xDE). These values
	 * could be customized based on different platform design.
	 * Disable VBUS protection here since the default values of
	 * VBUS_VOLTAGE_ALARM_HI_CFG and VBUS_OCP_HI_THRESHOLD are zero.
	 */
	rv = tcpc_read(port, ANX7447_REG_TCPC_CTRL_2, &reg);
	if (rv)
		return rv;
	reg &= ~ANX7447_REG_ENABLE_VBUS_PROTECT;
	rv = tcpc_write(port, ANX7447_REG_TCPC_CTRL_2, reg);
	if (rv)
		return rv;

	/*
	 * Specifically disable voltage alarms, as VBUS_VOLTAGE_ALARM_HI may
	 * trigger repeatedly despite being masked (b/153989733)
	 */
	rv = tcpc_update16(port, TCPC_REG_POWER_CTRL,
			   TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS, MASK_SET);
	if (rv)
		return rv;

	/* ADC enable, use to monitor VBUS voltage */
	rv = tcpc_read(port, ANX7447_REG_ADC_CTRL_1, &reg);
	if (rv)
		return rv;
	reg |= ANX7447_REG_ADCFSM_EN;
	rv = tcpc_write(port, ANX7447_REG_ADC_CTRL_1, reg);
	if (rv)
		return rv;

	/* Set VCONN OCP(Over Current Protection) threshold */
	rv = tcpc_read(port, ANX7447_REG_ANALOG_CTRL_8, &reg);
	if (rv)
		return rv;
	reg &= ~ANX7447_REG_VCONN_OCP_MASK;
	reg |= ANX7447_REG_VCONN_OCP_440mA;
	rv = tcpc_write(port, ANX7447_REG_ANALOG_CTRL_8, reg);

	/* Vconn SW protection time of inrush current */
	rv = tcpc_read(port, ANX7447_REG_ANALOG_CTRL_10, &reg);
	if (rv)
		return rv;
	reg &= ~ANX7447_REG_R_VCONN_PWR_PRT_INRUSH_TIME_MASK;
	reg |= ANX7447_REG_R_VCONN_PWR_PRT_INRUSH_TIME_2430US;
	rv = tcpc_write(port, ANX7447_REG_ANALOG_CTRL_10, reg);
	if (rv)
		return rv;

#ifdef CONFIG_USB_PD_TCPM_MUX
	/*
	 * Run mux_set() here for considering CCD(Case-Closed Debugging) case
	 * If this TCPC is not also the MUX then don't initialize to NONE
	 */
	while ((me != NULL) && (me->driver != &anx7447_usb_mux_driver))
		me = me->next_mux;

	/*
	 * Note that bypassing the usb_mux API is okay for internal driver calls
	 * since the task calling init already holds this port's mux lock.
	 */
	if (me != NULL &&
	    !(me->flags & USB_MUX_FLAG_NOT_TCPC))
		rv = anx7447_mux_set(me, USB_PD_MUX_NONE, &unused);
#endif /* CONFIG_USB_PD_TCPM_MUX */

	return rv;
}

static int anx7447_release(int port)
{
	return EC_SUCCESS;
}

#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
static int anx7447_get_vbus_voltage(int port)
{
	int vbus_volt = 0;

	tcpc_read16(port, TCPC_REG_VBUS_VOLTAGE, &vbus_volt);

	return vbus_volt;
}

int anx7447_set_power_supply_ready(int port)
{
	int count = 0;

	while (is_equal_greater_safe0v(port)) {
		if (count >= 10)
			break;
		msleep(100);
		count++;
	}

	return tcpc_write(port, TCPC_REG_COMMAND, 0x77);
}
#endif /* CONFIG_USB_PD_VBUS_DETECT_TCPC */

int anx7447_power_supply_reset(int port)
{
	return tcpc_write(port, TCPC_REG_COMMAND, 0x66);
}

int anx7447_board_charging_enable(int port, int enable)
{
	return tcpc_write(port, TCPC_REG_COMMAND, enable ? 0x55 : 0x44);
}

static void anx7447_tcpc_alert(int port)
{
	/* process and clear alert status */
	tcpci_tcpc_alert(port);
}

/*
 * timestamp of the next possible toggle to ensure the 2-ms spacing
 * between IRQ_HPD.
 */
static uint64_t hpd_deadline[CONFIG_USB_PD_PORT_MAX_COUNT];

void anx7447_tcpc_update_hpd_status(const struct usb_mux *me,
				    mux_state_t mux_state,
				    bool *ack_required)
{
	int reg = 0;
	int port = me->usb_port;
	int hpd_lvl = (mux_state & USB_PD_MUX_HPD_LVL) ? 1 : 0;
	int hpd_irq = (mux_state & USB_PD_MUX_HPD_IRQ) ? 1 : 0;

	/* This driver does not use host command ACKs */
	*ack_required = false;

	/*
	 * All calls within this method need to update to a mux_read/write calls
	 * that use the secondary address. This is a non-trival change and no
	 * one is using the anx7447 as a mux only (and probably never will since
	 * it doesn't have a re-driver). If that changes, we need to update this
	 * code.
	 */
	ASSERT(!(me->flags & USB_MUX_FLAG_NOT_TCPC));

	anx7447_set_hpd_level(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);

		/*
		 * For generate hardware HPD IRQ, need clear bit
		 * ANX7447_REG_HPD_IRQ0 first, then set it. This bit is not
		 * write clear.
		 */
		anx7447_reg_read(port, ANX7447_REG_HPD_CTRL_0, &reg);
		reg &= ~ANX7447_REG_HPD_IRQ0;
		anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg);
		reg |= ANX7447_REG_HPD_IRQ0;
		anx7447_reg_write(port, ANX7447_REG_HPD_CTRL_0, reg);
	}
	/* enforce 2-ms delay between HPD pulses */
	hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
}

void anx7447_tcpc_clear_hpd_status(int port)
{
	anx7447_hpd_output_en(port);
	anx7447_set_hpd_level(port, 0);
}

#ifdef CONFIG_USB_PD_TCPM_MUX
static int anx7447_mux_init(const struct usb_mux *me)
{
	int port = me->usb_port;
	bool unused;

	ASSERT(port < CONFIG_USB_PD_PORT_MAX_COUNT);

	memset(&mux[port], 0, sizeof(struct anx_usb_mux));

	/* init hpd status */
	anx7447_hpd_mode_init(port);
	anx7447_set_hpd_level(port, 0);
	anx7447_hpd_output_en(port);

	/*
	 * ANX initializes its muxes to (USB_PD_MUX_USB_ENABLED |
	 * USB_PD_MUX_DP_ENABLED) when reinitialized, we need to force
	 * initialize it to USB_PD_MUX_NONE
	 */
	return anx7447_mux_set(me, USB_PD_MUX_NONE, &unused);
}

#ifdef CONFIG_USB_PD_TCPM_ANX7447_AUX_PU_PD
static void anx7447_mux_safemode(const struct usb_mux *me, int on_off)
{
	int reg;

	mux_read(me, ANX7447_REG_ANALOG_CTRL_9, &reg);

	if (on_off)
		reg |= ANX7447_REG_SAFE_MODE;
	else
		reg &= ~(ANX7447_REG_SAFE_MODE);

	mux_write(me, ANX7447_REG_ANALOG_CTRL_9, reg);
	CPRINTS("C%d set mux to safemode %s, reg = 0x%x",
		me->usb_port, (on_off) ? "on" : "off", reg);
}

static inline void anx7447_configure_aux_src(const struct usb_mux *me,
					     int on_off)
{
	int reg;

	mux_read(me, ANX7447_REG_ANALOG_CTRL_9, &reg);

	if (on_off)
		reg |= ANX7447_REG_R_AUX_RES_PULL_SRC;
	else
		reg &= ~(ANX7447_REG_R_AUX_RES_PULL_SRC);

	mux_write(me, ANX7447_REG_ANALOG_CTRL_9, reg);

	CPRINTS("C%d set aux_src to %s, reg = 0x%x",
		me->usb_port, (on_off) ? "on" : "off", reg);
}
#endif

/*
 * Set mux.
 *
 * sstx and ssrx are the USB superspeed transmit and receive pairs. ml is the
 * DisplayPort Main Link. There are four lanes total. For example, DP cases
 * connect them all and dock cases connect 2 DP and USB.
 *
 * a2, a3, a10, a11, b2, b3, b10, b11 are pins on the USB-C connector.
 */
static int anx7447_mux_set(const struct usb_mux *me, mux_state_t mux_state,
			   bool *ack_required)
{
	int cc_direction;
	mux_state_t mux_type;
	int sw_sel = 0x00, aux_sw = 0x00;
	int rv;
	int port = me->usb_port;

	/* This driver does not use host command ACKs */
	*ack_required = false;

	cc_direction = mux_state & USB_PD_MUX_POLARITY_INVERTED;
	mux_type = mux_state & USB_PD_MUX_DOCK;
	CPRINTS("C%d mux_state = 0x%x, mux_type = 0x%x",
		port, mux_state, mux_type);
	if (cc_direction == 0) {
		/* cc1 connection */
		if (mux_type == USB_PD_MUX_DOCK) {
			/* ml0-a10/11, ml1-b2/b3, sstx-a2/a3, ssrx-b10/11 */
			sw_sel = 0x21;
			/* aux+ <-> sbu1, aux- <-> sbu2 */
			aux_sw = 0x03;
		} else if (mux_type == USB_PD_MUX_DP_ENABLED) {
			/* ml0-a10/11, ml1-b2/b3, ml2-a2/a3, ml3-b10/11 */
			sw_sel = 0x09;
			/* aux+ <-> sbu1, aux- <-> sbu2 */
			aux_sw = 0x03;
		} else if (mux_type == USB_PD_MUX_USB_ENABLED) {
			/* ssrxp<->b11, ssrxn<->b10, sstxp<->a2, sstxn<->a3 */
			sw_sel = 0x20;
		}
	} else {
		/* cc2 connection */
		if (mux_type == USB_PD_MUX_DOCK) {
			/* ml0-b10/11, ml1-a2/b3, sstx-b2/a3, ssrx-a10/11 */
			sw_sel = 0x12;
			/* aux+ <-> sbu2, aux- <-> sbu1 */
			aux_sw = 0x0C;
		} else if (mux_type == USB_PD_MUX_DP_ENABLED) {
			/* ml0-b10/11, ml1-a2/b3, ml2-b2/a3, ml3-a10/11 */
			sw_sel = 0x06;
			/* aux+ <-> sbu2, aux- <-> sbu1 */
			aux_sw = 0x0C;
		} else if (mux_type == USB_PD_MUX_USB_ENABLED) {
			/* ssrxp<->a11, ssrxn<->a10, sstxp<->b2, sstxn<->b3 */
			sw_sel = 0x10;
		}
	}

	/*
	 * Once need to configure the Mux, should set the mux to safe mode
	 * first. After the  mux configured, should set mux to normal mode.
	 */
#ifdef CONFIG_USB_PD_TCPM_ANX7447_AUX_PU_PD
	anx7447_mux_safemode(me, 1);
#endif
	rv = mux_write(me, ANX7447_REG_TCPC_SWITCH_0, sw_sel);
	rv |= mux_write(me, ANX7447_REG_TCPC_SWITCH_1, sw_sel);
	rv |= mux_write(me, ANX7447_REG_TCPC_AUX_SWITCH, aux_sw);

	mux[port].state = mux_state;

#ifdef CONFIG_USB_PD_TCPM_ANX7447_AUX_PU_PD
	/*
	 * DP and Dock mode: after configured the Mux, change the Mux to
	 * normal mode, otherwise: keep safe mode.
	 */
	if (mux_type != USB_PD_MUX_NONE) {
		anx7447_configure_aux_src(me, 1);
		anx7447_mux_safemode(me, 0);
	} else
		anx7447_configure_aux_src(me, 0);
#endif

	return rv;
}

/* current mux state */
static int anx7447_mux_get(const struct usb_mux *me, mux_state_t *mux_state)
{
	int port = me->usb_port;

	*mux_state = mux[port].state;

	return EC_SUCCESS;
}
#endif /* CONFIG_USB_PD_TCPM_MUX */

#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
static int anx7447_tcpc_drp_toggle(int port)
{
	int rv, reg;

	rv = tcpc_read(port, ANX7447_REG_ANALOG_CTRL_10, &reg);
	if (rv)
		return rv;
	/*
	 * When using Look4Connection command to toggle CC under normal mode
	 * the CABLE_DET_DIG shall be clear first.
	 */
	if (reg & ANX7447_REG_CABLE_DET_DIG) {
		reg &= ~ANX7447_REG_CABLE_DET_DIG;
		rv = tcpc_write(port, ANX7447_REG_ANALOG_CTRL_10, reg);
		if (rv)
			return rv;
	}

	return tcpci_tcpc_drp_toggle(port);
}
#endif

/* Override for tcpci_tcpm_set_cc */
static int anx7447_set_cc(int port, int pull)
{
	int rp, reg;

	rp = tcpc_read(port, ANX7447_REG_ANALOG_CTRL_10, &reg);
	if (rp)
		return rp;
	/*
	 * When setting CC status, should be confirm that the CC toggling
	 * process is stopped, the CABLE_DET_DIG shall be set to one.
	 */
	if ((reg & ANX7447_REG_CABLE_DET_DIG) == 0) {
		reg |= ANX7447_REG_CABLE_DET_DIG;
		rp = tcpc_write(port, ANX7447_REG_ANALOG_CTRL_10, reg);
		if (rp)
			return rp;
	}

	rp = tcpci_get_cached_rp(port);

	/* Set manual control, and set both CC lines to the same pull */
	return tcpc_write(port, TCPC_REG_ROLE_CTRL,
			  TCPC_REG_ROLE_CTRL_SET(0, rp, pull, pull));
}

/* Override for tcpci_tcpm_set_polarity */
static int anx7447_set_polarity(int port,
				enum tcpc_cc_polarity polarity)
{
	return tcpc_update8(port,
			    TCPC_REG_TCPC_CTRL,
			    TCPC_REG_TCPC_CTRL_SET(1),
			    polarity_rm_dts(polarity)
					? MASK_SET : MASK_CLR);
}

#ifdef CONFIG_CMD_TCPC_DUMP
static const struct tcpc_reg_dump_map anx7447_regs[] = {
	{
		.addr = ANX7447_REG_TCPC_SWITCH_0,
		.name = "SWITCH_0",
		.size = 1,
	},
	{
		.addr = ANX7447_REG_TCPC_SWITCH_1,
		.name = "SWITCH_1",
		.size = 1,
	},
	{
		.addr = ANX7447_REG_TCPC_AUX_SWITCH,
		.name = "AUX_SWITCH",
		.size = 1,
	},
	{
		.addr = ANX7447_REG_ADC_CTRL_1,
		.name = "ADC_CTRL_1",
		.size = 1,
	},
	{
		.addr = ANX7447_REG_ANALOG_CTRL_8,
		.name = "ANALOG_CTRL_8",
		.size = 1,
	},
	{
		.addr = ANX7447_REG_ANALOG_CTRL_10,
		.name = "ANALOG_CTRL_10",
		.size = 1,
	},
	{
		.addr = ANX7447_REG_TCPC_CTRL_2,
		.name = "TCPC_CTRL_2",
		.size = 1,
	},
};

const struct {
	const char *name;
	uint8_t addr;
} anx7447_alt_regs[] = {
	{
		.name = "HPD_CTRL_0",
		.addr = ANX7447_REG_HPD_CTRL_0,
	},
	{
		.name = "HPD_DEGLITCH_H",
		.addr = ANX7447_REG_HPD_DEGLITCH_H,
	},
	{
		.name = "INTP_SOURCE_0",
		.addr = ANX7447_REG_INTP_SOURCE_0,
	},
	{
		.name = "INTP_MASK_0",
		.addr = ANX7447_REG_INTP_MASK_0,
	},
	{
		.name = "INTP_CTRL_0",
		.addr = ANX7447_REG_INTP_CTRL_0,
	},
	{
		.name = "PAD_INTP_CTRL",
		.addr = ANX7447_REG_PAD_INTP_CTRL,
	},
};

/*
 * Dump registers for debug command.
 */
static void anx7447_dump_registers(int port)
{
	int i, val;

	tcpc_dump_std_registers(port);
	tcpc_dump_registers(port, anx7447_regs, ARRAY_SIZE(anx7447_regs));
	for (i = 0; i < ARRAY_SIZE(anx7447_alt_regs); i++) {
		anx7447_reg_read(port, anx7447_alt_regs[i].addr, &val);
		ccprintf("  %-26s(ALT/0x%02x) =   0x%02x\n",
				anx7447_alt_regs[i].name,
				anx7447_alt_regs[i].addr, (uint8_t)val);
		cflush();
	}
}
#endif /* defined(CONFIG_CMD_TCPC_DUMP) */

/*
 * ANX7447 is a TCPCI compatible port controller, with some caveats.
 * It seems to require both CC lines to be set always, instead of just
 * one at a time, according to TCPCI spec.  Thus, now that the TCPCI
 * driver more closely follows the spec, this driver requires
 * overrides for set_cc and set_polarity.
 */
const struct tcpm_drv anx7447_tcpm_drv = {
	.init			= &anx7447_init,
	.release		= &anx7447_release,
	.get_cc			= &tcpci_tcpm_get_cc,
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
	.check_vbus_level	= &tcpci_tcpm_check_vbus_level,
#endif
	.select_rp_value	= &tcpci_tcpm_select_rp_value,
	.set_cc			= &anx7447_set_cc,
	.set_polarity		= &anx7447_set_polarity,
#ifdef CONFIG_USB_PD_DECODE_SOP
	.sop_prime_enable	= &tcpci_tcpm_sop_prime_enable,
#endif
	.set_vconn		= &tcpci_tcpm_set_vconn,
	.set_msg_header		= &tcpci_tcpm_set_msg_header,
	.set_rx_enable		= &tcpci_tcpm_set_rx_enable,
	.get_message_raw	= &tcpci_tcpm_get_message_raw,
	.transmit		= &tcpci_tcpm_transmit,
	.tcpc_alert		= &anx7447_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		= anx7447_tcpc_drp_toggle,
#endif
	.get_chip_info		= &tcpci_get_chip_info,
#ifdef CONFIG_USB_PD_PPC
	.set_snk_ctrl		= &tcpci_tcpm_set_snk_ctrl,
	.set_src_ctrl		= &tcpci_tcpm_set_src_ctrl,
#endif
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
	.enter_low_power_mode	= &tcpci_enter_low_power_mode,
#endif
	.set_bist_test_mode	= &tcpci_set_bist_test_mode,
#ifdef CONFIG_CMD_TCPC_DUMP
	.dump_registers		= &anx7447_dump_registers,
#endif
};

#ifdef CONFIG_USB_PD_TCPM_MUX
const struct usb_mux_driver anx7447_usb_mux_driver = {
	.init = anx7447_mux_init,
	.set = anx7447_mux_set,
	.get = anx7447_mux_get,
};
#endif /* CONFIG_USB_PD_TCPM_MUX */
