/* Copyright 2019 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 "battery.h"
#include "battery_smart.h"
#include "board.h"
#include "charge_manager.h"
#include "charge_state.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "cros_version.h"
#include "ec_commands.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "tcpm/tcpm.h"
#include "util.h"
#include "usb_charge.h"
#include "usb_mux.h"
#include "usb_pd.h"
#include "usb_pd_timer.h"
#include "usb_pe_sm.h"
#include "usb_prl_sm.h"
#include "usb_tc_sm.h"
#include "usb_emsg.h"
#include "usb_sm.h"
#include "vpd_api.h"

#ifdef CONFIG_COMMON_RUNTIME
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
#else
#define CPRINTF(format, args...)
#define CPRINTS(format, args...)
#endif

/*
 * Define DEBUG_PRINT_FLAG_NAMES to print flag names when set and cleared.
 */
#undef DEBUG_PRINT_FLAG_NAMES

#ifdef DEBUG_PRINT_FLAG_NAMES
__maybe_unused static void print_flag(const char *group,
				      int set_or_clear,
				      int flag);
#define SET_FLAG(group, flags, flag)                     \
	do {                                             \
		print_flag(group, 1, flag);              \
		atomic_or(flags, (flag));                \
	} while (0)
#define CLR_FLAG(group, flags, flag)                     \
	do {                                             \
		int before = *flags;                     \
		atomic_clear_bits(flags, (flag));        \
		if (*flags != before)                    \
			print_flag(group, 0, flag);      \
	} while (0)
#else
#define SET_FLAG(group, flags, flag) atomic_or(flags, (flag))
#define CLR_FLAG(group, flags, flag) atomic_clear_bits(flags, (flag))
#endif


#define RCH_SET_FLAG(port, flag) SET_FLAG("RCH", &rch[port].flags, (flag))
#define RCH_CLR_FLAG(port, flag) CLR_FLAG("RCH", &rch[port].flags, (flag))
#define RCH_CHK_FLAG(port, flag) (rch[port].flags & (flag))

#define TCH_SET_FLAG(port, flag) SET_FLAG("TCH", &tch[port].flags, (flag))
#define TCH_CLR_FLAG(port, flag) CLR_FLAG("TCH", &tch[port].flags, (flag))
#define TCH_CHK_FLAG(port, flag) (tch[port].flags & (flag))

#define PRL_TX_SET_FLAG(port, flag) \
	SET_FLAG("PRL_TX", &prl_tx[port].flags, (flag))
#define PRL_TX_CLR_FLAG(port, flag) \
	CLR_FLAG("PRL_TX", &prl_tx[port].flags, (flag))
#define PRL_TX_CHK_FLAG(port, flag) (prl_tx[port].flags & (flag))

#define PRL_HR_SET_FLAG(port, flag) \
	SET_FLAG("PRL_HR", &prl_hr[port].flags, (flag))
#define PRL_HR_CLR_FLAG(port, flag) \
	CLR_FLAG("PRL_HR", &prl_hr[port].flags, (flag))
#define PRL_HR_CHK_FLAG(port, flag) (prl_hr[port].flags & (flag))

#define PDMSG_SET_FLAG(port, flag) SET_FLAG("PDMSG", &pdmsg[port].flags, (flag))
#define PDMSG_CLR_FLAG(port, flag) CLR_FLAG("PDMSG", &pdmsg[port].flags, (flag))
#define PDMSG_CHK_FLAG(port, flag) (pdmsg[port].flags & (flag))

/* Protocol Layer Flags */
/*
 * NOTE:
 *	These flags are used in multiple state machines and could have
 *	different meanings in each state machine.
 */
/* Flag to note message transmission completed */
#define PRL_FLAGS_TX_COMPLETE             BIT(0)
/* Flag to note that PRL requested to set SINK_NG CC state */
#define PRL_FLAGS_SINK_NG                 BIT(1)
/* Flag to note PRL waited for SINK_OK CC state before transmitting */
#define PRL_FLAGS_WAIT_SINK_OK            BIT(2)
/* Flag to note transmission error occurred */
#define PRL_FLAGS_TX_ERROR                BIT(3)
/* Flag to note PE triggered a hard reset */
#define PRL_FLAGS_PE_HARD_RESET           BIT(4)
/* Flag to note hard reset has completed */
#define PRL_FLAGS_HARD_RESET_COMPLETE     BIT(5)
/* Flag to note port partner sent a hard reset */
#define PRL_FLAGS_PORT_PARTNER_HARD_RESET BIT(6)
/*
 * Flag to note a message transmission has been requested. It is only cleared
 * when we send the message to the TCPC layer.
 */
#define PRL_FLAGS_MSG_XMIT                BIT(7)
/* Flag to note a message was received */
#define PRL_FLAGS_MSG_RECEIVED            BIT(8)
/* Flag to note aborting current TX message, not currently set */
#define PRL_FLAGS_ABORT                   BIT(9)
/* Flag to note current TX message uses chunking */
#define PRL_FLAGS_CHUNKING                BIT(10)

struct bit_name {
	int		value;
	const char	*name;
};

static __const_data const struct bit_name flag_bit_names[] = {
	{ PRL_FLAGS_TX_COMPLETE, "PRL_FLAGS_TX_COMPLETE" },
	{ PRL_FLAGS_SINK_NG, "PRL_FLAGS_SINK_NG" },
	{ PRL_FLAGS_WAIT_SINK_OK, "PRL_FLAGS_WAIT_SINK_OK" },
	{ PRL_FLAGS_TX_ERROR, "PRL_FLAGS_TX_ERROR" },
	{ PRL_FLAGS_PE_HARD_RESET, "PRL_FLAGS_PE_HARD_RESET" },
	{ PRL_FLAGS_HARD_RESET_COMPLETE, "PRL_FLAGS_HARD_RESET_COMPLETE" },
	{ PRL_FLAGS_PORT_PARTNER_HARD_RESET,
		"PRL_FLAGS_PORT_PARTNER_HARD_RESET" },
	{ PRL_FLAGS_MSG_XMIT, "PRL_FLAGS_MSG_XMIT" },
	{ PRL_FLAGS_MSG_RECEIVED, "PRL_FLAGS_MSG_RECEIVED" },
	{ PRL_FLAGS_ABORT, "PRL_FLAGS_ABORT" },
	{ PRL_FLAGS_CHUNKING, "PRL_FLAGS_CHUNKING" },
};

__maybe_unused static void print_bits(const char *group,
				      const char *desc,
				      int value,
				      const struct bit_name *names,
				      int names_size)
{
	int i;

	CPRINTF("%s %s 0x%x : ", group, desc, value);
	for (i = 0; i < names_size; i++) {
		if (value & names[i].value)
			CPRINTF("%s | ", names[i].name);
		value &= ~names[i].value;
	}
	if (value != 0)
		CPRINTF("0x%x", value);
	CPRINTF("\n");
}

__maybe_unused static void print_flag(const char *group,
				      int set_or_clear,
				      int flag)
{
	print_bits(group, set_or_clear ? "Set" : "Clr", flag, flag_bit_names,
		   ARRAY_SIZE(flag_bit_names));
}

/* PD counter definitions */
#define PD_MESSAGE_ID_COUNT 7

/* Size of PDMSG Chunk Buffer */
#define CHK_BUF_SIZE 7
#define CHK_BUF_SIZE_BYTES 28

/*
 * Debug log level - higher number == more log
 *   Level 0: disabled
 *   Level 1: not currently used
 *   Level 2: plus non-ping messages
 *   Level 3: plus ping packet and PRL states
 *
 * Note that higher log level causes timing changes and thus may affect
 * performance.
 */
#ifdef CONFIG_USB_PD_DEBUG_LEVEL
static const enum debug_level prl_debug_level = CONFIG_USB_PD_DEBUG_LEVEL;
#else
static enum debug_level prl_debug_level = DEBUG_LEVEL_1;
#endif

