/* Copyright 2016 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.
 *
 * Author : Analogix Semiconductor.
 */

/* Type-C port manager for Analogix's anx74xx chips */

#include "anx74xx.h"
#include "task.h"
#include "tcpci.h"
#include "tcpm.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_mux.h"
#include "usb_pd.h"
#include "usb_pd_tcpc.h"
#include "util.h"

#if !defined(CONFIG_USB_PD_TCPM_TCPCI)
#error "ANX74xx is using part of standard TCPCI control"
#error "Please upgrade your board configuration"
#endif

struct anx_state {
	int	polarity;
	int	vconn_en;
	int	mux_state;
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
	int	prev_mode;
#endif
};
#define clear_recvd_msg_int(port) do {\
		int reg, rv; \
		rv = tcpc_read(port, ANX74XX_REG_RECVD_MSG_INT, &reg); \
		if (!rv) \
			tcpc_write(port, ANX74XX_REG_RECVD_MSG_INT, \
			reg | 0x01); \
	} while (0)

static struct anx_state anx[CONFIG_USB_PD_PORT_COUNT];

/* Save the selected rp value */
static int selected_rp[CONFIG_USB_PD_PORT_COUNT];

static int anx74xx_tcpm_init(int port);

static void anx74xx_tcpm_set_auto_good_crc(int port, int enable)
{
	tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_2,
		   enable ? ANX74XX_REG_REPLY_SOP_EN : 0);
}

static void anx74xx_update_cable_det(int port, int mode)
{
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
	int reg;

	if (anx[port].prev_mode == mode)
		return;

	/* Update power mode */
	anx[port].prev_mode = mode;

	/* Get ANALOG_CTRL_0 for cable det bit */
	if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_0, &reg))
		return;

	if (mode == ANX74XX_STANDBY_MODE) {
		int cc_reg;

		/*
		 * The ANX4329 enters standby mode by setting PWR_EN signal
		 * low. In addition, RESET_L must be set low to keep the ANX3429
		 * in standby mode.
		 *
		 * Clearing bit 7 of ANX74XX_REG_ANALOG_CTRL_0 will cause the
		 * ANX3429 to clear the cable_det signal that goes from the
		 * ANX3429 to the EC. If this bit is cleared when a cable is
		 * attached then cable_det will go high once standby is entered.
		 *
		 * In some cases, such as when the chipset power state is
		 * S3/S5/G3 and a sink only adapter is connected to the port,
		 * this behavior is undesirable. The constant toggling between
		 * standby and normal mode means that effectively the ANX3429 is
		 * not in standby mode only consumes ~1 mW less than just
		 * remaining in normal mode. However when an E mark cable is
		 * connected, clearing bit 7 is required so that while the E
		 * mark cable configuration happens, the USB PD state machine
		 * will continue to wake up until the USB PD attach event can be
		 * regtistered.
		 *
		 * Therefore, the decision to clear bit 7 is based on the
		 * current CC status of the port. If the CC status is open for
		 * both CC lines OR if either CC line is showing Ra, then clear
		 * bit 7. Not clearing bit 7 has no impact for normal cables and
		 * prevents the constant toggle of standby<->normal when an
		 * adapter is connected that isn't allowed to attach. Clearing
		 * bit 7 when CC status reads Ra for either CC line allows the
		 * USB PD state machine to be woken until the attach event can
		 * happen. Note that in the case an E mark cable is connected
		 * and can't attach (i.e. sink only port <- Emark cable -> sink
		 * only adapter), then the ANX3429 will toggle indefinitely,
		 * until either the cable is removed, or the port drp status
		 * changes so the attach event can occur.
		 * .
		 */

		/* Read CC status to see if cable_det bit should be cleared */
		if (tcpc_read(port, ANX74XX_REG_CC_STATUS, &cc_reg))
			return;
		/* If open or either CC line is Ra, then clear cable_det */
		if (!cc_reg || (cc_reg & ANX74XX_CC_RA_MASK &&
				!(cc_reg & ANX74XX_CC_RD_MASK)))
			reg &= ~ANX74XX_REG_R_PIN_CABLE_DET;
	} else {
		reg |= ANX74XX_REG_R_PIN_CABLE_DET;
	}

	tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_0, reg);
#endif
}

static void anx74xx_set_power_mode(int port, int mode)
{
	/*
	 * Update PWR_EN and RESET_N signals to the correct level. High for
	 * Normal mode and low for Standby mode. When transitioning from standby
	 * to normal mode, must set the PWR_EN and RESET_N before attempting to
	 * modify cable_det bit of analog_ctrl_0. If going from Normal to
	 * Standby, updating analog_ctrl_0 must happen before setting PWR_EN and
	 * RESET_N low.
	 */
	if (mode == ANX74XX_NORMAL_MODE) {
		/* Take chip out of standby mode */
		board_set_tcpc_power_mode(port, mode);
		/* Update the cable det signal */
		anx74xx_update_cable_det(port, mode);
	} else {
		/* Update cable cable det signal */
		anx74xx_update_cable_det(port, mode);
		/*
		 * Delay between setting cable_det low and setting RESET_L low
		 * as recommended the ANX3429 datasheet.
		 */
		msleep(1);
		/* Put chip into standby mode */
		board_set_tcpc_power_mode(port, mode);
	}
}

