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

/* Type-C port manager */

#include "atomic.h"
#include "anx74xx.h"
#include "compile_time_macros.h"
#include "console.h"
#include "ec_commands.h"
#include "hooks.h"
#include "i2c.h"
#include "ps8xxx.h"
#include "task.h"
#include "tcpm/tcpci.h"
#include "tcpm/tcpm.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_common.h"
#include "usb_mux.h"
#include "usb_pd.h"
#include "usb_pd_tcpc.h"
#include "util.h"

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

STATIC_IF(CONFIG_USB_PD_DECODE_SOP)
	bool sop_prime_en[CONFIG_USB_PD_PORT_MAX_COUNT];
STATIC_IF(CONFIG_USB_PD_DECODE_SOP)
	int rx_en[CONFIG_USB_PD_PORT_MAX_COUNT];

#define TCPC_FLAGS_VSAFE0V(_flags) \
	((_flags & TCPC_FLAGS_TCPCI_REV2_0) && \
		!(_flags & TCPC_FLAGS_TCPCI_REV2_0_NO_VSAFE0V))

/****************************************************************************
 * TCPCI DEBUG Helpers
 */

/* TCPCI FAULT-0x01 is an invalid I2C operation was performed.  This tends
 * to have to do with the state of registers and the last write operation.
 * Defining DEBUG_I2C_FAULT_LAST_WRITE_OP will track the write operations,
 * excluding XFER and BlockWrites, in an attempt to give clues as to what
 * was written to the TCPCI that caused the issue.
 */
#undef DEBUG_I2C_FAULT_LAST_WRITE_OP

struct i2c_wrt_op {
	int addr;
	int reg;
	int val;
	int mask;
};
STATIC_IF(DEBUG_I2C_FAULT_LAST_WRITE_OP)
	struct i2c_wrt_op last_write_op[CONFIG_USB_PD_PORT_MAX_COUNT];

/*
 * AutoDischargeDisconnect has caused a number of issues with the
 * feature not being correctly enabled/disabled.  Defining
 * DEBUG_AUTO_DISCHARGE_DISCONNECT will output a line for each enable
 * and disable to help better understand any AutoDischargeDisconnect
 * issues.
 */
#undef DEBUG_AUTO_DISCHARGE_DISCONNECT

/*
 * ForcedDischarge debug to help coordinate with AutoDischarge.
 * Defining DEBUG_FORCED_DISCHARGE will output a line for each enable
 * and disable to help better understand any Discharge issues.
 */
#undef DEBUG_FORCED_DISCHARGE

/*
 * Seeing the CC Status and ROLE Control registers as well as the
 * CC that is being determined from this information can be
 * helpful.  Defining DEBUG_GET_CC will output a line that gives
 * this useful information
 */
#undef DEBUG_GET_CC

struct get_cc_values {
	int cc1;
	int cc2;
	int cc_sts;
	int role;
};
STATIC_IF(DEBUG_GET_CC)
	struct get_cc_values last_get_cc[CONFIG_USB_PD_PORT_MAX_COUNT];

/*
 * Seeing RoleCtrl updates can help determine why GetCC is not
 * working as it should be.
 */
#undef DEBUG_ROLE_CTRL_UPDATES

/****************************************************************************/

/*
 * Last reported VBus Level
 *
 * BIT(VBUS_SAFE0V) will indicate if in SAFE0V
 * BIT(VBUS_PRESENT) will indicate if in PRESENT in the TCPCI POWER_STATUS
 *
 * Note that VBUS_REMOVED cannot be distinguished from !VBUS_PRESENT with
 * this interface, but the trigger thresholds for Vbus Present should allow the
 * same bit to be used safely for both.
 *
 * TODO(b/149530538): Some TCPCs may be able to implement
 * VBUS_SINK_DISCONNECT_THRESHOLD to support vSinkDisconnectPD
 */