static enum sm_local_state local_state[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Protocol Transmit States (Section 6.11.2.2) */
enum usb_prl_tx_state {
	PRL_TX_PHY_LAYER_RESET,
	PRL_TX_WAIT_FOR_MESSAGE_REQUEST,
	PRL_TX_LAYER_RESET_FOR_TRANSMIT,
	PRL_TX_WAIT_FOR_PHY_RESPONSE,
	PRL_TX_SRC_SOURCE_TX,
	PRL_TX_SNK_START_AMS,
	PRL_TX_SRC_PENDING,
	PRL_TX_SNK_PENDING,
	PRL_TX_DISCARD_MESSAGE,
};

/* Protocol Hard Reset States (Section 6.11.2.4) */
enum usb_prl_hr_state {
	PRL_HR_WAIT_FOR_REQUEST,
	PRL_HR_RESET_LAYER,
	PRL_HR_WAIT_FOR_PHY_HARD_RESET_COMPLETE,
	PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE,
};

/* Chunked Rx states (Section 6.11.2.1.2) */
enum usb_rch_state {
	RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER,
	RCH_PASS_UP_MESSAGE,
	RCH_PROCESSING_EXTENDED_MESSAGE,
	RCH_REQUESTING_CHUNK,
	RCH_WAITING_CHUNK,
	RCH_REPORT_ERROR,
};

/* Chunked Tx states (Section 6.11.2.1.3) */
enum usb_tch_state {
	TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE,
	TCH_WAIT_FOR_TRANSMISSION_COMPLETE,
	TCH_CONSTRUCT_CHUNKED_MESSAGE,
	TCH_SENDING_CHUNKED_MESSAGE,
	TCH_WAIT_CHUNK_REQUEST,
	TCH_MESSAGE_RECEIVED,
	TCH_MESSAGE_SENT,
	TCH_REPORT_ERROR,
};

static const char * const prl_tx_state_names[] = {
	[PRL_TX_PHY_LAYER_RESET] = "PRL_TX_PHY_LAYER_RESET",
	[PRL_TX_WAIT_FOR_MESSAGE_REQUEST] = "PRL_TX_WAIT_FOR_MESSAGE_REQUEST",
	[PRL_TX_LAYER_RESET_FOR_TRANSMIT] = "PRL_TX_LAYER_RESET_FOR_TRANSMIT",
	[PRL_TX_WAIT_FOR_PHY_RESPONSE] = "PRL_TX_WAIT_FOR_PHY_RESPONSE",
	[PRL_TX_SRC_SOURCE_TX] = "PRL_TX_SRC_SOURCE_TX",
	[PRL_TX_SNK_START_AMS] = "PRL_TX_SNK_START_AMS",
	[PRL_TX_SRC_PENDING] = "PRL_TX_SRC_PENDING",
	[PRL_TX_SNK_PENDING] = "PRL_TX_SNK_PENDING",
	[PRL_TX_DISCARD_MESSAGE] = "PRL_TX_DISCARD_MESSAGE",
};

static const char * const prl_hr_state_names[] = {
	[PRL_HR_WAIT_FOR_REQUEST] = "PRL_HR_WAIT_FOR_REQUEST",
	[PRL_HR_RESET_LAYER] = "PRL_HR_RESET_LAYER",
	[PRL_HR_WAIT_FOR_PHY_HARD_RESET_COMPLETE]
		= "PRL_HR_WAIT_FOR_PHY_HARD_RESET_COMPLETE",
	[PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE]
		= "PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE",
};

__maybe_unused static const char * const rch_state_names[] = {
	[RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER]
		= "RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER",
	[RCH_PASS_UP_MESSAGE] = "RCH_PASS_UP_MESSAGE",
	[RCH_PROCESSING_EXTENDED_MESSAGE] = "RCH_PROCESSING_EXTENDED_MESSAGE",
	[RCH_REQUESTING_CHUNK] = "RCH_REQUESTING_CHUNK",
	[RCH_WAITING_CHUNK] = "RCH_WAITING_CHUNK",
	[RCH_REPORT_ERROR] = "RCH_REPORT_ERROR",
};

__maybe_unused static const char * const tch_state_names[] = {
	[TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE]
		= "TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE",
	[TCH_WAIT_FOR_TRANSMISSION_COMPLETE]
		= "TCH_WAIT_FOR_TRANSMISSION_COMPLETE",
	[TCH_CONSTRUCT_CHUNKED_MESSAGE] = "TCH_CONSTRUCT_CHUNKED_MESSAGE",
	[TCH_SENDING_CHUNKED_MESSAGE] = "TCH_SENDING_CHUNKED_MESSAGE",
	[TCH_WAIT_CHUNK_REQUEST] = "TCH_WAIT_CHUNK_REQUEST",
	[TCH_MESSAGE_RECEIVED] = "TCH_MESSAGE_RECEIVED",
	[TCH_MESSAGE_SENT] = "TCH_MESSAGE_SENT",
	[TCH_REPORT_ERROR] = "TCH_REPORT_ERROR",
};

/* Forward declare full list of states. Index by above enums. */
static const struct usb_state prl_tx_states[];
static const struct usb_state prl_hr_states[];

__maybe_unused static const struct usb_state rch_states[];
__maybe_unused static const struct usb_state tch_states[];

/* Chunked Rx State Machine Object */
static struct rx_chunked {
	/* state machine context */
	struct sm_ctx ctx;
	/* PRL_FLAGS */
	atomic_t flags;
	/* error to report when moving to rch_report_error state */
	enum pe_error error;
} rch[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Chunked Tx State Machine Object */
static struct tx_chunked {
	/* state machine context */
	struct sm_ctx ctx;
	/* state machine flags */
	atomic_t flags;
	/* error to report when moving to tch_report_error state */
	enum pe_error error;
} tch[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Message Reception State Machine Object */
static struct protocol_layer_rx {
	/* received message type */
	enum tcpci_msg_type sop;
	/* message ids for all valid port partners */
	int msg_id[NUM_SOP_STAR_TYPES];
} prl_rx[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Message Transmission State Machine Object */
static struct protocol_layer_tx {
	/* state machine context */
	struct sm_ctx ctx;
	/* state machine flags */
	atomic_t flags;
	/* last message type we transmitted */
	enum tcpci_msg_type last_xmit_type;
	/* message id counters for all 6 port partners */
	uint32_t msg_id_counter[NUM_SOP_STAR_TYPES];
	/* transmit status */
	int xmit_status;
} prl_tx[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Hard Reset State Machine Object */
static struct protocol_hard_reset {
	/* state machine context */
	struct sm_ctx ctx;
	/* state machine flags */
	atomic_t flags;
} prl_hr[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Chunking Message Object */
static struct pd_message {
	/* message status flags */
	atomic_t flags;
	/* SOP* */
	enum tcpci_msg_type xmit_type;
	/* type of message */
	uint8_t msg_type;
	/* PD revision */
	enum pd_rev_type rev[NUM_SOP_STAR_TYPES];
	/* Number of 32-bit objects in chk_buf */
	uint16_t data_objs;
	/* temp chunk buffer */
	uint32_t tx_chk_buf[CHK_BUF_SIZE];
	uint32_t rx_chk_buf[CHK_BUF_SIZE];
	uint32_t chunk_number_expected;
	uint32_t num_bytes_received;
#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	/* extended message */
	uint8_t ext;
	uint32_t chunk_number_to_send;
	uint32_t send_offset;
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */
} pdmsg[CONFIG_USB_PD_PORT_MAX_COUNT];

struct extended_msg rx_emsg[CONFIG_USB_PD_PORT_MAX_COUNT];
struct extended_msg tx_emsg[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Common Protocol Layer Message Transmission */
static void prl_tx_construct_message(int port);
static void prl_rx_wait_for_phy_message(const int port, int evt);
static void prl_copy_msg_to_buffer(int port);

#ifndef CONFIG_USB_PD_REV30
GEN_NOT_SUPPORTED(PRL_TX_SRC_SOURCE_TX);
#define PRL_TX_SRC_SOURCE_TX PRL_TX_SRC_SOURCE_TX_NOT_SUPPORTED
GEN_NOT_SUPPORTED(PRL_TX_SNK_START_AMS);
#define PRL_TX_SNK_START_AMS PRL_TX_SNK_START_AMS_NOT_SUPPORTED

GEN_NOT_SUPPORTED(RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);
#define RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER \
		RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER_NOT_SUPPORTED
GEN_NOT_SUPPORTED(RCH_PASS_UP_MESSAGE);
#define RCH_PASS_UP_MESSAGE RCH_PASS_UP_MESSAGE_NOT_SUPPORTED
GEN_NOT_SUPPORTED(RCH_PROCESSING_EXTENDED_MESSAGE);
#define RCH_PROCESSING_EXTENDED_MESSAGE \
		RCH_PROCESSING_EXTENDED_MESSAGE_NOT_SUPPORTED
GEN_NOT_SUPPORTED(RCH_REQUESTING_CHUNK);
#define RCH_REQUESTING_CHUNK RCH_REQUESTING_CHUNK_NOT_SUPPORTED
GEN_NOT_SUPPORTED(RCH_WAITING_CHUNK);
#define RCH_WAITING_CHUNK RCH_WAITING_CHUNK_NOT_SUPPORTED
GEN_NOT_SUPPORTED(RCH_REPORT_ERROR);
#define RCH_REPORT_ERROR RCH_REPORT_ERROR_NOT_SUPPORTED

GEN_NOT_SUPPORTED(TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
#define TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE \
		TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_WAIT_FOR_TRANSMISSION_COMPLETE);
#define TCH_WAIT_FOR_TRANSMISSION_COMPLETE \
		TCH_WAIT_FOR_TRANSMISSION_COMPLETE_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_CONSTRUCT_CHUNKED_MESSAGE);
#define TCH_CONSTRUCT_CHUNKED_MESSAGE \
		TCH_CONSTRUCT_CHUNKED_MESSAGE_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_SENDING_CHUNKED_MESSAGE);
#define TCH_SENDING_CHUNKED_MESSAGE TCH_SENDING_CHUNKED_MESSAGE_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_WAIT_CHUNK_REQUEST);
#define TCH_WAIT_CHUNK_REQUEST TCH_WAIT_CHUNK_REQUEST_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_MESSAGE_RECEIVED);
#define TCH_MESSAGE_RECEIVED TCH_MESSAGE_RECEIVED_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_MESSAGE_SENT);
#define TCH_MESSAGE_SENT TCH_MESSAGE_SENT_NOT_SUPPORTED
GEN_NOT_SUPPORTED(TCH_REPORT_ERROR);
#define TCH_REPORT_ERROR TCH_REPORT_ERROR_NOT_SUPPORTED
#endif /* !CONFIG_USB_PD_REV30 */

/* To store the time stamp when TCPC sets TX Complete Success */
static timestamp_t tcpc_tx_success_ts[CONFIG_USB_PD_PORT_MAX_COUNT];

/* Set the protocol transmit statemachine to a new state. */
static void set_state_prl_tx(const int port,
			     const enum usb_prl_tx_state new_state)
{
	set_state(port, &prl_tx[port].ctx, &prl_tx_states[new_state]);
}

/* Get the protocol transmit statemachine's current state. */
test_export_static enum usb_prl_tx_state prl_tx_get_state(const int port)
{
	return prl_tx[port].ctx.current - &prl_tx_states[0];
}

/* Print the protocol transmit statemachine's current state. */
static void print_current_prl_tx_state(const int port)
{
	if (prl_debug_level >= DEBUG_LEVEL_3)
		CPRINTS("C%d: %s", port,
				prl_tx_state_names[prl_tx_get_state(port)]);
}

/* Set the hard reset statemachine to a new state. */
static void set_state_prl_hr(const int port,
			     const enum usb_prl_hr_state new_state)
{
	set_state(port, &prl_hr[port].ctx, &prl_hr_states[new_state]);
}

/* Get the hard reset statemachine's current state. */
enum usb_prl_hr_state prl_hr_get_state(const int port)
{
	return prl_hr[port].ctx.current - &prl_hr_states[0];
}

/* Print the hard reset statemachine's current state. */
static void print_current_prl_hr_state(const int port)
{
	if (prl_debug_level >= DEBUG_LEVEL_3)
		CPRINTS("C%d: %s", port,
				prl_hr_state_names[prl_hr_get_state(port)]);
}

/* Set the chunked Rx statemachine to a new state. */
static void set_state_rch(const int port, const enum usb_rch_state new_state)
{
	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES))
		set_state(port, &rch[port].ctx, &rch_states[new_state]);
}

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
/* Get the chunked Rx statemachine's current state. */
test_export_static enum usb_rch_state rch_get_state(const int port)
{
	return rch[port].ctx.current - &rch_states[0];
}

/* Print the chunked Rx statemachine's current state. */
static void print_current_rch_state(const int port)
{
	if (prl_debug_level >= DEBUG_LEVEL_3)
		CPRINTS("C%d: %s", port,
				rch_state_names[rch_get_state(port)]);
}
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */

/* Set the chunked Tx statemachine to a new state. */
static void set_state_tch(const int port, const enum usb_tch_state new_state)
{
	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES))
		set_state(port, &tch[port].ctx, &tch_states[new_state]);
}

/* Get the chunked Tx statemachine's current state. */
test_export_static enum usb_tch_state tch_get_state(const int port)
{
	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES))
		return tch[port].ctx.current - &tch_states[0];
	else
		return 0;
}

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
/* Print the chunked Tx statemachine's current state. */
static void print_current_tch_state(const int port)
{
	if (prl_debug_level >= DEBUG_LEVEL_3)
		CPRINTS("C%d: %s", port,
				tch_state_names[tch_get_state(port)]);
}
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */


timestamp_t prl_get_tcpc_tx_success_ts(int port)
{
	return tcpc_tx_success_ts[port];
}

/* Sets the time stamp when TCPC reports TX success. */
static void set_tcpc_tx_success_ts(int port)
{
	tcpc_tx_success_ts[port] = get_time();
}

void pd_transmit_complete(int port, int status)
{
	if (status == TCPC_TX_COMPLETE_SUCCESS)
		set_tcpc_tx_success_ts(port);
	prl_tx[port].xmit_status = status;
}

void pd_execute_hard_reset(int port)
{
	/* Only allow async. function calls when state machine is running */
	if (!prl_is_running(port))
		return;

	PRL_HR_SET_FLAG(port, PRL_FLAGS_PORT_PARTNER_HARD_RESET);
	set_state_prl_hr(port, PRL_HR_RESET_LAYER);
	task_wake(PD_PORT_TO_TASK_ID(port));
}

void prl_execute_hard_reset(int port)
{
	/* Only allow async. function calls when state machine is running */
	if (!prl_is_running(port))
		return;

	PRL_HR_SET_FLAG(port, PRL_FLAGS_PE_HARD_RESET);
	set_state_prl_hr(port, PRL_HR_RESET_LAYER);
	task_wake(PD_PORT_TO_TASK_ID(port));
}

int prl_is_running(int port)
{
	return local_state[port] == SM_RUN;
}

static void prl_init(int port)
{
	int i;
	const struct sm_ctx cleared = {};

	/*
	 * flags without PRL_FLAGS_SINK_NG present means we are initially
	 * in SinkTxOK state
	 */
	prl_tx[port].flags = 0;
	if (IS_ENABLED(CONFIG_USB_PD_REV30))
		typec_select_src_collision_rp(port, SINK_TX_OK);
	prl_tx[port].last_xmit_type = TCPCI_MSG_SOP;
	prl_tx[port].xmit_status = TCPC_TX_UNSET;

	if (IS_ENABLED(CONFIG_USB_PD_REV30)) {
		tch[port].flags = 0;
		rch[port].flags = 0;
	}

	pdmsg[port].flags = 0;

	prl_hr[port].flags = 0;

	for (i = 0; i < NUM_SOP_STAR_TYPES; i++) {
		prl_rx[port].msg_id[i] = -1;
		prl_tx[port].msg_id_counter[i] = 0;
	}

	pd_timer_disable_range(port, PR_TIMER_RANGE);

	/* Clear state machines and set initial states */
	prl_tx[port].ctx = cleared;
	set_state_prl_tx(port, PRL_TX_PHY_LAYER_RESET);

	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
		rch[port].ctx = cleared;
		set_state_rch(port, RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);

		tch[port].ctx = cleared;
		set_state_tch(port, TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
	}

	prl_hr[port].ctx = cleared;
	set_state_prl_hr(port, PRL_HR_WAIT_FOR_REQUEST);
}

bool prl_is_busy(int port)
{
#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	return rch_get_state(port) !=
			RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER ||
		tch_get_state(port) !=
			TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE;
#else
	return false;
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */
}

void prl_set_debug_level(enum debug_level debug_level)
{
#ifndef CONFIG_USB_PD_DEBUG_LEVEL
	prl_debug_level = debug_level;
#endif
}

void prl_hard_reset_complete(int port)
{
	PRL_HR_SET_FLAG(port, PRL_FLAGS_HARD_RESET_COMPLETE);
	task_wake(PD_PORT_TO_TASK_ID(port));
}

void prl_send_ctrl_msg(int port,
		      enum tcpci_msg_type type,
		      enum pd_ctrl_msg_type msg)
{
	pdmsg[port].xmit_type = type;
	pdmsg[port].msg_type = msg;
	pdmsg[port].data_objs = 0;
	tx_emsg[port].len = 0;

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	pdmsg[port].ext = 0;

	TCH_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
#else
	PRL_TX_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */

	task_wake(PD_PORT_TO_TASK_ID(port));
}

void prl_send_data_msg(int port,
		      enum tcpci_msg_type type,
		      enum pd_data_msg_type msg)
{
	pdmsg[port].xmit_type = type;
	pdmsg[port].msg_type = msg;

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	pdmsg[port].ext = 0;

	TCH_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
#else
	prl_copy_msg_to_buffer(port);
	PRL_TX_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */

	task_wake(PD_PORT_TO_TASK_ID(port));
}

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
void prl_send_ext_data_msg(int port,
			  enum tcpci_msg_type type,
			  enum pd_ext_msg_type msg)
{
	pdmsg[port].xmit_type = type;
	pdmsg[port].msg_type = msg;
	pdmsg[port].ext = 1;

	TCH_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
	task_wake(PD_PORT_TO_TASK_ID(port));
}
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */

void prl_set_default_pd_revision(int port)
{
	/*
	 * Initialize to highest revision supported. If the port or cable
	 * partner doesn't support this revision, the Protocol Engine will
	 * lower this value to the revision supported by the partner.
	 */
	pdmsg[port].rev[TCPCI_MSG_SOP] = PD_REVISION;
	pdmsg[port].rev[TCPCI_MSG_SOP_PRIME] = PD_REVISION;
	pdmsg[port].rev[TCPCI_MSG_SOP_PRIME_PRIME] = PD_REVISION;
	pdmsg[port].rev[TCPCI_MSG_SOP_DEBUG_PRIME] = PD_REVISION;
	pdmsg[port].rev[TCPCI_MSG_SOP_DEBUG_PRIME_PRIME] = PD_REVISION;
}

void prl_reset_soft(int port)
{
	/* Do not change negotiated PD Revision Specification level */
	local_state[port] = SM_INIT;

	/* Ensure we process the reset quickly */
	task_wake(PD_PORT_TO_TASK_ID(port));
}

void prl_run(int port, int evt, int en)
{
	switch (local_state[port]) {
	case SM_PAUSED:
		if (!en)
			break;
		/* fall through */
	case SM_INIT:
		prl_init(port);
		local_state[port] = SM_RUN;
		/* fall through */
	case SM_RUN:
		if (!en) {
			/* Disable RX */
			if (IS_ENABLED(CONFIG_USB_CTVPD) ||
			    IS_ENABLED(CONFIG_USB_VPD))
				vpd_rx_enable(0);
			else
				tcpm_set_rx_enable(port, 0);

			local_state[port] = SM_PAUSED;
			break;
		}

		/* Run Protocol Layer Hard Reset state machine */
		run_state(port, &prl_hr[port].ctx);

		/*
		 * If the Hard Reset state machine is active, then there is no
		 * need to execute any other PRL state machines. When the hard
		 * reset is complete, all PRL state machines will have been
		 * reset.
		 */
		if (prl_hr_get_state(port) == PRL_HR_WAIT_FOR_REQUEST) {

			/* Run Protocol Layer Message Reception */
			prl_rx_wait_for_phy_message(port, evt);


			if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
				/*
				 * Run RX Chunked state machine after prl_rx.
				 * This is what informs the PE of incoming
				 * message. Its input is prl_rx
				 */
				run_state(port, &rch[port].ctx);

				/*
				 * Run TX Chunked state machine before prl_tx
				 * in case we need to split an extended message
				 * and prl_tx can send it for us
				 */
				run_state(port, &tch[port].ctx);
			}

			/* Run Protocol Layer Message Tx state machine */
			run_state(port, &prl_tx[port].ctx);

			if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES))
				/*
				 * Run TX Chunked state machine again after
				 * prl_tx so we can handle passing TX_COMPLETE
				 * (or failure) up to PE in a single iteration.
				 */
				run_state(port, &tch[port].ctx);
		}
		break;
	}
}

void prl_set_rev(int port, enum tcpci_msg_type type,
						enum pd_rev_type rev)
{
	/* We only store revisions for SOP* types. */
	ASSERT(type < NUM_SOP_STAR_TYPES);

	pdmsg[port].rev[type] = rev;
}

enum pd_rev_type prl_get_rev(int port, enum tcpci_msg_type type)
{
	/* We only store revisions for SOP* types. */
	ASSERT(type < NUM_SOP_STAR_TYPES);

	return pdmsg[port].rev[type];
}

static void prl_copy_msg_to_buffer(int port)
{
	/*
	 * Control Messages will have a length of 0 and
	 * no need to spend time with the tx_chk_buf
	 * for this path
	 */
	if (tx_emsg[port].len == 0) {
		pdmsg[port].data_objs = 0;
		return;
	}

	/*
	 * Make sure the Policy Engine isn't sending
	 * more than CHK_BUF_SIZE_BYTES. If so,
	 * truncate len. This will surely send a
	 * malformed packet resulting in the port
	 * partner soft\hard resetting us.
	 */
	if (tx_emsg[port].len > CHK_BUF_SIZE_BYTES)
		tx_emsg[port].len = CHK_BUF_SIZE_BYTES;

	/* Copy message to chunked buffer */
	memset((uint8_t *)pdmsg[port].tx_chk_buf, 0, CHK_BUF_SIZE_BYTES);
	memcpy((uint8_t *)pdmsg[port].tx_chk_buf, (uint8_t *)tx_emsg[port].buf,
		tx_emsg[port].len);
	/*
	 * Pad length to 4-byte boundary and
	 * convert to number of 32-bit objects.
	 * Since the value is shifted right by 2,
	 * no need to explicitly clear the lower
	 * 2-bits.
	 */
	pdmsg[port].data_objs = (tx_emsg[port].len + 3) >> 2;
}

static __maybe_unused int pdmsg_xmit_type_is_rev30(const int port)
{
	if (IS_ENABLED(CONFIG_USB_PD_REV30))
		return ((pdmsg[port].xmit_type < NUM_SOP_STAR_TYPES)
		&& (prl_get_rev(port, pdmsg[port].xmit_type) == PD_REV30));
	else
		return 0;
}

/* Returns true if the SOP port partner operates at PD rev3.0 */
static bool is_sop_rev30(const int port)
{
	return IS_ENABLED(CONFIG_USB_PD_REV30) &&
	       prl_get_rev(port, TCPCI_MSG_SOP) == PD_REV30;
}

/* Common Protocol Layer Message Transmission */
static void prl_tx_phy_layer_reset_entry(const int port)
{
	print_current_prl_tx_state(port);

	if (IS_ENABLED(CONFIG_USB_CTVPD)
	 || IS_ENABLED(CONFIG_USB_VPD)) {
		vpd_rx_enable(pd_is_connected(port));
	} else {
		/* Note: can't clear PHY messages due to TCPC architecture */
		/* Enable communications*/
		tcpm_set_rx_enable(port, pd_is_connected(port));
	}
	set_state_prl_tx(port, PRL_TX_WAIT_FOR_MESSAGE_REQUEST);
}