void anx74xx_tcpc_set_vbus(int port, int enable)
{
	int reg;

	tcpc_read(port, ANX74XX_REG_GPIO_CTRL_4_5, &reg);
	if (enable)
		reg |= ANX74XX_REG_SET_VBUS;
	else
		reg &= ~ANX74XX_REG_SET_VBUS;
	tcpc_write(port, ANX74XX_REG_GPIO_CTRL_4_5, reg);
}

#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
static void anx74xx_tcpc_discharge_vbus(int port, int enable)
{
	int reg;

	tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, &reg);
	if (enable)
		reg |= ANX74XX_REG_DISCHARGE_CTRL;
	else
		reg &= ~ANX74XX_REG_DISCHARGE_CTRL;
	tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg);
}
#endif

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

void anx74xx_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq)
{
	int reg;

	tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, &reg);
	if (hpd_lvl)
		reg |= ANX74XX_REG_HPD_OUT_DATA;
	else
		reg &= ~ANX74XX_REG_HPD_OUT_DATA;
	tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg);

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

		tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, &reg);
		reg &= ~ANX74XX_REG_HPD_OUT_DATA;
		tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg);
		usleep(HPD_DSTREAM_DEBOUNCE_IRQ);
		reg |= ANX74XX_REG_HPD_OUT_DATA;
		tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg);
	}
	/* enforce 2-ms delay between HPD pulses */
	hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
}

void anx74xx_tcpc_clear_hpd_status(int port)
{
	int reg;

	tcpc_read(port, ANX74XX_REG_HPD_CTRL_0, &reg);
	reg &= 0xcf;
	tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, reg);
}

#ifdef CONFIG_USB_PD_TCPM_MUX
static int anx74xx_tcpm_mux_init(int i2c_addr)
{
	int port = i2c_addr;

	/* Nothing to do here, ANX initializes its muxes
	 * as (MUX_USB_ENABLED | MUX_DP_ENABLED)
	 */
	anx[port].mux_state = MUX_USB_ENABLED | MUX_DP_ENABLED;

	return EC_SUCCESS;
}

static int anx74xx_tcpm_mux_enter_safe_mode(int port)
{
	int reg;

	if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, &reg))
		return EC_ERROR_UNKNOWN;
	if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg |
		       ANX74XX_REG_MODE_TRANS))
		return EC_ERROR_UNKNOWN;


	return EC_SUCCESS;
}

static int anx74xx_tcpm_mux_exit_safe_mode(int port)
{
	int reg;

	if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, &reg))
		return EC_ERROR_UNKNOWN;
	if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg &
		       ~ANX74XX_REG_MODE_TRANS))
		return EC_ERROR_UNKNOWN;


	return EC_SUCCESS;
}

static int anx74xx_tcpm_mux_exit(int port)
{
	int reg;

	/*
	 * Safe mode must be entered before any changes are made to the mux
	 * settings used to enable ALT_DP mode. This funciton is called either
	 * from anx74xx_tcpm_mux_set when TYPEC_MUX_NONE is selected as the new
	 * mux state, or when both cc lines are determined to be
	 * TYPEC_CC_VOLT_OPEN. Therefore, safe mode must be entered and exited
	 * here so that both entry paths are handled.
	 */
	if (anx74xx_tcpm_mux_enter_safe_mode(port))
		return EC_ERROR_UNKNOWN;

	/* Disconnect aux from sbu */
	if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, &reg))
		return EC_ERROR_UNKNOWN;
	if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg & 0xf))
		return EC_ERROR_UNKNOWN;

	/* Clear Bit[7:0] R_SWITCH */
	if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_1, 0x0))
		return EC_ERROR_UNKNOWN;
	/* Clear Bit[7:4] R_SWITCH_H */
	if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_5, &reg))
		return EC_ERROR_UNKNOWN;
	if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, reg & 0x0f))
		return EC_ERROR_UNKNOWN;

	/* Exit safe mode */
	if (anx74xx_tcpm_mux_exit_safe_mode(port))
		return EC_ERROR_UNKNOWN;

	return EC_SUCCESS;
}