static int tcpc_vbus[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Cached RP role values */
static int cached_rp[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Cache our Device Capabilities at init for later reference */
static int dev_cap_1[CONFIG_USB_PD_PORT_MAX_COUNT];

#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
int tcpc_addr_write(int port, int i2c_addr, int reg, int val)
{
	int rv;

	pd_wait_exit_low_power(port);

	if (IS_ENABLED(DEBUG_I2C_FAULT_LAST_WRITE_OP)) {
		last_write_op[port].addr = i2c_addr;
		last_write_op[port].reg  = reg;
		last_write_op[port].val  = val & 0xFF;
		last_write_op[port].mask = 0;
	}

	rv = i2c_write8(tcpc_config[port].i2c_info.port,
			i2c_addr, reg, val);

	pd_device_accessed(port);
	return rv;
}

int tcpc_addr_write16(int port, int i2c_addr, int reg, int val)
{
	int rv;

	pd_wait_exit_low_power(port);

	if (IS_ENABLED(DEBUG_I2C_FAULT_LAST_WRITE_OP)) {
		last_write_op[port].addr = i2c_addr;
		last_write_op[port].reg  = reg;
		last_write_op[port].val  = val & 0xFFFF;
		last_write_op[port].mask = 0;
	}

	rv = i2c_write16(tcpc_config[port].i2c_info.port,
			 i2c_addr, reg, val);

	pd_device_accessed(port);
	return rv;
}

int tcpc_addr_read(int port, int i2c_addr, int reg, int *val)
{
	int rv;

	pd_wait_exit_low_power(port);

	rv = i2c_read8(tcpc_config[port].i2c_info.port,
		       i2c_addr, reg, val);

	pd_device_accessed(port);
	return rv;
}

int tcpc_addr_read16(int port, int i2c_addr, int reg, int *val)
{
	pd_wait_exit_low_power(port);

	return tcpc_addr_read16_no_lpm_exit(port, i2c_addr, reg, val);
}

int tcpc_addr_read16_no_lpm_exit(int port, int i2c_addr, int reg, int *val)
{
	int rv;

	rv = i2c_read16(tcpc_config[port].i2c_info.port,
			i2c_addr, reg, val);

	pd_device_accessed(port);
	return rv;
}

int tcpc_read_block(int port, int reg, uint8_t *in, int size)
{
	int rv;

	pd_wait_exit_low_power(port);

	rv = i2c_read_block(tcpc_config[port].i2c_info.port,
			    tcpc_config[port].i2c_info.addr_flags,
			    reg, in, size);

	pd_device_accessed(port);
	return rv;
}

int tcpc_write_block(int port, int reg, const uint8_t *out, int size)
{
	int rv;

	pd_wait_exit_low_power(port);

	rv = i2c_write_block(tcpc_config[port].i2c_info.port,
			     tcpc_config[port].i2c_info.addr_flags,
			     reg, out, size);

	pd_device_accessed(port);
	return rv;
}

int tcpc_xfer(int port, const uint8_t *out, int out_size,
			uint8_t *in, int in_size)
{
	int rv;
	/* Dispatching to tcpc_xfer_unlocked reduces code size growth. */
	tcpc_lock(port, 1);
	rv = tcpc_xfer_unlocked(port, out, out_size, in, in_size,
				I2C_XFER_SINGLE);
	tcpc_lock(port, 0);
	return rv;
}

int tcpc_xfer_unlocked(int port, const uint8_t *out, int out_size,
			    uint8_t *in, int in_size, int flags)
{
	int rv;

	pd_wait_exit_low_power(port);

	rv = i2c_xfer_unlocked(tcpc_config[port].i2c_info.port,
			       tcpc_config[port].i2c_info.addr_flags,
			       out, out_size, in, in_size, flags);

	pd_device_accessed(port);
	return rv;
}

int tcpc_update8(int port, int reg,
		 uint8_t mask,
		 enum mask_update_action action)
{
	int rv;
	const int i2c_addr = tcpc_config[port].i2c_info.addr_flags;

	pd_wait_exit_low_power(port);

	if (IS_ENABLED(DEBUG_I2C_FAULT_LAST_WRITE_OP)) {
		last_write_op[port].addr = i2c_addr;
		last_write_op[port].reg  = reg;
		last_write_op[port].val  = 0;
		last_write_op[port].mask = (mask & 0xFF) | (action << 16);
	}

	rv = i2c_update8(tcpc_config[port].i2c_info.port,
			 i2c_addr, reg, mask, action);

	pd_device_accessed(port);
	return rv;
}

int tcpc_update16(int port, int reg,
		  uint16_t mask,
		  enum mask_update_action action)
{
	int rv;
	const int i2c_addr = tcpc_config[port].i2c_info.addr_flags;

	pd_wait_exit_low_power(port);

	if (IS_ENABLED(DEBUG_I2C_FAULT_LAST_WRITE_OP)) {
		last_write_op[port].addr = i2c_addr;
		last_write_op[port].reg  = reg;
		last_write_op[port].val  = 0;
		last_write_op[port].mask = (mask & 0xFFFF) | (action << 16);
	}

	rv = i2c_update16(tcpc_config[port].i2c_info.port,
			  i2c_addr, reg, mask, action);

	pd_device_accessed(port);
	return rv;
}

#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */

/*
 * TCPCI maintains and uses cached values for the RP and
 * last used PULL values.  Since TCPC drivers are allowed
 * to use some of the TCPCI functionality, these global
 * cached values need to be maintained in case part of the
 * used TCPCI functionality relies on these values
 */
void tcpci_set_cached_rp(int port, int rp)
{
	cached_rp[port] = rp;
}

int tcpci_get_cached_rp(int port)
{
	return cached_rp[port];
}

static int init_alert_mask(int port)
{
	int rv;
	uint16_t mask;

	/*
	 * Create mask of alert events that will cause the TCPC to
	 * signal the TCPM via the Alert# gpio line.
	 */
	mask = TCPC_REG_ALERT_TX_SUCCESS | TCPC_REG_ALERT_TX_FAILED |
		TCPC_REG_ALERT_TX_DISCARDED | TCPC_REG_ALERT_RX_STATUS |
		TCPC_REG_ALERT_RX_HARD_RST | TCPC_REG_ALERT_CC_STATUS |
		TCPC_REG_ALERT_FAULT
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
		| TCPC_REG_ALERT_POWER_STATUS
#endif
		;

	/* TCPCI Rev2 includes SAFE0V alerts */
	if (TCPC_FLAGS_VSAFE0V(tcpc_config[port].flags))
		mask |= TCPC_REG_ALERT_EXT_STATUS;

	if (IS_ENABLED(CONFIG_USB_PD_FRS_TCPC))
		mask |= TCPC_REG_ALERT_ALERT_EXT;

	/* Set the alert mask in TCPC */
	rv = tcpc_write16(port, TCPC_REG_ALERT_MASK, mask);

	if (IS_ENABLED(CONFIG_USB_PD_FRS_TCPC)) {
		if (rv)
			return rv;

		/* Sink FRS allowed */
		mask = TCPC_REG_ALERT_EXT_SNK_FRS;
		rv = tcpc_write(port, TCPC_REG_ALERT_EXTENDED_MASK, mask);
	}
	return rv;
}

static int clear_alert_mask(int port)
{
	return tcpc_write16(port, TCPC_REG_ALERT_MASK, 0);
}

static int init_power_status_mask(int port)
{
	uint8_t mask;
	int rv;

#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
	mask = TCPC_REG_POWER_STATUS_VBUS_PRES;
#else
	mask = 0;
#endif
	rv = tcpc_write(port, TCPC_REG_POWER_STATUS_MASK , mask);

	return rv;
}

static int clear_power_status_mask(int port)
{
	return tcpc_write(port, TCPC_REG_POWER_STATUS_MASK, 0);
}

static int tcpci_tcpm_get_power_status(int port, int *status)
{
	return tcpc_read(port, TCPC_REG_POWER_STATUS, status);
}

int tcpci_tcpm_select_rp_value(int port, int rp)
{
	/* Keep track of current RP value */
	tcpci_set_cached_rp(port, rp);

	return EC_SUCCESS;
}

void tcpci_tcpc_discharge_vbus(int port, int enable)
{
	if (IS_ENABLED(DEBUG_FORCED_DISCHARGE))
		CPRINTS("C%d: ForceDischarge %sABLED",
			port, enable ? "EN" : "DIS");

	tcpc_update8(port,
		     TCPC_REG_POWER_CTRL,
		     TCPC_REG_POWER_CTRL_FORCE_DISCHARGE,
		     (enable) ? MASK_SET : MASK_CLR);
}

/*
 * Auto Discharge Disconnect is supposed to be enabled when we
 * are connected and disabled after we are disconnected and
 * VBus is at SafeV0
 */
void tcpci_tcpc_enable_auto_discharge_disconnect(int port, int enable)
{
	if (IS_ENABLED(DEBUG_AUTO_DISCHARGE_DISCONNECT))
		CPRINTS("C%d: AutoDischargeDisconnect %sABLED",
			port, enable ? "EN" : "DIS");

	tcpc_update8(port,
		     TCPC_REG_POWER_CTRL,
		     TCPC_REG_POWER_CTRL_AUTO_DISCHARGE_DISCONNECT,
		     (enable) ? MASK_SET : MASK_CLR);
}

int tcpci_tcpc_debug_accessory(int port, bool enable)
{
	return tcpc_update8(port, TCPC_REG_CONFIG_STD_OUTPUT,
			    TCPC_REG_CONFIG_STD_OUTPUT_DBG_ACC_CONN_N,
			    enable ? MASK_CLR : MASK_SET);
}

int tcpci_tcpm_get_cc(int port, enum tcpc_cc_voltage_status *cc1,
	enum tcpc_cc_voltage_status *cc2)
{
	int role;
	int status;
	int cc1_present_rd, cc2_present_rd;
	int rv;

	/* errors will return CC as open */
	*cc1 = TYPEC_CC_VOLT_OPEN;
	*cc2 = TYPEC_CC_VOLT_OPEN;

	/* Get the ROLE CONTROL and CC STATUS values */
	rv = tcpc_read(port, TCPC_REG_ROLE_CTRL, &role);
	if (rv)
		return rv;

	rv = tcpc_read(port, TCPC_REG_CC_STATUS, &status);
	if (rv)
		return rv;

	/* Get the current CC values from the CC STATUS */
	*cc1 = TCPC_REG_CC_STATUS_CC1(status);
	*cc2 = TCPC_REG_CC_STATUS_CC2(status);

	/* Determine if we are presenting Rd */
	cc1_present_rd = 0;
	cc2_present_rd = 0;
	if (role & TCPC_REG_ROLE_CTRL_DRP_MASK) {
		/*
		 * We are doing DRP.  We will use the CC STATUS
		 * ConnectResult to determine if we are presenting
		 * Rd or Rp.
		 */
		int term;

		term = TCPC_REG_CC_STATUS_TERM(status);

		if (*cc1 != TYPEC_CC_VOLT_OPEN)
			cc1_present_rd = term;
		if (*cc2 != TYPEC_CC_VOLT_OPEN)
			cc2_present_rd = term;
	} else {
		/*
		 * We are not doing DRP.  We will use the ROLE CONTROL
		 * CC values to determine if we are presenting Rd or Rp.
		 */
		int role_cc1, role_cc2;

		role_cc1 = TCPC_REG_ROLE_CTRL_CC1(role);
		role_cc2 = TCPC_REG_ROLE_CTRL_CC2(role);

		if (*cc1 != TYPEC_CC_VOLT_OPEN)
			cc1_present_rd = !!(role_cc1 == TYPEC_CC_RD);
		if (*cc2 != TYPEC_CC_VOLT_OPEN)
			cc2_present_rd = !!(role_cc2 == TYPEC_CC_RD);
	}
	*cc1 |= cc1_present_rd << 2;
	*cc2 |= cc2_present_rd << 2;

	if (IS_ENABLED(DEBUG_GET_CC) &&
	    (last_get_cc[port].cc1 != *cc1 ||
	     last_get_cc[port].cc2 != *cc2 ||
	     last_get_cc[port].cc_sts != status ||
	     last_get_cc[port].role != role)) {

		CPRINTS("C%d: GET_CC cc1=%d cc2=%d cc_sts=0x%X role=0x%X",
			port, *cc1, *cc2, status, role);

		last_get_cc[port].cc1 = *cc1;
		last_get_cc[port].cc2 = *cc2;
		last_get_cc[port].cc_sts = status;
		last_get_cc[port].role = role;
	}
	return rv;
}

int tcpci_tcpm_set_cc(int port, int pull)
{
	int role = TCPC_REG_ROLE_CTRL_SET(TYPEC_NO_DRP,
					  tcpci_get_cached_rp(port),
					  pull, pull);

	if (IS_ENABLED(DEBUG_ROLE_CTRL_UPDATES))
		CPRINTS("C%d: SET_CC pull=%d role=0x%X", port, pull, role);

	return tcpc_write(port, TCPC_REG_ROLE_CTRL, role);
}

#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
int tcpci_set_role_ctrl(int port, enum tcpc_drp drp, enum tcpc_rp_value rp,
	enum tcpc_cc_pull pull)
{
	int role = TCPC_REG_ROLE_CTRL_SET(drp, rp, pull, pull);

	if (IS_ENABLED(DEBUG_ROLE_CTRL_UPDATES))
		CPRINTS("C%d: SET_ROLE_CTRL drp=%d rp=%d pull=%d role=0x%X",
			port, drp, rp, pull, role);

	return tcpc_write(port, TCPC_REG_ROLE_CTRL, role);
}

int tcpci_tcpc_drp_toggle(int port)
{
	int rv;
	enum tcpc_cc_pull pull;

	/*
	 * Set auto drp toggle
	 *
	 *     Set RC.DRP=1b (DRP)
	 *     Set RC.RpValue=00b (smallest Rp to save power)
	 *     Set RC.CC1=(Rp) or (Rd)
	 *     Set RC.CC2=(Rp) or (Rd)
	 *
	 * TCPCI r1 wants both lines to be set to Rd
	 * TCPCI r2 wants both lines to be set to Rp
	 *
	 * Set the Rp Value to be the minimal to save power
	 */
	pull = (tcpc_config[port].flags & TCPC_FLAGS_TCPCI_REV2_0)
			? TYPEC_CC_RP : TYPEC_CC_RD;

	rv = tcpci_set_role_ctrl(port, TYPEC_DRP, TYPEC_RP_USB, pull);
	if (rv)
		return rv;

	/* Set up to catch LOOK4CONNECTION alerts */
	rv = tcpc_update8(port,
			  TCPC_REG_TCPC_CTRL,
			  TCPC_REG_TCPC_CTRL_EN_LOOK4CONNECTION_ALERT,
			  MASK_SET);
	if (rv)
		return rv;

	/* Set Look4Connection command */
	rv = tcpc_write(port, TCPC_REG_COMMAND,
			TCPC_REG_COMMAND_LOOK4CONNECTION);

	return rv;
}
#endif

#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
int tcpci_enter_low_power_mode(int port)
{
	return tcpc_write(port, TCPC_REG_COMMAND, TCPC_REG_COMMAND_I2CIDLE);
}
#endif

int tcpci_tcpm_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_USB_PD_PPC
bool tcpci_tcpm_get_snk_ctrl(int port)
{
	int rv;
	int pwr_sts;

	rv = tcpci_tcpm_get_power_status(port, &pwr_sts);

	return rv == EC_SUCCESS &&
		pwr_sts & TCPC_REG_POWER_STATUS_SINKING_VBUS;
}

int tcpci_tcpm_set_snk_ctrl(int port, int enable)
{
	int cmd = enable ? TCPC_REG_COMMAND_SNK_CTRL_HIGH :
		TCPC_REG_COMMAND_SNK_CTRL_LOW;

	return tcpc_write(port, TCPC_REG_COMMAND, cmd);
}

bool tcpci_tcpm_get_src_ctrl(int port)
{
	int rv;
	int pwr_sts;

	rv = tcpci_tcpm_get_power_status(port, &pwr_sts);

	return rv == EC_SUCCESS &&
		pwr_sts & TCPC_REG_POWER_STATUS_SOURCING_VBUS;
}

int tcpci_tcpm_set_src_ctrl(int port, int enable)
{
	int cmd = enable ? TCPC_REG_COMMAND_SRC_CTRL_HIGH :
		TCPC_REG_COMMAND_SRC_CTRL_LOW;

	return tcpc_write(port, TCPC_REG_COMMAND, cmd);
}
#endif

__maybe_unused int tcpci_tcpm_sop_prime_enable(int port, bool enable)
{
	/* save SOP'/SOP'' enable state */
	sop_prime_en[port] = enable;

	if (rx_en[port]) {
		int detect_sop_en = TCPC_REG_RX_DETECT_SOP_HRST_MASK;

		if (enable) {
			detect_sop_en =
				TCPC_REG_RX_DETECT_SOP_SOPP_SOPPP_HRST_MASK;
		}

		return tcpc_write(port, TCPC_REG_RX_DETECT, detect_sop_en);
	}

	return EC_SUCCESS;
}

int tcpci_tcpm_set_vconn(int port, int enable)
{
	int reg, rv;

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

	reg &= ~TCPC_REG_POWER_CTRL_VCONN(1);
	reg |= TCPC_REG_POWER_CTRL_VCONN(enable);

	/*
	 * Add delay of writing TCPC_REG_POWER_CTRL makes
	 * CC status being judged correctly when disable VCONN.
	 * This may be a PS8XXX firmware issue, Parade is still trying.
	 * https://partnerissuetracker.corp.google.com/issues/185202064
	 */
	if (!enable)
		msleep(PS8XXX_VCONN_TURN_OFF_DELAY_US);

	return tcpc_write(port, TCPC_REG_POWER_CTRL, reg);
}

int tcpci_tcpm_set_msg_header(int port, int power_role, int data_role)
{
	return tcpc_write(port, TCPC_REG_MSG_HDR_INFO,
			  TCPC_REG_MSG_HDR_INFO_SET(data_role, power_role));
}

static int tcpm_alert_status(int port, int *alert)
{
	/* Read TCPC Alert register */
	return tcpc_read16(port, TCPC_REG_ALERT, alert);
}

static int tcpm_alert_ext_status(int port, int *alert_ext)
{
	/* Read TCPC Extended Alert register */
	return tcpc_read(port, TCPC_REG_ALERT_EXT, alert_ext);
}

static int tcpm_ext_status(int port, int *ext_status)
{
	/* Read TCPC Extended Status register */
	return tcpc_read(port, TCPC_REG_EXT_STATUS, ext_status);
}

int tcpci_tcpm_set_rx_enable(int port, int enable)
{
	int detect_sop_en = 0;

	if (IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) {
		/* save rx_on */
		rx_en[port] = enable;
	}


	if (enable) {
		detect_sop_en = TCPC_REG_RX_DETECT_SOP_HRST_MASK;

		if (IS_ENABLED(CONFIG_USB_PD_DECODE_SOP) &&
			sop_prime_en[port]) {
			/*
			 * Only the VCONN Source is allowed to communicate
			 * with the Cable Plugs.
			 */
			detect_sop_en =
				TCPC_REG_RX_DETECT_SOP_SOPP_SOPPP_HRST_MASK;
		}
	}

	/* If enable, then set RX detect for SOP and HRST */
	return tcpc_write(port, TCPC_REG_RX_DETECT, detect_sop_en);
}

#ifdef CONFIG_USB_PD_FRS_TCPC
int tcpci_tcpc_fast_role_swap_enable(int port, int enable)
{
	return tcpc_update8(port,
		     TCPC_REG_POWER_CTRL,
		     TCPC_REG_POWER_CTRL_FRS_ENABLE,
		     (enable) ? MASK_SET : MASK_CLR);
}
#endif

#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
bool tcpci_tcpm_check_vbus_level(int port, enum vbus_level level)
{
	if (level == VBUS_SAFE0V)
		return !!(tcpc_vbus[port] & BIT(VBUS_SAFE0V));
	else if (level == VBUS_PRESENT)
		return !!(tcpc_vbus[port] & BIT(VBUS_PRESENT));
	else
		return !(tcpc_vbus[port] & BIT(VBUS_PRESENT));
}
#endif

struct cached_tcpm_message {
	uint32_t header;
	uint32_t payload[7];
};

static int tcpci_rev2_0_tcpm_get_message_raw(int port, uint32_t *payload,
					     int *head)
{
	int rv = 0, cnt, reg = TCPC_REG_RX_BUFFER;
	int frm;
	uint8_t tmp[2];
	/*
	 * Register 0x30 is Readable Byte Count, Buffer frame type, and RX buf
	 * byte X.
	 */
	tcpc_lock(port, 1);
	rv = tcpc_xfer_unlocked(port, (uint8_t *)&reg, 1, tmp, 2,
				I2C_XFER_START);
	if (rv) {
		rv = EC_ERROR_UNKNOWN;
		goto clear;
	}
	cnt = tmp[0];
	frm = tmp[1];

	/*
	 * READABLE_BYTE_COUNT includes 3 bytes for frame type and header, and
	 * may be 0 if the TCPC saw a disconnect before the message read
	 */
	cnt -= 3;
	if ((cnt < 0) ||
	    (cnt > member_size(struct cached_tcpm_message, payload))) {
		/* Continue to send the stop bit with the header read */
		rv = EC_ERROR_UNKNOWN;
		cnt = 0;
	}

	/* The next two bytes are the header */
	rv |= tcpc_xfer_unlocked(port, NULL, 0, (uint8_t *)head, 2,
				cnt ? 0 : I2C_XFER_STOP);

	/* Encode message address in bits 31 to 28 */
	*head &= 0x0000ffff;
	*head |= PD_HEADER_SOP(frm);

	/* Execute read and I2C_XFER_STOP, even if header read failed */
	if (cnt > 0) {
		tcpc_xfer_unlocked(port, NULL, 0, (uint8_t *)payload, cnt,
				   I2C_XFER_STOP);
	}

clear:
	tcpc_lock(port, 0);
	/* Read complete, clear RX status alert bit */
	tcpc_write16(port, TCPC_REG_ALERT, TCPC_REG_ALERT_RX_STATUS);

	if (rv)
		return EC_ERROR_UNKNOWN;

	return EC_SUCCESS;
}

static int tcpci_rev1_0_tcpm_get_message_raw(int port, uint32_t *payload,
					     int *head)
{
	int rv, cnt, reg = TCPC_REG_RX_DATA;
	int frm;

	rv = tcpc_read(port, TCPC_REG_RX_BYTE_CNT, &cnt);

	/* RX_BYTE_CNT includes 3 bytes for frame type and header */
	if (rv != EC_SUCCESS || cnt < 3) {
		rv = EC_ERROR_UNKNOWN;
		goto clear;
	}
	cnt -= 3;
	if (cnt > member_size(struct cached_tcpm_message, payload)) {
		rv = EC_ERROR_UNKNOWN;
		goto clear;
	}

	if (IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) {
		rv = tcpc_read(port, TCPC_REG_RX_BUF_FRAME_TYPE, &frm);
		if (rv != EC_SUCCESS) {
			rv = EC_ERROR_UNKNOWN;
			goto clear;
		}
	}

	rv = tcpc_read16(port, TCPC_REG_RX_HDR, (int *)head);

	if (IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) {
		/* Encode message address in bits 31 to 28 */
		*head &= 0x0000ffff;
		*head |= PD_HEADER_SOP(frm);
	}

	if (rv == EC_SUCCESS && cnt > 0) {
		tcpc_read_block(port, reg, (uint8_t *)payload, cnt);
	}

clear:
	/* Read complete, clear RX status alert bit */
	tcpc_write16(port, TCPC_REG_ALERT, TCPC_REG_ALERT_RX_STATUS);

	return rv;
}

int tcpci_tcpm_get_message_raw(int port, uint32_t *payload, int *head)
{
	if (tcpc_config[port].flags & TCPC_FLAGS_TCPCI_REV2_0)
		return tcpci_rev2_0_tcpm_get_message_raw(port, payload, head);

	return tcpci_rev1_0_tcpm_get_message_raw(port, payload, head);
}

/* Cache depth needs to be power of 2 */
/* TODO: Keep track of the high water mark */
#define CACHE_DEPTH BIT(3)
#define CACHE_DEPTH_MASK (CACHE_DEPTH - 1)

struct queue {
	/*
	 * Head points to the index of the first empty slot to put a new RX
	 * message. Must be masked before used in lookup.
	 */
	uint32_t head;
	/*
	 * Tail points to the index of the first message for the PD task to
	 * consume. Must be masked before used in lookup.
	 */
	uint32_t tail;
	struct cached_tcpm_message buffer[CACHE_DEPTH];
};
static struct queue cached_messages[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Note this method can be called from an interrupt context. */
int tcpm_enqueue_message(const int port)
{
	int rv;
	struct queue *const q = &cached_messages[port];
	struct cached_tcpm_message *const head =
		&q->buffer[q->head & CACHE_DEPTH_MASK];

	if (q->head - q->tail == CACHE_DEPTH) {
		CPRINTS("C%d RX EC Buffer full!", port);
		return EC_ERROR_OVERFLOW;
	}

	/* Blank any old message, just in case. */
	memset(head, 0, sizeof(*head));
	/* Call the raw driver without caching */
	rv = tcpc_config[port].drv->get_message_raw(port, head->payload,
						    &head->header);
	if (rv) {
		CPRINTS("C%d: Could not retrieve RX message (%d)", port, rv);
		return rv;
	}

	/* Increment atomically to ensure get_message_raw happens-before */
	atomic_add(&q->head, 1);

	/* Wake PD task up so it can process incoming RX messages */
	task_set_event(PD_PORT_TO_TASK_ID(port), TASK_EVENT_WAKE);

	return EC_SUCCESS;
}

int tcpm_has_pending_message(const int port)
{
	const struct queue *const q = &cached_messages[port];

	return q->head != q->tail;
}

int tcpm_dequeue_message(const int port, uint32_t *const payload,
			 int *const header)
{
	struct queue *const q = &cached_messages[port];
	struct cached_tcpm_message *const tail =
		&q->buffer[q->tail & CACHE_DEPTH_MASK];

	if (!tcpm_has_pending_message(port)) {
		CPRINTS("C%d No message in RX buffer!", port);
		return EC_ERROR_BUSY;
	}

	/* Copy cache data in to parameters */
	*header = tail->header;
	memcpy(payload, tail->payload, sizeof(tail->payload));

	/* Increment atomically to ensure memcpy happens-before */
	atomic_add(&q->tail, 1);

	return EC_SUCCESS;
}

void tcpm_clear_pending_messages(int port)
{
	struct queue *const q = &cached_messages[port];

	q->tail = q->head;
}

int tcpci_tcpm_transmit(int port, enum tcpm_transmit_type type,
			uint16_t header, const uint32_t *data)
{
	int reg = TCPC_REG_TX_DATA;
	int rv, cnt = 4*PD_HEADER_CNT(header);

	/* If not SOP* transmission, just write to the transmit register */
	if (type >= NUM_SOP_STAR_TYPES) {
		/*
		 * Per TCPCI spec, do not specify retry (although the TCPC
		 * should ignore retry field for these 3 types).
		 */
		return tcpc_write(port, TCPC_REG_TRANSMIT,
			TCPC_REG_TRANSMIT_SET_WITHOUT_RETRY(type));
	}

	if (tcpc_config[port].flags & TCPC_FLAGS_TCPCI_REV2_0) {
		/*
		 * In TCPCI Rev 2.0, TX_BYTE_CNT and TX_BUF_BYTE_X are the same
		 * register.
		 */
		reg = TCPC_REG_TX_BUFFER;
		/* TX_BYTE_CNT includes extra bytes for message header */
		cnt += sizeof(header);
		tcpc_lock(port, 1);
		rv = tcpc_xfer_unlocked(port, (uint8_t *)&reg, 1, NULL, 0,
					I2C_XFER_START);
		rv |= tcpc_xfer_unlocked(port, (uint8_t *)&cnt, 1, NULL, 0, 0);
		if (cnt > sizeof(header)) {
			rv |= tcpc_xfer_unlocked(port, (uint8_t *)&header,
					 sizeof(header), NULL, 0, 0);
			rv |= tcpc_xfer_unlocked(port, (uint8_t *)data,
					 cnt-sizeof(header), NULL, 0,
					 I2C_XFER_STOP);
		} else {
			rv |= tcpc_xfer_unlocked(port, (uint8_t *)&header,
					sizeof(header), NULL, 0, I2C_XFER_STOP);
		}
		tcpc_lock(port, 0);

		/* If tcpc write fails, return error */
		if (rv)
			return rv;
	} else {
		/* TX_BYTE_CNT includes extra bytes for message header */
		rv = tcpc_write(port, TCPC_REG_TX_BYTE_CNT,
				cnt + sizeof(header));

		rv |= tcpc_write16(port, TCPC_REG_TX_HDR, header);

		/* If tcpc write fails, return error */
		if (rv)
			return rv;

		if (cnt > 0) {
			rv = tcpc_write_block(port, reg, (const uint8_t *)data,
					      cnt);

			/* If tcpc write fails, return error */
			if (rv)
				return rv;
		}
	}

	/*
	 * We always retry in TCPC hardware since the TCPM is too slow to
	 * respond within tRetry (~195 usec).
	 *
	 * The retry count used is dependent on the maximum PD revision
	 * supported at build time.
	 */
	return tcpc_write(port, TCPC_REG_TRANSMIT,
			  TCPC_REG_TRANSMIT_SET_WITH_RETRY(
				  pd_get_retry_count(port, type), type));
}

/*
 * Returns true if TCPC has reset based on reading mask registers.
 */
static int register_mask_reset(int port)
{
	int mask;

	mask = 0;
	tcpc_read16(port, TCPC_REG_ALERT_MASK, &mask);
	if (mask == TCPC_REG_ALERT_MASK_ALL)
		return 1;

	mask = 0;
	tcpc_read(port, TCPC_REG_POWER_STATUS_MASK, &mask);
	if (mask == TCPC_REG_POWER_STATUS_MASK_ALL)
		return 1;

	return 0;
}

static int tcpci_get_fault(int port, int *fault)
{
	return tcpc_read(port, TCPC_REG_FAULT_STATUS, fault);
}

static int tcpci_handle_fault(int port, int fault)
{
	int rv = EC_SUCCESS;

	CPRINTS("C%d FAULT 0x%02X detected", port, fault);

	if (IS_ENABLED(DEBUG_I2C_FAULT_LAST_WRITE_OP) &&
	    fault & TCPC_REG_FAULT_STATUS_I2C_INTERFACE_ERR) {
		if (last_write_op[port].mask == 0)
			CPRINTS("C%d I2C WR 0x%02X 0x%02X value=0x%X",
				port,
				last_write_op[port].addr,
				last_write_op[port].reg,
				last_write_op[port].val);
		else
			CPRINTS("C%d I2C UP 0x%02X 0x%02X op=%d mask=0x%X",
				port,
				last_write_op[port].addr,
				last_write_op[port].reg,
				last_write_op[port].mask >> 16,
				last_write_op[port].mask & 0xFFFF);
	}

	/* Report overcurrent to the OCP module if enabled */
	if ((dev_cap_1[port] & TCPC_REG_DEV_CAP_1_VBUS_OCP_REPORTING) &&
			IS_ENABLED(CONFIG_USBC_OCP) &&
			(fault & TCPC_REG_FAULT_STATUS_VBUS_OVER_CURRENT))
		pd_handle_overcurrent(port);

	if (tcpc_config[port].drv->handle_fault)
		rv = tcpc_config[port].drv->handle_fault(port, fault);

	return rv;
}

enum ec_error_list tcpci_set_bist_test_mode(const int port,
		const bool enable)
{
	int rv;

	rv = tcpc_update8(port, TCPC_REG_TCPC_CTRL,
			TCPC_REG_TCPC_CTRL_BIST_TEST_MODE,
			enable ? MASK_SET : MASK_CLR);
	rv |= tcpc_update16(port, TCPC_REG_ALERT_MASK,
			TCPC_REG_ALERT_RX_STATUS, enable ? MASK_CLR : MASK_SET);
	return rv;
}

static int tcpci_clear_fault(int port, int fault)
{
	int rv;

	rv = tcpc_write(port, TCPC_REG_FAULT_STATUS, fault);
	if (rv)
		return rv;

	return tcpc_write16(port, TCPC_REG_ALERT, TCPC_REG_ALERT_FAULT);
}

static void tcpci_check_vbus_changed(int port, int alert, uint32_t *pd_event)
{
	/*
	 * Check for VBus change
	 */
	/* TCPCI Rev2 includes Safe0V detection */
	if (TCPC_FLAGS_VSAFE0V(tcpc_config[port].flags) &&
	    (alert & TCPC_REG_ALERT_EXT_STATUS)) {
		int ext_status = 0;

		/* Determine if Safe0V was detected */
		tcpm_ext_status(port, &ext_status);
		if (ext_status & TCPC_REG_EXT_STATUS_SAFE0V)
			/* Safe0V=1 and Present=0 */
			tcpc_vbus[port] = BIT(VBUS_SAFE0V);
	}

	if (alert & TCPC_REG_ALERT_POWER_STATUS) {
		int pwr_status = 0;

		/* Determine reason for power status change */
		tcpci_tcpm_get_power_status(port, &pwr_status);
		if (pwr_status & TCPC_REG_POWER_STATUS_VBUS_PRES)
			/* Safe0V=0 and Present=1 */
			tcpc_vbus[port] = BIT(VBUS_PRESENT);
		else if (TCPC_FLAGS_VSAFE0V(tcpc_config[port].flags))
			/* TCPCI Rev2 detects Safe0V, so Present=0 */
			tcpc_vbus[port] &= ~BIT(VBUS_PRESENT);
		else {
			/*
			 * TCPCI Rev1 can not detect Safe0V, so treat this
			 * like a Safe0V detection.
			 *
			 * Safe0V=1 and Present=0
			 */
			tcpc_vbus[port] = BIT(VBUS_SAFE0V);
		}

		if (IS_ENABLED(CONFIG_USB_PD_VBUS_DETECT_TCPC) &&
		    IS_ENABLED(CONFIG_USB_CHARGER)) {
			/* Update charge manager with new VBUS state */
			usb_charger_vbus_change(port,
				!!(tcpc_vbus[port] & BIT(VBUS_PRESENT)));

			if (pd_event)
				*pd_event |= TASK_EVENT_WAKE;
		}
	}
}

/*
 * Don't let the TCPC try to pull from the RX buffer forever. We typical only
 * have 1 or 2 messages waiting.
 */
#define MAX_ALLOW_FAILED_RX_READS 10

void tcpci_tcpc_alert(int port)
{
	int alert = 0;
	int alert_ext = 0;
	int failed_attempts;
	uint32_t pd_event = 0;

	/* Read the Alert register from the TCPC */
	if (tcpm_alert_status(port, &alert)) {
		CPRINTS("C%d: Failed to read alert register", port);
		return;
	}

	/* Get Extended Alert register if needed */
	if (alert & TCPC_REG_ALERT_ALERT_EXT)
		tcpm_alert_ext_status(port, &alert_ext);

	/* Clear any pending faults */
	if (alert & TCPC_REG_ALERT_FAULT) {
		int fault;

		if (tcpci_get_fault(port, &fault) == EC_SUCCESS &&
		    fault != 0 &&
		    tcpci_handle_fault(port, fault) == EC_SUCCESS &&
		    tcpci_clear_fault(port, fault) == EC_SUCCESS)
			CPRINTS("C%d FAULT 0x%02X handled", port, fault);
	}

	/*
	 * Check for TX complete first b/c PD state machine waits on TX
	 * completion events. This will send an event to the PD tasks
	 * immediately
	 */
	if (alert & TCPC_REG_ALERT_TX_COMPLETE)
		pd_transmit_complete(port, alert & TCPC_REG_ALERT_TX_SUCCESS ?
					   TCPC_TX_COMPLETE_SUCCESS :
					   TCPC_TX_COMPLETE_FAILED);

	/* Pull all RX messages from TCPC into EC memory */
	failed_attempts = 0;
	while (alert & TCPC_REG_ALERT_RX_STATUS) {
		if (tcpm_enqueue_message(port))
			++failed_attempts;
		if (tcpm_alert_status(port, &alert))
			++failed_attempts;

		/* Ensure we don't loop endlessly */
		if (failed_attempts >= MAX_ALLOW_FAILED_RX_READS) {
			CPRINTS("C%d Cannot consume RX buffer after %d failed attempts!",
				port, failed_attempts);
			/*
			 * The port is in a bad state, we don't want to consume
			 * all EC resources so suspend the port for a little
			 * while.
			 */
			pd_set_suspend(port, 1);
			pd_deferred_resume(port);
			return;
		}
	}

	/*
	 * Clear all pending alert bits. Ext first because ALERT.AlertExtended
	 * is set if any bit of ALERT_EXTENDED is set.
	 */
	if (alert_ext)
		tcpc_write(port, TCPC_REG_ALERT_EXT, alert_ext);
	if (alert)
		tcpc_write16(port, TCPC_REG_ALERT, alert);

	if (alert & TCPC_REG_ALERT_CC_STATUS) {
		if (IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE)) {
			enum tcpc_cc_voltage_status cc1;
			enum tcpc_cc_voltage_status cc2;

			/*
			 * Some TCPCs generate CC Alerts when
			 * drp auto toggle is active and nothing
			 * is connected to the port. So, get the
			 * CC line status and only generate a
			 * PD_EVENT_CC if something is connected.
			 */
			tcpci_tcpm_get_cc(port, &cc1, &cc2);
			if (cc1 != TYPEC_CC_VOLT_OPEN ||
			    cc2 != TYPEC_CC_VOLT_OPEN)
				/* CC status cchanged, wake task */
				pd_event |= PD_EVENT_CC;
		} else {
			/* CC status changed, wake task */
			pd_event |= PD_EVENT_CC;
		}
	}

	tcpci_check_vbus_changed(port, alert, &pd_event);

	/* Check for Hard Reset received */
	if (alert & TCPC_REG_ALERT_RX_HARD_RST) {
		/* hard reset received */
		CPRINTS("C%d Hard Reset received", port);
		pd_event |= PD_EVENT_RX_HARD_RESET;
	}

	/* USB TCPCI Spec R2 V1.1 Section 4.7.3 Step 2
	 *
	 * The TCPC asserts both ALERT.TransmitSOP*MessageSuccessful and
	 * ALERT.TransmitSOP*MessageFailed regardless of the outcome of the
	 * transmission and asserts the Alert# pin.
	 */
	if (alert & TCPC_REG_ALERT_TX_SUCCESS &&
	    alert & TCPC_REG_ALERT_TX_FAILED)
		CPRINTS("C%d Hard Reset sent", port);

	if (IS_ENABLED(CONFIG_USB_PD_FRS_TCPC)
	    && (alert_ext & TCPC_REG_ALERT_EXT_SNK_FRS))
		pd_got_frs_signal(port);

	/*
	 * Check registers to see if we can tell that the TCPC has reset. If
	 * so, perform a tcpc_init.
	 */
	if (register_mask_reset(port))
		pd_event |= PD_EVENT_TCPC_RESET;

	/*
	 * Wait until all possible TCPC accesses in this function are complete
	 * prior to setting events and/or waking the pd task. When the PD
	 * task is woken and runs (which will happen during I2C transactions in
	 * this function), the pd task may put the TCPC into low power mode and
	 * the next I2C transaction to the TCPC will cause it to wake again.
	 */
	if (pd_event)
		task_set_event(PD_PORT_TO_TASK_ID(port), pd_event);
}

/*
 * This call will wake up the TCPC if it is in low power mode upon accessing the
 * i2c bus (but the pd state machine should put it back into low power mode).
 *
 * Once it's called, the chip info will be stored in cache, which can be
 * accessed by tcpm_get_chip_info without worrying about chip states.
 */
int tcpci_get_chip_info(int port, int live,
			struct ec_response_pd_chip_info_v1 *chip_info)
{
	static struct ec_response_pd_chip_info_v1
		cached_info[CONFIG_USB_PD_PORT_MAX_COUNT];
	struct ec_response_pd_chip_info_v1 *i;
	int error;
	int val;

	if (port >= board_get_usb_pd_port_count())
		return EC_ERROR_INVAL;

	i = &cached_info[port];


	/* If already cached && live data is not asked, return cached value */
	if (i->vendor_id && !live) {
		/*
		 * If chip_info is NULL, chip info will be stored in cache and
		 * can be read later by another call.
		 */
		if (chip_info)
			memcpy(chip_info, i, sizeof(*i));

		return EC_SUCCESS;
	}

	error = tcpc_read16(port, TCPC_REG_VENDOR_ID, &val);
	if (error)
		return error;
	i->vendor_id = val;

	error = tcpc_read16(port, TCPC_REG_PRODUCT_ID, &val);
	if (error)
		return error;
	i->product_id = val;

	error = tcpc_read16(port, TCPC_REG_BCD_DEV, &val);
	if (error)
		return error;
	i->device_id = val;

	/*
	 * This varies chip to chip; more specific driver code is expected to
	 * override this value if it can.
	 */
	i->fw_version_number = -1;

	/* Copy the cached value to return if chip_info is not NULL */
	if (chip_info)
		memcpy(chip_info, i, sizeof(*i));

	return EC_SUCCESS;
}

/*
 * Dissociate from the TCPC.
 */

int tcpci_tcpm_release(int port)
{
	int error;

	error = clear_alert_mask(port);
	if (error)
		return error;
	error = clear_power_status_mask(port);
	if (error)
		return error;
	/* Clear pending interrupts */
	error = tcpc_write16(port, TCPC_REG_ALERT, 0xffff);
	if (error)
		return error;

	return EC_SUCCESS;
}

/*
 * On TCPC i2c failure, make 30 tries (at least 300ms) before giving up
 * in order to allow the TCPC time to boot / reset.
 */
#define TCPM_INIT_TRIES 30

int tcpci_tcpm_init(int port)
{
	int error;
	int power_status;
	int tries = TCPM_INIT_TRIES;

	if (port >= board_get_usb_pd_port_count())
		return EC_ERROR_INVAL;

	while (1) {
		error = tcpci_tcpm_get_power_status(port, &power_status);
		/*
		 * If read succeeds and the uninitialized bit is clear, then
		 * initialization is complete, clear all alert bits and write
		 * the initial alert mask.
		 */
		if (!error && !(power_status & TCPC_REG_POWER_STATUS_UNINIT))
			break;
		if (--tries <= 0)
			return error ? error : EC_ERROR_TIMEOUT;
		msleep(10);
	}

	/*
	 * For TCPCI Rev 2.0, unless the TCPM sets
	 * TCPC_CONTROL.EnableLooking4ConnectionAlert bit, TCPC by default masks
	 * Alert assertion when CC_STATUS.Looking4Connection changes state.
	 */
	if (tcpc_config[port].flags & TCPC_FLAGS_TCPCI_REV2_0) {
		error = tcpc_update8(port, TCPC_REG_TCPC_CTRL,
				TCPC_REG_TCPC_CTRL_EN_LOOK4CONNECTION_ALERT,
				MASK_SET);
		if (error)
			CPRINTS("C%d: Failed to init TCPC_CTRL!", port);
	}

	/*
	 * Handle and clear any alerts, since we might be coming out of low
	 * power mode in response to an alert interrupt from the TCPC.
	 */
	tcpc_alert(port);
	/* Initialize power_status_mask */
	init_power_status_mask(port);

	if (TCPC_FLAGS_VSAFE0V(tcpc_config[port].flags)) {
		int ext_status = 0;

		/* Read Extended Status register */
		tcpm_ext_status(port, &ext_status);
		/* Initial level, set appropriately */
		if (power_status & TCPC_REG_POWER_STATUS_VBUS_PRES)
			tcpc_vbus[port] = BIT(VBUS_PRESENT);
		else if (ext_status & TCPC_REG_EXT_STATUS_SAFE0V)
			tcpc_vbus[port] = BIT(VBUS_SAFE0V);
		else
			tcpc_vbus[port] = 0;
	} else {
		/* Initial level, set appropriately */
		tcpc_vbus[port] = (power_status &
				   TCPC_REG_POWER_STATUS_VBUS_PRES)
					? BIT(VBUS_PRESENT)
					: BIT(VBUS_SAFE0V);
	}

	/*
	 * Force an update to the VBUS status in case the TCPC doesn't send a
	 * power status changed interrupt later.
	 */
	tcpci_check_vbus_changed(port,
		TCPC_REG_ALERT_POWER_STATUS | TCPC_REG_ALERT_EXT_STATUS,
		NULL);

	error = init_alert_mask(port);
	if (error)
		return error;

	/* Read chip info here when we know the chip is awake. */
	tcpm_get_chip_info(port, 1, NULL);

	/* Cache our device capabilities for future reference */
	tcpc_read16(port, TCPC_REG_DEV_CAP_1, &dev_cap_1[port]);

	return EC_SUCCESS;
}

#ifdef CONFIG_USB_PD_TCPM_MUX

/*
 * When the TCPC/MUX device is only used for the MUX, we need to initialize it
 * via mux init because tcpc_init won't run for the device. This is borrowed
 * from tcpc_init.
 */
int tcpci_tcpm_mux_init(const struct usb_mux *me)
{
	int error;
	int power_status;
	int tries = TCPM_INIT_TRIES;

	/* If this MUX is also the TCPC, then skip init */
	if (!(me->flags & USB_MUX_FLAG_NOT_TCPC))
		return EC_SUCCESS;

	/* Wait for the device to exit low power state */
	while (1) {
		error = mux_read(me, TCPC_REG_POWER_STATUS, &power_status);
		/*
		 * If read succeeds and the uninitialized bit is clear, then
		 * initialization is complete.
		 */
		if (!error && !(power_status & TCPC_REG_POWER_STATUS_UNINIT))
			break;
		if (--tries <= 0)
			return error ? error : EC_ERROR_TIMEOUT;
		msleep(10);
	}

	/* Turn off all alerts and acknowledge any pending IRQ */
	error = mux_write16(me, TCPC_REG_ALERT_MASK, 0);
	error |= mux_write16(me, TCPC_REG_ALERT, 0xffff);

	return error ? EC_ERROR_UNKNOWN : EC_SUCCESS;
}

int tcpci_tcpm_mux_enter_low_power(const struct usb_mux *me)
{
	/* If this MUX is also the TCPC, then skip low power */
	if (!(me->flags & USB_MUX_FLAG_NOT_TCPC))
		return EC_SUCCESS;

	return mux_write(me, TCPC_REG_COMMAND, TCPC_REG_COMMAND_I2CIDLE);
}

int tcpci_tcpm_mux_set(const struct usb_mux *me, mux_state_t mux_state)
{
	int rv;
	int reg = 0;

	/* Parameter is port only */
	rv = mux_read(me, TCPC_REG_CONFIG_STD_OUTPUT, &reg);
	if (rv != EC_SUCCESS)
		return rv;

	reg &= ~(TCPC_REG_CONFIG_STD_OUTPUT_MUX_MASK |
		 TCPC_REG_CONFIG_STD_OUTPUT_CONNECTOR_FLIPPED);
	if (mux_state & USB_PD_MUX_USB_ENABLED)
		reg |= TCPC_REG_CONFIG_STD_OUTPUT_MUX_USB;
	if (mux_state & USB_PD_MUX_DP_ENABLED)
		reg |= TCPC_REG_CONFIG_STD_OUTPUT_MUX_DP;
	if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
		reg |= TCPC_REG_CONFIG_STD_OUTPUT_CONNECTOR_FLIPPED;

	/* Parameter is port only */
	return mux_write(me, TCPC_REG_CONFIG_STD_OUTPUT, reg);
}

/* Reads control register and updates mux_state accordingly */
int tcpci_tcpm_mux_get(const struct usb_mux *me, mux_state_t *mux_state)
{
	int rv;
	int reg = 0;

	*mux_state = 0;

	/* Parameter is port only */
	rv = mux_read(me, TCPC_REG_CONFIG_STD_OUTPUT, &reg);
	if (rv != EC_SUCCESS)
		return rv;

	if (reg & TCPC_REG_CONFIG_STD_OUTPUT_MUX_USB)
		*mux_state |= USB_PD_MUX_USB_ENABLED;
	if (reg & TCPC_REG_CONFIG_STD_OUTPUT_MUX_DP)
		*mux_state |= USB_PD_MUX_DP_ENABLED;
	if (reg & TCPC_REG_CONFIG_STD_OUTPUT_CONNECTOR_FLIPPED)
		*mux_state |= USB_PD_MUX_POLARITY_INVERTED;

	return EC_SUCCESS;
}

const struct usb_mux_driver tcpci_tcpm_usb_mux_driver = {
	.init = &tcpci_tcpm_mux_init,
	.set = &tcpci_tcpm_mux_set,
	.get = &tcpci_tcpm_mux_get,
	.enter_low_power_mode = &tcpci_tcpm_mux_enter_low_power,
};

#endif /* CONFIG_USB_PD_TCPM_MUX */

#ifdef CONFIG_CMD_TCPC_DUMP
static const struct tcpc_reg_dump_map tcpc_regs[] = {
	{
		.addr = TCPC_REG_VENDOR_ID,
		.name = "VENDOR_ID",
		.size = 2,
	},
	{
		.addr = TCPC_REG_PRODUCT_ID,
		.name = "PRODUCT_ID",
		.size = 2,
	},
	{
		.addr = TCPC_REG_BCD_DEV,
		.name = "BCD_DEV",
		.size = 2,
	},
	{
		.addr = TCPC_REG_TC_REV,
		.name = "TC_REV",
		.size = 2,
	},
	{
		.addr = TCPC_REG_PD_REV,
		.name = "PD_REV",
		.size = 2,
	},
	{
		.addr = TCPC_REG_PD_INT_REV,
		.name = "PD_INT_REV",
		.size = 2,
	},
	{
		.addr = TCPC_REG_ALERT,
		.name = "ALERT",
		.size = 2,
	},
	{
		.addr = TCPC_REG_ALERT_MASK,
		.name = "ALERT_MASK",
		.size = 2,
	},
	{
		.addr = TCPC_REG_POWER_STATUS_MASK,
		.name = "POWER_STATUS_MASK",
		.size = 1,
	},
	{
		.addr = TCPC_REG_FAULT_STATUS_MASK,
		.name = "FAULT_STATUS_MASK",
		.size = 1,
	},
	{
		.addr = TCPC_REG_EXT_STATUS_MASK,
		.name = "EXT_STATUS_MASK",
		.size = 1
	},
	{
		.addr = TCPC_REG_ALERT_EXTENDED_MASK,
		.name = "ALERT_EXTENDED_MASK",
		.size = 1,
	},
	{
		.addr = TCPC_REG_CONFIG_STD_OUTPUT,
		.name = "CONFIG_STD_OUTPUT",
		.size = 1,
	},
	{
		.addr = TCPC_REG_TCPC_CTRL,
		.name = "TCPC_CTRL",
		.size = 1,
	},
	{
		.addr = TCPC_REG_ROLE_CTRL,
		.name = "ROLE_CTRL",
		.size = 1,
	},
	{
		.addr = TCPC_REG_FAULT_CTRL,
		.name = "FAULT_CTRL",
		.size = 1,
	},
	{
		.addr = TCPC_REG_POWER_CTRL,
		.name = "POWER_CTRL",
		.size = 1,
	},
	{
		.addr = TCPC_REG_CC_STATUS,
		.name = "CC_STATUS",
		.size = 1,
	},
	{
		.addr = TCPC_REG_POWER_STATUS,
		.name = "POWER_STATUS",
		.size = 1,
	},
	{
		.addr = TCPC_REG_FAULT_STATUS,
		.name = "FAULT_STATUS",
		.size = 1,
	},
	{
		.addr = TCPC_REG_EXT_STATUS,
		.name = "EXT_STATUS",
		.size = 1,
	},
	{
		.addr = TCPC_REG_ALERT_EXT,
		.name = "ALERT_EXT",
		.size = 1,
	},
	{
		.addr = TCPC_REG_DEV_CAP_1,
		.name = "DEV_CAP_1",
		.size = 2,
	},
	{
		.addr = TCPC_REG_DEV_CAP_2,
		.name = "DEV_CAP_2",
		.size = 2,
	},
	{
		.addr = TCPC_REG_STD_INPUT_CAP,
		.name = "STD_INPUT_CAP",
		.size = 1,
	},
	{
		.addr = TCPC_REG_STD_OUTPUT_CAP,
		.name = "STD_OUTPUT_CAP",
		.size = 1,
	},
	{
		.addr = TCPC_REG_CONFIG_EXT_1,
		.name = "CONFIG_EXT_1",
		.size = 1,
	},
	{
		.addr = TCPC_REG_MSG_HDR_INFO,
		.name = "MSG_HDR_INFO",
		.size = 1,
	},
	{
		.addr = TCPC_REG_RX_DETECT,
		.name = "RX_DETECT",
		.size = 1,
	},
	{
		.addr = TCPC_REG_RX_BYTE_CNT,
		.name = "RX_BYTE_CNT",
		.size = 1,
	},
	{
		.addr = TCPC_REG_RX_BUF_FRAME_TYPE,
		.name = "RX_BUF_FRAME_TYPE",
		.size = 1,
	},
	{
		.addr = TCPC_REG_TRANSMIT,
		.name = "TRANSMIT",
		.size = 1,
	},
	{
		.addr = TCPC_REG_VBUS_VOLTAGE,
		.name = "VBUS_VOLTAGE",
		.size = 2,
	},
	{
		.addr = TCPC_REG_VBUS_SINK_DISCONNECT_THRESH,
		.name = "VBUS_SINK_DISCONNECT_THRESH",
		.size = 2,
	},
	{
		.addr = TCPC_REG_VBUS_STOP_DISCHARGE_THRESH,
		.name = "VBUS_STOP_DISCHARGE_THRESH",
		.size = 2,
	},
	{
		.addr = TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG,
		.name = "VBUS_VOLTAGE_ALARM_HI_CFG",
		.size = 2,
	},
	{
		.addr = TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG,
		.name = "VBUS_VOLTAGE_ALARM_LO_CFG",
		.size = 2,
	},
};

/*
 * Dump standard TCPC registers.
 */
void tcpc_dump_std_registers(int port)
{
	tcpc_dump_registers(port, tcpc_regs, ARRAY_SIZE(tcpc_regs));
}
#endif

const struct tcpm_drv tcpci_tcpm_drv = {
	.init			= &tcpci_tcpm_init,
	.release		= &tcpci_tcpm_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			= &tcpci_tcpm_set_cc,
	.set_polarity		= &tcpci_tcpm_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		= &tcpci_tcpc_alert,
#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
	.tcpc_discharge_vbus	= &tcpci_tcpc_discharge_vbus,
#endif
	.tcpc_enable_auto_discharge_disconnect =
		&tcpci_tcpc_enable_auto_discharge_disconnect,
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
	.drp_toggle		= &tcpci_tcpc_drp_toggle,
#endif
	.get_chip_info		= &tcpci_get_chip_info,
#ifdef CONFIG_USB_PD_PPC
	.get_snk_ctrl		= &tcpci_tcpm_get_snk_ctrl,
	.set_snk_ctrl		= &tcpci_tcpm_set_snk_ctrl,
	.get_src_ctrl		= &tcpci_tcpm_get_src_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		= &tcpc_dump_std_registers,
#endif
};