static void prl_tx_wait_for_message_request_entry(const int port)
{
	/* No phy layer response is pending */
	prl_tx[port].xmit_status = TCPC_TX_UNSET;
	print_current_prl_tx_state(port);
}

static void prl_tx_wait_for_message_request_run(const int port)
{
	/* Clear any AMS flags and state if we are no longer in an AMS */
	if (IS_ENABLED(CONFIG_USB_PD_REV30) && !pe_in_local_ams(port)) {
		/* Note PRL_Tx_Src_Sink_Tx is embedded here. */
		if (PRL_TX_CHK_FLAG(port, PRL_FLAGS_SINK_NG)) {
			typec_select_src_collision_rp(port, SINK_TX_OK);
			typec_update_cc(port);
		}
		PRL_TX_CLR_FLAG(port,
				PRL_FLAGS_SINK_NG | PRL_FLAGS_WAIT_SINK_OK);
	}

	/*
	 * Check if we are starting an AMS and need to wait and/or set the CC
	 * lines appropriately.
	 */
	if (IS_ENABLED(CONFIG_USB_PD_REV30) && is_sop_rev30(port) &&
	    pe_in_local_ams(port)) {
		if (PRL_TX_CHK_FLAG(port, PRL_FLAGS_SINK_NG |
					  PRL_FLAGS_WAIT_SINK_OK)) {
			/*
			 * If we are already in an AMS then allow the
			 * multi-message AMS to continue, even if we
			 * swap power roles.
			 *
			 * Fall Through using the current AMS
			 */
		} else {
			/*
			 * Start of SRC AMS notification received from
			 * Policy Engine
			 */
			if (pd_get_power_role(port) == PD_ROLE_SOURCE) {
				PRL_TX_SET_FLAG(port, PRL_FLAGS_SINK_NG);
				set_state_prl_tx(port, PRL_TX_SRC_SOURCE_TX);
			} else {
				PRL_TX_SET_FLAG(port, PRL_FLAGS_WAIT_SINK_OK);
				set_state_prl_tx(port, PRL_TX_SNK_START_AMS);
			}
			return;
		}
	}

	/* Handle non Rev 3.0 or subsequent messages in AMS sequence */
	if (PRL_TX_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT)) {
		PRL_TX_CLR_FLAG(port, PRL_FLAGS_MSG_XMIT);
		/*
		 * Soft Reset Message Message pending
		 */
		if ((pdmsg[port].msg_type == PD_CTRL_SOFT_RESET) &&
						(tx_emsg[port].len == 0)) {
			set_state_prl_tx(port, PRL_TX_LAYER_RESET_FOR_TRANSMIT);
		}
		/*
		 * Message pending (except Soft Reset)
		 */
		else {
			/* NOTE: PRL_TX_Construct_Message State embedded here */
			prl_tx_construct_message(port);
			set_state_prl_tx(port, PRL_TX_WAIT_FOR_PHY_RESPONSE);
		}

		return;
	}
}

static void increment_msgid_counter(int port)
{
	/* If the last message wasn't an SOP* message, no need to increment */
	if (prl_tx[port].last_xmit_type >= NUM_SOP_STAR_TYPES)
		return;

	prl_tx[port].msg_id_counter[prl_tx[port].last_xmit_type] =
		(prl_tx[port].msg_id_counter[prl_tx[port].last_xmit_type] + 1) &
		PD_MESSAGE_ID_COUNT;
}

/*
 * PrlTxDiscard
 */
static void prl_tx_discard_message_entry(const int port)
{
	print_current_prl_tx_state(port);

	/*
	 * Discard queued message
	 * Note: We differ from spec here, which allows us to not discard on
	 * incoming SOP' or SOP''.  However this would get the TCH out of sync.
	 *
	 * prl_tx will be set to this state following message reception in
	 * prl_rx. So this path will be entered following each rx message. If
	 * this state is entered, and there is either a message from the PE
	 * pending, or if a message was passed to the phy and there is either no
	 * response yet, or it was discarded in the phy layer, then a tx message
	 * discard event has been detected.
	 */
	if (PRL_TX_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT) ||
	    prl_tx[port].xmit_status == TCPC_TX_WAIT ||
	    prl_tx[port].xmit_status == TCPC_TX_COMPLETE_DISCARDED) {
		PRL_TX_CLR_FLAG(port, PRL_FLAGS_MSG_XMIT);
		increment_msgid_counter(port);
		pe_report_discard(port);
	}

	set_state_prl_tx(port, PRL_TX_PHY_LAYER_RESET);
}

#ifdef CONFIG_USB_PD_REV30
/*
 * PrlTxSrcSourceTx
 */
static void prl_tx_src_source_tx_entry(const int port)
{
	print_current_prl_tx_state(port);

	/* Set Rp = SinkTxNG */
	typec_select_src_collision_rp(port, SINK_TX_NG);
	typec_update_cc(port);
}

static void prl_tx_src_source_tx_run(const int port)
{
	if (PRL_TX_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT)) {
		/*
		 * Don't clear pending XMIT flag here. Wait until we send so
		 * we can detect if we dropped this message or not.
		 */
		set_state_prl_tx(port, PRL_TX_SRC_PENDING);
	}
}

/*
 * PrlTxSnkStartAms
 */
static void prl_tx_snk_start_ams_entry(const int port)
{
	print_current_prl_tx_state(port);
}

static void prl_tx_snk_start_ams_run(const int port)
{
	if (PRL_TX_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT)) {
		/*
		 * Don't clear pending XMIT flag here. Wait until we send so
		 * we can detect if we dropped this message or not.
		 */
		set_state_prl_tx(port, PRL_TX_SNK_PENDING);
	}
}
#endif /* CONFIG_USB_PD_REV30 */

/*
 * PrlTxLayerResetForTransmit
 */
static void prl_tx_layer_reset_for_transmit_entry(const int port)
{
	print_current_prl_tx_state(port);

	if (pdmsg[port].xmit_type < NUM_SOP_STAR_TYPES) {
		/*
		 * This state is only used during soft resets. Reset only the
		 * matching message type.
		 *
		 * From section 6.3.13 Soft Reset Message in the USB PD 3.0
		 * v2.0 spec, Soft_Reset Message Shall be targeted at a
		 * specific entity depending on the type of SOP* Packet used.
		 */
		prl_tx[port].msg_id_counter[pdmsg[port].xmit_type] = 0;

		/*
		 * From section 6.11.2.3.2, the MessageID should be cleared
		 * from the PRL_Rx_Layer_Reset_for_Receive state. However, we
		 * don't implement a full state machine for PRL RX states so
		 * clear the MessageID here.
		 */
		prl_rx[port].msg_id[pdmsg[port].xmit_type] = -1;
	}
}

static void prl_tx_layer_reset_for_transmit_run(const int port)
{
	/* NOTE: PRL_Tx_Construct_Message State embedded here */
	prl_tx_construct_message(port);
	set_state_prl_tx(port, PRL_TX_WAIT_FOR_PHY_RESPONSE);
}

static uint32_t get_sop_star_header(const int port)
{
	const int is_sop_packet = pdmsg[port].xmit_type == TCPCI_MSG_SOP;
	int ext;

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	ext = pdmsg[port].ext;
#else
	ext = 0;
#endif

	/* SOP vs SOP'/SOP" headers are different. Replace fields as needed */
	return PD_HEADER(
		pdmsg[port].msg_type,
		is_sop_packet ?
			pd_get_power_role(port) : tc_get_cable_plug(port),
		is_sop_packet ?
			pd_get_data_role(port) : 0,
		prl_tx[port].msg_id_counter[pdmsg[port].xmit_type],
		pdmsg[port].data_objs,
		pdmsg[port].rev[pdmsg[port].xmit_type],
		ext);
}

static void prl_tx_construct_message(const int port)
{
	/* The header is unused for hard reset, etc. */
	const uint32_t header = pdmsg[port].xmit_type < NUM_SOP_STAR_TYPES ?
		get_sop_star_header(port) : 0;

	/* Save SOP* so the correct msg_id_counter can be incremented */
	prl_tx[port].last_xmit_type = pdmsg[port].xmit_type;

	/* Indicate that a tx message is being passed to the phy layer */
	prl_tx[port].xmit_status = TCPC_TX_WAIT;
	/*
	 * PRL_FLAGS_TX_COMPLETE could be set if this function is called before
	 * the Policy Engine is informed of the previous transmission. Clear the
	 * flag so that this message can be sent.
	 */
	PDMSG_CLR_FLAG(port, PRL_FLAGS_TX_COMPLETE);

	/*
	 * Pass message to PHY Layer. It handles retries in hardware as the EC
	 * cannot handle the required timing ~ 1ms (tReceive + tRetry).
	 *
	 * Note if we ever start sending large, extendend messages, then we
	 * should not retry those messages. We do not support that and probably
	 * never will (since we support chunking).
	 */
	tcpm_transmit(port, pdmsg[port].xmit_type, header,
		      pdmsg[port].tx_chk_buf);
}

/*
 * PrlTxWaitForPhyResponse
 */
static void prl_tx_wait_for_phy_response_entry(const int port)
{
	print_current_prl_tx_state(port);

	pd_timer_enable(port, PR_TIMER_TCPC_TX_TIMEOUT, PD_T_TCPC_TX_TIMEOUT);
}

static void prl_tx_wait_for_phy_response_run(const int port)
{
	/* Wait until TX is complete */

	/*
	 * NOTE: The TCPC will set xmit_status to TCPC_TX_COMPLETE_DISCARDED
	 *       when a GoodCRC containing an incorrect MessageID is received.
	 *       This condition satisfies the PRL_Tx_Match_MessageID state
	 *       requirement.
	 */

	if (prl_tx[port].xmit_status == TCPC_TX_COMPLETE_SUCCESS) {
		/* NOTE: PRL_TX_Message_Sent State embedded here. */
		/* Increment messageId counter */
		increment_msgid_counter(port);

		/* Inform Policy Engine Message was sent */
		if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES))
			PDMSG_SET_FLAG(port, PRL_FLAGS_TX_COMPLETE);
		else
			pe_message_sent(port);

		/*
		 * This event reduces the time of informing the policy engine of
		 * the transmission by one state machine cycle
		 */
		task_wake(PD_PORT_TO_TASK_ID(port));
		set_state_prl_tx(port, PRL_TX_WAIT_FOR_MESSAGE_REQUEST);
	} else if (pd_timer_is_expired(port, PR_TIMER_TCPC_TX_TIMEOUT) ||
		   prl_tx[port].xmit_status == TCPC_TX_COMPLETE_FAILED) {
		/*
		 * NOTE: PRL_Tx_Transmission_Error State embedded
		 * here.
		 */

		if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
			/*
			 * State tch_wait_for_transmission_complete will
			 * inform policy engine of error
			 */
			PDMSG_SET_FLAG(port, PRL_FLAGS_TX_ERROR);
		} else {
			/* Report Error To Policy Engine */
			pe_report_error(port, ERR_TCH_XMIT,
					prl_tx[port].last_xmit_type);
		}

		/* Increment message id counter */
		increment_msgid_counter(port);
		set_state_prl_tx(port, PRL_TX_WAIT_FOR_MESSAGE_REQUEST);
	}
}