static int anx74xx_mux_aux_to_sbu(int port, int polarity, int enabled)
{
	int reg;
	const int aux_mask = ANX74XX_REG_AUX_SWAP_SET_CC2 |
		ANX74XX_REG_AUX_SWAP_SET_CC1;

	/*
	 * Get the current value of analog_ctrl_2 register. Note, that safe mode
	 * is enabled and exited by the calling function, so only have to worry
	 * about setting the correct value for the upper 4 bits of analog_ctrl_2
	 * here.
	 */
	if (tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_2, &reg))
		return EC_ERROR_UNKNOWN;

	/* Assume aux_p/n lines are not connected */
	reg &= ~aux_mask;

	if (enabled) {
		/* If enabled, connect aux to sbu based on desired  polarity */
		if (polarity)
			reg |= ANX74XX_REG_AUX_SWAP_SET_CC2;
		else
			reg |= ANX74XX_REG_AUX_SWAP_SET_CC1;
	}
	/* Write new aux <-> sbu settings */
	if (tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_2, reg))
		return EC_ERROR_UNKNOWN;

	return EC_SUCCESS;
}

static int anx74xx_tcpm_mux_set(int i2c_addr, mux_state_t mux_state)
{
	int reg;
	int pin_cfg = 0;
	int rv;
	int port = i2c_addr;

	if (!(mux_state & ~MUX_POLARITY_INVERTED)) {
		anx[port].mux_state = mux_state;
		return anx74xx_tcpm_mux_exit(port);
	}

	rv = tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_5, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;
	reg &= 0x0f;

	if (mux_state & MUX_USB_ENABLED) {
		/* Set pin assignment D */
		if (mux_state & MUX_POLARITY_INVERTED) {
			pin_cfg = ANX74XX_REG_MUX_DP_MODE_BDF_CC2;
			reg |= ANX74XX_REG_MUX_SSTX_B;
		} else {
			pin_cfg = ANX74XX_REG_MUX_DP_MODE_BDF_CC1;
			reg |= ANX74XX_REG_MUX_SSTX_A;
		}
	} else if (mux_state & MUX_DP_ENABLED) {
		/* Set pin assignment C */
		if (mux_state & MUX_POLARITY_INVERTED) {
			pin_cfg = ANX74XX_REG_MUX_DP_MODE_ACE_CC2;
			reg |= ANX74XX_REG_MUX_ML2_B;
		} else {
			pin_cfg = ANX74XX_REG_MUX_DP_MODE_ACE_CC1;
			reg |= ANX74XX_REG_MUX_ML2_A;
		}
	} else if (!mux_state) {
		return anx74xx_tcpm_mux_exit(port);
	} else {
		return  EC_ERROR_UNIMPLEMENTED;
	}

	/*
	 * Safe mode must be entererd prior to any changes to the mux related to
	 * ALT_DP mode. Therefore, first enable safe mode prior to updating the
	 * values for analog_ctrl_1, analog_ctrl_5, and analog_ctrl_2.
	 */
	if (anx74xx_tcpm_mux_enter_safe_mode(port))
		return EC_ERROR_UNKNOWN;

	/* Write updated pin assignment */
	rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_1, pin_cfg);
	/* Write Rswitch config bits */
	rv |= tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, reg);
	if (rv)
		return EC_ERROR_UNKNOWN;

	/* Configure DP aux to sbu settings */
	if (anx74xx_mux_aux_to_sbu(port, mux_state & MUX_POLARITY_INVERTED,
				   mux_state & MUX_DP_ENABLED))
		return EC_ERROR_UNKNOWN;

	/* Exit safe mode */
	if (anx74xx_tcpm_mux_exit_safe_mode(port))
		return EC_ERROR_UNKNOWN;

	anx[port].mux_state = mux_state;

	return EC_SUCCESS;
}

/* current mux state  */
static int anx74xx_tcpm_mux_get(int i2c_addr, mux_state_t *mux_state)
{
	int port = i2c_addr;

	*mux_state = anx[port].mux_state;

	return EC_SUCCESS;
}

const struct usb_mux_driver anx74xx_tcpm_usb_mux_driver = {
	.init = anx74xx_tcpm_mux_init,
	.set = anx74xx_tcpm_mux_set,
	.get = anx74xx_tcpm_mux_get,
};
#endif /* CONFIG_USB_PD_TCPM_MUX */

static int anx74xx_init_analog(int port)
{
	int reg, rv = EC_SUCCESS;

	/* Analog settings for chip */
	rv |= tcpc_write(port, ANX74XX_REG_HPD_CONTROL,
			 ANX74XX_REG_HPD_OP_MODE);
	rv |= tcpc_write(port, ANX74XX_REG_HPD_CTRL_0,
			 ANX74XX_REG_HPD_DEFAULT);
	if (rv)
		return rv;
	rv = tcpc_read(port, ANX74XX_REG_GPIO_CTRL_4_5, &reg);
	if (rv)
		return rv;
	reg &= ANX74XX_REG_VBUS_GPIO_MODE;
	reg |= ANX74XX_REG_VBUS_OP_ENABLE;
	rv = tcpc_write(port, ANX74XX_REG_GPIO_CTRL_4_5, reg);
	if (rv)
		return rv;
	rv = tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, &reg);
	if (rv)
		return rv;
	reg |= ANX74XX_REG_TX_MODE_ENABLE;
	rv = tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg);

	return rv;
}

