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

#include "mock/tcpci_i2c_mock.h"
#include "task.h"
#include "tcpm/tcpci.h"
#include "test_util.h"
#include "timer.h"
#include "usb_pd_tcpm.h"

#ifndef TEST_BUILD
#error "Mocks should only be in the test build."
#endif

#define BUFFER_SIZE 100
#define VERIFY_TIMEOUT (5 * SECOND)

struct tcpci_reg {
	uint8_t		offset;
	uint8_t		size;
	uint16_t	value;
	const char	*name;
};

#define TCPCI_REG(reg_name, reg_size)					\
	[reg_name] = { .offset = (reg_name), .size = (reg_size),	\
			.value = 0, .name = #reg_name, }

static struct tcpci_reg tcpci_regs[] = {
	TCPCI_REG(TCPC_REG_VENDOR_ID, 2),
	TCPCI_REG(TCPC_REG_PRODUCT_ID, 2),
	TCPCI_REG(TCPC_REG_BCD_DEV, 2),
	TCPCI_REG(TCPC_REG_TC_REV, 2),
	TCPCI_REG(TCPC_REG_PD_REV, 2),
	TCPCI_REG(TCPC_REG_PD_INT_REV, 2),
	TCPCI_REG(TCPC_REG_ALERT, 2),
	TCPCI_REG(TCPC_REG_ALERT_MASK, 2),
	TCPCI_REG(TCPC_REG_POWER_STATUS_MASK, 1),
	TCPCI_REG(TCPC_REG_FAULT_STATUS_MASK, 1),
	TCPCI_REG(TCPC_REG_EXT_STATUS_MASK, 1),
	TCPCI_REG(TCPC_REG_ALERT_EXTENDED_MASK, 1),
	TCPCI_REG(TCPC_REG_CONFIG_STD_OUTPUT, 1),
	TCPCI_REG(TCPC_REG_TCPC_CTRL, 1),
	TCPCI_REG(TCPC_REG_ROLE_CTRL, 1),
	TCPCI_REG(TCPC_REG_FAULT_CTRL, 1),
	TCPCI_REG(TCPC_REG_POWER_CTRL, 1),
	TCPCI_REG(TCPC_REG_CC_STATUS, 1),
	TCPCI_REG(TCPC_REG_POWER_STATUS, 1),
	TCPCI_REG(TCPC_REG_FAULT_STATUS, 1),
	TCPCI_REG(TCPC_REG_EXT_STATUS, 1),
	TCPCI_REG(TCPC_REG_ALERT_EXT, 1),
	TCPCI_REG(TCPC_REG_DEV_CAP_1, 2),
	TCPCI_REG(TCPC_REG_DEV_CAP_2, 2),
	TCPCI_REG(TCPC_REG_STD_INPUT_CAP, 1),
	TCPCI_REG(TCPC_REG_STD_OUTPUT_CAP, 1),
	TCPCI_REG(TCPC_REG_CONFIG_EXT_1, 1),
	TCPCI_REG(TCPC_REG_MSG_HDR_INFO, 1),
	TCPCI_REG(TCPC_REG_RX_DETECT, 1),
	TCPCI_REG(TCPC_REG_RX_BUFFER, BUFFER_SIZE),
	TCPCI_REG(TCPC_REG_TRANSMIT, 1),
	TCPCI_REG(TCPC_REG_TX_BUFFER, BUFFER_SIZE),
	TCPCI_REG(TCPC_REG_VBUS_VOLTAGE, 2),
	TCPCI_REG(TCPC_REG_VBUS_SINK_DISCONNECT_THRESH, 2),
	TCPCI_REG(TCPC_REG_VBUS_STOP_DISCHARGE_THRESH, 2),
	TCPCI_REG(TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG, 2),
	TCPCI_REG(TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG, 2),
	TCPCI_REG(TCPC_REG_COMMAND, 1),
};

static uint8_t tx_buffer[BUFFER_SIZE];
static int tx_pos = -1;
static int tx_msg_cnt;
static int tx_retry_cnt = -1;
static uint8_t rx_buffer[BUFFER_SIZE];
static int rx_pos = -1;

static const char * const ctrl_msg_name[] = {
	[0]				= "C-RSVD_0",
	[PD_CTRL_GOOD_CRC]		= "C-GOODCRC",
	[PD_CTRL_GOTO_MIN]		= "C-GOTOMIN",
	[PD_CTRL_ACCEPT]		= "C-ACCEPT",
	[PD_CTRL_REJECT]		= "C-REJECT",
	[PD_CTRL_PING]			= "C-PING",
	[PD_CTRL_PS_RDY]		= "C-PSRDY",
	[PD_CTRL_GET_SOURCE_CAP]	= "C-GET_SRC_CAP",
	[PD_CTRL_GET_SINK_CAP]		= "C-GET_SNK_CAP",
	[PD_CTRL_DR_SWAP]		= "C-DR_SWAP",
	[PD_CTRL_PR_SWAP]		= "C-PR_SWAP",
	[PD_CTRL_VCONN_SWAP]		= "C-VCONN_SW",
	[PD_CTRL_WAIT]			= "C-WAIT",
	[PD_CTRL_SOFT_RESET]		= "C-SOFT-RESET",
	[14]				= "C-RSVD_14",
	[15]				= "C-RSVD_15",
	[PD_CTRL_NOT_SUPPORTED]		= "C-NOT_SUPPORTED",
	[PD_CTRL_GET_SOURCE_CAP_EXT]	= "C-GET_SRC_CAP-EXT",
	[PD_CTRL_GET_STATUS]		= "C-GET-STATUS",
	[PD_CTRL_FR_SWAP]		= "C-FR_SWAP",
	[PD_CTRL_GET_PPS_STATUS]	= "C-GET_PPS_STATUS",
	[PD_CTRL_GET_COUNTRY_CODES]	= "C-GET_COUNTRY_CODES",
};

static const char * const data_msg_name[] = {
	[0]				= "D-RSVD_0",
	[PD_DATA_SOURCE_CAP]		= "D-SRC_CAP",
	[PD_DATA_REQUEST]		= "D-REQUEST",
	[PD_DATA_BIST]			= "D-BIST",
	[PD_DATA_SINK_CAP]		= "D-SNK_CAP",
	/* 5-14 Reserved for REV 2.0 */
	[PD_DATA_BATTERY_STATUS]	= "D-BATTERY_STATUS",
	[PD_DATA_ALERT]			= "D-ALERT",
	[PD_DATA_GET_COUNTRY_INFO]	= "D-GET_COUNTRY_CODES",
	/* 8-14 Reserved for REV 3.0 */
	[PD_DATA_ENTER_USB]		= "D-ENTER_USB",
	[PD_DATA_VENDOR_DEF]		= "D-VDM",
};

static const char * const ext_msg_name[] = {
	[0]				= "X-RSVD_0",
	[PD_EXT_SOURCE_CAP]		= "X-SRC_CAP",
	[PD_EXT_STATUS]			= "X-STATUS",
	[PD_EXT_GET_BATTERY_CAP]	= "X-GET_BATTERY_CAP",
	[PD_EXT_GET_BATTERY_STATUS]	= "X-GET_BATTERY_STATUS",
	[PD_EXT_BATTERY_CAP]		= "X-BATTERY_CAP",
	[PD_EXT_GET_MANUFACTURER_INFO]	= "X-GET_MFR_INFO",
	[PD_EXT_MANUFACTURER_INFO]	= "X-MFR_INFO",
	[PD_EXT_SECURITY_REQUEST]	= "X-SECURITY_REQ",
	[PD_EXT_SECURITY_RESPONSE]	= "X-SECURITY_RESP",
	[PD_EXT_FIRMWARE_UPDATE_REQUEST] = "X-FW_UP_REQ",
	[PD_EXT_FIRMWARE_UPDATE_RESPONSE] = "X-FW_UP_RESP",
	[PD_EXT_PPS_STATUS]		= "X-PPS_STATUS",
	[PD_EXT_COUNTRY_INFO]		= "X-COUNTRY_INFO",
	[PD_EXT_COUNTRY_CODES]		= "X-COUNTRY_CODES",
};

static const char * const rev_name[] = {
	[PD_REV10] = "1.0",
	[PD_REV20] = "2.0",
	[PD_REV30] = "3.0",
	[3] = "RSVD",
};

static const char * const drole_name[] = {
	[PD_ROLE_UFP] = "UFP",
	[PD_ROLE_DFP] = "DFP",
};

static const char * const prole_name[] = {
	[PD_ROLE_SINK] = "SNK",
	[PD_ROLE_SOURCE] = "SRC",
};

static void print_header(const char *prefix, uint16_t header)
{
	int type  = PD_HEADER_TYPE(header);
	int drole = PD_HEADER_DROLE(header);
	int rev   = PD_HEADER_REV(header);
	int prole = PD_HEADER_PROLE(header);
	int id    = PD_HEADER_ID(header);
	int cnt   = PD_HEADER_CNT(header);
	int ext   = PD_HEADER_EXT(header);
	const char *name = ext	? ext_msg_name[type]
				: cnt
					? data_msg_name[type]
					: ctrl_msg_name[type];

	ccprints("%s header=0x%x [%s %s %s %s id=%d cnt=%d ext=%d]",
		 prefix, header,
		 name, drole_name[drole], rev_name[rev], prole_name[prole],
		 id, cnt, ext);
}

static bool dead_battery(void)
{
	return false;
}

static bool debug_accessory_indicator_supported(void)
{
	return true;
}

static int verify_transmit(enum tcpci_msg_type want_tx_type,
			   int want_tx_retry,
			   enum pd_ctrl_msg_type want_ctrl_msg,
			   enum pd_data_msg_type want_data_msg,
			   int timeout)
{
	uint64_t end_time = get_time().val + timeout;

	/*
	 * Check that nothing was already transmitted. This ensures that all
	 * transmits are checked, and the test stays in sync with the code
	 * being tested.
	 */
	TEST_EQ(tcpci_regs[TCPC_REG_TRANSMIT].value, 0, "%d");

	/* Now wait for the expected message to be transmitted. */
	while (get_time().val < end_time) {
		if (tcpci_regs[TCPC_REG_TRANSMIT].value != 0) {
			int tx_type = TCPC_REG_TRANSMIT_TYPE(
				tcpci_regs[TCPC_REG_TRANSMIT].value);
			int tx_retry = TCPC_REG_TRANSMIT_RETRY(
				tcpci_regs[TCPC_REG_TRANSMIT].value);
			uint16_t header = UINT16_FROM_BYTE_ARRAY_LE(
						tx_buffer, 1);
			int pd_type  = PD_HEADER_TYPE(header);
			int pd_cnt   = PD_HEADER_CNT(header);

			TEST_EQ(tx_type, want_tx_type, "%d");
			if (want_tx_retry >= 0)
				TEST_EQ(tx_retry, want_tx_retry, "%d");

			if (want_ctrl_msg != 0) {
				TEST_EQ(pd_type, want_ctrl_msg, "0x%x");
				TEST_EQ(pd_cnt, 0, "%d");
			}
			if (want_data_msg != 0) {
				TEST_EQ(pd_type, want_data_msg, "0x%x");
				TEST_GE(pd_cnt, 1, "%d");
			}

			tcpci_regs[TCPC_REG_TRANSMIT].value = 0;
			return EC_SUCCESS;
		}
		task_wait_event(5 * MSEC);
	}
	TEST_ASSERT(0);
	return EC_ERROR_UNKNOWN;
}

int verify_tcpci_transmit(enum tcpci_msg_type tx_type,
			  enum pd_ctrl_msg_type ctrl_msg,
			  enum pd_data_msg_type data_msg)
{
	return verify_transmit(tx_type, -1,
			       ctrl_msg, data_msg,
			       VERIFY_TIMEOUT);
}

int verify_tcpci_tx_timeout(enum tcpci_msg_type tx_type,
			    enum pd_ctrl_msg_type ctrl_msg,
			    enum pd_data_msg_type data_msg,
			    int timeout)
{
	return verify_transmit(tx_type, -1,
			       ctrl_msg, data_msg,
			       timeout);
}

int verify_tcpci_tx_retry_count(enum tcpci_msg_type tx_type,
				enum pd_ctrl_msg_type ctrl_msg,
				enum pd_data_msg_type data_msg,
				int retry_count)
{
	return verify_transmit(tx_type, retry_count,
			       ctrl_msg, data_msg,
			       VERIFY_TIMEOUT);
}

int verify_tcpci_tx_with_data(enum tcpci_msg_type tx_type,
			      enum pd_data_msg_type data_msg,
			      uint8_t *data,
			      int data_bytes,
			      int *msg_len,
			      int timeout)
{
	int rv;

	if (timeout <= 0)
		timeout = VERIFY_TIMEOUT;

	rv = verify_transmit(tx_type, -1,
			     0, data_msg,
			     timeout);
	if (!rv) {
		TEST_NE(data, NULL, "%p");
		TEST_GE(data_bytes, tx_msg_cnt, "%d");
		memcpy(data, tx_buffer, tx_msg_cnt);
		if (msg_len)
			*msg_len = tx_msg_cnt;
	}
	return rv;
}

int verify_tcpci_possible_tx(struct possible_tx possible[],
			     int possible_cnt,
			     int *found_index,
			     uint8_t *data,
			     int data_bytes,
			     int *msg_len,
			     int timeout)
{
	bool assert_on_timeout = true;
	uint64_t end_time;

	*found_index = -1;

	if (timeout <= 0) {
		timeout = VERIFY_TIMEOUT;
		assert_on_timeout = false;
	}
	end_time = get_time().val + timeout;

	/*
	 * Check that nothing was already transmitted. This ensures that all
	 * transmits are checked, and the test stays in sync with the code
	 * being tested.
	 */
	TEST_EQ(tcpci_regs[TCPC_REG_TRANSMIT].value, 0, "%d");

	/* Now wait for the expected message to be transmitted. */
	while (get_time().val < end_time) {
		if (tcpci_regs[TCPC_REG_TRANSMIT].value != 0) {
			int i;
			int tx_type = TCPC_REG_TRANSMIT_TYPE(
				tcpci_regs[TCPC_REG_TRANSMIT].value);
			uint16_t header = UINT16_FROM_BYTE_ARRAY_LE(
						tx_buffer, 1);
			int pd_type  = PD_HEADER_TYPE(header);
			int pd_cnt   = PD_HEADER_CNT(header);

			for (i = 0; i < possible_cnt; ++i) {
				int want_tx_type = possible[i].tx_type;
				int want_ctrl_msg = possible[i].ctrl_msg;
				int want_data_msg = possible[i].data_msg;

				if (tx_type != want_tx_type)
					continue;

				if (want_ctrl_msg != 0) {
					if (pd_type != want_ctrl_msg ||
					    pd_cnt != 0)
						continue;
				}
				if (want_data_msg != 0) {
					if (pd_type != want_data_msg ||
					    pd_cnt == 0)
						continue;

					if (data != NULL) {
						TEST_GE(data_bytes,
							tx_msg_cnt, "%d");
						memcpy(data, tx_buffer,
						       tx_msg_cnt);
					}
					if (msg_len != NULL)
						*msg_len = tx_msg_cnt;
				}
				*found_index = i;
				tcpci_regs[TCPC_REG_TRANSMIT].value = 0;
				return EC_SUCCESS;
			}
			return EC_ERROR_UNKNOWN;
		}
		task_wait_event(5 * MSEC);
	}
	if (assert_on_timeout)
		TEST_ASSERT(0);

	return EC_ERROR_TIMEOUT;
}

void mock_tcpci_receive(enum tcpci_msg_type sop, uint16_t header,
			uint32_t *payload)
{
	int i;

	rx_buffer[0] = 3 + (PD_HEADER_CNT(header) * 4);
	rx_buffer[1] = sop;
	rx_buffer[2] = header & 0xFF;
	rx_buffer[3] = (header >> 8) & 0xFF;

	if (rx_buffer[0] >= BUFFER_SIZE) {
		ccprints("ERROR: rx too large");
		return;
	}

	for (i = 4; i < rx_buffer[0]; i += 4) {
		rx_buffer[i] = *payload & 0xFF;
		rx_buffer[i+1] = (*payload >> 8) & 0xFF;
		rx_buffer[i+2] = (*payload >> 16) & 0xFF;
		rx_buffer[i+3] = (*payload >> 24) & 0xFF;
		payload++;
	}

	rx_pos = 0;
}

/*****************************************************************************
 * TCPCI register reset values
 *
 * These values are from USB Type-C Port Controller Interface Specification
 * Revision 2.0, Version 1.2,
 */
static void tcpci_reset_register_masks(void)
{
	/*
	 * Using table 4-1 for default mask values
	 */
	tcpci_regs[TCPC_REG_ALERT_MASK].value =			0x7FFF;
	tcpci_regs[TCPC_REG_POWER_STATUS_MASK].value =		0xFF;
	tcpci_regs[TCPC_REG_FAULT_STATUS_MASK].value =		0xFF;
	tcpci_regs[TCPC_REG_EXT_STATUS_MASK].value =		0x01;
	tcpci_regs[TCPC_REG_ALERT_EXTENDED_MASK].value =	0x07;
}

static void tcpci_reset_register_defaults(void)
{
	int i;

	/* Default all registers to 0 and then overwrite if they are not */
	for (i = 0; i < ARRAY_SIZE(tcpci_regs); i++)
		tcpci_regs[i].value = 0;

	/* Type-C Release 1,3 */
	tcpci_regs[TCPC_REG_TC_REV].value =			0x0013;
	/* PD Revision 3.0 Version 1.2 */
	tcpci_regs[TCPC_REG_PD_REV].value =			0x3012;
	/* PD Interface Revision 2.0, Version 1.1 */
	tcpci_regs[TCPC_REG_PD_INT_REV].value =			0x2011;

	tcpci_reset_register_masks();

	tcpci_regs[TCPC_REG_CONFIG_STD_OUTPUT].value =
			TCPC_REG_CONFIG_STD_OUTPUT_AUDIO_CONN_N |
			TCPC_REG_CONFIG_STD_OUTPUT_DBG_ACC_CONN_N;

	tcpci_regs[TCPC_REG_POWER_CTRL].value =
			TCPC_REG_POWER_CTRL_VOLT_ALARM_DIS |
			TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS;

	tcpci_regs[TCPC_REG_FAULT_STATUS].value =
			TCPC_REG_FAULT_STATUS_ALL_REGS_RESET;

	tcpci_regs[TCPC_REG_DEV_CAP_1].value =
			TCPC_REG_DEV_CAP_1_SOURCE_VBUS |
			TCPC_REG_DEV_CAP_1_SINK_VBUS |
			TCPC_REG_DEV_CAP_1_PWRROLE_SRC_SNK_DRP |
			TCPC_REG_DEV_CAP_1_SRC_RESISTOR_RP_3P0_1P5_DEF;

	/*
	 * Using table 4-17 to get the default Role Control and
	 * Message Header Info register values.
	 */
	switch (mock_tcpci_get_reg(TCPC_REG_DEV_CAP_1) &
			TCPC_REG_DEV_CAP_1_PWRROLE_MASK) {
	case TCPC_REG_DEV_CAP_1_PWRROLE_SRC_OR_SNK:
	case TCPC_REG_DEV_CAP_1_PWRROLE_SNK:
	case TCPC_REG_DEV_CAP_1_PWRROLE_SNK_ACC:
		tcpci_regs[TCPC_REG_ROLE_CTRL].value =		0x0A;
		tcpci_regs[TCPC_REG_MSG_HDR_INFO].value =	0x04;
		break;

	case TCPC_REG_DEV_CAP_1_PWRROLE_DRP:
		if (dead_battery())
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x0A;
		else if (debug_accessory_indicator_supported())
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x4A;
		else
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x0F;
		tcpci_regs[TCPC_REG_MSG_HDR_INFO].value =	0x04;
		break;

	case TCPC_REG_DEV_CAP_1_PWRROLE_SRC:
		if (!dead_battery())
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x05;
		tcpci_regs[TCPC_REG_MSG_HDR_INFO].value =	0x0D;
		break;

	case TCPC_REG_DEV_CAP_1_PWRROLE_SRC_SNK_DRP_ADPT_CBL:
	case TCPC_REG_DEV_CAP_1_PWRROLE_SRC_SNK_DRP:
		if (dead_battery())
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x0A;
		else if (debug_accessory_indicator_supported())
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x4A;
		else
			tcpci_regs[TCPC_REG_ROLE_CTRL].value =	0x0F;
		tcpci_regs[TCPC_REG_MSG_HDR_INFO].value =	0x04;
		break;
	}
}
/*****************************************************************************/

void mock_tcpci_reset(void)
{
	tcpci_reset_register_defaults();
}

void mock_tcpci_set_reg(int reg_offset, uint16_t value)
{
	struct tcpci_reg *reg = tcpci_regs + reg_offset;

	reg->value = value;
	ccprints("TCPCI mock set %s = 0x%x",  reg->name, reg->value);
}

void mock_tcpci_set_reg_bits(int reg_offset, uint16_t mask)
{
	struct tcpci_reg *reg = tcpci_regs + reg_offset;
	uint16_t old_value = reg->value;

	reg->value |= mask;
	ccprints("TCPCI mock set bits %s (mask=0x%x) = 0x%x -> 0x%x",
		 reg->name, mask, old_value, reg->value);
}

void mock_tcpci_clr_reg_bits(int reg_offset, uint16_t mask)
{
	struct tcpci_reg *reg = tcpci_regs + reg_offset;
	uint16_t old_value = reg->value;

	reg->value &= ~mask;
	ccprints("TCPCI mock clr bits %s (mask=0x%x) = 0x%x -> 0x%x",
		 reg->name, mask, old_value, reg->value);
}

uint16_t mock_tcpci_get_reg(int reg_offset)
{
	return tcpci_regs[reg_offset].value;
}

int tcpci_i2c_xfer(int port, uint16_t addr_flags,
		const uint8_t *out, int out_size,
		uint8_t *in, int in_size, int flags)
{
	struct tcpci_reg *reg;

	if (port != I2C_PORT_HOST_TCPC) {
		ccprints("ERROR: wrong I2C port %d", port);
		return EC_ERROR_UNKNOWN;
	}
	if (addr_flags != MOCK_TCPCI_I2C_ADDR_FLAGS) {
		ccprints("ERROR: wrong I2C address 0x%x", addr_flags);
		return EC_ERROR_UNKNOWN;
	}

	if (rx_pos > 0) {
		if (rx_pos + in_size > rx_buffer[0] + 1) {
			ccprints("ERROR: rx in_size");
			return EC_ERROR_UNKNOWN;
		}
		memcpy(in, rx_buffer + rx_pos, in_size);
		rx_pos	+= in_size;
		if (rx_pos == rx_buffer[0] + 1) {
			print_header("RX", UINT16_FROM_BYTE_ARRAY_LE(
						rx_buffer, 2));
			rx_pos = -1;
		}
		return EC_SUCCESS;
	}

	if (out_size == 0) {
		ccprints("ERROR: out_size == 0");
		return EC_ERROR_UNKNOWN;
	}
	if (tx_pos != -1) {
		if (tx_pos + out_size > BUFFER_SIZE) {
			ccprints("ERROR: tx out_size");
			return EC_ERROR_UNKNOWN;
		}
		memcpy(tx_buffer + tx_pos, out, out_size);
		tx_pos += out_size;
		tx_msg_cnt = tx_pos;
		if (tx_pos > 0 && tx_pos == tx_buffer[0] + 1) {
			print_header("TX", UINT16_FROM_BYTE_ARRAY_LE(
						tx_buffer, 1));
			tx_pos = -1;
			tx_retry_cnt = -1;
		}
		return EC_SUCCESS;
	}
	reg = tcpci_regs + *out;
	if (*out >= ARRAY_SIZE(tcpci_regs) || reg->size == 0) {
		ccprints("ERROR: unknown reg 0x%x", *out);
		return EC_ERROR_UNKNOWN;
	}
	if (reg->offset == TCPC_REG_TX_BUFFER) {
		if (tx_pos != -1) {
			ccprints("ERROR: TCPC_REG_TX_BUFFER not ready");
			return EC_ERROR_UNKNOWN;
		}
		tx_pos = 0;
		tx_msg_cnt = 0;
		if (out_size != 1) {
			ccprints("ERROR: TCPC_REG_TX_BUFFER out_size != 1");
			return EC_ERROR_UNKNOWN;
		}
	} else if (reg->offset == TCPC_REG_RX_BUFFER) {
		if (rx_pos != 0) {
			ccprints("ERROR: TCPC_REG_RX_BUFFER not ready");
			return EC_ERROR_UNKNOWN;
		}
		if (in_size > BUFFER_SIZE || in_size > rx_buffer[0]) {
			ccprints("ERROR: TCPC_REG_RX_BUFFER in_size");
			return EC_ERROR_UNKNOWN;
		}
		memcpy(in, rx_buffer, in_size);
		rx_pos += in_size;
	} else if (out_size == 1) {
		if (in_size != reg->size) {
			ccprints("ERROR: %s in_size %d != %d", reg->name,
				 in_size, reg->size);
			return EC_ERROR_UNKNOWN;
		}
		if (reg->size == 1)
			in[0] = reg->value;
		else if (reg->size == 2) {
			in[0] = reg->value;
			in[1] = reg->value >> 8;
		}
	} else {
		uint16_t value = 0;

		if (in_size != 0) {
			ccprints("ERROR: in_size != 0");
			return EC_ERROR_UNKNOWN;
		}
		if (out_size != reg->size + 1) {
			ccprints("ERROR: out_size != %d", reg->size + 1);
			return EC_ERROR_UNKNOWN;
		}
		if (reg->size == 1)
			value = out[1];
		else if (reg->size == 2)
			value = out[1] + (out[2] << 8);
		ccprints("%s TCPCI write %s = 0x%x",
			 task_get_name(task_get_current()),
			 reg->name, value);
		if (reg->offset == TCPC_REG_ALERT)
			reg->value &= ~value;
		else
			reg->value = value;
	}
	return EC_SUCCESS;
}
DECLARE_TEST_I2C_XFER(tcpci_i2c_xfer);

void tcpci_register_dump(void)
{
	int reg;
	int cc1, cc2;

	ccprints("********* TCPCI Register Dump ***********");
	reg = mock_tcpci_get_reg(TCPC_REG_ALERT);
	ccprints("TCPC_REG_ALERT        = 0x%08X", reg);
	if (reg) {
		if (reg & BIT(0))
			ccprints("\t0001: CC Status");
		if (reg & BIT(1))
			ccprints("\t0002: Power Status");
		if (reg & BIT(2))
			ccprints("\t0004: Received SOP* Message Status");
		if (reg & BIT(3))
			ccprints("\t0008: Received Hard Reset");
		if (reg & BIT(4))
			ccprints("\t0010: Transmit SOP* Message Failed");
		if (reg & BIT(5))
			ccprints("\t0020: Transmit SOP* Message Discarded");
		if (reg & BIT(6))
			ccprints("\t0040: Transmit SOP* Message Successful");
		if (reg & BIT(7))
			ccprints("\t0080: Vbus Voltage Alarm Hi");
		if (reg & BIT(8))
			ccprints("\t0100: Vbus Voltage Alarm Lo");
		if (reg & BIT(9))
			ccprints("\t0200: Fault");
		if (reg & BIT(10))
			ccprints("\t0400: Rx Buffer Overflow");
		if (reg & BIT(11))
			ccprints("\t0800: Vbus Sink Disconnect Detected");
		if (reg & BIT(12))
			ccprints("\t1000: Beginning SOP* Message Status");
		if (reg & BIT(13))
			ccprints("\t2000: Extended Status");
		if (reg & BIT(14))
			ccprints("\t4000: Alert Extended");
		if (reg & BIT(15))
			ccprints("\t8000: Vendor Defined Alert");
	}

	reg = mock_tcpci_get_reg(TCPC_REG_TCPC_CTRL);
	ccprints("TCPC_REG_TCPC_CTRL    = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: Plug Orientation FLIP");
	if (reg & BIT(1))
		ccprints("\t02: BIST Test Mode");
	if (reg & (BIT(2) | BIT(3))) {
		switch ((reg >> 2) & 3) {
		case 2:
			ccprints("\t08: Enable Clock Stretching");
			break;
		case 3:
			ccprints("\t0C: Enable Clock Stretching if !Alert");
			break;
		}
	}
	if (reg & BIT(4))
		ccprints("\t10: Debug Accessory controlled by TCPM");
	if (reg & BIT(5))
		ccprints("\t20: Watchdog Timer enabled");
	if (reg & BIT(6))
		ccprints("\t40: Looking4Connection Alert enabled");
	if (reg & BIT(7))
		ccprints("\t80: SMBus PEC enabled");

	reg = mock_tcpci_get_reg(TCPC_REG_ROLE_CTRL);
	ccprints("TCPC_REG_ROLE_CTRL    = 0x%04X", reg);
	cc1 = (reg >> 0) & 3;
	switch (cc1) {
	case 0:
		ccprints("\t00: CC1 == Ra");
		break;
	case 1:
		ccprints("\t01: CC1 == Rp");
		break;
	case 2:
		ccprints("\t02: CC1 == Rd");
		break;
	case 3:
		ccprints("\t03: CC1 == OPEN");
		break;
	}
	cc2 = (reg >> 2) & 3;
	switch (cc2) {
	case 0:
		ccprints("\t00: CC2 == Ra");
		break;
	case 1:
		ccprints("\t04: CC2 == Rp");
		break;
	case 2:
		ccprints("\t08: CC2 == Rd");
		break;
	case 3:
		ccprints("\t0C: CC2 == OPEN");
		break;
	}
	switch ((reg >> 4) & 3) {
	case 0:
		ccprints("\t00: Rp Value == default");
		break;
	case 1:
		ccprints("\t10: Rp Value == 1.5A");
		break;
	case 2:
		ccprints("\t20: Rp Value == 3A");
		break;
	}
	if (reg & BIT(6))
		ccprints("\t40: DRP");

	reg = mock_tcpci_get_reg(TCPC_REG_FAULT_CTRL);
	ccprints("TCPC_REG_FAULT_CTRL   = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: Vconn Over Current Fault");
	if (reg & BIT(1))
		ccprints("\t02: Vbus OVP Fault");
	if (reg & BIT(2))
		ccprints("\t04: Vbus OCP Fault");
	if (reg & BIT(3))
		ccprints("\t08: Vbus Discharge Fault");
	if (reg & BIT(4))
		ccprints("\t10: Force OFF Vbus");

	reg = mock_tcpci_get_reg(TCPC_REG_POWER_CTRL);
	ccprints("TCPC_REG_POWER_CTRL   = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: Enable Vconn");
	if (reg & BIT(1))
		ccprints("\t02: Vconn Power Supported");
	if (reg & BIT(2))
		ccprints("\t04: Force Discharge");
	if (reg & BIT(3))
		ccprints("\t08: Enable Bleed Discharge");
	if (reg & BIT(4))
		ccprints("\t10: Auto Discharge Disconnect");
	if (reg & BIT(5))
		ccprints("\t20: Disable Voltage Alarms");
	if (reg & BIT(6))
		ccprints("\t40: VBUS_VOLTAGE monitor disabled");
	if (reg & BIT(7))
		ccprints("\t80: Fast Role Swap enabled");

	reg = mock_tcpci_get_reg(TCPC_REG_CC_STATUS);
	ccprints("TCPC_REG_CC_STATUS    = 0x%04X", reg);
	switch ((reg >> 0) & 3) {
	case 0:
		switch (cc1) {
		case 1:
			ccprints("\t00: CC1-Rp SRC.Open");
			break;
		case 2:
			ccprints("\t00: CC1-Rd SNK.Open");
			break;
		}
		break;
	case 1:
		switch (cc1) {
		case 1:
			ccprints("\t01: CC1-Rp SRC.Ra");
			break;
		case 2:
			ccprints("\t01: CC1-Rd SNK.Default");
			break;
		}
		break;
	case 2:
		switch (cc1) {
		case 1:
			ccprints("\t02: CC1-Rp SRC.Rd");
			break;
		case 2:
			ccprints("\t02: CC1-Rd SNK.Power1.5");
			break;
		}
		break;
	case 3:
		switch (cc1) {
		case 2:
			ccprints("\t03: CC1-Rd SNK.Power3.0");
			break;
		}
		break;
	}
	switch ((reg >> 2) & 3) {
	case 0:
		switch (cc2) {
		case 1:
			ccprints("\t00: CC2-Rp SRC.Open");
			break;
		case 2:
			ccprints("\t00: CC2-Rd SNK.Open");
			break;
		}
		break;
	case 1:
		switch (cc2) {
		case 1:
			ccprints("\t04: CC2-Rp SRC.Ra");
			break;
		case 2:
			ccprints("\t04: CC2-Rd SNK.Default");
			break;
		}
		break;
	case 2:
		switch (cc2) {
		case 1:
			ccprints("\t08: CC2-Rp SRC.Rd");
			break;
		case 2:
			ccprints("\t08: CC2-Rd SNK.Power1.5");
			break;
		}
		break;
	case 3:
		switch (cc2) {
		case 2:
			ccprints("\t0C: CC2-Rd SNK.Power3.0");
			break;
		}
		break;
	}
	if (reg & BIT(4))
		ccprints("\t10: Presenting Rd");
	else
		ccprints("\t00: Presenting Rp");
	if (reg & BIT(5))
		ccprints("\t20: Looking4Connection");

	reg = mock_tcpci_get_reg(TCPC_REG_POWER_STATUS);
	ccprints("TCPC_REG_POWER_STATUS = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: Sinking Vbus");
	if (reg & BIT(1))
		ccprints("\t02: Vconn Present");
	if (reg & BIT(2))
		ccprints("\t04: Vbus Present");
	if (reg & BIT(3))
		ccprints("\t08: Vbus Detect enabled");
	if (reg & BIT(4))
		ccprints("\t10: Sourcing Vbus");
	if (reg & BIT(5))
		ccprints("\t20: Sourcing non-default voltage");
	if (reg & BIT(6))
		ccprints("\t40: TCPC Initialization");
	if (reg & BIT(7))
		ccprints("\t80: Debug Accessory Connected");

	reg = mock_tcpci_get_reg(TCPC_REG_FAULT_STATUS);
	ccprints("TCPC_REG_FAULT_STATUS = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: I2C Interface Error");
	if (reg & BIT(1))
		ccprints("\t02: Vconn Over Current Fault");
	if (reg & BIT(2))
		ccprints("\t04: Vbus OVP Fault");
	if (reg & BIT(3))
		ccprints("\t08: Vbus OCP Fault");
	if (reg & BIT(4))
		ccprints("\t10: Forced Discharge Failed");
	if (reg & BIT(5))
		ccprints("\t20: Auto Discharge Failed");
	if (reg & BIT(6))
		ccprints("\t40: Force OFF Vbus");
	if (reg & BIT(7))
		ccprints("\t80: TCPCI Registers Reset2Default");

	reg = mock_tcpci_get_reg(TCPC_REG_EXT_STATUS);
	ccprints("TCPC_REG_EXT_STATUS   = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: Vbus is at vSafe0V");

	reg = mock_tcpci_get_reg(TCPC_REG_ALERT_EXT);
	ccprints("TCPC_REG_ALERT_EXT    = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: SNK Fast Role Swap");
	if (reg & BIT(1))
		ccprints("\t02: SRC Fast Role Swap");
	if (reg & BIT(2))
		ccprints("\t04: Timer Expired");

	reg = mock_tcpci_get_reg(TCPC_REG_COMMAND);
	ccprints("TCPC_REG_COMMAND      = 0x%04X", reg);
	switch (reg) {
	case 0x11:
		ccprints("\t11: WakeI2C");
		break;
	case 0x22:
		ccprints("\t22: DisableVbusDetect");
		break;
	case 0x33:
		ccprints("\t33: EnableVbusDetect");
		break;
	case 0x44:
		ccprints("\t44: DisableSinkVbus");
		break;
	case 0x55:
		ccprints("\t55: SinkVbus");
		break;
	case 0x66:
		ccprints("\t66: DisableSourceVbus");
		break;
	case 0x77:
		ccprints("\t77: SourceVbusDefaultVoltage");
		break;
	case 0x88:
		ccprints("\t88: SourceVbusNondefaultVoltage");
		break;
	case 0x99:
		ccprints("\t99: Looking4Connection");
		break;
	case 0xAA:
		ccprints("\tAA: RxOneMore");
		break;
	case 0xCC:
		ccprints("\tCC: SendFRSwapSignal");
		break;
	case 0xDD:
		ccprints("\tDD: ResetTransmitBuffer");
		break;
	case 0xEE:
		ccprints("\tEE: ResetReceiveBuffer");
		break;
	case 0xFF:
		ccprints("\tFF: I2C Idle");
		break;
	}

	reg = mock_tcpci_get_reg(TCPC_REG_MSG_HDR_INFO);
	ccprints("TCPC_REG_MSG_HDR_INFO = 0x%04X", reg);
	if (reg & BIT(0))
		ccprints("\t01: Power Role SRC");
	else
		ccprints("\t00: Power Role SNK");
	switch ((reg >> 1) & 3) {
	case 0:
		ccprints("\t00: PD Revision 1.0");
		break;
	case 1:
		ccprints("\t02: PD Revision 2.0");
		break;
	case 2:
		ccprints("\t04: PD Revision 3.0");
		break;
	}
	if (reg & BIT(3))
		ccprints("\t08: Data Role DFP");
	else
		ccprints("\t00: Data Role UFP");
	if (reg & BIT(4))
		ccprints("\t10: Message originating from Cable Plug");
	else
		ccprints("\t00: Message originating from SRC/SNK/DRP");

	reg = mock_tcpci_get_reg(TCPC_REG_RX_BUFFER);
	ccprints("TCPC_REG_RX_BUFFER    = 0x%04X", reg);

	reg = mock_tcpci_get_reg(TCPC_REG_TRANSMIT);
	ccprints("TCPC_REG_TRANSMIT     = 0x%04X", reg);
	ccprints("*****************************************");
}