static void prl_tx_wait_for_phy_response_exit(const int port)
{
	pd_timer_disable(port, PR_TIMER_TCPC_TX_TIMEOUT);
}

/* Source Protocol Layer Message Transmission */
/*
 * PrlTxSrcPending
 */
static void prl_tx_src_pending_entry(const int port)
{
	print_current_prl_tx_state(port);

	/* Start SinkTxTimer */
	pd_timer_enable(port, PR_TIMER_SINK_TX, PD_T_SINK_TX);
}

static void prl_tx_src_pending_run(const int port)
{
	if (pd_timer_is_expired(port, PR_TIMER_SINK_TX)) {
		/*
		 * We clear the pending XMIT flag here right before we send so
		 * we can detect if we discarded this message or not
		 */
		PRL_TX_CLR_FLAG(port, PRL_FLAGS_MSG_XMIT);

		/*
		 * Soft Reset Message pending &
		 * SinkTxTimer timeout
		 */
		if ((tx_emsg[port].len == 0) &&
			(pdmsg[port].msg_type == PD_CTRL_SOFT_RESET)) {
			set_state_prl_tx(port, PRL_TX_LAYER_RESET_FOR_TRANSMIT);
		}
		/* Message pending (except Soft Reset) &
		 * SinkTxTimer timeout
		 */
		else {
			prl_tx_construct_message(port);
			set_state_prl_tx(port, PRL_TX_WAIT_FOR_PHY_RESPONSE);
		}

		return;
	}
}

static void prl_tx_src_pending_exit(int port)
{
	pd_timer_disable(port, PR_TIMER_SINK_TX);
}

/*
 * PrlTxSnkPending
 */
static void prl_tx_snk_pending_entry(const int port)
{
	print_current_prl_tx_state(port);
}

static void prl_tx_snk_pending_run(const int port)
{
	bool start_tx = false;

	/*
	 * Wait unit the SRC applies SINK_TX_OK so we can transmit. In FRS mode,
	 * don't wait for SINK_TX_OK since either the source (and Rp) could be
	 * gone or the TCPC CC_STATUS update time could be too long to meet
	 * tFRSwapInit.
	 */
	if (pe_in_frs_mode(port)) {
		/* shortcut to save some i2c_xfer calls on the FRS path. */
		start_tx = true;
	} else {
		enum tcpc_cc_voltage_status cc1, cc2;

		tcpm_get_cc(port, &cc1, &cc2);
		start_tx = (cc1 == TYPEC_CC_VOLT_RP_3_0 ||
			    cc2 == TYPEC_CC_VOLT_RP_3_0);
	}
	if (start_tx) {
		/*
		 * We clear the pending XMIT flag here right before we send so
		 * we can detect if we discarded this message or not
		 */
		PRL_TX_CLR_FLAG(port, PRL_FLAGS_MSG_XMIT);

		/*
		 * Soft Reset Message Message pending &
		 * Rp = SinkTxOk
		 */
		if ((pdmsg[port].msg_type == PD_CTRL_SOFT_RESET) &&
					(tx_emsg[port].len == 0)) {
			set_state_prl_tx(port, PRL_TX_LAYER_RESET_FOR_TRANSMIT);
		}
		/*
		 * Message pending (except Soft Reset) &
		 * Rp = SinkTxOk
		 */
		else {
			prl_tx_construct_message(port);
			set_state_prl_tx(port, PRL_TX_WAIT_FOR_PHY_RESPONSE);
		}
		return;
	}
}

/* Hard Reset Operation */
void prl_hr_send_msg_to_phy(const int port)
{
	/* Header is not used for hard reset */
	const uint32_t header = 0;

	pdmsg[port].xmit_type = TCPCI_MSG_TX_HARD_RESET;

	/*
	 * These flags could be set if this function is called before the
	 * Policy Engine is informed of the previous transmission. Clear the
	 * flags so that this message can be sent.
	 */
	prl_tx[port].xmit_status = TCPC_TX_UNSET;
	PDMSG_CLR_FLAG(port, PRL_FLAGS_TX_COMPLETE);

	/* Pass message to PHY Layer */
	tcpm_transmit(port, pdmsg[port].xmit_type, header,
		      pdmsg[port].tx_chk_buf);
}

static void prl_hr_wait_for_request_entry(const int port)
{
	print_current_prl_hr_state(port);

	prl_hr[port].flags = 0;
}

static void prl_hr_wait_for_request_run(const int port)
{
	if (PRL_HR_CHK_FLAG(port, PRL_FLAGS_PE_HARD_RESET |
				PRL_FLAGS_PORT_PARTNER_HARD_RESET))
		set_state_prl_hr(port, PRL_HR_RESET_LAYER);
}

/*
 * PrlHrResetLayer
 */
static void prl_hr_reset_layer_entry(const int port)
{
	int i;

	print_current_prl_hr_state(port);

	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
		tch[port].flags = 0;
		rch[port].flags = 0;
	}

	pdmsg[port].flags = 0;

	/* Hard reset resets messageIDCounters for all TX types */
	for (i = 0; i < NUM_SOP_STAR_TYPES; i++) {
		prl_rx[port].msg_id[i] = -1;
		prl_tx[port].msg_id_counter[i] = 0;
	}

	/* Disable RX */
	if (IS_ENABLED(CONFIG_USB_CTVPD) ||
	    IS_ENABLED(CONFIG_USB_VPD))
		vpd_rx_enable(0);
	else
		tcpm_set_rx_enable(port, 0);

	/*
	 * PD r3.0 v2.0, ss6.2.1.1.5:
	 * After a physical or logical (USB Type-C Error Recovery) Attach, a
	 * Port discovers the common Specification Revision level between itself
	 * and its Port Partner and/or the Cable Plug(s), and uses this
	 * Specification Revision level until a Detach, Hard Reset or Error
	 * Recovery happens.
	 *
	 * This covers the Hard Reset case.
	 */
	prl_set_default_pd_revision(port);

	/* Inform the AP of Hard Reset */
	if (IS_ENABLED(CONFIG_USB_PD_HOST_CMD))
		pd_notify_event(port, PD_STATUS_EVENT_HARD_RESET);

	/*
	 * Protocol Layer message transmission transitions to
	 * PRL_Tx_Wait_For_Message_Request state.
	 */
	set_state_prl_tx(port, PRL_TX_WAIT_FOR_MESSAGE_REQUEST);

	return;
}

static void prl_hr_reset_layer_run(const int port)
{
	/*
	 * Protocol Layer reset Complete &
	 * Hard Reset was initiated by Policy Engine
	 */
	if (PRL_HR_CHK_FLAG(port, PRL_FLAGS_PE_HARD_RESET)) {
		/*
		 * Request PHY to perform a Hard Reset. Note
		 * PRL_HR_Request_Reset state is embedded here.
		 */
		prl_hr_send_msg_to_phy(port);
		set_state_prl_hr(port, PRL_HR_WAIT_FOR_PHY_HARD_RESET_COMPLETE);
	}
	/*
	 * Protocol Layer reset complete &
	 * Hard Reset was initiated by Port Partner
	 */
	else {
		/* Inform Policy Engine of the Hard Reset */
		pe_got_hard_reset(port);
		set_state_prl_hr(port, PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE);
	}
}

/*
 * PrlHrWaitForPhyHardResetComplete
 */
static void prl_hr_wait_for_phy_hard_reset_complete_entry(const int port)
{
	print_current_prl_hr_state(port);

	/* Start HardResetCompleteTimer */
	pd_timer_enable(port, PR_TIMER_HARD_RESET_COMPLETE,
			PD_T_PS_HARD_RESET);
}

static void prl_hr_wait_for_phy_hard_reset_complete_run(const int port)
{
	/*
	 * Wait for hard reset from PHY
	 * or timeout
	 */
	if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_COMPLETE) ||
	    pd_timer_is_expired(port, PR_TIMER_HARD_RESET_COMPLETE)) {
		/* PRL_HR_PHY_Hard_Reset_Requested */

		/* Inform Policy Engine Hard Reset was sent */
		pe_hard_reset_sent(port);
		set_state_prl_hr(port, PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE);

		return;
	}
}

static void prl_hr_wait_for_phy_hard_reset_complete_exit(int port)
{
	pd_timer_disable(port, PR_TIMER_HARD_RESET_COMPLETE);
}

/*
 * PrlHrWaitForPeHardResetComplete
 */
static void prl_hr_wait_for_pe_hard_reset_complete_entry(const int port)
{
	print_current_prl_hr_state(port);
}

static void prl_hr_wait_for_pe_hard_reset_complete_run(const int port)
{
	/*
	 * Wait for Hard Reset complete indication from Policy Engine
	 */
	if (PRL_HR_CHK_FLAG(port, PRL_FLAGS_HARD_RESET_COMPLETE))
		set_state_prl_hr(port, PRL_HR_WAIT_FOR_REQUEST);
}

static void prl_hr_wait_for_pe_hard_reset_complete_exit(const int port)
{
	/* Exit from Hard Reset */

	set_state_prl_tx(port, PRL_TX_PHY_LAYER_RESET);
	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
		set_state_rch(port, RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);
		set_state_tch(port, TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
	}
}

static void copy_chunk_to_ext(int port)
{
	/* Calculate number of bytes */
	pdmsg[port].num_bytes_received =
				(PD_HEADER_CNT(rx_emsg[port].header) * 4);

	/* Copy chunk into extended message */
	memcpy((uint8_t *)rx_emsg[port].buf, (uint8_t *)pdmsg[port].rx_chk_buf,
		pdmsg[port].num_bytes_received);

	/* Set extended message length */
	rx_emsg[port].len = pdmsg[port].num_bytes_received;
}

#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
/*
 * Chunked Rx State Machine
 */
/*
 * RchWaitForMessageFromProtocolLayer
 */
static void rch_wait_for_message_from_protocol_layer_entry(const int port)
{
	print_current_rch_state(port);

	/* Clear Abort flag */
	PDMSG_CLR_FLAG(port, PRL_FLAGS_ABORT);

	/* All Messages are chunked */
	rch[port].flags = PRL_FLAGS_CHUNKING;
}