static int anx74xx_send_message(int port, uint16_t header,
				const uint32_t *payload,
				int type,
				uint8_t len)
{
	int reg, rv = EC_SUCCESS;
	uint8_t *buf = NULL;
	int num_retry = 0, i = 0;
	/* If sending Soft_reset, clear received message */
	/* Soft Reset Message type = 1101 and Number of Data Object = 0 */
	if ((header & 0x700f) == 0x000d) {
		/*
		* When sending soft reset,
		* the Rx buffer of ANX3429 shall be clear
		*/
		rv = tcpc_read(port, ANX74XX_REG_CTRL_FW, &reg);
		rv |= tcpc_write(
			port, ANX74XX_REG_CTRL_FW, reg | CLEAR_RX_BUFFER);
		if (rv)
			return EC_ERROR_UNKNOWN;
		tcpc_write(port, ANX74XX_REG_RECVD_MSG_INT, 0xFF);
	}
	/* Inform chip about message length and TX type
	 * type->bit-0..2, len->bit-3..7
	 */
	reg = (len << 3) & 0xf8;
	reg |= type & 0x07;
	rv |= tcpc_write(port, ANX74XX_REG_TX_CTRL_2, reg);
	if (rv)
		return EC_ERROR_UNKNOWN;

	/* Enqueue Header */
	rv = tcpc_write(port, ANX74XX_REG_TX_HEADER_L, (header & 0xff));
	if (rv)
		return EC_ERROR_UNKNOWN;
	rv = tcpc_write(port, ANX74XX_REG_TX_HEADER_H, (header >> 8));
	if (rv)
		return EC_ERROR_UNKNOWN;
	/* Enqueue payload */
	if (len > 2) {
		len -= 2;
		buf = (uint8_t *)payload;
		while (1) {
			if (i < 18)
				rv = tcpc_write(port,
						ANX74XX_REG_TX_START_ADDR_0 + i,
						*buf);
			else
				rv = tcpc_write(port,
					ANX74XX_REG_TX_START_ADDR_1 + i - 18,
						*buf);
			if (rv) {
				num_retry++;
			} else {
				buf++;
				len--;
				num_retry = 0;
				i++;
			}
			if (len == 0 || num_retry >= 3)
				break;
		}
		/* If enqueue failed, do not request anx to transmit
		 * messages, FIFO will get cleared in next call
		 * before enqueue.
		 * num_retry = 0, refer to success
		 */
		if (num_retry)
			return EC_ERROR_UNKNOWN;
	}
	/* Request a data transmission
	 * This bit will be cleared by ANX after TX success
	 */
	rv = tcpc_read(port, ANX74XX_REG_CTRL_COMMAND, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;
	reg |= ANX74XX_REG_TX_SEND_DATA_REQ;
	rv |= tcpc_write(port, ANX74XX_REG_CTRL_COMMAND, reg);

	return rv;
}

static int anx74xx_read_pd_obj(int port,
				uint8_t *buf,
				int plen)
{
	int rv = EC_SUCCESS, i;
	int reg, addr = ANX74XX_REG_PD_RX_DATA_OBJ;

	/* Read PD data objects from ANX */
	for (i = 0; i < plen ; i++) {
		/* Register sequence changes for last two bytes, if
		 * plen is greater than 26
		 */
		if (i == 26)
			addr = ANX74XX_REG_PD_RX_DATA_OBJ_M;
		rv = tcpc_read(port, addr + i, &reg);
		if (rv)
			break;
		buf[i] = reg;
	}
	clear_recvd_msg_int(port);
	return rv;
}

static int anx74xx_check_cc_type(int cc_reg)
{
	int cc;

	switch (cc_reg & ANX74XX_REG_CC_STATUS_MASK) {
	case BIT_VALUE_OF_SRC_CC_RD:
		cc = TYPEC_CC_VOLT_RD;
		break;

	case BIT_VALUE_OF_SRC_CC_RA:
		cc = TYPEC_CC_VOLT_RA;
		break;

	case BIT_VALUE_OF_SNK_CC_DEFAULT:
		cc = TYPEC_CC_VOLT_SNK_DEF;
		break;

	case BIT_VALUE_OF_SNK_CC_1_P_5:
		cc = TYPEC_CC_VOLT_SNK_1_5;
		break;

	case BIT_VALUE_OF_SNK_CC_3_P_0:
		cc = TYPEC_CC_VOLT_SNK_3_0;
		break;

	default:
		/* If no bits are set, then nothing is attached */
		cc = TYPEC_CC_VOLT_OPEN;
	}

	return cc;
}

static int anx74xx_tcpm_get_cc(int port, int *cc1, int *cc2)
{
	int rv = EC_SUCCESS;
	int reg = 0;

	/* Read tcpc cc status register */
	rv |= tcpc_read(port, ANX74XX_REG_CC_STATUS, &reg);
	/* Check for cc1 type */
	*cc1 = anx74xx_check_cc_type(reg);
	/*
	 * Check for cc2 type (note cc2 bits are upper 4 of cc status
	 * register.
	 */
	*cc2 = anx74xx_check_cc_type(reg >> 4);

	/* clear HPD status*/
	if (!(*cc1) && !(*cc2)) {
		anx74xx_tcpc_clear_hpd_status(port);
#ifdef CONFIG_USB_PD_TCPM_MUX
		anx74xx_tcpm_mux_exit(port);
#endif
	}

	return EC_SUCCESS;
}

static int anx74xx_rp_control(int port, int rp)
{
	int reg;
	int rv;

	rv = tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_6, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;

	/* clear Bit[0,1] R_RP to default Rp's value */
	reg &= ~0x03;

	switch (rp) {
	case TYPEC_RP_1A5:
		/* Set Rp strength to 12K for presenting 1.5A */
		reg |= ANX74XX_REG_CC_PULL_RP_12K;
		break;
	case TYPEC_RP_3A0:
		/* Set Rp strength to 4K for presenting 3A */
		reg |= ANX74XX_REG_CC_PULL_RP_4K;
		break;
	case TYPEC_RP_USB:
	default:
		/* default: Set Rp strength to 36K */
		break;
	}

	return tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_6, reg);
}