static void rch_wait_for_message_from_protocol_layer_run(const int port)
{
	if (RCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		RCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		/*
		 * Are we communicating with a PD3.0 device and is
		 * this an extended message?
		 */
		if (pdmsg_xmit_type_is_rev30(port)
		    && PD_HEADER_EXT(rx_emsg[port].header)) {
			uint16_t exhdr =
					GET_EXT_HEADER(*pdmsg[port].rx_chk_buf);
			uint8_t chunked = PD_EXT_HEADER_CHUNKED(exhdr);

			/*
			 * Received Extended Message &
			 * (Chunking = 1 & Chunked = 1)
			 */
			if ((RCH_CHK_FLAG(port, PRL_FLAGS_CHUNKING)) &&
								chunked) {
				/*
				 * RCH_Processing_Extended_Message first chunk
				 * entry processing embedded here
				 *
				 * This is the first chunk:
				 * Set Chunk_number_expected = 0 and
				 * Num_Bytes_Received = 0
				 */
				pdmsg[port].chunk_number_expected = 0;
				pdmsg[port].num_bytes_received = 0;
				pdmsg[port].msg_type =
					PD_HEADER_TYPE(rx_emsg[port].header);

				set_state_rch(port,
					      RCH_PROCESSING_EXTENDED_MESSAGE);
			}
			/*
			 * (Received Extended Message &
			 * (Chunking = 0 & Chunked = 0))
			 */
			else if (!RCH_CHK_FLAG(port, PRL_FLAGS_CHUNKING) &&
								!chunked) {
				/* Copy chunk to extended buffer */
				copy_chunk_to_ext(port);
				set_state_rch(port, RCH_PASS_UP_MESSAGE);
			}
			/*
			 * Chunked != Chunking
			 */
			else {
				rch[port].error = ERR_RCH_CHUNKED;
				set_state_rch(port, RCH_REPORT_ERROR);
			}
		}
		/*
		 * Received Non-Extended Message
		 */
		else if (!PD_HEADER_EXT(rx_emsg[port].header)) {
			/* Copy chunk to extended buffer */
			copy_chunk_to_ext(port);
			set_state_rch(port, RCH_PASS_UP_MESSAGE);
		}
		/*
		 * Received an Extended Message while communicating at a
		 * revision lower than PD3.0
		 */
		else {
			rch[port].error = ERR_RCH_CHUNKED;
			set_state_rch(port, RCH_REPORT_ERROR);
		}
	}
}

/*
 * RchPassUpMessage
 */
static void rch_pass_up_message_entry(const int port)
{
	print_current_rch_state(port);

	/* Pass Message to Policy Engine */
	pe_message_received(port);
	set_state_rch(port, RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);
}

/*
 * RchProcessingExtendedMessage
 */
static void rch_processing_extended_message_entry(const int port)
{
	print_current_rch_state(port);
}

static void rch_processing_extended_message_run(const int port)
{
	uint16_t exhdr = GET_EXT_HEADER(pdmsg[port].rx_chk_buf[0]);
	uint8_t chunk_num = PD_EXT_HEADER_CHUNK_NUM(exhdr);
	uint32_t data_size = PD_EXT_HEADER_DATA_SIZE(exhdr);
	uint32_t byte_num;

	/*
	 * Abort Flag Set
	 */
	if (PDMSG_CHK_FLAG(port, PRL_FLAGS_ABORT))
		set_state_rch(port, RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);

	/*
	 * If expected Chunk Number:
	 *   Append data to Extended_Message_Buffer
	 *   Increment Chunk_number_Expected
	 *   Adjust Num Bytes Received
	 */
	else if (chunk_num == pdmsg[port].chunk_number_expected) {
		byte_num = data_size - pdmsg[port].num_bytes_received;

		if (byte_num >= PD_MAX_EXTENDED_MSG_CHUNK_LEN)
			byte_num = PD_MAX_EXTENDED_MSG_CHUNK_LEN;

		/* Make sure extended message buffer does not overflow */
		if (pdmsg[port].num_bytes_received +
					byte_num > EXTENDED_BUFFER_SIZE) {
			rch[port].error = ERR_RCH_CHUNKED;
			set_state_rch(port, RCH_REPORT_ERROR);
			return;
		}

		/* Append data */
		/* Add 2 to chk_buf to skip over extended message header */
		memcpy(((uint8_t *)rx_emsg[port].buf +
				pdmsg[port].num_bytes_received),
				(uint8_t *)pdmsg[port].rx_chk_buf + 2,
				byte_num);
		/* increment chunk number expected */
		pdmsg[port].chunk_number_expected++;
		/* adjust num bytes received */
		pdmsg[port].num_bytes_received += byte_num;

		/* Was that the last chunk? */
		if (pdmsg[port].num_bytes_received >= data_size) {
			rx_emsg[port].len = pdmsg[port].num_bytes_received;
			 /* Pass Message to Policy Engine */
			set_state_rch(port, RCH_PASS_UP_MESSAGE);
		}
		/*
		 * Message not Complete
		 */
		else
			set_state_rch(port, RCH_REQUESTING_CHUNK);
	}
	/*
	 * Unexpected Chunk Number
	 */
	else {
		rch[port].error = ERR_RCH_CHUNKED;
		set_state_rch(port, RCH_REPORT_ERROR);
	}
}

/*
 * RchRequestingChunk
 */
static void rch_requesting_chunk_entry(const int port)
{
	print_current_rch_state(port);

	/*
	 * Send Chunk Request to Protocol Layer
	 * with chunk number = Chunk_Number_Expected
	 */
	pdmsg[port].tx_chk_buf[0] = PD_EXT_HEADER(
				pdmsg[port].chunk_number_expected,
				1, /* Request Chunk */
				0 /* Data Size */
				);

	pdmsg[port].data_objs = 1;
	pdmsg[port].ext = 1;
	pdmsg[port].xmit_type = prl_rx[port].sop;
	PRL_TX_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
	task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_TX);
}

static void rch_requesting_chunk_run(const int port)
{
	/*
	 * Message Transmitted received from Protocol Layer
	 */
	if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_COMPLETE)) {
		PDMSG_CLR_FLAG(port, PRL_FLAGS_TX_COMPLETE);
		set_state_rch(port, RCH_WAITING_CHUNK);
	} else if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_ERROR)) {
		/* Transmission Error from Protocol Layer detetected */
		rch[port].error = ERR_RCH_CHUNKED;
		set_state_rch(port, RCH_REPORT_ERROR);
	} else if (RCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		/*
		 * It is possible to have both message received and the chunk
		 * request transmit complete before a full PRL SM run. But, the
		 * PRL_RX state machine runs prior to RCH, but before PRL_TX, so
		 * PRL_FLAGS_MSG_RECEIVED can be set without
		 * PRL_FLAGS_TX_COMPLETE set at this point (though it will be
		 * set as soon as PRL_TX is executed next.
		 */
		set_state_rch(port, RCH_WAITING_CHUNK);
	}
}

/*
 * RchWaitingChunk
 */
static void rch_waiting_chunk_entry(const int port)
{
	print_current_rch_state(port);

	/*
	 * Start ChunkSenderResponseTimer
	 */
	pd_timer_enable(port, PR_TIMER_CHUNK_SENDER_RESPONSE,
			PD_T_CHUNK_SENDER_RESPONSE);
}

static void rch_waiting_chunk_run(const int port)
{
	if (RCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		/*
		 * Because of the 5 msec tick time, it is possible to have both
		 * msg_received and tx_complete flags set for a given PRL sm
		 * run. Since prl_rx runs prior to the tx state machines, clear
		 * the tx_complete flag as the next chunk has already been
		 * received.
		 */
		if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_COMPLETE))
			PDMSG_CLR_FLAG(port, PRL_FLAGS_TX_COMPLETE);

		/*
		 * Leave PRL_FLAGS_MSG_RECEIVED flag set just in case an error
		 * is detected. If an error is detected, PRL_FLAGS_MSG_RECEIVED
		 * will be cleared in rch_report_error state.
		 */

		if (PD_HEADER_EXT(rx_emsg[port].header)) {
			uint16_t exhdr =
				GET_EXT_HEADER(pdmsg[port].rx_chk_buf[0]);
			/*
			 * Other Message Received from Protocol Layer
			 */
			if (PD_EXT_HEADER_REQ_CHUNK(exhdr) ||
			    !PD_EXT_HEADER_CHUNKED(exhdr)) {
				rch[port].error = ERR_RCH_CHUNKED;
				set_state_rch(port, RCH_REPORT_ERROR);
			}
			/*
			 * Chunk response Received from Protocol Layer
			 */
			else {
				/*
				 * No error was detected, so clear
				 * PRL_FLAGS_MSG_RECEIVED flag.
				 */
				RCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
				set_state_rch(port,
					      RCH_PROCESSING_EXTENDED_MESSAGE);
			}
		}
	}
	/*
	 * ChunkSenderResponseTimer Timeout
	 */
	else if (pd_timer_is_expired(port, PR_TIMER_CHUNK_SENDER_RESPONSE)) {
		rch[port].error = ERR_RCH_CHUNK_WAIT_TIMEOUT;
		set_state_rch(port, RCH_REPORT_ERROR);
	}
}

static void rch_waiting_chunk_exit(int port)
{
	pd_timer_disable(port, PR_TIMER_CHUNK_SENDER_RESPONSE);
}

/*
 * RchReportError
 */
static void rch_report_error_entry(const int port)
{
	print_current_rch_state(port);

	/*
	 * If the state was entered because a message was received,
	 * this message is passed to the Policy Engine.
	 */
	if (RCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		RCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);

		/* Copy chunk to extended buffer */
		copy_chunk_to_ext(port);
		/* Pass Message to Policy Engine */
		pe_message_received(port);
		/* Report error */
		pe_report_error(port, ERR_RCH_MSG_REC, prl_rx[port].sop);
	} else {
		pe_report_error(port, rch[port].error, prl_rx[port].sop);
	}
}

static void rch_report_error_run(const int port)
{
	set_state_rch(port, RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);
}

/*
 * Chunked Tx State Machine
 */

/*
 * TchWaitForMessageRequestFromPe
 */
static void tch_wait_for_message_request_from_pe_entry(const int port)
{
	print_current_tch_state(port);

	/* Clear Abort flag */
	PDMSG_CLR_FLAG(port, PRL_FLAGS_ABORT);

	/* All Messages are chunked */
	tch[port].flags = PRL_FLAGS_CHUNKING;
}

static void tch_wait_for_message_request_from_pe_run(const int port)
{
	/*
	 * Any message received and not in state TCH_Wait_Chunk_Request
	 */
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
	} else if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_XMIT);
		/*
		 * Rx Chunking State != RCH_Wait_For_Message_From_Protocol_Layer
		 * & Abort Supported
		 *
		 * Discard the Message
		 */
		if (rch_get_state(port) !=
				RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER) {
			tch[port].error = ERR_TCH_XMIT;
			set_state_tch(port, TCH_REPORT_ERROR);
		} else {
			/*
			 * Extended Message Request & Chunking
			 */
			if (pdmsg_xmit_type_is_rev30(port)
			    && pdmsg[port].ext
			    && TCH_CHK_FLAG(port, PRL_FLAGS_CHUNKING)) {
				/*
				 * NOTE: TCH_Prepare_To_Send_Chunked_Message
				 * embedded here.
				 */
				pdmsg[port].send_offset = 0;
				pdmsg[port].chunk_number_to_send = 0;
				set_state_tch(port,
					      TCH_CONSTRUCT_CHUNKED_MESSAGE);
			} else
			/*
			 * Non-Extended Message Request
			 */
			{
				/* NOTE: TCH_Pass_Down_Message embedded here */
				prl_copy_msg_to_buffer(port);

				/* Pass Message to Protocol Layer */
				PRL_TX_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
				set_state_tch(port,
					TCH_WAIT_FOR_TRANSMISSION_COMPLETE);
			}
		}
	}
}

/*
 * TchWaitForTransmissionComplete
 */
static void tch_wait_for_transmission_complete_entry(const int port)
{
	print_current_tch_state(port);
}

static void tch_wait_for_transmission_complete_run(const int port)
{
	/*
	 * Inform Policy Engine that Message was sent.
	 */
	if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_COMPLETE)) {
		PDMSG_CLR_FLAG(port, PRL_FLAGS_TX_COMPLETE);
		set_state_tch(port, TCH_MESSAGE_SENT);
		return;
	}
	/*
	 * Inform Policy Engine of Tx Error
	 */
	else if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_ERROR)) {
		PDMSG_CLR_FLAG(port, PRL_FLAGS_TX_ERROR);
		tch[port].error = ERR_TCH_XMIT;
		set_state_tch(port, TCH_REPORT_ERROR);
		return;
	}
	/*
	 * A message was received while TCH is waiting for the phy to complete
	 * sending a tx message.
	 *
	 * Because of our prl_sm architecture and I2C access delays for TCPCs,
	 * it's possible to have a message received and the prl_tx state not be
	 * in its default waiting state. To avoid a false protocol error, only
	 * jump to TCH_MESSAGE_RECEIVED if the phy layer has not indicated that
	 * the tx message was sent successfully.
	 */
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED) &&
	     prl_tx[port].xmit_status != TCPC_TX_COMPLETE_SUCCESS) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
		return;
	}
}

/*
 * TchConstructChunkedMessage
 */
static void tch_construct_chunked_message_entry(const int port)
{
	uint16_t *ext_hdr;
	uint8_t *data;
	uint16_t num;

	print_current_tch_state(port);

	/*
	 * Any message received and not in state TCH_Wait_Chunk_Request
	 */
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
		return;
	}

	/* Prepare to copy chunk into chk_buf */

	ext_hdr = (uint16_t *)pdmsg[port].tx_chk_buf;
	data = ((uint8_t *)pdmsg[port].tx_chk_buf + 2);
	num = tx_emsg[port].len - pdmsg[port].send_offset;

	if (num > PD_MAX_EXTENDED_MSG_CHUNK_LEN)
		num = PD_MAX_EXTENDED_MSG_CHUNK_LEN;

	/* Set the chunks extended header */
	*ext_hdr = PD_EXT_HEADER(pdmsg[port].chunk_number_to_send,
				 0, /* Chunk Request */
				 tx_emsg[port].len);

	/* Copy the message chunk into chk_buf */
	memset(data, 0, 28);
	memcpy(data, tx_emsg[port].buf + pdmsg[port].send_offset, num);
	pdmsg[port].send_offset += num;

	/*
	 * Add in 2 bytes for extended header
	 * pad out to 4-byte boundary
	 * convert to number of 4-byte words
	 * Since the value is shifted right by 2,
	 * no need to explicitly clear the lower
	 * 2-bits.
	 */
	pdmsg[port].data_objs = (num + 2 + 3) >> 2;

	/* Pass message chunk to Protocol Layer */
	PRL_TX_SET_FLAG(port, PRL_FLAGS_MSG_XMIT);
}

static void tch_construct_chunked_message_run(const int port)
{
	if (PDMSG_CHK_FLAG(port, PRL_FLAGS_ABORT))
		set_state_tch(port, TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
	else
		set_state_tch(port, TCH_SENDING_CHUNKED_MESSAGE);
}

/*
 * TchSendingChunkedMessage
 */
static void tch_sending_chunked_message_entry(const int port)
{
	print_current_tch_state(port);
}

static void tch_sending_chunked_message_run(const int port)
{
	/*
	 * Transmission Error
	 */
	if (PDMSG_CHK_FLAG(port, PRL_FLAGS_TX_ERROR)) {
		tch[port].error = ERR_TCH_XMIT;
		set_state_tch(port, TCH_REPORT_ERROR);
	}
	/*
	 * Message Transmitted from Protocol Layer &
	 * Last Chunk
	 */
	else if (tx_emsg[port].len == pdmsg[port].send_offset)
		set_state_tch(port, TCH_MESSAGE_SENT);
	/*
	 * Any message received and not in state TCH_Wait_Chunk_Request
	 */
	else if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
	}
	/*
	 * Message Transmitted from Protocol Layer &
	 * Not Last Chunk
	 */
	else
		set_state_tch(port, TCH_WAIT_CHUNK_REQUEST);
}

/*
 * TchWaitChunkRequest
 */
static void tch_wait_chunk_request_entry(const int port)
{
	print_current_tch_state(port);

	/* Increment Chunk Number to Send */
	pdmsg[port].chunk_number_to_send++;
	/* Start Chunk Sender Request Timer */
	pd_timer_enable(port, PR_TIMER_CHUNK_SENDER_REQUEST,
			PD_T_CHUNK_SENDER_REQUEST);
}

static void tch_wait_chunk_request_run(const int port)
{
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);

		if (PD_HEADER_EXT(rx_emsg[port].header)) {
			uint16_t exthdr;

			exthdr = GET_EXT_HEADER(pdmsg[port].rx_chk_buf[0]);
			if (PD_EXT_HEADER_REQ_CHUNK(exthdr)) {
				/*
				 * Chunk Request Received &
				 * Chunk Number = Chunk Number to Send
				 */
				if (PD_EXT_HEADER_CHUNK_NUM(exthdr) ==
				    pdmsg[port].chunk_number_to_send) {
					set_state_tch(port,
						TCH_CONSTRUCT_CHUNKED_MESSAGE);
				}
				/*
				 * Chunk Request Received &
				 * Chunk Number != Chunk Number to Send
				 */
				else {
					tch[port].error = ERR_TCH_CHUNKED;
					set_state_tch(port, TCH_REPORT_ERROR);
				}
				return;
			}
		}

		/*
		 * Other message received
		 */
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
	}
	/*
	 * ChunkSenderRequestTimer timeout
	 */
	else if (pd_timer_is_expired(port, PR_TIMER_CHUNK_SENDER_REQUEST))
		set_state_tch(port, TCH_MESSAGE_SENT);
}

static void tch_wait_chunk_request_exit(int port)
{
	pd_timer_disable(port, PR_TIMER_CHUNK_SENDER_REQUEST);
}

/*
 * TchMessageReceived
 */
static void tch_message_received_entry(const int port)
{
	print_current_tch_state(port);

	/* Pass message to chunked Rx */
	RCH_SET_FLAG(port, PRL_FLAGS_MSG_RECEIVED);

	/* Clear extended message objects */
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_XMIT);
		pe_report_discard(port);
	}
	pdmsg[port].data_objs = 0;
}