static int anx74xx_tcpm_select_rp_value(int port, int rp)
{
	/* For ANX3429 cannot get cc correctly when Rp != USB_Default */
	selected_rp[port] = rp;
	return EC_SUCCESS;
}


static int anx74xx_cc_software_ctrl(int port, int enable)
{
	int rv;
	int reg;

	rv = tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;

	if (enable)
		reg |= ANX74XX_REG_CC_SW_CTRL_ENABLE;
	else
		reg &= ~ANX74XX_REG_CC_SW_CTRL_ENABLE;

	rv |= tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg);
	return rv;
}

static int anx74xx_tcpm_set_cc(int port, int pull)
{
	int rv = EC_SUCCESS;
	int reg;

	/* Enable CC software Control */
	rv = anx74xx_cc_software_ctrl(port, 1);
	if (rv)
		return EC_ERROR_UNKNOWN;

	switch (pull) {
	case TYPEC_CC_RP:
	/* Enable Rp */
		rv |= tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, &reg);
		if (rv)
			return EC_ERROR_UNKNOWN;
		reg |= ANX74XX_REG_CC_PULL_RP;
		rv |= tcpc_write(port, ANX74XX_REG_ANALOG_STATUS, reg);
		break;
	case TYPEC_CC_RD:
	/* Enable Rd */
		rv |= tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, &reg);
		if (rv)
			return EC_ERROR_UNKNOWN;
		reg &= ANX74XX_REG_CC_PULL_RD;
		rv |= tcpc_write(port, ANX74XX_REG_ANALOG_STATUS, reg);
		break;
	default:
		rv = EC_ERROR_UNKNOWN;
		break;
	}

	return rv;
}

#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \
	defined(CONFIG_USB_PD_TCPC_LOW_POWER)
static void anx74xx_handle_power_mode(int port, int mode)
{
	if (mode == ANX74XX_STANDBY_MODE) {
		anx74xx_set_power_mode(port, mode);
	} else if (anx[port].prev_mode != ANX74XX_NORMAL_MODE) {
		/*
		 * TODO: Interrupt high follows CC line hence ignore multiple
		 * interrupts.
		 */
		anx74xx_tcpm_init(port);
	}
}

static int anx74xx_tcpc_drp_toggle(int port, int enable)
{
	/*
	 * When using low power mode, this function is an entry to point to
	 * bring the ANX3429 in to or out of standby mode. DRP toggle is
	 * associated with the chip being in standby mode.
	 */
	anx74xx_handle_power_mode(port, enable ? ANX74XX_STANDBY_MODE :
				  ANX74XX_NORMAL_MODE);

	return EC_SUCCESS;
}
#endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE && CONFIG_USB_PD_TCPC_LOW_POWER */

static int anx74xx_tcpm_set_polarity(int port, int polarity)
{
	int reg, mux_state, rv = EC_SUCCESS;

	rv |= tcpc_read(port, ANX74XX_REG_CC_SOFTWARE_CTRL, &reg);
	if (polarity) /* Inform ANX to use CC2 */
		reg &= ~ANX74XX_REG_SELECT_CC1;
	else /* Inform ANX to use CC1 */
		reg |= ANX74XX_REG_SELECT_CC1;
	rv |= tcpc_write(port, ANX74XX_REG_CC_SOFTWARE_CTRL, reg);

	anx[port].polarity = polarity;

	/* Update mux polarity */
#ifdef CONFIG_USB_PD_TCPM_MUX
	mux_state = anx[port].mux_state & ~MUX_POLARITY_INVERTED;
	if (polarity)
		mux_state |= MUX_POLARITY_INVERTED;
	anx74xx_tcpm_mux_set(port, mux_state);
#endif
	return rv;
}

int anx74xx_tcpc_get_fw_version(int port, int *version)
{
	return tcpc_read(port, ANX74XX_REG_FW_VERSION, version);
}

static int anx74xx_tcpm_set_vconn(int port, int enable)
{
	int reg, rv = EC_SUCCESS;

	/* switch VCONN to Non CC line */
	rv |= tcpc_read(port, ANX74XX_REG_INTP_VCONN_CTRL, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;
	if (enable) {
		if (anx[port].polarity)
			reg |= ANX74XX_REG_VCONN_1_ENABLE;
		else
			reg |= ANX74XX_REG_VCONN_2_ENABLE;
	} else {
		reg &= ANX74XX_REG_VCONN_DISABLE;
	}
	rv |= tcpc_write(port, ANX74XX_REG_INTP_VCONN_CTRL, reg);
	anx[port].vconn_en = enable;

	return rv;
}

static int anx74xx_tcpm_set_msg_header(int port, int power_role, int data_role)
{
	return tcpc_write(port, ANX74XX_REG_TX_AUTO_GOODCRC_1,
		  ANX74XX_REG_AUTO_GOODCRC_SET(!!data_role, !!power_role));
}

static int anx74xx_alert_status(int port, int *alert)
{
	int reg, rv = EC_SUCCESS;

	/* Clear soft irq bit */
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_SOURCE_3,
			 ANX74XX_REG_CLEAR_SOFT_IRQ);
	*alert = 0;
	rv = tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;

	/*Clear msg received bit, until read it by TCPM*/
	rv |= tcpc_write(port, ANX74XX_REG_RECVD_MSG_INT, (reg & 0xFE));

	if (reg & ANX74XX_REG_IRQ_CC_MSG_INT)
		*alert |= ANX74XX_REG_ALERT_MSG_RECV;

	if (reg & ANX74XX_REG_IRQ_CC_STATUS_INT)
		*alert |= ANX74XX_REG_ALERT_CC_CHANGE;

	if (reg & ANX74XX_REG_IRQ_GOOD_CRC_INT)
		*alert |= ANX74XX_REG_ALERT_TX_ACK_RECV;

	if (reg & ANX74XX_REG_IRQ_TX_FAIL_INT)
		*alert |= ANX74XX_REG_ALERT_TX_MSG_ERROR;

	rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_SOURCE_1, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;
	/* Clears interrupt bits */
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_SOURCE_1, reg);

	/* Check for Hard Reset done bit */
	if (reg & ANX74XX_REG_ALERT_TX_HARD_RESETOK)
		*alert |= ANX74XX_REG_ALERT_TX_HARD_RESETOK;

	/* Read TCPC Alert register2 */
	rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_SOURCE_2, &reg);

	/* Clears interrupt bits */
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_SOURCE_2, reg);

	if (reg & ANX74XX_REG_EXT_HARD_RST)
		*alert |= ANX74XX_REG_ALERT_HARD_RST_RECV;

	return rv;
}

static int anx74xx_tcpm_set_rx_enable(int port, int enable)
{
	int reg, rv;

	rv = tcpc_read(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK, &reg);
	if (rv)
		return rv;
	if (enable) {
		reg &= ~(ANX74XX_REG_IRQ_CC_MSG_INT);
		anx74xx_tcpm_set_auto_good_crc(port, 1);
		anx74xx_rp_control(port, selected_rp[port]);
	} else {
		/* Disable RX message by masking interrupt */
		reg |= (ANX74XX_REG_IRQ_CC_MSG_INT);
		anx74xx_tcpm_set_auto_good_crc(port, 0);
		anx74xx_rp_control(port, TYPEC_RP_USB);
	}
	/*When this function was call, the interrupt status shall be cleared*/
	tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG, 0);

	return tcpc_write(port, ANX74XX_REG_IRQ_SOURCE_RECV_MSG_MASK, reg);
}

#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
static int anx74xx_tcpm_get_vbus_level(int port)
{
	int reg = 0;

	tcpc_read(port, ANX74XX_REG_ANALOG_STATUS, &reg);
	return ((reg & ANX74XX_REG_VBUS_STATUS) ? 1 : 0);
}
#endif