static void tch_message_received_run(const int port)
{
	set_state_tch(port, TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
}

/*
 * TchMessageSent
 */
static void tch_message_sent_entry(const int port)
{
	print_current_tch_state(port);

	/* Tell PE message was sent */
	pe_message_sent(port);

	/*
	 * Any message received and not in state TCH_Wait_Chunk_Request
	 * MUST be checked after notifying PE
	 */
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
		return;
	}



	set_state_tch(port, TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
}

/*
 * TchReportError
 */
static void tch_report_error_entry(const int port)
{
	print_current_tch_state(port);

	/* Report Error To Policy Engine */
	pe_report_error(port, tch[port].error, prl_tx[port].last_xmit_type);

	/*
	 * Any message received and not in state TCH_Wait_Chunk_Request
	 * MUST be checked after notifying PE
	 */
	if (TCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED)) {
		TCH_CLR_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		set_state_tch(port, TCH_MESSAGE_RECEIVED);
		return;
	}


	set_state_tch(port, TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
}
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */

/*
 * Protocol Layer Message Reception State Machine
 */
static void prl_rx_wait_for_phy_message(const int port, int evt)
{
	uint32_t header;
	uint8_t type;
	uint8_t cnt;
	int8_t msid;

	/*
	 * If PD3, wait for the RX chunk SM to copy the pdmsg into the extended
	 * buffer before overwriting pdmsg.
	 */
	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES) &&
	    RCH_CHK_FLAG(port, PRL_FLAGS_MSG_RECEIVED))
		return;

	/* If we don't have any message, just stop processing now. */
	if (!tcpm_has_pending_message(port) ||
	    tcpm_dequeue_message(port, pdmsg[port].rx_chk_buf, &header))
		return;

	rx_emsg[port].header = header;
	type = PD_HEADER_TYPE(header);
	cnt = PD_HEADER_CNT(header);
	msid = PD_HEADER_ID(header);
	prl_rx[port].sop = PD_HEADER_GET_SOP(header);

	/* Make sure an incorrect count doesn't overflow the chunk buffer */
	if (cnt > CHK_BUF_SIZE)
		cnt = CHK_BUF_SIZE;

	/* dump received packet content (only dump ping at debug level MAX) */
	if ((prl_debug_level >= DEBUG_LEVEL_2 && type != PD_CTRL_PING) ||
		prl_debug_level >= DEBUG_LEVEL_3) {
		int p;

		ccprintf("C%d: RECV %04x/%d ", port, header, cnt);
		for (p = 0; p < cnt; p++)
			ccprintf("[%d]%08x ", p, pdmsg[port].rx_chk_buf[p]);
		ccprintf("\n");
	}

	/*
	 * Ignore messages sent to the cable from our
	 * port partner if we aren't Vconn powered device.
	 */
	if (!IS_ENABLED(CONFIG_USB_CTVPD) &&
	    !IS_ENABLED(CONFIG_USB_VPD) &&
	    PD_HEADER_GET_SOP(header) != TCPCI_MSG_SOP &&
	    PD_HEADER_PROLE(header) == PD_PLUG_FROM_DFP_UFP)
		return;

	/* Handle incoming soft reset as special case */
	if (cnt == 0 && type == PD_CTRL_SOFT_RESET) {
		/* Clear MessageIdCounter */
		prl_tx[port].msg_id_counter[prl_rx[port].sop] = 0;
		/* Clear stored MessageID value */
		prl_rx[port].msg_id[prl_rx[port].sop] = -1;

		/* Soft Reset occurred */
		set_state_prl_tx(port, PRL_TX_PHY_LAYER_RESET);

		if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
			set_state_rch(port,
				RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER);
			set_state_tch(port,
				TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE);
		}

		/*
		 * Inform Policy Engine of Soft Reset. Note perform this after
		 * performing the protocol layer reset, otherwise we will lose
		 * the PE's outgoing ACCEPT message to the soft reset.
		 */
		pe_got_soft_reset(port);

		return;
	}

	/*
	 * Ignore if this is a duplicate message. Stop processing.
	 */
	if (prl_rx[port].msg_id[prl_rx[port].sop] == msid)
		return;

	/*
	 * Discard any pending tx message if this is
	 * not a ping message (length must be checked to verify this is a
	 * control message, rather than data)
	 */
	if ((cnt > 0) || (type != PD_CTRL_PING)) {
		/*
		 * Note: Spec dictates that we always go into
		 * PRL_Tx_Discard_Message upon receivng a message.  However, due
		 * to our TCPC architecture we may be receiving a transmit
		 * complete at the same time as a response so only do this if a
		 * message is pending.
		 */
		if (prl_tx[port].xmit_status != TCPC_TX_COMPLETE_SUCCESS ||
		    PRL_TX_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT))
			set_state_prl_tx(port, PRL_TX_DISCARD_MESSAGE);
	}

	/* Store Message Id */
	prl_rx[port].msg_id[prl_rx[port].sop] = msid;

	if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) {
		/* RTR Chunked Message Router States. */
		/*
		 * Received Ping from Protocol Layer
		 */
		if (cnt == 0 && type == PD_CTRL_PING) {
			/* NOTE: RTR_PING State embedded here. */
			rx_emsg[port].len = 0;
			pe_message_received(port);
			return;
		}
		/*
		 * Message (not Ping) Received from
		 * Protocol Layer & Doing Tx Chunks
		 *
		 * Also, handle the case where a message has been
		 * queued for sending but a message is received before
		 * tch_wait_for_message_request_from_pe has been run
		 */
		else if (tch_get_state(port) !=
				TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE ||
			 TCH_CHK_FLAG(port, PRL_FLAGS_MSG_XMIT)) {
			/* NOTE: RTR_TX_CHUNKS State embedded here. */
			/*
			 * Send Message to Tx Chunk
			 * Chunk State Machine
			 */
			TCH_SET_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		}
		/*
		 * Message (not Ping) Received from
		 * Protocol Layer & Not Doing Tx Chunks
		 */
		else {
			/* NOTE: RTR_RX_CHUNKS State embedded here. */
			/*
			 * Send Message to Rx
			 * Chunk State Machine
			 */
			RCH_SET_FLAG(port, PRL_FLAGS_MSG_RECEIVED);
		}
	} else {
		/* Copy chunk to extended buffer */
		copy_chunk_to_ext(port);
		/* Send message to Policy Engine */
		pe_message_received(port);
	}

	task_wake(PD_PORT_TO_TASK_ID(port));
}

/* All necessary Protocol Transmit States (Section 6.11.2.2) */
static __const_data const struct usb_state prl_tx_states[] = {
	[PRL_TX_PHY_LAYER_RESET] = {
		.entry  = prl_tx_phy_layer_reset_entry,
	},
	[PRL_TX_WAIT_FOR_MESSAGE_REQUEST] = {
		.entry  = prl_tx_wait_for_message_request_entry,
		.run    = prl_tx_wait_for_message_request_run,
	},
	[PRL_TX_LAYER_RESET_FOR_TRANSMIT] = {
		.entry  = prl_tx_layer_reset_for_transmit_entry,
		.run    = prl_tx_layer_reset_for_transmit_run,
	},
	[PRL_TX_WAIT_FOR_PHY_RESPONSE] = {
		.entry  = prl_tx_wait_for_phy_response_entry,
		.run    = prl_tx_wait_for_phy_response_run,
		.exit   = prl_tx_wait_for_phy_response_exit,
	},
#ifdef CONFIG_USB_PD_REV30
	[PRL_TX_SRC_SOURCE_TX] = {
		.entry  = prl_tx_src_source_tx_entry,
		.run    = prl_tx_src_source_tx_run,
	},
	[PRL_TX_SNK_START_AMS] = {
		.entry  = prl_tx_snk_start_ams_entry,
		.run    = prl_tx_snk_start_ams_run,
	},
#endif /* CONFIG_USB_PD_REV30 */
	[PRL_TX_SRC_PENDING] = {
		.entry  = prl_tx_src_pending_entry,
		.run    = prl_tx_src_pending_run,
		.exit	= prl_tx_src_pending_exit,
	},
	[PRL_TX_SNK_PENDING] = {
		.entry  = prl_tx_snk_pending_entry,
		.run    = prl_tx_snk_pending_run,
	},
	[PRL_TX_DISCARD_MESSAGE] = {
		.entry  = prl_tx_discard_message_entry,
	},
};

/* All necessary Protocol Hard Reset States (Section 6.11.2.4) */
static __const_data const struct usb_state prl_hr_states[] = {
	[PRL_HR_WAIT_FOR_REQUEST] = {
		.entry  = prl_hr_wait_for_request_entry,
		.run    = prl_hr_wait_for_request_run,
	},
	[PRL_HR_RESET_LAYER] = {
		.entry  = prl_hr_reset_layer_entry,
		.run    = prl_hr_reset_layer_run,
	},
	[PRL_HR_WAIT_FOR_PHY_HARD_RESET_COMPLETE] = {
		.entry  = prl_hr_wait_for_phy_hard_reset_complete_entry,
		.run    = prl_hr_wait_for_phy_hard_reset_complete_run,
		.exit	= prl_hr_wait_for_phy_hard_reset_complete_exit,
	},
	[PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE] = {
		.entry  = prl_hr_wait_for_pe_hard_reset_complete_entry,
		.run    = prl_hr_wait_for_pe_hard_reset_complete_run,
		.exit   = prl_hr_wait_for_pe_hard_reset_complete_exit,
	},
};

/* All necessary Chunked Rx states (Section 6.11.2.1.2) */
__maybe_unused static const struct usb_state rch_states[] = {
#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	[RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER] = {
		.entry  = rch_wait_for_message_from_protocol_layer_entry,
		.run    = rch_wait_for_message_from_protocol_layer_run,
	},
	[RCH_PASS_UP_MESSAGE] = {
		.entry  = rch_pass_up_message_entry,
	},
	[RCH_PROCESSING_EXTENDED_MESSAGE] = {
		.entry  = rch_processing_extended_message_entry,
		.run    = rch_processing_extended_message_run,
	},
	[RCH_REQUESTING_CHUNK] = {
		.entry  = rch_requesting_chunk_entry,
		.run    = rch_requesting_chunk_run,
	},
	[RCH_WAITING_CHUNK] = {
		.entry  = rch_waiting_chunk_entry,
		.run    = rch_waiting_chunk_run,
		.exit	= rch_waiting_chunk_exit,
	},
	[RCH_REPORT_ERROR] = {
		.entry  = rch_report_error_entry,
		.run    = rch_report_error_run,
	},
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */
};

/* All necessary Chunked Tx states (Section 6.11.2.1.3) */
__maybe_unused static const struct usb_state tch_states[] = {
#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	[TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE] = {
		.entry  = tch_wait_for_message_request_from_pe_entry,
		.run    = tch_wait_for_message_request_from_pe_run,
	},
	[TCH_WAIT_FOR_TRANSMISSION_COMPLETE] = {
		.entry  = tch_wait_for_transmission_complete_entry,
		.run    = tch_wait_for_transmission_complete_run,
	},
	[TCH_CONSTRUCT_CHUNKED_MESSAGE] = {
		.entry  = tch_construct_chunked_message_entry,
		.run    = tch_construct_chunked_message_run,
	},
	[TCH_SENDING_CHUNKED_MESSAGE] = {
		.entry  = tch_sending_chunked_message_entry,
		.run    = tch_sending_chunked_message_run,
	},
	[TCH_WAIT_CHUNK_REQUEST] = {
		.entry  = tch_wait_chunk_request_entry,
		.run    = tch_wait_chunk_request_run,
		.exit	= tch_wait_chunk_request_exit,
	},
	[TCH_MESSAGE_RECEIVED] = {
		.entry  = tch_message_received_entry,
		.run    = tch_message_received_run,
	},
	[TCH_MESSAGE_SENT] = {
		.entry  = tch_message_sent_entry,
	},
	[TCH_REPORT_ERROR] = {
		.entry  = tch_report_error_entry,
	},
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */
};

#ifdef TEST_BUILD

const struct test_sm_data test_prl_sm_data[] = {
	{
		.base = prl_tx_states,
		.size = ARRAY_SIZE(prl_tx_states),
		.names = prl_tx_state_names,
		.names_size = ARRAY_SIZE(prl_tx_state_names),
	},
	{
		.base = prl_hr_states,
		.size = ARRAY_SIZE(prl_hr_states),
		.names = prl_hr_state_names,
		.names_size = ARRAY_SIZE(prl_hr_state_names),
	},
#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
	{
		.base = rch_states,
		.size = ARRAY_SIZE(rch_states),
		.names = rch_state_names,
		.names_size = ARRAY_SIZE(rch_state_names),
	},
	{
		.base = tch_states,
		.size = ARRAY_SIZE(tch_states),
		.names = tch_state_names,
		.names_size = ARRAY_SIZE(tch_state_names),
	},
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */
};
BUILD_ASSERT(ARRAY_SIZE(prl_tx_states) == ARRAY_SIZE(prl_tx_state_names));
BUILD_ASSERT(ARRAY_SIZE(prl_hr_states) == ARRAY_SIZE(prl_hr_state_names));
#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES
BUILD_ASSERT(ARRAY_SIZE(rch_states) == ARRAY_SIZE(rch_state_names));
BUILD_ASSERT(ARRAY_SIZE(tch_states) == ARRAY_SIZE(tch_state_names));
#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */
const int test_prl_sm_data_size = ARRAY_SIZE(test_prl_sm_data);
#endif