static int anx74xx_tcpm_get_message(int port, uint32_t *payload, int *head)
{
	int reg = 0, rv = EC_SUCCESS;
	int len = 0;

	/* Fetch the header */
	rv |= tcpc_read16(port, ANX74XX_REG_PD_HEADER, &reg);
	if (rv) {
		clear_recvd_msg_int(port);
		return EC_ERROR_UNKNOWN;
	}
	*head = reg;

	len = PD_HEADER_CNT(*head) * 4;
	if (!len) {
		clear_recvd_msg_int(port);
		return EC_SUCCESS;
	}

	/* Receive message : assuming payload have enough
	 * memory allocated
	 */
	rv |= anx74xx_read_pd_obj(port, (uint8_t *)payload, len);
	if (rv)
		return EC_ERROR_UNKNOWN;

	return rv;
}

static int anx74xx_tcpm_transmit(int port, enum tcpm_transmit_type type,
		  uint16_t header,
		  const uint32_t *data)
{
	uint8_t len = 0;
	int ret = 0, reg = 0;

	switch (type) {
	/* ANX is aware of type */
	case TCPC_TX_SOP:
	case TCPC_TX_SOP_PRIME:
	case TCPC_TX_SOP_PRIME_PRIME:
		len = PD_HEADER_CNT(header) * 4 + 2;
		ret = anx74xx_send_message(port, header,
						  data, type, len);
		break;
	case TCPC_TX_HARD_RESET:
	/* Request HARD RESET */
		tcpc_read(port, ANX74XX_REG_TX_CTRL_1, &reg);
		reg |= ANX74XX_REG_TX_HARD_RESET_REQ;
		ret = tcpc_write(port, ANX74XX_REG_TX_CTRL_1, reg);
	/*After Hard Reset, TCPM shall disable goodCRC*/
		anx74xx_tcpm_set_auto_good_crc(port, 0);
		break;
	case TCPC_TX_CABLE_RESET:
	/* Request CABLE RESET */
		tcpc_read(port, ANX74XX_REG_TX_CTRL_1, &reg);
		reg |= ANX74XX_REG_TX_CABLE_RESET_REQ;
		ret = tcpc_write(port, ANX74XX_REG_TX_CTRL_1, reg);
		break;
	case TCPC_TX_BIST_MODE_2:
	/* Request BIST MODE 2 */
		reg = ANX74XX_REG_TX_BIST_START
				| ANX74XX_REG_TX_BIXT_FOREVER | (0x02 << 4);
		ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, reg);
		msleep(1);
		ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL,
					reg | ANX74XX_REG_TX_BIST_ENABLE);
		msleep(30);
		tcpc_read(port, ANX74XX_REG_TX_BIST_CTRL, &reg);
		ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL,
					reg | ANX74XX_REG_TX_BIST_STOP);
		ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL,
					reg & (~ANX74XX_REG_TX_BIST_STOP));
		ret = tcpc_write(port, ANX74XX_REG_TX_BIST_CTRL, 0);
		break;
	default:
		return EC_ERROR_UNIMPLEMENTED;
	}

	return ret;
}

void anx74xx_tcpc_alert(int port)
{
	int status;

	/* Check the alert status from anx74xx */
	if (anx74xx_alert_status(port, &status))
		status = 0;
	if (status) {

		if (status & ANX74XX_REG_ALERT_CC_CHANGE) {
			/* CC status changed, wake task */
			task_set_event(PD_PORT_TO_TASK_ID(port),
					PD_EVENT_CC, 0);
		}

		/* If alert is to receive a message */
		if (status & ANX74XX_REG_ALERT_MSG_RECV) {
			/* Set a PD_EVENT_RX */
			task_set_event(PD_PORT_TO_TASK_ID(port),
						PD_EVENT_RX, 0);
		}
		if (status & ANX74XX_REG_ALERT_TX_ACK_RECV) {
			/* Inform PD about this TX success */
			pd_transmit_complete(port,
						TCPC_TX_COMPLETE_SUCCESS);
		}
		if (status & ANX74XX_REG_ALERT_TX_MSG_ERROR) {
			/* let PD does not wait for this */
			pd_transmit_complete(port,
					      TCPC_TX_COMPLETE_FAILED);
		}
		if (status & ANX74XX_REG_ALERT_TX_CABLE_RESETOK) {
			/* ANX hardware clears the request bit */
			pd_transmit_complete(port,
					      TCPC_TX_COMPLETE_SUCCESS);
		}
		if (status & ANX74XX_REG_ALERT_TX_HARD_RESETOK) {
			/* ANX hardware clears the request bit */
			pd_transmit_complete(port,
					     TCPC_TX_COMPLETE_SUCCESS);
		}
		if (status & ANX74XX_REG_ALERT_HARD_RST_RECV) {
			/* hard reset received */
			pd_execute_hard_reset(port);
			task_wake(PD_PORT_TO_TASK_ID(port));
		}
	}
}

static int anx74xx_tcpm_init(int port)
{
	int rv = 0, reg;

	memset(&anx[port], 0, sizeof(struct anx_state));
	/* Bring chip in normal mode to work */
	anx74xx_set_power_mode(port, ANX74XX_NORMAL_MODE);

	/* Initialize analog section of ANX */
	rv |= anx74xx_init_analog(port);

	/* disable all interrupts */
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_MASK_1,
			 ANX74XX_REG_CLEAR_SET_BITS);

	/* Initialize interrupt polarity */
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_STATUS,
			tcpc_config[port].pol == TCPC_ALERT_ACTIVE_LOW ?
			ANX74XX_REG_IRQ_POL_LOW :
			ANX74XX_REG_IRQ_POL_HIGH);

	/* unmask interrupts */
	rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_MASK_1, &reg);
	reg &= (~ANX74XX_REG_ALERT_TX_MSG_ERROR);
	reg &= (~ANX74XX_REG_ALERT_TX_CABLE_RESETOK);
	reg &= (~ANX74XX_REG_ALERT_TX_HARD_RESETOK);
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_MASK_1, reg);

	rv |= tcpc_read(port, ANX74XX_REG_IRQ_EXT_MASK_2, &reg);
	reg &= (~ANX74XX_REG_EXT_HARD_RST);
	rv |= tcpc_write(port, ANX74XX_REG_IRQ_EXT_MASK_2, reg);

	/*  HPD pin output enable*/
	rv |= tcpc_write(port, ANX74XX_REG_HPD_CTRL_0, ANX74XX_REG_HPD_DEFAULT);

	if (rv)
		return EC_ERROR_UNKNOWN;

	/* Set AVDD10_BMC to 1.08 */
	rv |= tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_5, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;
	rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_5, (reg & 0xf3));
	if (rv)
		return EC_ERROR_UNKNOWN;

	/* Decrease BMC TX lowest swing voltage */
	rv |= tcpc_read(port, ANX74XX_REG_ANALOG_CTRL_11, &reg);
	if (rv)
		return EC_ERROR_UNKNOWN;
	rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_11, (reg & 0x3f) | 0x40);
	if (rv)
		return EC_ERROR_UNKNOWN;

	/* Set BMC TX cap slew rate to 400ns */
	rv = tcpc_write(port, ANX74XX_REG_ANALOG_CTRL_12, 0x4);
	if (rv)
		return EC_ERROR_UNKNOWN;

	tcpm_get_chip_info(port, 1, NULL);

	return EC_SUCCESS;
}

/*
 * Dissociate from the TCPC.
 */

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

const struct tcpm_drv anx74xx_tcpm_drv = {
	.init			= &anx74xx_tcpm_init,
	.release		= &anx74xx_tcpm_release,
	.get_cc			= &anx74xx_tcpm_get_cc,
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
	.get_vbus_level		= &anx74xx_tcpm_get_vbus_level,
#endif
	.select_rp_value	= &anx74xx_tcpm_select_rp_value,
	.set_cc			= &anx74xx_tcpm_set_cc,
	.set_polarity		= &anx74xx_tcpm_set_polarity,
	.set_vconn		= &anx74xx_tcpm_set_vconn,
	.set_msg_header		= &anx74xx_tcpm_set_msg_header,
	.set_rx_enable		= &anx74xx_tcpm_set_rx_enable,
	.get_message		= &anx74xx_tcpm_get_message,
	.transmit		= &anx74xx_tcpm_transmit,
	.tcpc_alert		= &anx74xx_tcpc_alert,
#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
	.tcpc_discharge_vbus	= &anx74xx_tcpc_discharge_vbus,
#endif
	.get_chip_info		= &tcpci_get_chip_info,
#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \
		defined(CONFIG_USB_PD_TCPC_LOW_POWER)
	.drp_toggle		= &anx74xx_tcpc_drp_toggle,
#endif
};

#ifdef CONFIG_CMD_I2C_STRESS_TEST_TCPC
struct i2c_stress_test_dev anx74xx_i2c_stress_test_dev = {
	.reg_info = {
		.read_reg = ANX74XX_REG_VENDOR_ID_L,
		.read_val = ANX74XX_VENDOR_ID & 0xFF,
		.write_reg = ANX74XX_REG_CC_SOFTWARE_CTRL,
	},
	.i2c_read = &tcpc_i2c_read,
	.i2c_write = &tcpc_i2c_write,
};
#endif /* CONFIG_CMD_I2C_STRESS_TEST_TCPC */
