summaryrefslogtreecommitdiff
path: root/common/usbc
diff options
context:
space:
mode:
authorSam Hurst <shurst@google.com>2019-04-18 12:47:52 -0700
committerCommit Bot <commit-bot@chromium.org>2019-07-30 21:25:24 +0000
commit2e3109ec6b964776e7c5bd12f1fc058ca96d33f7 (patch)
tree23ea5eaf67e50094ae683f5d16f09a332a1303c4 /common/usbc
parentb99b1b10c3f2070e3fbd8d5cb2d0927f04f642ea (diff)
downloadchrome-ec-2e3109ec6b964776e7c5bd12f1fc058ca96d33f7.tar.gz
type-c: USB Type-C State Machine based on Release 1.4 of the spec.
Implements DRP with Accessory, and Try.SRC as detailed in Release 1.4 of the USB Type-C specification. BUG=b:130895206 BRANCH=none TEST=manual Used Atlas device to verify that it could be charged from PD and none PD charges at 5V/3A. Attached USB dock and verifed access to USB Thumb drive. Performed same tests on Hatch Port 0 on Hatch was used to run this CL, merged with PD functionality, on the PD2.0 Compliance tester. All tests pass except for a few physical layer tests. The test report has been added to the bug. Change-Id: Ic4869e20e5b4c2ba6c827d92e40c70f3140f2518 Signed-off-by: Sam Hurst <shurst@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1574667 Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-by: Aseda Aboagye <aaboagye@chromium.org> Tested-by: Sam Hurst <shurst@google.com> Commit-Queue: Sam Hurst <shurst@google.com>
Diffstat (limited to 'common/usbc')
-rw-r--r--common/usbc/build.mk22
-rw-r--r--common/usbc/usb_pe_ctvpd_sm.c207
-rw-r--r--common/usbc/usb_prl_sm.c1901
-rw-r--r--common/usbc/usb_sm.c177
-rw-r--r--common/usbc/usb_tc_ctvpd_sm.c1653
-rw-r--r--common/usbc/usb_tc_drp_acc_trysrc_sm.c1724
-rw-r--r--common/usbc/usb_tc_vpd_sm.c335
-rw-r--r--common/usbc/usbc_task.c298
8 files changed, 6317 insertions, 0 deletions
diff --git a/common/usbc/build.mk b/common/usbc/build.mk
new file mode 100644
index 0000000000..e1a90cdb32
--- /dev/null
+++ b/common/usbc/build.mk
@@ -0,0 +1,22 @@
+# 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.
+
+# Build for USB Type-C and Power Delivery
+
+# Note that this variable includes the trailing "/"
+_usbc_dir:=$(dir $(lastword $(MAKEFILE_LIST)))
+
+ifneq ($(CONFIG_USB_SM_FRAMEWORK),)
+all-obj-$(CONFIG_USB_SM_FRAMEWORK)+=$(_usbc_dir)usb_sm.o
+all-obj-$(CONFIG_USB_TYPEC_SM)+=$(_usbc_dir)usbc_task.o
+all-obj-$(CONFIG_USB_PRL_SM)+=$(_usbc_dir)usb_prl_sm.o
+ifneq ($(CONFIG_USB_PE_SM),)
+all-obj-$(CONFIG_USB_TYPEC_VPD)+=$(_usbc_dir)usb_pe_ctvpd_sm.o
+all-obj-$(CONFIG_USB_TYPEC_CTVPD)+=$(_usbc_dir)usb_pe_ctvpd_sm.o
+endif
+all-obj-$(CONFIG_USB_TYPEC_VPD)+=$(_usbc_dir)usb_tc_vpd_sm.o
+all-obj-$(CONFIG_USB_TYPEC_CTVPD)+=$(_usbc_dir)usb_tc_ctvpd_sm.o
+all-obj-$(CONFIG_USB_TYPEC_DRP_ACC_TRYSRC)+=\
+ $(_usbc_dir)usb_tc_drp_acc_trysrc_sm.o
+endif
diff --git a/common/usbc/usb_pe_ctvpd_sm.c b/common/usbc/usb_pe_ctvpd_sm.c
new file mode 100644
index 0000000000..fd4cbcbf0e
--- /dev/null
+++ b/common/usbc/usb_pe_ctvpd_sm.c
@@ -0,0 +1,207 @@
+/* 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 "common.h"
+#include "console.h"
+#include "task.h"
+#include "util.h"
+#include "usb_pd.h"
+#include "usb_pd_tcpm.h"
+#include "usb_pe_sm.h"
+#include "usb_prl_sm.h"
+#include "usb_tc_sm.h"
+#include "usb_emsg.h"
+#include "usb_sm.h"
+
+/* USB Policy Engine Charge-Through VCONN Powered Device module */
+
+/* Policy Engine Flags */
+#define PE_FLAGS_MSG_RECEIVED (1 << 0)
+
+enum l_state {
+ PE_INIT,
+ PE_RUN,
+ PE_PAUSED
+};
+
+static enum l_state local_state = PE_INIT;
+
+/**
+ * This is the PE Port object that contains information needed to
+ * implement a VCONN and Charge-Through VCONN Powered Device.
+ */
+static struct policy_engine {
+ /*
+ * struct sm_obj must be first. This is the state machine
+ * object that keeps track of the current and last state
+ * of the state machine.
+ */
+ struct sm_obj obj;
+ /* port flags, see PE_FLAGS_* */
+ uint32_t flags;
+} pe[CONFIG_USB_PD_PORT_COUNT];
+
+/* Policy Engine states */
+DECLARE_STATE(pe, request, WITH_RUN, NOOP);
+
+void pe_init(int port)
+{
+ pe[port].flags = 0;
+ sm_init_state(port, PE_OBJ(port), pe_request);
+}
+
+void usbc_policy_engine(int port, int evt, int en)
+{
+ switch (local_state) {
+ case PE_INIT:
+ pe_init(port);
+ local_state = PE_RUN;
+ /* fall through */
+ case PE_RUN:
+ if (!en) {
+ local_state = PE_PAUSED;
+ break;
+ }
+
+ sm_run_state_machine(port, PE_OBJ(port), SM_RUN_SIG);
+ break;
+ case PE_PAUSED:
+ if (en)
+ local_state = PE_INIT;
+ break;
+ }
+}
+
+void pe_pass_up_message(int port)
+{
+ pe[port].flags |= PE_FLAGS_MSG_RECEIVED;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void pe_hard_reset_sent(int port)
+{
+ /* Do nothing */
+}
+
+void pe_got_hard_reset(int port)
+{
+ /* Do nothing */
+}
+
+void pe_report_error(int port, enum pe_error e)
+{
+ /* Do nothing */
+}
+
+void pe_got_soft_reset(int port)
+{
+ /* Do nothing */
+}
+
+void pe_message_sent(int port)
+{
+ /* Do nothing */
+}
+
+static int pe_request(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*pe_request_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int pe_request_entry(int port)
+{
+ return 0;
+}
+
+static int pe_request_run(int port)
+{
+ uint32_t *payload = (uint32_t *)emsg[port].buf;
+ uint32_t header = emsg[port].header;
+ uint32_t vdo = payload[0];
+
+ if (pe[port].flags & PE_FLAGS_MSG_RECEIVED) {
+ pe[port].flags &= ~PE_FLAGS_MSG_RECEIVED;
+
+ /*
+ * Only support Structured VDM Discovery
+ * Identity message
+ */
+
+ if (PD_HEADER_TYPE(header) != PD_DATA_VENDOR_DEF)
+ return 0;
+
+ if (PD_HEADER_CNT(header) == 0)
+ return 0;
+
+ if (!PD_VDO_SVDM(vdo))
+ return 0;
+
+ if (PD_VDO_CMD(vdo) != CMD_DISCOVER_IDENT)
+ return 0;
+
+#ifdef CONFIG_USB_TYPEC_CTVPD
+ /*
+ * We have a valid DISCOVER IDENTITY message.
+ * Attempt to reset support timer
+ */
+ tc_reset_support_timer(port);
+#endif
+ /* Prepare to send ACK */
+
+ /* VDM Header */
+ payload[0] = VDO(
+ USB_VID_GOOGLE,
+ 1, /* Structured VDM */
+ VDO_SVDM_VERS(1) |
+ VDO_CMDT(CMDT_RSP_ACK) |
+ CMD_DISCOVER_IDENT);
+
+ /* ID Header VDO */
+ payload[1] = VDO_IDH(
+ 0, /* Not a USB Host */
+ 1, /* Capable of being enumerated as USB Device */
+ IDH_PTYPE_VPD,
+ 0, /* Modal Operation Not Supported */
+ USB_VID_GOOGLE);
+
+ /* Cert State VDO */
+ payload[2] = 0;
+
+ /* Product VDO */
+ payload[3] = VDO_PRODUCT(
+ CONFIG_USB_PID,
+ USB_BCD_DEVICE);
+
+ /* VPD VDO */
+ payload[4] = VDO_VPD(
+ VPD_HW_VERSION,
+ VPD_FW_VERSION,
+ VPD_MAX_VBUS_20V,
+ VPD_VBUS_IMP(VPD_VBUS_IMPEDANCE),
+ VPD_GND_IMP(VPD_GND_IMPEDANCE),
+#ifdef CONFIG_USB_TYPEC_CTVPD
+ VPD_CTS_SUPPORTED
+#else
+ VPD_CTS_NOT_SUPPORTED
+#endif
+ );
+
+ /* 20 bytes, 5 data objects */
+ emsg[port].len = 20;
+
+ /* Set to highest revision supported by both ports. */
+ prl_set_rev(port, (PD_HEADER_REV(header) > PD_REV30) ?
+ PD_REV30 : PD_HEADER_REV(header));
+
+ /* Send the ACK */
+ prl_send_data_msg(port, TCPC_TX_SOP_PRIME,
+ PD_DATA_VENDOR_DEF);
+ }
+
+ return 0;
+}
diff --git a/common/usbc/usb_prl_sm.c b/common/usbc/usb_prl_sm.c
new file mode 100644
index 0000000000..f013497cc4
--- /dev/null
+++ b/common/usbc/usb_prl_sm.c
@@ -0,0 +1,1901 @@
+/* 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 "ec_commands.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "registers.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "tcpm.h"
+#include "util.h"
+#include "usb_charge.h"
+#include "usb_mux.h"
+#include "usb_pd.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"
+#include "version.h"
+
+/* Protocol Layer Flags */
+#define PRL_FLAGS_TX_COMPLETE BIT(0)
+#define PRL_FLAGS_START_AMS BIT(1)
+#define PRL_FLAGS_END_AMS BIT(2)
+#define PRL_FLAGS_TX_ERROR BIT(3)
+#define PRL_FLAGS_PE_HARD_RESET BIT(4)
+#define PRL_FLAGS_HARD_RESET_COMPLETE BIT(5)
+#define PRL_FLAGS_PORT_PARTNER_HARD_RESET BIT(6)
+#define PRL_FLAGS_MSG_XMIT BIT(7)
+#define PRL_FLAGS_MSG_RECEIVED BIT(8)
+#define PRL_FLAGS_ABORT BIT(9)
+#define PRL_FLAGS_CHUNKING BIT(10)
+
+/* PD counter definitions */
+#define PD_MESSAGE_ID_COUNT 7
+
+#define RCH_OBJ(port) (SM_OBJ(rch[port]))
+#define TCH_OBJ(port) (SM_OBJ(tch[port]))
+#define PRL_TX_OBJ(port) (SM_OBJ(prl_tx[port]))
+#define PRL_HR_OBJ(port) (SM_OBJ(prl_hr[port]))
+
+#define RCH_TEST_OBJ(port) (SM_OBJ(rch[(port)].obj))
+#define TCH_TEST_OBJ(port) (SM_OBJ(tch[(port)].obj))
+#define PRL_TX_TEST_OBJ(port) (SM_OBJ(prl_tx[(port)].obj))
+#define PRL_HR_TEST_OBJ(port) (SM_OBJ(prl_hr[(port)].obj))
+
+static enum sm_local_state local_state[CONFIG_USB_PD_PORT_COUNT] = {SM_INIT};
+
+/* Chunked Rx State Machine Object */
+static struct rx_chunked {
+ /* struct sm_obj must be first. */
+ struct sm_obj obj;
+ /* state id */
+ enum rch_state_id state_id;
+ /* PRL_FLAGS */
+ uint32_t flags;
+ /* protocol timer */
+ uint64_t chunk_sender_response_timer;
+} rch[CONFIG_USB_PD_PORT_COUNT];
+
+/* Chunked Tx State Machine Object */
+static struct tx_chunked {
+ /* struct sm_obj must be first. */
+ struct sm_obj obj;
+ /* state id */
+ enum tch_state_id state_id;
+ /* state machine flags */
+ uint32_t flags;
+ /* protocol timer */
+ uint64_t chunk_sender_request_timer;
+} tch[CONFIG_USB_PD_PORT_COUNT];
+
+/* Message Reception State Machine Object */
+static struct protocol_layer_rx {
+ /* message ids for all valid port partners */
+ int msg_id[NUM_XMIT_TYPES];
+} prl_rx[CONFIG_USB_PD_PORT_COUNT];
+
+/* Message Transmission State Machine Object */
+static struct protocol_layer_tx {
+ /* struct sm_obj must be first. */
+ struct sm_obj obj;
+ /* state id */
+ enum prl_tx_state_id state_id;
+ /* state machine flags */
+ uint32_t flags;
+ /* protocol timer */
+ uint64_t sink_tx_timer;
+ /* tcpc transmit timeout */
+ uint64_t tcpc_tx_timeout;
+ /* Last SOP* we transmitted to */
+ uint8_t sop;
+ /* message id counters for all 6 port partners */
+ uint32_t msg_id_counter[NUM_XMIT_TYPES];
+ /* message retry counter */
+ uint32_t retry_counter;
+ /* transmit status */
+ int xmit_status;
+} prl_tx[CONFIG_USB_PD_PORT_COUNT];
+
+/* Hard Reset State Machine Object */
+static struct protocol_hard_reset {
+ /* struct sm_obj must be first. */
+ struct sm_obj obj;
+ /* state id */
+ enum prl_hr_state_id state_id;
+ /* state machine flags */
+ uint32_t flags;
+ /* protocol timer */
+ uint64_t hard_reset_complete_timer;
+} prl_hr[CONFIG_USB_PD_PORT_COUNT];
+
+/* Chunking Message Object */
+static struct pd_message {
+ /* message status flags */
+ uint32_t status_flags;
+
+ /* SOP* */
+ enum tcpm_transmit_type xmit_type;
+ /* type of message */
+ uint8_t msg_type;
+ /* extended message */
+ uint8_t ext;
+ /* PD revision */
+ enum pd_rev_type rev;
+ /* Number of 32-bit objects in chk_buf */
+ uint16_t data_objs;
+ /* temp chunk buffer */
+ uint32_t chk_buf[7];
+ uint32_t chunk_number_expected;
+ uint32_t num_bytes_received;
+ uint32_t chunk_number_to_send;
+ uint32_t send_offset;
+} pdmsg[CONFIG_USB_PD_PORT_COUNT];
+
+struct extended_msg emsg[CONFIG_USB_PD_PORT_COUNT];
+
+/* Protocol Layer States */
+/* Common Protocol Layer Message Transmission */
+static void prl_tx_construct_message(int port);
+
+DECLARE_STATE(prl, tx_phy_layer_reset, WITH_RUN, NOOP);
+DECLARE_STATE(prl, tx_wait_for_message_request, WITH_RUN, NOOP);
+DECLARE_STATE(prl, tx_layer_reset_for_transmit, WITH_RUN, NOOP);
+DECLARE_STATE(prl, tx_wait_for_phy_response, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(prl, tx_src_source_tx, WITH_RUN, NOOP);
+DECLARE_STATE(prl, tx_snk_start_ams, WITH_RUN, NOOP);
+
+/* Source Protocol Layser Message Transmission */
+DECLARE_STATE(prl, tx_src_pending, WITH_RUN, NOOP);
+
+/* Sink Protocol Layer Message Transmission */
+DECLARE_STATE(prl, tx_snk_pending, WITH_RUN, NOOP);
+DECLARE_STATE(prl, tx_discard_message, WITH_RUN, NOOP);
+
+/* Protocol Layer Message Reception */
+static int prl_rx_wait_for_phy_message(int port, int evt);
+
+/* Hard Reset Operation */
+DECLARE_STATE(prl, hr_wait_for_request, WITH_RUN, NOOP);
+DECLARE_STATE(prl, hr_reset_layer, WITH_RUN, NOOP);
+DECLARE_STATE(prl, hr_wait_for_phy_hard_reset_complete, WITH_RUN, NOOP);
+DECLARE_STATE(prl, hr_wait_for_pe_hard_reset_complete, WITH_RUN, WITH_EXIT);
+
+/* Chunked Rx */
+DECLARE_STATE(rch, wait_for_message_from_protocol_layer, WITH_RUN, NOOP);
+DECLARE_STATE(rch, processing_extended_message, WITH_RUN, NOOP);
+DECLARE_STATE(rch, requesting_chunk, WITH_RUN, NOOP);
+DECLARE_STATE(rch, waiting_chunk, WITH_RUN, NOOP);
+DECLARE_STATE(rch, report_error, WITH_RUN, NOOP);
+
+/* Chunked Tx */
+DECLARE_STATE(tch, wait_for_message_request_from_pe, WITH_RUN, NOOP);
+DECLARE_STATE(tch, wait_for_transmission_complete, WITH_RUN, NOOP);
+DECLARE_STATE(tch, construct_chunked_message, WITH_RUN, NOOP);
+DECLARE_STATE(tch, sending_chunked_message, WITH_RUN, NOOP);
+DECLARE_STATE(tch, wait_chunk_request, WITH_RUN, NOOP);
+DECLARE_STATE(tch, message_received, WITH_RUN, NOOP);
+
+void pd_transmit_complete(int port, int status)
+{
+ prl_tx[port].xmit_status = status;
+}
+
+void pd_execute_hard_reset(int port)
+{
+ /* Only allow async. function calls when state machine is running */
+ if (local_state[port] != SM_RUN)
+ return;
+
+ prl_hr[port].flags |= PRL_FLAGS_PORT_PARTNER_HARD_RESET;
+ sm_set_state(port, PRL_HR_OBJ(port), prl_hr_reset_layer);
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void prl_execute_hard_reset(int port)
+{
+ /* Only allow async. function calls when state machine is running */
+ if (local_state[port] != SM_RUN)
+ return;
+
+ prl_hr[port].flags |= PRL_FLAGS_PE_HARD_RESET;
+ sm_set_state(port, PRL_HR_OBJ(port), prl_hr_reset_layer);
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void prl_init(int port)
+{
+ int i;
+
+ prl_tx[port].flags = 0;
+ prl_tx[port].xmit_status = TCPC_TX_UNSET;
+
+ tch[port].flags = 0;
+ rch[port].flags = 0;
+
+ /*
+ * Initialize to highest revision supported. If the port partner
+ * doesn't support this revision, the Protocol Engine will lower
+ * this value to the revision supported by the port partner.
+ */
+ pdmsg[port].rev = PD_REV30;
+ pdmsg[port].status_flags = 0;
+
+ prl_hr[port].flags = 0;
+
+ for (i = 0; i < NUM_XMIT_TYPES; i++) {
+ prl_rx[port].msg_id[i] = -1;
+ prl_tx[port].msg_id_counter[i] = 0;
+ }
+
+ sm_init_state(port, PRL_TX_OBJ(port), prl_tx_phy_layer_reset);
+ sm_init_state(port, RCH_OBJ(port),
+ rch_wait_for_message_from_protocol_layer);
+ sm_init_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+ sm_init_state(port, PRL_HR_OBJ(port), prl_hr_wait_for_request);
+}
+
+enum rch_state_id get_rch_state_id(int port)
+{
+ return rch[port].state_id;
+}
+
+enum tch_state_id get_tch_state_id(int port)
+{
+ return tch[port].state_id;
+}
+
+enum prl_tx_state_id get_prl_tx_state_id(int port)
+{
+ return prl_tx[port].state_id;
+}
+
+enum prl_hr_state_id get_prl_hr_state_id(int port)
+{
+ return prl_hr[port].state_id;
+}
+
+void prl_start_ams(int port)
+{
+ prl_tx[port].flags |= PRL_FLAGS_START_AMS;
+}
+
+void prl_end_ams(int port)
+{
+ prl_tx[port].flags |= PRL_FLAGS_END_AMS;
+}
+
+void prl_hard_reset_complete(int port)
+{
+ prl_hr[port].flags |= PRL_FLAGS_HARD_RESET_COMPLETE;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void prl_send_ctrl_msg(int port,
+ enum tcpm_transmit_type type,
+ enum pd_ctrl_msg_type msg)
+{
+ pdmsg[port].xmit_type = type;
+ pdmsg[port].msg_type = msg;
+ pdmsg[port].ext = 0;
+ emsg[port].len = 0;
+
+ tch[port].flags |= PRL_FLAGS_MSG_XMIT;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void prl_send_data_msg(int port,
+ enum tcpm_transmit_type type,
+ enum pd_data_msg_type msg)
+{
+ pdmsg[port].xmit_type = type;
+ pdmsg[port].msg_type = msg;
+ pdmsg[port].ext = 0;
+
+ tch[port].flags |= PRL_FLAGS_MSG_XMIT;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void prl_send_ext_data_msg(int port,
+ enum tcpm_transmit_type type,
+ enum pd_ext_msg_type msg)
+{
+ pdmsg[port].xmit_type = type;
+ pdmsg[port].msg_type = msg;
+ pdmsg[port].ext = 1;
+
+ tch[port].flags |= PRL_FLAGS_MSG_XMIT;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+}
+
+void prl_reset(int port)
+{
+ local_state[port] = SM_INIT;
+}
+
+void usbc_protocol_layer(int port, int evt, int en)
+{
+ switch (local_state[port]) {
+ case SM_INIT:
+ prl_init(port);
+ local_state[port] = SM_RUN;
+ /* fall through */
+ case SM_RUN:
+ /* If disabling, wait until message is sent. */
+ if (!en && tch[port].state_id ==
+ TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE) {
+ /* Disable RX */
+#if defined(CONFIG_USB_TYPEC_CTVPD) || defined(CONFIG_USB_TYPEC_VPD)
+ vpd_rx_enable(0);
+#else
+ tcpm_set_rx_enable(port, 0);
+#endif
+ local_state[port] = SM_PAUSED;
+ break;
+ }
+
+ /* Run Protocol Layer Message Reception */
+ prl_rx_wait_for_phy_message(port, evt);
+
+ /* Run RX Chunked state machine */
+ sm_run_state_machine(port, RCH_OBJ(port), SM_RUN_SIG);
+
+ /* Run TX Chunked state machine */
+ sm_run_state_machine(port, TCH_OBJ(port), SM_RUN_SIG);
+
+ /* Run Protocol Layer Message Transmission state machine */
+ sm_run_state_machine(port, PRL_TX_OBJ(port), SM_RUN_SIG);
+
+ /* Run Protocol Layer Hard Reset state machine */
+ sm_run_state_machine(port, PRL_HR_OBJ(port), SM_RUN_SIG);
+ break;
+ case SM_PAUSED:
+ if (en)
+ local_state[port] = SM_INIT;
+ break;
+ }
+}
+
+enum sm_local_state prl_get_local_state(int port)
+{
+ return local_state[port];
+}
+
+void prl_set_rev(int port, enum pd_rev_type rev)
+{
+ pdmsg[port].rev = rev;
+}
+
+enum pd_rev_type prl_get_rev(int port)
+{
+ return pdmsg[port].rev;
+}
+
+/* Common Protocol Layer Message Transmission */
+static int prl_tx_phy_layer_reset(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_phy_layer_reset_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_phy_layer_reset_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_PHY_LAYER_RESET;
+
+#if defined(CONFIG_USB_TYPEC_CTVPD) || defined(CONFIG_USB_TYPEC_VPD)
+ vpd_rx_enable(1);
+#else
+ tcpm_init(port);
+ tcpm_set_rx_enable(port, 1);
+#endif
+
+ return 0;
+}
+
+static int prl_tx_phy_layer_reset_run(int port)
+{
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_message_request);
+ return 0;
+}
+
+static int prl_tx_wait_for_message_request(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_wait_for_message_request_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_wait_for_message_request_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_WAIT_FOR_MESSAGE_REQUEST;
+
+ /* Reset RetryCounter */
+ prl_tx[port].retry_counter = 0;
+
+ return 0;
+}
+
+static int prl_tx_wait_for_message_request_run(int port)
+{
+ if (prl_tx[port].flags & PRL_FLAGS_MSG_XMIT) {
+ prl_tx[port].flags &= ~PRL_FLAGS_MSG_XMIT;
+ /*
+ * Soft Reset Message Message pending
+ */
+ if ((pdmsg[port].msg_type == PD_CTRL_SOFT_RESET) &&
+ (emsg[port].len == 0)) {
+ sm_set_state(port, PRL_TX_OBJ(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);
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_phy_response);
+ }
+
+ return 0;
+ } else if ((pdmsg[port].rev == PD_REV30) &&
+ (prl_tx[port].flags &
+ (PRL_FLAGS_START_AMS | PRL_FLAGS_END_AMS))) {
+ if (tc_get_power_role(port) == PD_ROLE_SOURCE) {
+ /*
+ * Start of AMS notification received from
+ * Policy Engine
+ */
+ if (prl_tx[port].flags & PRL_FLAGS_START_AMS) {
+ prl_tx[port].flags &= ~PRL_FLAGS_START_AMS;
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_src_source_tx);
+ return 0;
+ }
+ /*
+ * End of AMS notification received from
+ * Policy Engine
+ */
+ else if (prl_tx[port].flags & PRL_FLAGS_END_AMS) {
+ prl_tx[port].flags &= ~PRL_FLAGS_END_AMS;
+ /* Set Rp = SinkTxOk */
+ tcpm_select_rp_value(port, SINK_TX_OK);
+ tcpm_set_cc(port, TYPEC_CC_RP);
+ prl_tx[port].retry_counter = 0;
+ prl_tx[port].flags = 0;
+ }
+ } else {
+ if (prl_tx[port].flags & PRL_FLAGS_START_AMS) {
+ prl_tx[port].flags &= ~PRL_FLAGS_START_AMS;
+ /*
+ * First Message in AMS notification
+ * received from Policy Engine.
+ */
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_snk_start_ams);
+ return 0;
+ }
+ }
+ }
+
+ return SM_RUN_SUPER;
+}
+
+static void increment_msgid_counter(int port)
+{
+ prl_tx[port].msg_id_counter[prl_tx[port].sop] =
+ (prl_tx[port].msg_id_counter[prl_tx[port].sop] + 1) &
+ PD_MESSAGE_ID_COUNT;
+}
+
+/*
+ * PrlTxDiscard
+ */
+static int prl_tx_discard_message(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_discard_message_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_discard_message_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_DISCARD_MESSAGE;
+
+ /* Increment msgidCounter */
+ increment_msgid_counter(port);
+ sm_set_state(port, PRL_TX_OBJ(port), prl_tx_phy_layer_reset);
+
+ return 0;
+}
+
+static int prl_tx_discard_message_run(int port)
+{
+ return SM_RUN_SUPER;
+}
+
+/*
+ * PrlTxSrcSourceTx
+ */
+static int prl_tx_src_source_tx(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_src_source_tx_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_src_source_tx_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_SRC_SOURCE_TX;
+
+ /* Set Rp = SinkTxNG */
+ tcpm_select_rp_value(port, SINK_TX_NG);
+ tcpm_set_cc(port, TYPEC_CC_RP);
+
+ return 0;
+}
+
+static int prl_tx_src_source_tx_run(int port)
+{
+ if (prl_tx[port].flags & PRL_FLAGS_MSG_XMIT) {
+ prl_tx[port].flags &= ~PRL_FLAGS_MSG_XMIT;
+
+ sm_set_state(port, PRL_TX_OBJ(port), prl_tx_src_pending);
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * PrlTxSnkStartAms
+ */
+static int prl_tx_snk_start_ams(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_snk_start_ams_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_snk_start_ams_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_SNK_START_OF_AMS;
+ return 0;
+}
+
+static int prl_tx_snk_start_ams_run(int port)
+{
+ if (prl_tx[port].flags & PRL_FLAGS_MSG_XMIT) {
+ prl_tx[port].flags &= ~PRL_FLAGS_MSG_XMIT;
+
+ sm_set_state(port, PRL_TX_OBJ(port), prl_tx_snk_pending);
+ return 0;
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * PrlTxLayerResetForTransmit
+ */
+static int prl_tx_layer_reset_for_transmit(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_layer_reset_for_transmit_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_layer_reset_for_transmit_entry(int port)
+{
+ int i;
+
+ prl_tx[port].state_id = PRL_TX_LAYER_RESET_FOR_TRANSMIT;
+
+ /* Reset MessageIdCounters */
+ for (i = 0; i < NUM_XMIT_TYPES; i++)
+ prl_tx[port].msg_id_counter[i] = 0;
+
+ return 0;
+}
+
+static int prl_tx_layer_reset_for_transmit_run(int port)
+{
+ /* NOTE: PRL_Tx_Construct_Message State embedded here */
+ prl_tx_construct_message(port);
+ sm_set_state(port, PRL_TX_OBJ(port), prl_tx_wait_for_phy_response);
+
+ return 0;
+}
+
+static void prl_tx_construct_message(int port)
+{
+ uint32_t header = PD_HEADER(
+ pdmsg[port].msg_type,
+ tc_get_power_role(port),
+ tc_get_data_role(port),
+ prl_tx[port].msg_id_counter[pdmsg[port].xmit_type],
+ pdmsg[port].data_objs,
+ pdmsg[port].rev,
+ pdmsg[port].ext);
+
+ /* Save SOP* so the correct msg_id_counter can be incremented */
+ prl_tx[port].sop = pdmsg[port].xmit_type;
+
+ /* Pass message to PHY Layer */
+ tcpm_transmit(port, pdmsg[port].xmit_type, header,
+ pdmsg[port].chk_buf);
+}
+
+/*
+ * PrlTxWaitForPhyResponse
+ */
+static int prl_tx_wait_for_phy_response(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_wait_for_phy_response_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_wait_for_phy_response_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_WAIT_FOR_PHY_RESPONSE;
+
+ prl_tx[port].tcpc_tx_timeout = get_time().val + PD_T_TCPC_TX_TIMEOUT;
+ return 0;
+}
+
+static int prl_tx_wait_for_phy_response_run(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 satifies the PRL_Tx_Match_MessageID state
+ * requirement.
+ */
+
+ if (get_time().val > prl_tx[port].tcpc_tx_timeout ||
+ prl_tx[port].xmit_status == TCPC_TX_COMPLETE_FAILED ||
+ prl_tx[port].xmit_status == TCPC_TX_COMPLETE_DISCARDED) {
+
+ /* NOTE: PRL_Tx_Check_RetryCounter State embedded here. */
+
+ /* Increment check RetryCounter */
+ prl_tx[port].retry_counter++;
+
+ /*
+ * (RetryCounter > nRetryCount) | Large Extended Message
+ */
+ if (prl_tx[port].retry_counter > N_RETRY_COUNT ||
+ (pdmsg[port].ext &&
+ PD_EXT_HEADER_DATA_SIZE(GET_EXT_HEADER(
+ pdmsg[port].chk_buf[0]) > 26))) {
+
+ /*
+ * NOTE: PRL_Tx_Transmission_Error State embedded
+ * here.
+ */
+
+ /*
+ * State tch_wait_for_transmission_complete will
+ * inform policy engine of error
+ */
+ pdmsg[port].status_flags |= PRL_FLAGS_TX_ERROR;
+
+ /* Increment message id counter */
+ increment_msgid_counter(port);
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_message_request);
+ return 0;
+ }
+
+ /* Try to resend the message. */
+ /* NOTE: PRL_TX_Construct_Message State embedded here. */
+ prl_tx_construct_message(port);
+ return 0;
+ }
+
+ 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 */
+ pdmsg[port].status_flags |= PRL_FLAGS_TX_COMPLETE;
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_message_request);
+ return 0;
+ }
+
+ return SM_RUN_SUPER;
+}
+
+static int prl_tx_wait_for_phy_response_exit(int port)
+{
+ prl_tx[port].xmit_status = TCPC_TX_UNSET;
+ return 0;
+}
+
+/* Source Protocol Layer Message Transmission */
+/*
+ * PrlTxSrcPending
+ */
+static int prl_tx_src_pending(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_src_pending_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_src_pending_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_SRC_PENDING;
+
+ /* Start SinkTxTimer */
+ prl_tx[port].sink_tx_timer = get_time().val + PD_T_SINK_TX;
+
+ return 0;
+}
+
+static int prl_tx_src_pending_run(int port)
+{
+
+ if (get_time().val > prl_tx[port].sink_tx_timer) {
+ /*
+ * Soft Reset Message pending &
+ * SinkTxTimer timeout
+ */
+ if ((emsg[port].len == 0) &&
+ (pdmsg[port].msg_type == PD_CTRL_SOFT_RESET)) {
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_layer_reset_for_transmit);
+ }
+ /* Message pending (except Soft Reset) &
+ * SinkTxTimer timeout
+ */
+ else {
+ prl_tx_construct_message(port);
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_phy_response);
+ }
+
+ return 0;
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * PrlTxSnkPending
+ */
+static int prl_tx_snk_pending(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_tx_snk_pending_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_tx_snk_pending_entry(int port)
+{
+ prl_tx[port].state_id = PRL_TX_SNK_PENDING;
+ return 0;
+}
+
+static int prl_tx_snk_pending_run(int port)
+{
+ int cc1;
+ int cc2;
+
+ tcpm_get_cc(port, &cc1, &cc2);
+ if (cc1 == TYPEC_CC_VOLT_RP_3_0 || cc2 == TYPEC_CC_VOLT_RP_3_0) {
+ /*
+ * Soft Reset Message Message pending &
+ * Rp = SinkTxOk
+ */
+ if ((pdmsg[port].msg_type == PD_CTRL_SOFT_RESET) &&
+ (emsg[port].len == 0)) {
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_layer_reset_for_transmit);
+ }
+ /*
+ * Message pending (except Soft Reset) &
+ * Rp = SinkTxOk
+ */
+ else {
+ prl_tx_construct_message(port);
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_phy_response);
+ }
+ return 0;
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/* Hard Reset Operation */
+
+static int prl_hr_wait_for_request(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_hr_wait_for_request_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_hr_wait_for_request_entry(int port)
+{
+ prl_hr[port].state_id = PRL_HR_WAIT_FOR_REQUEST;
+
+ prl_hr[port].flags = 0;
+ return 0;
+}
+
+static int prl_hr_wait_for_request_run(int port)
+{
+ if (prl_hr[port].flags & PRL_FLAGS_PE_HARD_RESET ||
+ prl_hr[port].flags & PRL_FLAGS_PORT_PARTNER_HARD_RESET) {
+ sm_set_state(port, PRL_HR_OBJ(port), prl_hr_reset_layer);
+ }
+
+ return 0;
+}
+
+/*
+ * PrlHrResetLayer
+ */
+static int prl_hr_reset_layer(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_hr_reset_layer_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_hr_reset_layer_entry(int port)
+{
+ int i;
+
+ prl_hr[port].state_id = PRL_HR_RESET_LAYER;
+
+ /* reset messageIDCounters */
+ for (i = 0; i < NUM_XMIT_TYPES; i++)
+ prl_tx[port].msg_id_counter[i] = 0;
+ /*
+ * Protocol Layer message transmission transitions to
+ * PRL_Tx_Wait_For_Message_Request state.
+ */
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_wait_for_message_request);
+
+ return 0;
+}
+
+static int prl_hr_reset_layer_run(int port)
+{
+ /*
+ * Protocol Layer reset Complete &
+ * Hard Reset was initiated by Policy Engine
+ */
+ if (prl_hr[port].flags & PRL_FLAGS_PE_HARD_RESET) {
+ /* Request PHY to perform a Hard Reset */
+ prl_send_ctrl_msg(port, TCPC_TX_HARD_RESET, 0);
+ sm_set_state(port, PRL_HR_OBJ(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);
+ sm_set_state(port, PRL_HR_OBJ(port),
+ prl_hr_wait_for_pe_hard_reset_complete);
+ }
+
+ return 0;
+}
+
+/*
+ * PrlHrWaitForPhyHardResetComplete
+ */
+static int prl_hr_wait_for_phy_hard_reset_complete(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_hr_wait_for_phy_hard_reset_complete_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_hr_wait_for_phy_hard_reset_complete_entry(int port)
+{
+ prl_hr[port].state_id = PRL_HR_WAIT_FOR_PHY_HARD_RESET_COMPLETE;
+
+ /* Start HardResetCompleteTimer */
+ prl_hr[port].hard_reset_complete_timer =
+ get_time().val + PD_T_PS_HARD_RESET;
+
+ return 0;
+}
+
+static int prl_hr_wait_for_phy_hard_reset_complete_run(int port)
+{
+ /*
+ * Wait for hard reset from PHY
+ * or timeout
+ */
+ if ((pdmsg[port].status_flags & PRL_FLAGS_TX_COMPLETE) ||
+ (get_time().val > prl_hr[port].hard_reset_complete_timer)) {
+ /* PRL_HR_PHY_Hard_Reset_Requested */
+
+ /* Inform Policy Engine Hard Reset was sent */
+ pe_hard_reset_sent(port);
+ sm_set_state(port, PRL_HR_OBJ(port),
+ prl_hr_wait_for_pe_hard_reset_complete);
+
+ return 0;
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * PrlHrWaitForPeHardResetComplete
+ */
+static int prl_hr_wait_for_pe_hard_reset_complete(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*prl_hr_wait_for_pe_hard_reset_complete_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int prl_hr_wait_for_pe_hard_reset_complete_entry(int port)
+{
+ prl_hr[port].state_id = PRL_HR_WAIT_FOR_PE_HARD_RESET_COMPLETE;
+ return 0;
+}
+
+static int prl_hr_wait_for_pe_hard_reset_complete_run(int port)
+{
+ /*
+ * Wait for Hard Reset complete indication from Policy Engine
+ */
+ if (prl_hr[port].flags & PRL_FLAGS_HARD_RESET_COMPLETE)
+ sm_set_state(port, PRL_HR_OBJ(port), prl_hr_wait_for_request);
+
+ return SM_RUN_SUPER;
+}
+
+static int prl_hr_wait_for_pe_hard_reset_complete_exit(int port)
+{
+ /* Exit from Hard Reset */
+
+ sm_set_state(port, PRL_TX_OBJ(port), prl_tx_phy_layer_reset);
+ sm_set_state(port, RCH_OBJ(port),
+ rch_wait_for_message_from_protocol_layer);
+ sm_set_state(port, TCH_OBJ(port), tch_wait_for_message_request_from_pe);
+
+ return 0;
+}
+
+static void copy_chunk_to_ext(int port)
+{
+ /* Calculate number of bytes */
+ pdmsg[port].num_bytes_received = (PD_HEADER_CNT(emsg[port].header) * 4);
+
+ /* Copy chunk into extended message */
+ memcpy((uint8_t *)emsg[port].buf, (uint8_t *)pdmsg[port].chk_buf,
+ pdmsg[port].num_bytes_received);
+
+ /* Set extended message length */
+ emsg[port].len = pdmsg[port].num_bytes_received;
+}
+
+/*
+ * Chunked Rx State Machine
+ */
+static int rch_wait_for_message_from_protocol_layer(int port,
+ enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*rch_wait_for_message_from_protocol_layer_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static inline void rch_clear_abort_set_chunking(int port)
+{
+ /* Clear Abort flag */
+ pdmsg[port].status_flags &= ~PRL_FLAGS_ABORT;
+
+ /* All Messages are chunked */
+ rch[port].flags = PRL_FLAGS_CHUNKING;
+}
+
+static int rch_wait_for_message_from_protocol_layer_entry(int port)
+{
+ rch[port].state_id = RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER;
+ rch_clear_abort_set_chunking(port);
+ return 0;
+}
+
+static int rch_wait_for_message_from_protocol_layer_run(int port)
+{
+ if (rch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ rch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+ /*
+ * Are we communicating with a PD3.0 device and is
+ * this an extended message?
+ */
+ if (pdmsg[port].rev == PD_REV30 &&
+ PD_HEADER_EXT(emsg[port].header)) {
+ uint16_t exhdr = GET_EXT_HEADER(*pdmsg[port].chk_buf);
+ uint8_t chunked = PD_EXT_HEADER_CHUNKED(exhdr);
+
+ /*
+ * Received Extended Message &
+ * (Chunking = 1 & Chunked = 1)
+ */
+ if ((rch[port].flags & PRL_FLAGS_CHUNKING) &&
+ chunked) {
+ sm_set_state(port, RCH_OBJ(port),
+ rch_processing_extended_message);
+ return 0;
+ }
+ /*
+ * (Received Extended Message &
+ * (Chunking = 0 & Chunked = 0))
+ */
+ else if (!(rch[port].flags &
+ PRL_FLAGS_CHUNKING) && !chunked) {
+ /* Copy chunk to extended buffer */
+ copy_chunk_to_ext(port);
+ /* Pass Message to Policy Engine */
+ pe_pass_up_message(port);
+ /* Clear Abort flag and set Chunking */
+ rch_clear_abort_set_chunking(port);
+ }
+ /*
+ * Chunked != Chunking
+ */
+ else {
+ sm_set_state(port, RCH_OBJ(port),
+ rch_report_error);
+ return 0;
+ }
+ }
+ /*
+ * Received Non-Extended Message
+ */
+ else if (!PD_HEADER_EXT(emsg[port].header)) {
+ /* Copy chunk to extended buffer */
+ copy_chunk_to_ext(port);
+ /* Pass Message to Policy Engine */
+ pe_pass_up_message(port);
+ /* Clear Abort flag and set Chunking */
+ rch_clear_abort_set_chunking(port);
+ }
+ /*
+ * Received an Extended Message while communicating at a
+ * revision lower than PD3.0
+ */
+ else {
+ sm_set_state(port, RCH_OBJ(port),
+ rch_report_error);
+ return 0;
+ }
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * RchProcessingExtendedMessage
+ */
+static int rch_processing_extended_message(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*rch_processing_extended_message_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int rch_processing_extended_message_entry(int port)
+{
+ uint32_t header = emsg[port].header;
+ uint16_t exhdr = GET_EXT_HEADER(pdmsg[port].chk_buf[0]);
+ uint8_t chunk_num = PD_EXT_HEADER_CHUNK_NUM(exhdr);
+
+ rch[port].state_id = RCH_PROCESSING_EXTENDED_MESSAGE;
+
+ /*
+ * If first chunk:
+ * Set Chunk_number_expected = 0 and
+ * Num_Bytes_Received = 0
+ */
+ if (chunk_num == 0) {
+ pdmsg[port].chunk_number_expected = 0;
+ pdmsg[port].num_bytes_received = 0;
+ pdmsg[port].msg_type = PD_HEADER_TYPE(header);
+ }
+
+ return 0;
+}
+
+static int rch_processing_extended_message_run(int port)
+{
+ uint16_t exhdr = GET_EXT_HEADER(pdmsg[port].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[port].status_flags & PRL_FLAGS_ABORT) {
+ sm_set_state(port, RCH_OBJ(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 > 25)
+ byte_num = 26;
+
+ /* Make sure extended message buffer does not overflow */
+ if (pdmsg[port].num_bytes_received +
+ byte_num > EXTENDED_BUFFER_SIZE) {
+ sm_set_state(port, RCH_OBJ(port), rch_report_error);
+ return 0;
+ }
+
+ /* Append data */
+ /* Add 2 to chk_buf to skip over extended message header */
+ memcpy(((uint8_t *)emsg[port].buf +
+ pdmsg[port].num_bytes_received),
+ (uint8_t *)pdmsg[port].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) {
+ emsg[port].len = pdmsg[port].num_bytes_received;
+ /* Pass Message to Policy Engine */
+ pe_pass_up_message(port);
+ sm_set_state(port, RCH_OBJ(port),
+ rch_wait_for_message_from_protocol_layer);
+ }
+ /*
+ * Message not Complete
+ */
+ else
+ sm_set_state(port, RCH_OBJ(port), rch_requesting_chunk);
+ }
+ /*
+ * Unexpected Chunk Number
+ */
+ else
+ sm_set_state(port, RCH_OBJ(port), rch_report_error);
+
+ return 0;
+}
+
+/*
+ * RchRequestingChunk
+ */
+static int rch_requesting_chunk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*rch_requesting_chunk_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int rch_requesting_chunk_entry(int port)
+{
+ rch[port].state_id = RCH_REQUESTING_CHUNK;
+
+ /*
+ * Send Chunk Request to Protocol Layer
+ * with chunk number = Chunk_Number_Expected
+ */
+ pdmsg[port].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;
+ prl_tx[port].flags |= PRL_FLAGS_MSG_XMIT;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_TX, 0);
+
+ return 0;
+}
+
+static int rch_requesting_chunk_run(int port)
+{
+ /*
+ * Transmission Error from Protocol Layer or
+ * Message Received From Protocol Layer
+ */
+ if (rch[port].flags & PRL_FLAGS_MSG_RECEIVED ||
+ pdmsg[port].status_flags & PRL_FLAGS_TX_ERROR) {
+ /*
+ * Leave PRL_FLAGS_MSG_RECEIVED flag set. It'll be
+ * cleared in rch_report_error state
+ */
+ sm_set_state(port, RCH_OBJ(port), rch_report_error);
+ }
+ /*
+ * Message Transmitted received from Protocol Layer
+ */
+ else if (pdmsg[port].status_flags & PRL_FLAGS_TX_COMPLETE) {
+ pdmsg[port].status_flags &= ~PRL_FLAGS_TX_COMPLETE;
+ sm_set_state(port, RCH_OBJ(port), rch_waiting_chunk);
+ } else
+ return SM_RUN_SUPER;
+
+ return 0;
+}
+
+/*
+ * RchWaitingChunk
+ */
+static int rch_waiting_chunk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*rch_waiting_chunk_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int rch_waiting_chunk_entry(int port)
+{
+ rch[port].state_id = RCH_WAITING_CHUNK;
+
+ /*
+ * Start ChunkSenderResponseTimer
+ */
+ rch[port].chunk_sender_response_timer =
+ get_time().val + PD_T_CHUNK_SENDER_RESPONSE;
+
+ return 0;
+}
+
+static int rch_waiting_chunk_run(int port)
+{
+ if ((rch[port].flags & PRL_FLAGS_MSG_RECEIVED)) {
+ /*
+ * 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(emsg[port].header)) {
+ uint16_t exhdr = GET_EXT_HEADER(pdmsg[port].chk_buf[0]);
+ /*
+ * Other Message Received from Protocol Layer
+ */
+ if (PD_EXT_HEADER_REQ_CHUNK(exhdr) ||
+ !PD_EXT_HEADER_CHUNKED(exhdr)) {
+ sm_set_state(port, RCH_OBJ(port),
+ rch_report_error);
+ }
+ /*
+ * Chunk response Received from Protocol Layer
+ */
+ else {
+ /*
+ * No error wad detected, so clear
+ * PRL_FLAGS_MSG_RECEIVED flag.
+ */
+ rch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+ sm_set_state(port, RCH_OBJ(port),
+ rch_processing_extended_message);
+ }
+
+ return 0;
+ }
+ }
+ /*
+ * ChunkSenderResponseTimer Timeout
+ */
+ else if (get_time().val > rch[port].chunk_sender_response_timer) {
+ sm_set_state(port, RCH_OBJ(port), rch_report_error);
+ return 0;
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * RchReportError
+ */
+static int rch_report_error(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*rch_report_error_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int rch_report_error_entry(int port)
+{
+ rch[port].state_id = RCH_REPORT_ERROR;
+
+ /*
+ * If the state was entered because a message was received,
+ * this message is passed to the Policy Engine.
+ */
+ if (rch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ rch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+
+ /* Copy chunk to extended buffer */
+ copy_chunk_to_ext(port);
+ /* Pass Message to Policy Engine */
+ pe_pass_up_message(port);
+ /* Report error */
+ pe_report_error(port, ERR_RCH_MSG_REC);
+ } else {
+ /* Report error */
+ pe_report_error(port, ERR_RCH_CHUNKED);
+ }
+
+ return 0;
+}
+
+static int rch_report_error_run(int port)
+{
+ sm_set_state(port, RCH_OBJ(port),
+ rch_wait_for_message_from_protocol_layer);
+
+ return 0;
+}
+
+/*
+ * Chunked Tx State Machine
+ */
+static int tch_wait_for_message_request_from_pe(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tch_wait_for_message_request_from_pe_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static inline void tch_clear_abort_set_chunking(int port)
+{
+ /* Clear Abort flag */
+ pdmsg[port].status_flags &= ~PRL_FLAGS_ABORT;
+
+ /* All Messages are chunked */
+ tch[port].flags = PRL_FLAGS_CHUNKING;
+}
+
+static int tch_wait_for_message_request_from_pe_entry(int port)
+{
+ tch[port].state_id = TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE;
+ tch_clear_abort_set_chunking(port);
+ return 0;
+}
+
+static int tch_wait_for_message_request_from_pe_run(int port)
+{
+ /*
+ * Any message received and not in state TCH_Wait_Chunk_Request
+ */
+ if (tch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ tch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+ sm_set_state(port, TCH_OBJ(port), tch_message_received);
+ return 0;
+ } else if (tch[port].flags & PRL_FLAGS_MSG_XMIT) {
+ tch[port].flags &= ~PRL_FLAGS_MSG_XMIT;
+ /*
+ * Rx Chunking State != RCH_Wait_For_Message_From_Protocol_Layer
+ * & Abort Supported
+ *
+ * Discard the Message
+ */
+ if (rch[port].state_id !=
+ RCH_WAIT_FOR_MESSAGE_FROM_PROTOCOL_LAYER) {
+ /* Report Error To Policy Engine */
+ pe_report_error(port, ERR_TCH_XMIT);
+ tch_clear_abort_set_chunking(port);
+ } else {
+ /*
+ * Extended Message Request & Chunking
+ */
+ if ((pdmsg[port].rev == PD_REV30) && pdmsg[port].ext &&
+ (tch[port].flags & PRL_FLAGS_CHUNKING)) {
+ pdmsg[port].send_offset = 0;
+ pdmsg[port].chunk_number_to_send = 0;
+ sm_set_state(port, TCH_OBJ(port),
+ tch_construct_chunked_message);
+ } else
+ /*
+ * Non-Extended Message Request
+ */
+ {
+ /* Make sure buffer doesn't overflow */
+ if (emsg[port].len > BUFFER_SIZE) {
+ /* Report Error To Policy Engine */
+ pe_report_error(port, ERR_TCH_XMIT);
+ tch_clear_abort_set_chunking(port);
+ return 0;
+ }
+
+ /* Copy message to chunked buffer */
+ memset((uint8_t *)pdmsg[port].chk_buf,
+ 0, BUFFER_SIZE);
+ memcpy((uint8_t *)pdmsg[port].chk_buf,
+ (uint8_t *)emsg[port].buf,
+ emsg[port].len);
+ /*
+ * Pad length to 4-byte boundery 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 =
+ (emsg[port].len + 3) >> 2;
+ /* Pass Message to Protocol Layer */
+ prl_tx[port].flags |= PRL_FLAGS_MSG_XMIT;
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_transmission_complete);
+ }
+
+ return 0;
+ }
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/*
+ * TchWaitForTransmissionComplete
+ */
+static int tch_wait_for_transmission_complete(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tch_wait_for_transmission_complete_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tch_wait_for_transmission_complete_entry(int port)
+{
+ tch[port].state_id = TCH_WAIT_FOR_TRANSMISSION_COMPLETE;
+ return 0;
+}
+
+static int tch_wait_for_transmission_complete_run(int port)
+{
+ /*
+ * Any message received and not in state TCH_Wait_Chunk_Request
+ */
+ if (tch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ tch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+ sm_set_state(port, TCH_OBJ(port), tch_message_received);
+ return 0;
+ }
+
+ /*
+ * Inform Policy Engine that Message was sent.
+ */
+ if (pdmsg[port].status_flags & PRL_FLAGS_TX_COMPLETE) {
+ pdmsg[port].status_flags &= ~PRL_FLAGS_TX_COMPLETE;
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+
+ /* Tell PE message was sent */
+ pe_message_sent(port);
+ }
+ /*
+ * Inform Policy Engine of Tx Error
+ */
+ else if (pdmsg[port].status_flags & PRL_FLAGS_TX_ERROR) {
+ pdmsg[port].status_flags &= ~PRL_FLAGS_TX_ERROR;
+ /* Tell PE an error occurred */
+ pe_report_error(port, ERR_TCH_XMIT);
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+ }
+
+ return 0;
+}
+
+/*
+ * TchConstructChunkedMessage
+ */
+static int tch_construct_chunked_message(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tch_construct_chunked_message_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tch_construct_chunked_message_entry(int port)
+{
+ uint16_t *ext_hdr;
+ uint8_t *data;
+ uint16_t num;
+
+ tch[port].state_id = TCH_CONSTRUCT_CHUNKED_MESSAGE;
+
+ /*
+ * Any message received and not in state TCH_Wait_Chunk_Request
+ */
+ if (tch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ tch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+ sm_set_state(port, TCH_OBJ(port), tch_message_received);
+ return 0;
+ }
+ /* Prepare to copy chunk into chk_buf */
+
+ ext_hdr = (uint16_t *)pdmsg[port].chk_buf;
+ data = ((uint8_t *)pdmsg[port].chk_buf + 2);
+ num = emsg[port].len - pdmsg[port].send_offset;
+
+ if (num > 26)
+ num = 26;
+
+ /* Set the chunks extended header */
+ *ext_hdr = PD_EXT_HEADER(pdmsg[port].chunk_number_to_send,
+ 0, /* Chunk Request */
+ emsg[port].len);
+
+ /* Copy the message chunk into chk_buf */
+ memset(data, 0, 28);
+ memcpy(data, 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[port].flags |= PRL_FLAGS_MSG_XMIT;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+
+ return 0;
+}
+
+static int tch_construct_chunked_message_run(int port)
+{
+ if (pdmsg[port].status_flags & PRL_FLAGS_ABORT)
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+ else
+ sm_set_state(port, TCH_OBJ(port),
+ tch_sending_chunked_message);
+ return 0;
+}
+
+/*
+ * TchSendingChunkedMessage
+ */
+static int tch_sending_chunked_message(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tch_sending_chunked_message_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tch_sending_chunked_message_entry(int port)
+{
+ tch[port].state_id = TCH_SENDING_CHUNKED_MESSAGE;
+ return 0;
+}
+
+static int tch_sending_chunked_message_run(int port)
+{
+ /*
+ * Any message received and not in state TCH_Wait_Chunk_Request
+ */
+ if (tch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ tch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+ sm_set_state(port, TCH_OBJ(port), tch_message_received);
+ return 0;
+ }
+
+ /*
+ * Transmission Error
+ */
+ if (pdmsg[port].status_flags & PRL_FLAGS_TX_ERROR) {
+ pe_report_error(port, ERR_TCH_XMIT);
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+ }
+ /*
+ * Message Transmitted from Protocol Layer &
+ * Last Chunk
+ */
+ else if (emsg[port].len == pdmsg[port].send_offset) {
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+
+ /* Tell PE message was sent */
+ pe_message_sent(port);
+ }
+ /*
+ * Message Transmitted from Protocol Layer &
+ * Not Last Chunk
+ */
+ else
+ sm_set_state(port, TCH_OBJ(port), tch_wait_chunk_request);
+
+ return 0;
+}
+
+/*
+ * TchWaitChunkRequest
+ */
+static int tch_wait_chunk_request(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tch_wait_chunk_request_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tch_wait_chunk_request_entry(int port)
+{
+ tch[port].state_id = TCH_WAIT_CHUNK_REQUEST;
+
+ /* Increment Chunk Number to Send */
+ pdmsg[port].chunk_number_to_send++;
+ /* Start Chunk Sender Request Timer */
+ tch[port].chunk_sender_request_timer =
+ get_time().val + PD_T_CHUNK_SENDER_REQUEST;
+ return 0;
+}
+
+static int tch_wait_chunk_request_run(int port)
+{
+ if (tch[port].flags & PRL_FLAGS_MSG_RECEIVED) {
+ tch[port].flags &= ~PRL_FLAGS_MSG_RECEIVED;
+
+ if (PD_HEADER_EXT(emsg[port].header)) {
+ uint16_t exthdr;
+
+ exthdr = GET_EXT_HEADER(pdmsg[port].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) {
+ sm_set_state(port, TCH_OBJ(port),
+ tch_construct_chunked_message);
+ }
+ /*
+ * Chunk Request Received &
+ * Chunk Number != Chunk Number to Send
+ */
+ else {
+ pe_report_error(port, ERR_TCH_CHUNKED);
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+ }
+ return 0;
+ }
+ }
+
+ /*
+ * Other message received
+ */
+ sm_set_state(port, TCH_OBJ(port), tch_message_received);
+ }
+ /*
+ * ChunkSenderRequestTimer timeout
+ */
+ else if (get_time().val >=
+ tch[port].chunk_sender_request_timer) {
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+
+ /* Tell PE message was sent */
+ pe_message_sent(port);
+ }
+
+ return 0;
+}
+
+/*
+ * TchMessageReceived
+ */
+static int tch_message_received(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tch_message_received_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tch_message_received_entry(int port)
+{
+ tch[port].state_id = TCH_MESSAGE_RECEIVED;
+
+ /* Pass message to chunked Rx */
+ rch[port].flags |= PRL_FLAGS_MSG_RECEIVED;
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0);
+
+ return 0;
+}
+
+static int tch_message_received_run(int port)
+{
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+
+ return 0;
+}
+
+/*
+ * Protocol Layer Message Reception State Machine
+ */
+static int prl_rx_wait_for_phy_message(int port, int evt)
+{
+ uint32_t header;
+ uint8_t type;
+ uint8_t cnt;
+ uint8_t sop;
+ int8_t msid;
+ int ret;
+
+ /* process any potential incoming message */
+ if (tcpm_has_pending_message(port)) {
+ ret = tcpm_dequeue_message(port, pdmsg[port].chk_buf, &header);
+ if (ret == 0) {
+ emsg[port].header = header;
+ type = PD_HEADER_TYPE(header);
+ cnt = PD_HEADER_CNT(header);
+ msid = PD_HEADER_ID(header);
+ sop = PD_HEADER_GET_SOP(header);
+
+ if (cnt == 0 && type == PD_CTRL_SOFT_RESET) {
+ int i;
+
+ for (i = 0; i < NUM_XMIT_TYPES; i++) {
+ /* Clear MessageIdCounter */
+ prl_tx[port].msg_id_counter[i] = 0;
+ /* Clear stored MessageID value */
+ prl_rx[port].msg_id[i] = -1;
+ }
+
+ /* Inform Policy Engine of Soft Reset */
+ pe_got_soft_reset(port);
+
+ /* Soft Reset occurred */
+ sm_set_state(port, PRL_TX_OBJ(port),
+ prl_tx_phy_layer_reset);
+ sm_set_state(port, RCH_OBJ(port),
+ rch_wait_for_message_from_protocol_layer);
+ sm_set_state(port, TCH_OBJ(port),
+ tch_wait_for_message_request_from_pe);
+ }
+
+ /*
+ * Ignore if this is a duplicate message.
+ */
+ if (prl_rx[port].msg_id[sop] != msid) {
+ /*
+ * Discard any pending tx message if this is
+ * not a ping message
+ */
+ if ((pdmsg[port].rev == PD_REV30) &&
+ (cnt == 0) && type != PD_CTRL_PING) {
+ if (prl_tx[port].state_id ==
+ PRL_TX_SRC_PENDING ||
+ prl_tx[port].state_id ==
+ PRL_TX_SNK_PENDING) {
+ sm_set_state(port,
+ PRL_TX_OBJ(port),
+ prl_tx_discard_message);
+ }
+ }
+
+ /* Store Message Id */
+ prl_rx[port].msg_id[sop] = msid;
+
+ /* RTR Chunked Message Router States. */
+ /*
+ * Received Ping from Protocol Layer
+ */
+ if (cnt == 0 && type == PD_CTRL_PING) {
+ /* NOTE: RTR_PING State embedded
+ * here.
+ */
+ emsg[port].len = 0;
+ pe_pass_up_message(port);
+ return 0;
+ }
+ /*
+ * Message (not Ping) Received from
+ * Protocol Layer & Doing Tx Chunks
+ */
+ else if (tch[port].state_id !=
+ TCH_WAIT_FOR_MESSAGE_REQUEST_FROM_PE) {
+ /* NOTE: RTR_TX_CHUNKS State embedded
+ * here.
+ */
+ /*
+ * Send Message to Tx Chunk
+ * Chunk State Machine
+ */
+ tch[port].flags |=
+ 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[port].flags |=
+ PRL_FLAGS_MSG_RECEIVED;
+ }
+
+ task_set_event(PD_PORT_TO_TASK_ID(port),
+ PD_EVENT_SM, 0);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/common/usbc/usb_sm.c b/common/usbc/usb_sm.c
new file mode 100644
index 0000000000..0260406450
--- /dev/null
+++ b/common/usbc/usb_sm.c
@@ -0,0 +1,177 @@
+/* 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 "common.h"
+#include "task.h"
+#include "usb_pd.h"
+#include "usb_sm.h"
+#include "util.h"
+#include "console.h"
+
+void sm_init_state(int port, struct sm_obj *obj, sm_state target)
+{
+#if (CONFIG_SM_NESTING_NUM > 0)
+ int i;
+
+ sm_state tmp_super[CONFIG_SM_NESTING_NUM];
+#endif
+
+ obj->last_state = NULL;
+ obj->task_state = target;
+
+#if (CONFIG_SM_NESTING_NUM > 0)
+
+ /* Prepare to execute all entry actions of the target's super states */
+
+ /*
+ * Get targets super state. This will be NULL if the target
+ * has no super state
+ */
+ tmp_super[CONFIG_SM_NESTING_NUM - 1] =
+ (sm_state)(uintptr_t)target(port, SM_SUPER_SIG);
+
+ /* Get all super states of the target */
+ for (i = CONFIG_SM_NESTING_NUM - 1; i > 0; i--) {
+ if (tmp_super[i] != NULL)
+ tmp_super[i - 1] =
+ (sm_state)(uintptr_t)tmp_super[i](port, SM_SUPER_SIG);
+ else
+ tmp_super[i - 1] = NULL;
+ }
+
+ /* Execute all super state entry actions in forward order */
+ for (i = 0; i < CONFIG_SM_NESTING_NUM; i++)
+ if (tmp_super[i] != NULL)
+ tmp_super[i](port, SM_ENTRY_SIG);
+#endif
+
+ /* Now execute the target entry action */
+ target(port, SM_ENTRY_SIG);
+}
+
+int sm_set_state(int port, struct sm_obj *obj, sm_state target)
+{
+#if (CONFIG_SM_NESTING_NUM > 0)
+ int i;
+ int no_execute;
+
+ sm_state tmp_super[CONFIG_SM_NESTING_NUM];
+ sm_state target_super;
+ sm_state last_super;
+ sm_state super;
+
+ /* Execute all exit actions is reverse order */
+
+ /* Get target's super state */
+ target_super = (sm_state)(uintptr_t)target(port, SM_SUPER_SIG);
+ tmp_super[0] = obj->task_state;
+
+ do {
+ /* Execute exit action */
+ tmp_super[0](port, SM_EXIT_SIG);
+
+ /* Get super state */
+ tmp_super[0] =
+ (sm_state)(uintptr_t)tmp_super[0](port, SM_SUPER_SIG);
+ /*
+ * No need to execute a super state's exit action that has
+ * shared ancestry with the target.
+ */
+ super = target_super;
+ while (super != NULL) {
+ if (tmp_super[0] == super) {
+ tmp_super[0] = NULL;
+ break;
+ }
+
+ /* Get target state next super state if it exists */
+ super = (sm_state)(uintptr_t)super(port, SM_SUPER_SIG);
+ }
+ } while (tmp_super[0] != NULL);
+
+ /* All done executing the exit actions */
+#else
+ obj->task_state(port, SM_EXIT_SIG);
+#endif
+ /* update the state variables */
+ obj->last_state = obj->task_state;
+ obj->task_state = target;
+
+#if (CONFIG_SM_NESTING_NUM > 0)
+ /* Prepare to execute all entry actions of the target's super states */
+
+ tmp_super[CONFIG_SM_NESTING_NUM - 1] =
+ (sm_state)(uintptr_t)target(port, SM_SUPER_SIG);
+
+ /* Get all super states of the target */
+ for (i = CONFIG_SM_NESTING_NUM - 1; i > 0; i--) {
+ if (tmp_super[i] != NULL)
+ tmp_super[i - 1] =
+ (sm_state)(uintptr_t)tmp_super[i](port, SM_SUPER_SIG);
+ else
+ tmp_super[i - 1] = NULL;
+ }
+
+ /* Get super state of last state */
+ last_super = (sm_state)(uintptr_t)obj->last_state(port, SM_SUPER_SIG);
+
+ /* Execute all super state entry actions in forward order */
+ for (i = 0; i < CONFIG_SM_NESTING_NUM; i++) {
+ /* No super state */
+ if (tmp_super[i] == NULL)
+ continue;
+
+ /*
+ * We only want to execute the target state's super state entry
+ * action if it doesn't share a super state with the previous
+ * state.
+ */
+ super = last_super;
+ no_execute = 0;
+ while (super != NULL) {
+ if (tmp_super[i] == super) {
+ no_execute = 1;
+ break;
+ }
+
+ /* Get last state's next super state if it exists */
+ super = (sm_state)(uintptr_t)super(port, SM_SUPER_SIG);
+ }
+
+ /* Execute super state's entry */
+ if (!no_execute)
+ tmp_super[i](port, SM_ENTRY_SIG);
+ }
+#endif
+
+ /* Now execute the target entry action */
+ target(port, SM_ENTRY_SIG);
+
+ return 0;
+}
+
+void sm_run_state_machine(int port, struct sm_obj *obj, enum sm_signal sig)
+{
+#if (CONFIG_SM_NESTING_NUM > 0)
+ sm_state state = obj->task_state;
+
+ do {
+ state = (sm_state)(uintptr_t)state(port, sig);
+ } while (state != NULL);
+#else
+ obj->task_state(port, sig);
+#endif
+}
+
+int sm_do_nothing(int port)
+{
+ return 0;
+}
+
+int sm_get_super_state(int port)
+{
+ return SM_RUN_SUPER;
+}
+
diff --git a/common/usbc/usb_tc_ctvpd_sm.c b/common/usbc/usb_tc_ctvpd_sm.c
new file mode 100644
index 0000000000..4097f98ce7
--- /dev/null
+++ b/common/usbc/usb_tc_ctvpd_sm.c
@@ -0,0 +1,1653 @@
+/* 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 "common.h"
+#include "console.h"
+#include "system.h"
+#include "task.h"
+#include "tcpm.h"
+#include "usb_pd.h"
+#include "usb_tc_ctvpd_sm.h"
+#include "usb_tc_sm.h"
+#include "vpd_api.h"
+
+/* USB Type-C CTVPD module */
+
+#ifdef CONFIG_COMMON_RUNTIME
+#define CPRINTF(format, args...) cprintf(CC_HOOK, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_HOOK, format, ## args)
+#else /* CONFIG_COMMON_RUNTIME */
+#define CPRINTF(format, args...)
+#define CPRINTS(format, args...)
+#endif
+
+/* Type-C Layer Flags */
+#define TC_FLAGS_VCONN_ON (1 << 0)
+
+#define SUPPORT_TIMER_RESET_INIT 0
+#define SUPPORT_TIMER_RESET_REQUEST 1
+#define SUPPORT_TIMER_RESET_COMPLETE 2
+
+/**
+ * This is the Type-C Port object that contains information needed to
+ * implement a Charge Through VCONN Powered Device.
+ */
+struct type_c tc[CONFIG_USB_PD_PORT_COUNT];
+
+/* Type-C states */
+DECLARE_STATE(tc, disabled, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, error_recovery, WITH_RUN, NOOP);
+DECLARE_STATE(tc, unattached_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attach_wait_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attached_snk, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, try_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, unattached_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attach_wait_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, try_wait_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attached_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, ct_try_snk, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, ct_attach_wait_unsupported, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, ct_attached_unsupported, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, ct_unattached_unsupported, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, ct_unattached_vpd, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, ct_disabled_vpd, WITH_RUN, NOOP);
+DECLARE_STATE(tc, ct_attached_vpd, WITH_RUN, NOOP);
+DECLARE_STATE(tc, ct_attach_wait_vpd, WITH_RUN, WITH_EXIT);
+
+/* Super States */
+DECLARE_STATE(tc, host_rard_ct_rd, NOOP, NOOP);
+DECLARE_STATE(tc, host_open_ct_open, NOOP, NOOP);
+DECLARE_STATE(tc, vbus_cc_iso, NOOP, NOOP);
+DECLARE_STATE(tc, host_rp3_ct_rd, NOOP, NOOP);
+DECLARE_STATE(tc, host_rp3_ct_rpu, NOOP, NOOP);
+DECLARE_STATE(tc, host_rpu_ct_rd, NOOP, NOOP);
+
+void tc_reset_support_timer(int port)
+{
+ tc[port].support_timer_reset |= SUPPORT_TIMER_RESET_REQUEST;
+}
+
+void tc_state_init(int port, enum typec_state_id start_state)
+{
+ int res = 0;
+ sm_state this_state;
+
+ res = tc_restart_tcpc(port);
+ if (res)
+ this_state = tc_disabled;
+ else
+ this_state = (start_state == TC_UNATTACHED_SRC) ?
+ tc_unattached_src : tc_unattached_snk;
+
+ CPRINTS("TCPC p%d init %s", port, res ? "failed" : "ready");
+
+ sm_init_state(port, TC_OBJ(port), this_state);
+
+ /* Disable pd state machines */
+ tc[port].pd_enable = 0;
+ tc[port].evt_timeout = 10*MSEC;
+ tc[port].power_role = PD_PLUG_CABLE_VPD;
+ tc[port].data_role = 0; /* Reserved for VPD */
+ tc[port].billboard_presented = 0;
+ tc[port].flags = 0;
+}
+
+void tc_event_check(int port, int evt)
+{
+ /* Do Nothing */
+}
+
+/**
+ * Disabled
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Remove the terminations from Host
+ * Remove the terminations from Charge-Through
+ */
+static int tc_disabled(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_disabled_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_open_ct_open);
+}
+
+static int tc_disabled_entry(int port)
+{
+ tc[port].state_id = TC_DISABLED;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+ return 0;
+}
+
+static int tc_disabled_run(int port)
+{
+ task_wait_event(-1);
+ return SM_RUN_SUPER;
+}
+
+static int tc_disabled_exit(int port)
+{
+#ifndef CONFIG_USB_PD_TCPC
+ if (tc_restart_tcpc(port) != 0) {
+ CPRINTS("TCPC p%d restart failed!", port);
+ return 0;
+ }
+#endif
+ CPRINTS("TCPC p%d resumed!", port);
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * ErrorRecovery
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Remove the terminations from Host
+ * Remove the terminations from Charge-Through
+ */
+static int tc_error_recovery(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_error_recovery_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_open_ct_open);
+}
+
+static int tc_error_recovery_entry(int port)
+{
+ tc[port].state_id = TC_ERROR_RECOVERY;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+ /* Use cc_debounce state variable for error recovery timeout */
+ tc[port].cc_debounce = get_time().val + PD_T_ERROR_RECOVERY;
+ return 0;
+}
+
+static int tc_error_recovery_run(int port)
+{
+ if (get_time().val > tc[port].cc_debounce)
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * Unattached.SNK
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place Ra on VCONN and Rd on Host CC
+ * Place Rd on Charge-Through CCs
+ */
+static int tc_unattached_snk(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_unattached_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rard_ct_rd);
+}
+
+static int tc_unattached_snk_entry(int port)
+{
+ tc[port].state_id = TC_UNATTACHED_SNK;
+ if (tc[port].obj.last_state != tc_unattached_src)
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].flags &= ~TC_FLAGS_VCONN_ON;
+ tc[port].cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_unattached_snk_run(int port)
+{
+ int host_cc;
+ int new_cc_state;
+ int cc1;
+ int cc2;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ /*
+ * Transition to AttachWait.SNK when a Source connection is
+ * detected, as indicated by the SNK.Rp state on its Host-side
+ * port’s CC pin.
+ */
+ if (cc_is_rp(host_cc))
+ return sm_set_state(port, TC_OBJ(port), tc_attach_wait_snk);
+
+ /* Check Charge-Through CCs for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ if (cc_is_rp(cc1) != cc_is_rp(cc2))
+ new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ new_cc_state = PD_CC_NONE;
+
+ /* Debounce Charge-Through CC state */
+ if (tc[port].cc_state != new_cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+ }
+
+ /* If we are here, Host CC must be open */
+
+ /* Wait for Charge-Through CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Unattached.SRC when the state of the Host-side port’s CC pin is
+ * SNK.Open for tDRP − dcSRC.DRP ∙ tDRP and both of the following
+ * is detected on the Charge-Through port.
+ * 1) SNK.Rp state is detected on exactly one of the CC1 or CC2
+ * pins for at least tCCDebounce
+ * 2) VBUS is detected
+ */
+ if (vpd_is_ct_vbus_present() &&
+ tc[port].cc_state == PD_CC_DFP_ATTACHED)
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_src);
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * AttachWait.SNK
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place Ra on VCONN and Rd on Host CC
+ * Place Rd on Charge-Through CCs
+ */
+static int tc_attach_wait_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attach_wait_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rard_ct_rd);
+}
+
+static int tc_attach_wait_snk_entry(int port)
+{
+ tc[port].state_id = TC_ATTACH_WAIT_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+ tc[port].host_cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_attach_wait_snk_run(int port)
+{
+ int host_new_cc_state;
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ if (cc_is_rp(host_cc))
+ host_new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ host_new_cc_state = PD_CC_NONE;
+
+ /* Debounce the Host CC state */
+ if (tc[port].host_cc_state != host_new_cc_state) {
+ tc[port].host_cc_state = host_new_cc_state;
+ if (host_new_cc_state == PD_CC_DFP_ATTACHED)
+ tc[port].host_cc_debounce = get_time().val +
+ PD_T_CC_DEBOUNCE;
+ else
+ tc[port].host_cc_debounce = get_time().val +
+ PD_T_PD_DEBOUNCE;
+ return 0;
+ }
+
+ /* Wait for Host CC debounce */
+ if (get_time().val < tc[port].host_cc_debounce)
+ return 0;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Attached.SNK after the state of the Host-side port’s CC pin is
+ * SNK.Rp for at least tCCDebounce and either host-side VCONN or
+ * VBUS is detected.
+ *
+ * Transition to Unattached.SNK when the state of both the CC1 and
+ * CC2 pins is SNK.Open for at least tPDDebounce.
+ */
+ if (tc[port].host_cc_state == PD_CC_DFP_ATTACHED &&
+ (vpd_is_vconn_present() || vpd_is_host_vbus_present()))
+ sm_set_state(port, TC_OBJ(port), tc_attached_snk);
+ else if (tc[port].host_cc_state == PD_CC_NONE)
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * Attached.SNK
+ */
+static int tc_attached_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attached_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_attached_snk_entry(int port)
+{
+ tc[port].state_id = TC_ATTACHED_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ /*
+ * This state can only be entered from states AttachWait.SNK
+ * and Try.SNK. So the Host port is isolated from the
+ * Charge-Through port. We only need to High-Z the
+ * Charge-Through ports CC1 and CC2 pins.
+ */
+ vpd_ct_set_pull(TYPEC_CC_OPEN, 0);
+
+ tc[port].host_cc_state = PD_CC_UNSET;
+
+ /* Start Charge-Through support timer */
+ tc[port].support_timer_reset = SUPPORT_TIMER_RESET_INIT;
+ tc[port].support_timer = get_time().val + PD_T_AME;
+
+ /* Sample host CC every 2ms */
+ tc_set_timeout(port, 2*MSEC);
+
+ return 0;
+}
+
+static int tc_attached_snk_run(int port)
+{
+ int host_new_cc_state;
+ int host_cc;
+
+ /* Has host vbus and vconn been removed */
+ if (!vpd_is_host_vbus_present() && !vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /*
+ * Reset the Charge-Through Support Timer when it first
+ * receives any USB PD Structured VDM Command it supports,
+ * which is the Discover Identity command. And this is only
+ * done one time.
+ */
+ if (tc[port].support_timer_reset == SUPPORT_TIMER_RESET_REQUEST) {
+ tc[port].support_timer_reset |= SUPPORT_TIMER_RESET_COMPLETE;
+ tc[port].support_timer = get_time().val + PD_T_AME;
+ }
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ if (cc_is_rp(host_cc))
+ host_new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ host_new_cc_state = PD_CC_NONE;
+
+ /* Debounce the Host CC state */
+ if (tc[port].host_cc_state != host_new_cc_state) {
+ tc[port].host_cc_state = host_new_cc_state;
+ tc[port].host_cc_debounce = get_time().val + PD_T_VPDCTDD;
+ return 0;
+ }
+
+ /* Wait for Host CC debounce */
+ if (get_time().val < tc[port].host_cc_debounce)
+ return 0;
+
+ if (vpd_is_vconn_present()) {
+ if (!(tc[port].flags & TC_FLAGS_VCONN_ON)) {
+ /* VCONN detected. Remove RA */
+ vpd_host_set_pull(TYPEC_CC_RD, 0);
+ tc[port].flags |= TC_FLAGS_VCONN_ON;
+ }
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition
+ * to CTUnattached.VPD if VCONN is present and the state of
+ * its Host-side port’s CC pin is SNK.Open for tVPDCTDD.
+ */
+ if (tc[port].host_cc_state == PD_CC_NONE)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_unattached_vpd);
+ }
+
+ /* Check the Support Timer */
+ if (get_time().val > tc[port].support_timer &&
+ !tc[port].billboard_presented) {
+ /*
+ * Present USB Billboard Device Class interface
+ * indicating that Charge-Through is not supported
+ */
+ tc[port].billboard_presented = 1;
+ vpd_present_billboard(BB_SNK);
+ }
+
+ return 0;
+}
+
+static int tc_attached_snk_exit(int port)
+{
+ /* Reset timeout value to 10ms */
+ tc_set_timeout(port, 10*MSEC);
+ tc[port].billboard_presented = 0;
+ vpd_present_billboard(BB_NONE);
+
+ return 0;
+}
+
+/**
+ * Super State HOST_RA_CT_RD
+ */
+static int tc_host_rard_ct_rd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_rard_ct_rd_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_rard_ct_rd_entry(int port)
+{
+ /* Place Ra on VCONN and Rd on Host CC */
+ vpd_host_set_pull(TYPEC_CC_RA_RD, 0);
+
+ /* Place Rd on Charge-Through CCs */
+ vpd_ct_set_pull(TYPEC_CC_RD, 0);
+
+ return 0;
+}
+
+/**
+ * Super State HOST_OPEN_CT_OPEN
+ */
+static int tc_host_open_ct_open(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_open_ct_open_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_open_ct_open_entry(int port)
+{
+ /* Remove the terminations from Host */
+ vpd_host_set_pull(TYPEC_CC_OPEN, 0);
+
+ /* Remove the terminations from Charge-Through */
+ vpd_ct_set_pull(TYPEC_CC_OPEN, 0);
+
+ return 0;
+}
+
+/**
+ * Super State VBUS_CC_ISO
+ */
+static int tc_vbus_cc_iso(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_vbus_cc_iso_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_vbus_cc_iso_entry(int port)
+{
+ /* Isolate the Host-side port from the Charge-Through port */
+ vpd_vbus_pass_en(0);
+
+ /* Remove Charge-Through side port CCs */
+ vpd_ct_cc_sel(CT_OPEN);
+
+ /* Enable mcu communication and cc */
+ vpd_mcu_cc_en(1);
+
+ return 0;
+}
+
+/**
+ * Unattached.SRC
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RpUSB on Host CC
+ * Place Rd on Charge-Through CCs
+ */
+static int tc_unattached_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_unattached_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rpu_ct_rd);
+}
+
+static int tc_unattached_src_entry(int port)
+{
+ tc[port].state_id = TC_UNATTACHED_SRC;
+ if (tc[port].obj.last_state != tc_unattached_snk)
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Get power from VBUS */
+ vpd_vconn_pwr_sel_odl(PWR_VBUS);
+
+ /* Make sure it's the Charge-Through Port's VBUS */
+ if (!vpd_is_ct_vbus_present())
+ return sm_set_state(port, TC_OBJ(port), tc_error_recovery);
+
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_SRC;
+
+ return 0;
+}
+
+static int tc_unattached_src_run(int port)
+{
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ /*
+ * Transition to AttachWait.SRC when host-side VBUS is
+ * vSafe0V and SRC.Rd state is detected on the Host-side
+ * port’s CC pin.
+ */
+ if (!vpd_is_host_vbus_present() && host_cc == TYPEC_CC_VOLT_RD)
+ return sm_set_state(port, TC_OBJ(port), tc_attach_wait_src);
+
+ /*
+ * Transition to Unattached.SNK within tDRPTransition or
+ * if Charge-Through VBUS is removed.
+ */
+ if (!vpd_is_ct_vbus_present() ||
+ get_time().val > tc[port].next_role_swap)
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * AttachWait.SRC
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RpUSB on Host CC
+ * Place Rd on Charge-Through CCs
+ */
+static int tc_attach_wait_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attach_wait_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rpu_ct_rd);
+}
+
+static int tc_attach_wait_src_entry(int port)
+{
+ tc[port].state_id = TC_ATTACH_WAIT_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].host_cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_attach_wait_src_run(int port)
+{
+ int host_new_cc_state;
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ if (host_cc == TYPEC_CC_VOLT_RD)
+ host_new_cc_state = PD_CC_UFP_ATTACHED;
+ else
+ host_new_cc_state = PD_CC_NONE;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition
+ * to Unattached.SNK when the SRC.Open state is detected on the
+ * Host-side port’s CC or if Charge-Through VBUS falls below
+ * vSinkDisconnect. The Charge-Through VCONN-Powered USB Device
+ * shall detect the SRC.Open state within tSRCDisconnect, but
+ * should detect it as quickly as possible.
+ */
+ if (host_new_cc_state == PD_CC_NONE || !vpd_is_ct_vbus_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /* Debounce the Host CC state */
+ if (tc[port].host_cc_state != host_new_cc_state) {
+ tc[port].host_cc_state = host_new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+ return 0;
+ }
+
+ /* Wait for Host CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Try.SNK when the host-side VBUS is at vSafe0V and the SRC.Rd
+ * state is on the Host-side port’s CC pin for at least tCCDebounce.
+ */
+ if (tc[port].host_cc_state == PD_CC_UFP_ATTACHED &&
+ !vpd_is_host_vbus_present())
+ return sm_set_state(port, TC_OBJ(port), tc_try_snk);
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * Attached.SRC
+ */
+static int tc_attached_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attached_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_attached_src_entry(int port)
+{
+ tc[port].state_id = TC_ATTACHED_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ /* Connect Charge-Through VBUS to Host VBUS */
+ vpd_vbus_pass_en(1);
+
+ /*
+ * Get power from VBUS. No need to test because
+ * the Host VBUS is connected to the Charge-Through
+ * VBUS
+ */
+ vpd_vconn_pwr_sel_odl(PWR_VBUS);
+
+ return 0;
+}
+
+static int tc_attached_src_run(int port)
+{
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Unattached.SNK when VBUS falls below vSinkDisconnect or the
+ * Host-side port’s CC pin is SRC.Open. The Charge-Through
+ * VCONNPowered USB Device shall detect the SRC.Open state within
+ * tSRCDisconnect, but should detect it as quickly as possible.
+ */
+ if (!vpd_is_ct_vbus_present() || host_cc == TYPEC_CC_VOLT_OPEN)
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * Super State HOST_RPU_CT_RD
+ */
+static int tc_host_rpu_ct_rd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_rpu_ct_rd_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_rpu_ct_rd_entry(int port)
+{
+ /* Place RpUSB on Host CC */
+ vpd_host_set_pull(TYPEC_CC_RP, TYPEC_RP_USB);
+
+ /* Place Rd on Charge-Through CCs */
+ vpd_ct_set_pull(TYPEC_CC_RD, 0);
+
+ return 0;
+}
+
+/**
+ * Try.SNK
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place Ra on VCONN and Rd on Host CC
+ * Place Rd on Charge-Through CCs
+ */
+static int tc_try_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_try_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rard_ct_rd);
+}
+
+static int tc_try_snk_entry(int port)
+{
+ tc[port].state_id = TC_TRY_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Get power from VBUS */
+ vpd_vconn_pwr_sel_odl(PWR_VBUS);
+
+ /* Make sure it's the Charge-Through Port's VBUS */
+ if (!vpd_is_ct_vbus_present())
+ return sm_set_state(port, TC_OBJ(port), tc_error_recovery);
+
+ tc[port].host_cc_state = PD_CC_UNSET;
+
+ /* Using next_role_swap timer as try_src timer */
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_TRY;
+
+ return 0;
+}
+
+static int tc_try_snk_run(int port)
+{
+ int host_new_cc_state;
+ int host_cc;
+
+ /*
+ * Wait for tDRPTry before monitoring the Charge-Through
+ * port’s CC pins for the SNK.Rp
+ */
+ if (get_time().val < tc[port].next_role_swap)
+ return 0;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ if (cc_is_rp(host_cc))
+ host_new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ host_new_cc_state = PD_CC_NONE;
+
+ /* Debounce the Host CC state */
+ if (tc[port].host_cc_state != host_new_cc_state) {
+ tc[port].host_cc_state = host_new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_DEBOUNCE;
+ return 0;
+ }
+
+ /* Wait for Host CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * The Charge-Through VCONN-Powered USB Device shall then transition to
+ * Attached.SNK when the SNK.Rp state is detected on the Host-side
+ * port’s CC pin for at least tTryCCDebounce and VBUS or VCONN is
+ * detected on Host-side port.
+ *
+ * Alternatively, the Charge-Through VCONN-Powered USB Device shall
+ * transition to TryWait.SRC if Host-side SNK.Rp state is not detected
+ * for tTryCCDebounce.
+ */
+ if (tc[port].host_cc_state == PD_CC_DFP_ATTACHED &&
+ (vpd_is_host_vbus_present() || vpd_is_vconn_present()))
+ sm_set_state(port, TC_OBJ(port), tc_attached_snk);
+ else if (tc[port].host_cc_state == PD_CC_NONE)
+ sm_set_state(port, TC_OBJ(port), tc_try_wait_src);
+
+ return 0;
+}
+
+/**
+ * TryWait.SRC
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RpUSB on Host CC
+ * Place Rd on Charge-Through CCs
+ */
+static int tc_try_wait_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_try_wait_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rpu_ct_rd);
+}
+
+static int tc_try_wait_src_entry(int port)
+{
+ tc[port].state_id = TC_TRY_WAIT_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].host_cc_state = PD_CC_UNSET;
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_TRY;
+
+ return 0;
+}
+
+static int tc_try_wait_src_run(int port)
+{
+ int host_new_cc_state;
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ if (host_cc == TYPEC_CC_VOLT_RD)
+ host_new_cc_state = PD_CC_UFP_ATTACHED;
+ else
+ host_new_cc_state = PD_CC_NONE;
+
+ /* Debounce the Host CC state */
+ if (tc[port].host_cc_state != host_new_cc_state) {
+ tc[port].host_cc_state = host_new_cc_state;
+ tc[port].host_cc_debounce =
+ get_time().val + PD_T_TRY_CC_DEBOUNCE;
+ return 0;
+ }
+
+ if (get_time().val > tc[port].host_cc_debounce) {
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition
+ * to Attached.SRC when host-side VBUS is at vSafe0V and the
+ * SRC.Rd state is detected on the Host-side port’s CC pin for
+ * at least tTryCCDebounce.
+ */
+ if (tc[port].host_cc_state == PD_CC_UFP_ATTACHED &&
+ !vpd_is_host_vbus_present())
+ return sm_set_state(port, TC_OBJ(port),
+ tc_attached_src);
+ }
+
+ if (get_time().val > tc[port].next_role_swap) {
+ /*
+ * The Charge-Through VCONN-Powered USB Device shall transition
+ * to Unattached.SNK after tDRPTry if the Host-side port’s CC
+ * pin is not in the SRC.Rd state.
+ */
+ if (tc[port].host_cc_state == PD_CC_NONE)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_unattached_snk);
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * CTTry.SNK
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RP3A0 on Host CC
+ * Connect Charge-Through Rd
+ * Get power from VCONN
+ */
+static int tc_ct_try_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_try_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rp3_ct_rd);
+}
+
+static int tc_ct_try_snk_entry(int port)
+{
+ tc[port].state_id = TC_CTTRY_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ tc[port].cc_state = PD_CC_UNSET;
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_TRY;
+
+ return 0;
+}
+
+static int tc_ct_try_snk_run(int port)
+{
+ int new_cc_state;
+ int cc1;
+ int cc2;
+
+ /*
+ * Wait for tDRPTry before monitoring the Charge-Through
+ * port’s CC pins for the SNK.Rp
+ */
+ if (get_time().val < tc[port].next_role_swap)
+ return 0;
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ if (cc_is_rp(cc1) || cc_is_rp(cc2))
+ new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ new_cc_state = PD_CC_NONE;
+
+ /*
+ * The Charge-Through VCONN-Powered USB Device shall transition
+ * to Unattached.SNK if VCONN falls below vVCONNDisconnect.
+ */
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /* Debounce the CT CC state */
+ if (tc[port].cc_state != new_cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_DEBOUNCE;
+ tc[port].try_wait_debounce = get_time().val + PD_T_TRY_WAIT;
+
+ return 0;
+ }
+
+ if (get_time().val > tc[port].cc_debounce) {
+ /*
+ * The Charge-Through VCONN-Powered USB Device shall then
+ * transition to CTAttached.VPD when the SNK.Rp state is
+ * detected on the Charge-Through port’s CC pins for at
+ * least tTryCCDebounce and VBUS is detected on
+ * Charge-Through port.
+ */
+ if (tc[port].cc_state == PD_CC_DFP_ATTACHED &&
+ vpd_is_ct_vbus_present())
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_attached_vpd);
+ }
+
+ if (get_time().val > tc[port].try_wait_debounce) {
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition
+ * to CTAttached.Unsupported if SNK.Rp state is not detected
+ * for tDRPTryWait.
+ */
+ if (tc[port].cc_state == PD_CC_NONE)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_attached_unsupported);
+ }
+
+ return SM_RUN_SUPER;
+}
+
+static int tc_ct_try_snk_exit(int port)
+{
+ /* Disable PD */
+ tc[port].pd_enable = 0;
+
+ return 0;
+}
+
+/**
+ * CTAttachWait.Unsupported
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RP3A0 on Host CC
+ * Place RPUSB on Charge-Through CC
+ * Get power from VCONN
+ */
+static int tc_ct_attach_wait_unsupported(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_attach_wait_unsupported_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rp3_ct_rpu);
+}
+
+static int tc_ct_attach_wait_unsupported_entry(int port)
+{
+ tc[port].state_id = TC_CTATTACH_WAIT_UNSUPPORTED;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ tc[port].cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_ct_attach_wait_unsupported_run(int port)
+{
+ int new_cc_state;
+ int cc1;
+ int cc2;
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ if (cc_is_at_least_one_rd(cc1, cc2))
+ new_cc_state = PD_CC_DFP_ATTACHED;
+ else if (cc_is_audio_acc(cc1, cc2))
+ new_cc_state = PD_CC_AUDIO_ACC;
+ else /* (cc1 == TYPEC_CC_VOLT_OPEN or cc2 == TYPEC_CC_VOLT_OPEN */
+ new_cc_state = PD_CC_NONE;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Unattached.SNK if VCONN falls below vVCONNDisconnect.
+ */
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /* Debounce the cc state */
+ if (tc[port].cc_state != new_cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+ return 0;
+ }
+
+ /* Wait for CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTUnattached.VPD when the state of either the Charge-Through
+ * Port’s CC1 or CC2 pin is SRC.Open for at least tCCDebounce.
+ *
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTTry.SNK if the state of at least one of the Charge-Through
+ * port’s CC pins is SRC.Rd, or if the state of both the CC1 and CC2
+ * pins is SRC.Ra. for at least tCCDebounce.
+ */
+ if (new_cc_state == PD_CC_NONE)
+ sm_set_state(port, TC_OBJ(port), tc_ct_unattached_vpd);
+ else /* PD_CC_DFP_ATTACHED or PD_CC_AUDIO_ACC */
+ sm_set_state(port, TC_OBJ(port), tc_ct_try_snk);
+
+ return 0;
+}
+
+static int tc_ct_attach_wait_unsupported_exit(int port)
+{
+ /* Disable PD */
+ tc[port].pd_enable = 0;
+
+ return 0;
+}
+
+/**
+ * CTAttached.Unsupported
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RP3A0 on Host CC
+ * Place RPUSB on Charge-Through CC
+ * Get power from VCONN
+ */
+static int tc_ct_attached_unsupported(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_attached_unsupported_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rp3_ct_rpu);
+}
+
+static int tc_ct_attached_unsupported_entry(int port)
+{
+ tc[port].state_id = TC_CTATTACHED_UNSUPPORTED;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Present Billboard device */
+ vpd_present_billboard(BB_SNK);
+
+ return 0;
+}
+
+static int tc_ct_attached_unsupported_run(int port)
+{
+ int cc1;
+ int cc2;
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /*
+ * The Charge-Through VCONN-Powered USB Device shall transition to
+ * CTUnattached.VPD when SRC.Open state is detected on both the
+ * Charge-Through port’s CC pins or the SRC.Open state is detected
+ * on one CC pin and SRC.Ra is detected on the other CC pin.
+ */
+ if ((cc1 == TYPEC_CC_VOLT_OPEN && cc2 == TYPEC_CC_VOLT_OPEN) ||
+ (cc1 == TYPEC_CC_VOLT_OPEN && cc2 == TYPEC_CC_VOLT_RA) ||
+ (cc1 == TYPEC_CC_VOLT_RA && cc2 == TYPEC_CC_VOLT_OPEN))
+ return sm_set_state(port, TC_OBJ(port), tc_ct_unattached_vpd);
+
+ return SM_RUN_SUPER;
+}
+
+static int tc_ct_attached_unsupported_exit(int port)
+{
+ vpd_present_billboard(BB_NONE);
+
+ return 0;
+}
+
+/**
+ * CTUnattached.Unsupported
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RP3A0 on Host CC
+ * Place RPUSB on Charge-Through CC
+ * Get power from VCONN
+ */
+static int tc_ct_unattached_unsupported(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_unattached_unsupported_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rp3_ct_rpu);
+}
+
+static int tc_ct_unattached_unsupported_entry(int port)
+{
+ tc[port].state_id = TC_CTUNATTACHED_UNSUPPORTED;
+ if (tc[port].obj.last_state != tc_ct_unattached_vpd)
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_SRC;
+
+ return 0;
+}
+
+static int tc_ct_unattached_unsupported_run(int port)
+{
+ int cc1;
+ int cc2;
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTAttachWait.Unsupported when a Sink connection is detected on
+ * the Charge-Through port, as indicated by the SRC.Rd state on at
+ * least one of the Charge-Through port’s CC pins or SRC.Ra state
+ * on both the CC1 and CC2 pins.
+ */
+ if (cc_is_at_least_one_rd(cc1, cc2) || cc_is_audio_acc(cc1, cc2))
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_attach_wait_unsupported);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Unattached.SNK if VCONN falls below vVCONNDisconnect.
+ */
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTUnattached.VPD within tDRPTransition after dcSRC.DRP ∙ tDRP.
+ */
+ if (get_time().val > tc[port].next_role_swap)
+ return sm_set_state(port, TC_OBJ(port), tc_ct_unattached_vpd);
+
+ return SM_RUN_SUPER;
+}
+
+static int tc_ct_unattached_unsupported_exit(int port)
+{
+ /* Disable PD */
+ tc[port].pd_enable = 0;
+
+ return 0;
+}
+
+/**
+ * CTUnattached.VPD
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RP3A0 on Host CC
+ * Connect Charge-Through Rd
+ * Get power from VCONN
+ */
+static int tc_ct_unattached_vpd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_unattached_vpd_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rp3_ct_rd);
+}
+
+static int tc_ct_unattached_vpd_entry(int port)
+{
+ tc[port].state_id = TC_CTUNATTACHED_VPD;
+ if (tc[port].obj.last_state != tc_ct_unattached_unsupported)
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ tc[port].cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_ct_unattached_vpd_run(int port)
+{
+ int new_cc_state;
+ int cc1;
+ int cc2;
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ if (cc_is_rp(cc1) != cc_is_rp(cc2))
+ new_cc_state = PD_CC_DFP_ATTACHED;
+ else if (!cc_is_rp(cc1) && !cc_is_rp(cc2))
+ new_cc_state = PD_CC_NONE;
+ else
+ new_cc_state = PD_CC_UNSET;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTAttachWait.VPD when a Source connection is detected on the
+ * Charge-Through port, as indicated by the SNK.Rp state on
+ * exactly one of the Charge-Through port’s CC pins.
+ */
+ if (new_cc_state == PD_CC_DFP_ATTACHED)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_attach_wait_vpd);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * Unattached.SNK if VCONN falls below vVCONNDisconnect.
+ */
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port),
+ tc_unattached_snk);
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_DRP_SRC;
+ return 0;
+ }
+
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTUnattached.Unsupported within tDRPTransition after the state
+ * of both the Charge-Through port’s CC1 and CC2 pins is SNK.Open
+ * for tDRP-dcSRC.DRP ∙ tDRP, or if directed.
+ */
+ if (tc[port].cc_state == PD_CC_NONE)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_unattached_unsupported);
+
+ return SM_RUN_SUPER;
+}
+
+static int tc_ct_unattached_vpd_exit(int port)
+{
+ /* Disable PD */
+ tc[port].pd_enable = 0;
+
+ return 0;
+}
+
+/**
+ * CTDisabled.VPD
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Remove the terminations from Host
+ * Remove the terminations from Charge-Through
+ */
+static int tc_ct_disabled_vpd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_disabled_vpd_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_open_ct_open);
+}
+
+static int tc_ct_disabled_vpd_entry(int port)
+{
+ tc[port].state_id = TC_CTDISABLED_VPD;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Get power from VBUS */
+ vpd_vconn_pwr_sel_odl(PWR_VBUS);
+
+ tc[port].next_role_swap = get_time().val + PD_T_VPDDISABLE;
+
+ return 0;
+}
+
+static int tc_ct_disabled_vpd_run(int port)
+{
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition
+ * to Unattached.SNK after tVPDDisable.
+ */
+ if (get_time().val > tc[port].next_role_swap)
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * CTAttached.VPD
+ */
+static int tc_ct_attached_vpd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_attached_vpd_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_ct_attached_vpd_entry(int port)
+{
+ int cc1;
+ int cc2;
+
+ tc[port].state_id = TC_CTATTACHED_VPD;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Get power from VCONN */
+ vpd_vconn_pwr_sel_odl(PWR_VCONN);
+
+ /*
+ * Detect which of the Charge-Through port’s CC1 or CC2
+ * pins is connected through the cable
+ */
+ vpd_ct_get_cc(&cc1, &cc2);
+ tc[port].ct_cc = cc_is_rp(cc2) ? CT_CC2 : CT_CC1;
+
+ /*
+ * 1. Remove or reduce any additional capacitance on the
+ * Host-side CC port
+ */
+ vpd_mcu_cc_en(0);
+
+ /*
+ * 2. Disable the Rp termination advertising 3.0 A on the
+ * host port’s CC pin
+ */
+ vpd_host_set_pull(TYPEC_CC_OPEN, 0);
+
+ /*
+ * 3. Passively multiplex the detected Charge-Through port’s
+ * CC pin through to the host port’s CC
+ */
+ vpd_ct_cc_sel(tc[port].ct_cc);
+
+ /*
+ * 4. Disable the Rd on the Charge-Through port’s CC1 and CC2
+ * pins
+ */
+ vpd_ct_set_pull(TYPEC_CC_OPEN, 0);
+
+ /*
+ * 5. Connect the Charge-Through port’s VBUS through to the
+ * host port’s VBUS
+ */
+ vpd_vbus_pass_en(1);
+
+ tc[port].cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_ct_attached_vpd_run(int port)
+{
+ int new_cc_state;
+ int cc1;
+ int cc2;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTDisabled.VPD if VCONN falls below vVCONNDisconnect.
+ */
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_ct_disabled_vpd);
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+ if ((tc[port].ct_cc ? cc2 : cc1) == TYPEC_CC_VOLT_OPEN)
+ new_cc_state = PD_CC_NONE;
+ else
+ new_cc_state = PD_CC_DFP_ATTACHED;
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_VPDCTDD;
+ return 0;
+ }
+
+ if (get_time().val < tc[port].pd_debounce)
+ return 0;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTUnattached.VPD when VBUS falls below vSinkDisconnect and the
+ * state of the passed-through CC pin is SNK.Open for tVPDCTDD.
+ */
+ if (tc[port].cc_state == PD_CC_NONE && !vpd_is_ct_vbus_present())
+ sm_set_state(port, TC_OBJ(port), tc_ct_unattached_vpd);
+
+ return 0;
+}
+
+/**
+ * CTAttachWait.VPD
+ *
+ * Super State Entry Actions:
+ * Isolate the Host-side port from the Charge-Through port
+ * Enable mcu communication
+ * Place RP3A0 on Host CC
+ * Connect Charge-Through Rd
+ * Get power from VCONN
+ */
+static int tc_ct_attach_wait_vpd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_ct_attach_wait_vpd_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rp3_ct_rd);
+}
+
+static int tc_ct_attach_wait_vpd_entry(int port)
+{
+ tc[port].state_id = TC_CTATTACH_WAIT_VPD;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ tc[port].cc_state = PD_CC_UNSET;
+
+ /* Sample CCs every 2ms */
+ tc_set_timeout(port, 2 * MSEC);
+ return 0;
+}
+
+static int tc_ct_attach_wait_vpd_run(int port)
+{
+ int new_cc_state;
+ int cc1;
+ int cc2;
+
+ /* Check CT CC for connection */
+ vpd_ct_get_cc(&cc1, &cc2);
+
+ if (cc_is_rp(cc1) != cc_is_rp(cc2))
+ new_cc_state = PD_CC_DFP_ATTACHED;
+ else if (!cc_is_rp(cc1) && !cc_is_rp(cc2))
+ new_cc_state = PD_CC_NONE;
+ else
+ new_cc_state = PD_CC_UNSET;
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTDisabled.VPD if VCONN falls below vVCONNDisconnect.
+ */
+ if (!vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_ct_disabled_vpd);
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val +
+ PD_T_CC_DEBOUNCE;
+ tc[port].pd_debounce = get_time().val +
+ PD_T_PD_DEBOUNCE;
+ return 0;
+ }
+
+ if (get_time().val > tc[port].pd_debounce) {
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition
+ * to CTUnattached.VPD when the state of both the Charge-Through
+ * port’s CC1 and CC2 pins are SNK.Open for at least
+ * tPDDebounce.
+ */
+ if (tc[port].cc_state == PD_CC_NONE)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_unattached_vpd);
+ }
+
+ if (get_time().val > tc[port].cc_debounce) {
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall transition to
+ * CTAttached.VPD after the state of only one of the
+ * Charge-Through port’s CC1 or CC2 pins is SNK.Rp for at
+ * least tCCDebounce and VBUS on the Charge-Through port is
+ * detected.
+ */
+ if (tc[port].cc_state == PD_CC_DFP_ATTACHED &&
+ vpd_is_ct_vbus_present())
+ return sm_set_state(port, TC_OBJ(port),
+ tc_ct_attached_vpd);
+ }
+
+ return SM_RUN_SUPER;
+}
+
+static int tc_ct_attach_wait_vpd_exit(int port)
+{
+ /* Disable PD */
+ tc[port].pd_enable = 0;
+
+ /* Reset timeout value to 10ms */
+ tc_set_timeout(port, 10 * MSEC);
+
+ return 0;
+}
+
+/**
+ * Super State HOST_RP3_CT_RD
+ */
+static int tc_host_rp3_ct_rd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_rp3_ct_rd_sig[sig])(port);
+
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_rp3_ct_rd_entry(int port)
+{
+ /* Place RP3A0 on Host CC */
+ vpd_host_set_pull(TYPEC_CC_RP, TYPEC_RP_3A0);
+
+ /* Connect Charge-Through Rd */
+ vpd_ct_set_pull(TYPEC_CC_RD, 0);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall
+ * ensure that it is powered by VCONN
+ */
+
+ /* Make sure vconn is on */
+ if (!vpd_is_vconn_present())
+ sm_set_state(port, TC_OBJ(port), tc_error_recovery);
+
+ /* Get power from VCONN */
+ vpd_vconn_pwr_sel_odl(PWR_VCONN);
+
+ return 0;
+}
+
+/**
+ * Super State HOST_RP3_CT_RPU
+ */
+static int tc_host_rp3_ct_rpu(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_rp3_ct_rpu_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_rp3_ct_rpu_entry(int port)
+{
+ /* Place RP3A0 on Host CC */
+ vpd_host_set_pull(TYPEC_CC_RP, TYPEC_RP_3A0);
+
+ /* Place RPUSB on Charge-Through CC */
+ vpd_ct_set_pull(TYPEC_CC_RP, TYPEC_RP_USB);
+
+ /*
+ * A Charge-Through VCONN-Powered USB Device shall
+ * ensure that it is powered by VCONN
+ */
+
+ /* Make sure vconn is on */
+ if (!vpd_is_vconn_present())
+ sm_set_state(port, TC_OBJ(port), tc_error_recovery);
+
+ /* Get power from VCONN */
+ vpd_vconn_pwr_sel_odl(PWR_VCONN);
+
+ return 0;
+}
diff --git a/common/usbc/usb_tc_drp_acc_trysrc_sm.c b/common/usbc/usb_tc_drp_acc_trysrc_sm.c
new file mode 100644
index 0000000000..cd2d8f3612
--- /dev/null
+++ b/common/usbc/usb_tc_drp_acc_trysrc_sm.c
@@ -0,0 +1,1724 @@
+/* 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 "charge_manager.h"
+#include "charge_state.h"
+#include "common.h"
+#include "console.h"
+#include "hooks.h"
+#include "system.h"
+#include "task.h"
+#include "tcpm.h"
+#include "usb_mux.h"
+#include "usb_pd.h"
+#include "usb_tc_drp_acc_trysrc_sm.h"
+#include "usb_tc_sm.h"
+#include "usbc_ppc.h"
+
+/*
+ * USB Type-C DRP with Accessory and Try.SRC module
+ * See Figure 4-16 in Release 1.4 of USB Type-C Spec.
+ */
+
+#ifdef CONFIG_COMMON_RUNTIME
+#define CPRINTF(format, args...) cprintf(CC_HOOK, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_HOOK, format, ## args)
+#else /* CONFIG_COMMON_RUNTIME */
+#define CPRINTF(format, args...)
+#define CPRINTS(format, args...)
+#endif
+
+/* Type-C Layer Flags */
+#define TC_FLAGS_VCONN_ON BIT(0)
+#define TC_FLAGS_TS_DTS_PARTNER BIT(1)
+#define TC_FLAGS_VBUS_NEVER_LOW BIT(2)
+#define TC_FLAGS_LPM_TRANSITION BIT(3)
+#define TC_FLAGS_LPM_ENGAGED BIT(4)
+#define TC_FLAGS_LPM_REQUESTED BIT(5)
+
+
+struct type_c tc[CONFIG_USB_PD_PORT_COUNT];
+
+/* Port dual-role state */
+enum pd_dual_role_states drp_state[CONFIG_USB_PD_PORT_COUNT] = {
+ [0 ... (CONFIG_USB_PD_PORT_COUNT - 1)] =
+ CONFIG_USB_PD_INITIAL_DRP_STATE};
+
+/*
+ * 4 entry rw_hash table of type-C devices that AP has firmware updates for.
+ */
+#ifdef CONFIG_COMMON_RUNTIME
+#define RW_HASH_ENTRIES 4
+static struct ec_params_usb_pd_rw_hash_entry rw_hash_table[RW_HASH_ENTRIES];
+#endif
+
+static void tc_set_data_role(int port, int role);
+
+#ifdef CONFIG_USB_PD_TRY_SRC
+/* Enable variable for Try.SRC states */
+static uint8_t pd_try_src_enable;
+static void pd_update_try_source(void);
+#endif
+
+/*
+ * Type-C State Hierarchy (Sub-States are listed inside the boxes)
+ *
+ * |TC_CC_RD --------------| |TC_CC_RP ------------------------|
+ * | | | |
+ * | TC_UNATTACHED_SNK | | TC_UNATTACHED_SRC |
+ * | TC_ATTACH_WAIT_SNK | | TC_ATTACH_WAIT_SRC |
+ * | TC_TRY_WAIT_SNK | | TC_TRY_SRC |
+ * | TC_DBG_ACC_SNK | | TC_UNORIENTED_DBG_ACC_SRC |
+ * |-----------------------| |---------------------------------|
+ *
+ * |TC_CC_OPEN -----------|
+ * | |
+ * | TC_DISABLED |
+ * | TC_ERROR_RECOVERY |
+ * |----------------------|
+ *
+ * TC_ATTACHED_SNK TC_ATTACHED_SRC
+ *
+ */
+
+/*
+ * Type-C states
+ */
+DECLARE_STATE(tc, disabled, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, error_recovery, WITH_RUN, NOOP);
+DECLARE_STATE(tc, unattached_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attach_wait_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attached_snk, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, dbg_acc_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, unattached_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attach_wait_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attached_src, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, unoriented_dbg_acc_src, WITH_RUN, NOOP);
+
+#ifdef CONFIG_USB_PD_TRY_SRC
+DECLARE_STATE(tc, try_src, WITH_RUN, NOOP);
+DECLARE_STATE(tc, try_wait_snk, WITH_RUN, NOOP);
+#endif
+
+/* Super States */
+DECLARE_STATE(tc, cc_rd, NOOP, NOOP);
+DECLARE_STATE(tc, cc_rp, NOOP, NOOP);
+DECLARE_STATE(tc, cc_open, NOOP, NOOP);
+
+/*
+ * Public Functions
+ *
+ * NOTE: Functions prefixed with pd_ are defined in usb_pd.h
+ * Functions prefixed with tc_ are defined int usb_tc_sm.h
+ */
+
+#ifndef CONFIG_USB_PRL_SM
+
+/*
+ * These pd_ functions are implemented in common/usb_prl_sm.c
+ */
+
+void pd_transmit_complete(int port, int status)
+{
+ /* DO NOTHING */
+}
+
+void pd_execute_hard_reset(int port)
+{
+ /* DO NOTHING */
+}
+
+void pd_set_vbus_discharge(int port, int enable)
+{
+ /* DO NOTHING */
+}
+
+uint16_t pd_get_identity_vid(int port)
+{
+ /* DO NOTHING */
+ return 0;
+}
+
+#endif /* !defined(CONFIG_USB_PRL_SM) */
+
+void pd_update_contract(int port)
+{
+ /* DO NOTHING */
+}
+
+void pd_set_new_power_request(int port)
+{
+ /* DO NOTHING */
+}
+
+void pd_request_power_swap(int port)
+{
+ /* DO NOTHING */
+}
+
+void pd_set_suspend(int port, int enable)
+{
+ sm_state state;
+
+ if (pd_is_port_enabled(port) == !enable)
+ return;
+
+ if (enable)
+ state = tc_disabled;
+ else
+ state = (TC_DEFAULT_STATE(port) == TC_UNATTACHED_SRC) ?
+ tc_unattached_src : tc_unattached_snk;
+
+ sm_set_state(port, TC_OBJ(port), state);
+}
+
+int pd_is_port_enabled(int port)
+{
+ return !(tc[port].state_id == TC_DISABLED);
+}
+
+int pd_fetch_acc_log_entry(int port)
+{
+ return EC_RES_SUCCESS;
+}
+
+int pd_get_polarity(int port)
+{
+ return tc[port].polarity;
+}
+
+int pd_get_role(int port)
+{
+ return tc[port].data_role;
+}
+
+int pd_is_vbus_present(int port)
+{
+ if (IS_ENABLED(CONFIG_USB_PD_VBUS_DETECT_TCPC))
+ return tcpm_get_vbus_level(port);
+ else
+ return pd_snk_is_vbus_provided(port);
+}
+
+void pd_vbus_low(int port)
+{
+ TC_CLR_FLAG(port, TC_FLAGS_VBUS_NEVER_LOW);
+}
+
+int pd_is_connected(int port)
+{
+ return (tc[port].state_id == TC_ATTACHED_SNK) ||
+ (tc[port].state_id == TC_ATTACHED_SRC);
+}
+
+#ifdef CONFIG_USB_PD_ALT_MODE_DFP
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+void pd_prepare_sysjump(void)
+{
+ /*
+ * We can't be in an alternate mode since PD comm is disabled, so
+ * no need to send the event
+ */
+}
+#endif
+
+void tc_src_power_off(int port)
+{
+ if (tc[port].state_id == TC_ATTACHED_SRC) {
+ /* Remove VBUS */
+ pd_power_supply_reset(port);
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) {
+ charge_manager_set_ceil(port, CEIL_REQUESTOR_PD,
+ CHARGE_CEIL_NONE);
+ }
+ }
+}
+
+void tc_start_error_recovery(int port)
+{
+ /*
+ * Async. function call:
+ * The port should transition to the ErrorRecovery state
+ * from any other state when directed.
+ */
+ sm_set_state(port, TC_OBJ(port), tc_error_recovery);
+}
+
+void tc_state_init(int port, enum typec_state_id start_state)
+{
+ int res = 0;
+ sm_state this_state;
+
+ res = tc_restart_tcpc(port);
+ if (res)
+ this_state = tc_disabled;
+ else
+ this_state = (start_state == TC_UNATTACHED_SRC) ?
+ tc_unattached_src : tc_unattached_snk;
+
+ CPRINTS("TCPC p%d init %s", port, res ? "failed" : "ready");
+
+ sm_init_state(port, TC_OBJ(port), this_state);
+
+ if (IS_ENABLED(CONFIG_USBC_SS_MUX))
+ /* Initialize USB mux to its default state */
+ usb_mux_init(port);
+
+ tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP);
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) {
+ /* Initialize PD and type-C supplier current limits to 0 */
+ pd_set_input_current_limit(port, 0, 0);
+ typec_set_input_current_limit(port, 0, 0);
+ charge_manager_update_dualrole(port, CAP_UNKNOWN);
+ }
+
+ tc[port].flags = 0;
+ tc[port].evt_timeout = 5*MSEC;
+}
+
+/*
+ * Private Functions
+ */
+
+void tc_event_check(int port, int evt)
+{
+ /* NO EVENTS TO CHECK */
+}
+
+/*
+ * CC values for regular sources and Debug sources (aka DTS)
+ *
+ * Source type Mode of Operation CC1 CC2
+ * ---------------------------------------------
+ * Regular Default USB Power RpUSB Open
+ * Regular USB-C @ 1.5 A Rp1A5 Open
+ * Regular USB-C @ 3 A Rp3A0 Open
+ * DTS Default USB Power Rp3A0 Rp1A5
+ * DTS USB-C @ 1.5 A Rp1A5 RpUSB
+ * DTS USB-C @ 3 A Rp3A0 RpUSB
+ */
+
+static void tc_set_data_role(int port, int role)
+{
+ tc[port].data_role = role;
+
+ if (IS_ENABLED(CONFIG_USBC_SS_MUX))
+ set_usb_mux_with_current_data_role(port);
+
+ /* Notify TCPC of role update */
+ tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role);
+}
+
+static void sink_stop_drawing_current(int port)
+{
+ pd_set_input_current_limit(port, 0, 0);
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) {
+ typec_set_input_current_limit(port, 0, 0);
+ charge_manager_set_ceil(port,
+ CEIL_REQUESTOR_PD, CHARGE_CEIL_NONE);
+ }
+}
+
+#ifdef CONFIG_USB_PD_TRY_SRC
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+static void pd_update_try_source(void)
+{
+ int i;
+ int try_src = 0;
+
+#ifndef CONFIG_CHARGER
+ int batt_soc = board_get_battery_soc();
+#else
+ int batt_soc = charge_get_percent();
+#endif
+
+ try_src = 0;
+ for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++)
+ try_src |= drp_state[i] == PD_DRP_TOGGLE_ON;
+
+ /*
+ * Enable try source when dual-role toggling AND battery is present
+ * and at some minimum percentage.
+ */
+ pd_try_src_enable = try_src &&
+ batt_soc >= CONFIG_USB_PD_TRY_SRC_MIN_BATT_SOC;
+
+#ifdef CONFIG_BATTERY_REVIVE_DISCONNECT
+ /*
+ * Don't attempt Try.Src if the battery is in the disconnect state. The
+ * discharge FET may not be enabled and so attempting Try.Src may cut
+ * off our only power source at the time.
+ */
+ pd_try_src_enable &= (battery_get_disconnect_state() ==
+ BATTERY_NOT_DISCONNECTED);
+#elif defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \
+ defined(CONFIG_BATTERY_PRESENT_GPIO)
+ /*
+ * When battery is cutoff in ship mode it may not be reliable to
+ * check if battery is present with its state of charge.
+ * Also check if battery is initialized and ready to provide power.
+ */
+ pd_try_src_enable &= (battery_is_present() == BP_YES);
+#endif /* CONFIG_BATTERY_PRESENT_[CUSTOM|GPIO] */
+
+}
+DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pd_update_try_source, HOOK_PRIO_DEFAULT);
+#endif /* CONFIG_USB_PD_TRY_SRC */
+
+#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO
+static inline void pd_dev_dump_info(uint16_t dev_id, uint8_t *hash)
+{
+ int j;
+
+ ccprintf("DevId:%d.%d Hash:", HW_DEV_ID_MAJ(dev_id),
+ HW_DEV_ID_MIN(dev_id));
+ for (j = 0; j < PD_RW_HASH_SIZE; j += 4) {
+ ccprintf(" 0x%02x%02x%02x%02x", hash[j + 3], hash[j + 2],
+ hash[j + 1], hash[j]);
+ }
+ ccprintf("\n");
+}
+#endif /* CONFIG_CMD_PD_DEV_DUMP_INFO */
+
+#if defined(CONFIG_CHARGE_MANAGER)
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+
+/*
+ * Returns type C current limit (mA) based upon cc_voltage (mV).
+ */
+static typec_current_t get_typec_current_limit(int polarity, int cc1, int cc2)
+{
+ typec_current_t charge;
+ int cc = polarity ? cc2 : cc1;
+ int cc_alt = polarity ? cc1 : cc2;
+
+ if (cc == TYPEC_CC_VOLT_RP_3_0 && cc_alt != TYPEC_CC_VOLT_RP_1_5)
+ charge = 3000;
+ else if (cc == TYPEC_CC_VOLT_RP_1_5)
+ charge = 1500;
+ else if (cc == TYPEC_CC_VOLT_RP_DEF)
+ charge = 500;
+ else
+ charge = 0;
+
+ if (cc_is_rp(cc_alt))
+ charge |= TYPEC_CURRENT_DTS_MASK;
+
+ return charge;
+}
+#endif
+
+static void set_vconn(int port, int enable)
+{
+ if (enable == TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON))
+ return;
+
+ if (enable)
+ TC_SET_FLAG(port, TC_FLAGS_VCONN_ON);
+ else
+ TC_CLR_FLAG(port, TC_FLAGS_VCONN_ON);
+
+ /*
+ * We always need to tell the TCPC to enable Vconn first, otherwise some
+ * TCPCs get confused and think the CC line is in over voltage mode and
+ * immediately disconnects. If there is a PPC, both devices will
+ * potentially source Vconn, but that should be okay since Vconn has
+ * "make before break" electrical requirements when swapping anyway.
+ */
+ tcpm_set_vconn(port, enable);
+
+ if (IS_ENABLED(CONFIG_USBC_PPC_VCONN))
+ ppc_set_vconn(port, enable);
+}
+
+#ifdef CONFIG_USB_PD_TCPM_TCPCI
+static uint32_t pd_ports_to_resume;
+static void resume_pd_port(void)
+{
+ uint32_t port;
+ uint32_t suspended_ports = atomic_read_clear(&pd_ports_to_resume);
+
+ while (suspended_ports) {
+ port = __builtin_ctz(suspended_ports);
+ suspended_ports &= ~(1 << port);
+ pd_set_suspend(port, 0);
+ }
+}
+DECLARE_DEFERRED(resume_pd_port);
+
+void pd_deferred_resume(int port)
+{
+ atomic_or(&pd_ports_to_resume, 1 << port);
+ hook_call_deferred(&resume_pd_port_data, SECOND);
+}
+#endif /* CONFIG_USB_PD_DEFERRED_RESUME */
+
+/*
+ * HOST COMMANDS
+ */
+static int hc_pd_ports(struct host_cmd_handler_args *args)
+{
+ struct ec_response_usb_pd_ports *r = args->response;
+
+ r->num_ports = CONFIG_USB_PD_PORT_COUNT;
+ args->response_size = sizeof(*r);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_USB_PD_PORTS,
+ hc_pd_ports,
+ EC_VER_MASK(0));
+
+static int hc_remote_rw_hash_entry(struct host_cmd_handler_args *args)
+{
+ int i;
+ int idx = 0;
+ int found = 0;
+ const struct ec_params_usb_pd_rw_hash_entry *p = args->params;
+ static int rw_hash_next_idx;
+
+ if (!p->dev_id)
+ return EC_RES_INVALID_PARAM;
+
+ for (i = 0; i < RW_HASH_ENTRIES; i++) {
+ if (p->dev_id == rw_hash_table[i].dev_id) {
+ idx = i;
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ idx = rw_hash_next_idx;
+ rw_hash_next_idx = rw_hash_next_idx + 1;
+ if (rw_hash_next_idx == RW_HASH_ENTRIES)
+ rw_hash_next_idx = 0;
+ }
+
+ memcpy(&rw_hash_table[idx], p, sizeof(*p));
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_USB_PD_RW_HASH_ENTRY,
+ hc_remote_rw_hash_entry,
+ EC_VER_MASK(0));
+
+static int hc_remote_pd_dev_info(struct host_cmd_handler_args *args)
+{
+ const uint8_t *port = args->params;
+ struct ec_params_usb_pd_rw_hash_entry *r = args->response;
+
+ if (*port >= CONFIG_USB_PD_PORT_COUNT)
+ return EC_RES_INVALID_PARAM;
+
+ r->dev_id = tc[*port].dev_id;
+
+ if (r->dev_id)
+ memcpy(r->dev_rw_hash, tc[*port].dev_rw_hash, PD_RW_HASH_SIZE);
+
+ r->current_image = tc[*port].current_image;
+
+ args->response_size = sizeof(*r);
+ return EC_RES_SUCCESS;
+}
+
+DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DEV_INFO,
+ hc_remote_pd_dev_info,
+ EC_VER_MASK(0));
+
+#ifdef CONFIG_USBC_PPC
+static void pd_send_hard_reset(int port)
+{
+ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SEND_HARD_RESET, 0);
+}
+
+static uint32_t port_oc_reset_req;
+
+static void re_enable_ports(void)
+{
+ uint32_t ports = atomic_read_clear(&port_oc_reset_req);
+
+ while (ports) {
+ int port = __fls(ports);
+
+ ports &= ~BIT(port);
+
+ /*
+ * Let the board know that the overcurrent is
+ * over since we're going to attempt re-enabling
+ * the port.
+ */
+ board_overcurrent_event(port, 0);
+
+ pd_send_hard_reset(port);
+ /*
+ * TODO(b/117854867): PD3.0 to send an alert message
+ * indicating OCP after explicit contract.
+ */
+ }
+}
+DECLARE_DEFERRED(re_enable_ports);
+
+void pd_handle_overcurrent(int port)
+{
+ /* Keep track of the overcurrent events. */
+ CPRINTS("C%d: overcurrent!", port);
+
+ if (IS_ENABLED(CONFIG_USB_PD_LOGGING))
+ pd_log_event(PD_EVENT_PS_FAULT, PD_LOG_PORT_SIZE(port, 0),
+ PS_FAULT_OCP, NULL);
+
+ ppc_add_oc_event(port);
+ /* Let the board specific code know about the OC event. */
+ board_overcurrent_event(port, 1);
+
+ /* Wait 1s before trying to re-enable the port. */
+ atomic_or(&port_oc_reset_req, BIT(port));
+ hook_call_deferred(&re_enable_ports_data, SECOND);
+}
+#endif /* defined(CONFIG_USBC_PPC) */
+
+#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
+/* 10 ms is enough time for any TCPC transaction to complete. */
+#define PD_LPM_DEBOUNCE_US (10 * MSEC)
+
+/* This is only called from the PD tasks that owns the port. */
+static void handle_device_access(int port)
+{
+ /* This should only be called from the PD task */
+ assert(port == TASK_ID_TO_PD_PORT(task_get_current()));
+
+ tc[port].low_power_time = get_time().val + PD_LPM_DEBOUNCE_US;
+ if (TC_CHK_FLAG(port, TC_FLAGS_LPM_ENGAGED)) {
+ CPRINTS("TCPC p%d Exit Low Power Mode", port);
+ TC_CLR_FLAG(port, TC_FLAGS_LPM_ENGAGED |
+ TC_FLAGS_LPM_REQUESTED);
+ /*
+ * Wake to ensure we make another pass through the main task
+ * loop after clearing the flags.
+ */
+ task_wake(PD_PORT_TO_TASK_ID(port));
+ }
+}
+
+static int pd_device_in_low_power(int port)
+{
+ /*
+ * If we are actively waking the device up in the PD task, do not
+ * let TCPC operation wait or retry because we are in low power mode.
+ */
+ if (port == TASK_ID_TO_PD_PORT(task_get_current()) &&
+ TC_CHK_FLAG(port, TC_FLAGS_LPM_TRANSITION))
+ return 0;
+
+ return TC_CHK_FLAG(port, TC_FLAGS_LPM_ENGAGED);
+}
+
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+static int reset_device_and_notify(int port)
+{
+ int rv;
+ int task, waiting_tasks;
+
+ /* This should only be called from the PD task */
+ assert(port == TASK_ID_TO_PD_PORT(task_get_current()));
+
+ TC_SET_FLAG(port, TC_FLAGS_LPM_TRANSITION);
+ rv = tcpm_init(port);
+ TC_CLR_FLAG(port, TC_FLAGS_LPM_TRANSITION);
+
+ if (rv == EC_SUCCESS)
+ CPRINTS("TCPC p%d init ready", port);
+ else
+ CPRINTS("TCPC p%d init failed!", port);
+
+ /*
+ * Before getting the other tasks that are waiting, clear the reset
+ * event from this PD task to prevent multiple reset/init events
+ * occurring.
+ *
+ * The double reset event happens when the higher priority PD interrupt
+ * task gets an interrupt during the above tcpm_init function. When that
+ * occurs, the higher priority task waits correctly for us to finish
+ * waking the TCPC, but it has also set PD_EVENT_TCPC_RESET again, which
+ * would result in a second, unnecessary init.
+ */
+ atomic_clear(task_get_event_bitmap(task_get_current()),
+ PD_EVENT_TCPC_RESET);
+
+ waiting_tasks = atomic_read_clear(&tc[port].tasks_waiting_on_reset);
+
+ /*
+ * Now that we are done waking up the device, handle device access
+ * manually because we ignored it while waking up device.
+ */
+ handle_device_access(port);
+
+ /* Clear SW LPM state; the state machine will set it again if needed */
+ TC_CLR_FLAG(port, TC_FLAGS_LPM_REQUESTED);
+
+ /* Wake up all waiting tasks. */
+ while (waiting_tasks) {
+ task = __fls(waiting_tasks);
+ waiting_tasks &= ~BIT(task);
+ task_set_event(task, TASK_EVENT_PD_AWAKE, 0);
+ }
+
+ return rv;
+}
+
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+static void pd_wait_for_wakeup(int port)
+{
+ if (port == TASK_ID_TO_PD_PORT(task_get_current())) {
+ /* If we are in the PD task, we can directly reset */
+ reset_device_and_notify(port);
+ } else {
+ /* Otherwise, we need to wait for the TCPC reset to complete */
+ atomic_or(&tc[port].tasks_waiting_on_reset,
+ 1 << task_get_current());
+ /*
+ * NOTE: We could be sending the PD task the reset event while
+ * it is already processing the reset event. If that occurs,
+ * then we will reset the TCPC multiple times, which is
+ * undesirable but most likely benign. Empirically, this doesn't
+ * happen much, but it if starts occurring, we can add a guard
+ * to prevent/reduce it.
+ */
+ task_set_event(PD_PORT_TO_TASK_ID(port),
+ PD_EVENT_TCPC_RESET, 0);
+ task_wait_event_mask(TASK_EVENT_PD_AWAKE, -1);
+ }
+}
+
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+void pd_wait_exit_low_power(int port)
+{
+ if (pd_device_in_low_power(port))
+ pd_wait_for_wakeup(port);
+}
+
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+/*
+ * This can be called from any task. If we are in the PD task, we can handle
+ * immediately. Otherwise, we need to notify the PD task via event.
+ */
+void pd_device_accessed(int port)
+{
+ if (port == TASK_ID_TO_PD_PORT(task_get_current())) {
+ /* Ignore any access to device while it is waking up */
+ if (TC_CHK_FLAG(port, TC_FLAGS_LPM_TRANSITION))
+ return;
+
+ handle_device_access(port);
+ } else {
+ task_set_event(PD_PORT_TO_TASK_ID(port),
+ PD_EVENT_DEVICE_ACCESSED, 0);
+ }
+}
+
+/*
+ * TODO(b/137493121): Move this function to a separate file that's shared
+ * between the this and the original stack.
+ */
+void pd_prevent_low_power_mode(int port, int prevent)
+{
+ const int current_task_mask = (1 << task_get_current());
+
+ if (prevent)
+ atomic_or(&tc[port].tasks_preventing_lpm, current_task_mask);
+ else
+ atomic_clear(&tc[port].tasks_preventing_lpm, current_task_mask);
+}
+
+#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */
+
+static void sink_power_sub_states(int port)
+{
+ int cc1;
+ int cc2;
+ enum tcpc_cc_voltage_status new_cc_voltage;
+
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ if (tc[port].polarity)
+ cc1 = cc2;
+
+ if (cc1 == TYPEC_CC_VOLT_RP_DEF)
+ new_cc_voltage = TYPEC_CC_VOLT_RP_DEF;
+ else if (cc1 == TYPEC_CC_VOLT_RP_1_5)
+ new_cc_voltage = TYPEC_CC_VOLT_RP_1_5;
+ else if (cc1 == TYPEC_CC_VOLT_RP_3_0)
+ new_cc_voltage = TYPEC_CC_VOLT_RP_3_0;
+ else
+ new_cc_voltage = TYPEC_CC_VOLT_OPEN;
+
+ /* Debounce the cc state */
+ if (new_cc_voltage != tc[port].cc_voltage) {
+ tc[port].cc_voltage = new_cc_voltage;
+ tc[port].cc_debounce =
+ get_time().val + PD_T_RP_VALUE_CHANGE;
+ return;
+ }
+
+ if (tc[port].cc_debounce == 0 ||
+ get_time().val < tc[port].cc_debounce)
+ return;
+
+ tc[port].cc_debounce = 0;
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) {
+ tc[port].typec_curr =
+ get_typec_current_limit(tc[port].polarity, cc1, cc1);
+
+ typec_set_input_current_limit(port,
+ tc[port].typec_curr, TYPE_C_VOLTAGE);
+ charge_manager_update_dualrole(port, CAP_DEDICATED);
+ }
+}
+
+
+/*
+ * TYPE-C State Implementations
+ */
+
+/**
+ * Disabled
+ *
+ * Super State Entry Actions:
+ * Remove the terminations from CC
+ * Set's VBUS and VCONN off
+ */
+static int tc_disabled(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_disabled_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_open);
+}
+
+static int tc_disabled_entry(int port)
+{
+ tc[port].state_id = TC_DISABLED;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ return 0;
+}
+
+static int tc_disabled_run(int port)
+{
+ task_wait_event(-1);
+ return SM_RUN_SUPER;
+}
+
+static int tc_disabled_exit(int port)
+{
+ if (!IS_ENABLED(CONFIG_USB_PD_TCPC)) {
+ if (tc_restart_tcpc(port) != 0) {
+ CPRINTS("TCPC p%d restart failed!", port);
+ return 0;
+ }
+ }
+
+ CPRINTS("TCPC p%d resumed!", port);
+
+ return 0;
+}
+
+/**
+ * ErrorRecovery
+ *
+ * Super State Entry Actions:
+ * Remove the terminations from CC
+ * Set's VBUS and VCONN off
+ */
+static int tc_error_recovery(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_error_recovery_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_open);
+}
+
+static int tc_error_recovery_entry(int port)
+{
+ tc[port].state_id = TC_ERROR_RECOVERY;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].timeout = get_time().val + PD_T_ERROR_RECOVERY;
+ return 0;
+}
+
+static int tc_error_recovery_run(int port)
+{
+ if (tc[port].timeout > 0 && get_time().val > tc[port].timeout) {
+ tc[port].timeout = 0;
+ tc_state_init(port, TC_UNATTACHED_SRC);
+ }
+
+ return 0;
+}
+
+/**
+ * Unattached.SNK
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rd on CC
+ * Set power role to SINK
+ */
+static int tc_unattached_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_unattached_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rd);
+}
+
+static int tc_unattached_snk_entry(int port)
+{
+ tc[port].state_id = TC_UNATTACHED_SNK;
+ if (tc[port].obj.last_state != tc_unattached_src)
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER))
+ charge_manager_update_dualrole(port, CAP_UNKNOWN);
+
+ /*
+ * Indicate that the port is disconnected so the board
+ * can restore state from any previous data swap.
+ */
+ pd_execute_data_swap(port, PD_ROLE_DISCONNECTED);
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_SNK;
+
+ return 0;
+}
+
+static int tc_unattached_snk_run(int port)
+{
+ int cc1;
+ int cc2;
+
+ /*
+ * TODO(b/137498392): Add wait before sampling the CC
+ * status after role changes
+ */
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ /*
+ * The port shall transition to AttachWait.SNK when a Source
+ * connection is detected, as indicated by the SNK.Rp state
+ * on at least one of its CC pins.
+ *
+ * A DRP shall transition to Unattached.SRC within tDRPTransition
+ * after the state of both CC pins is SNK.Open for
+ * tDRP − dcSRC.DRP ∙ tDRP.
+ */
+ if (cc_is_rp(cc1) || cc_is_rp(cc2)) {
+ /* Connection Detected */
+ sm_set_state(port, TC_OBJ(port), tc_attach_wait_snk);
+ } else if (get_time().val > tc[port].next_role_swap) {
+ /* DRP Toggle */
+ sm_set_state(port, TC_OBJ(port), tc_unattached_src);
+ }
+
+ return 0;
+}
+
+/**
+ * AttachWait.SNK
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rd on CC
+ * Set power role to SINK
+ */
+static int tc_attach_wait_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attach_wait_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rd);
+}
+
+static int tc_attach_wait_snk_entry(int port)
+{
+ tc[port].state_id = TC_ATTACH_WAIT_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].cc_state = PD_CC_UNSET;
+ return 0;
+}
+
+static int tc_attach_wait_snk_run(int port)
+{
+ int cc1;
+ int cc2;
+ enum pd_cc_states new_cc_state;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ if (cc_is_rp(cc1) && cc_is_rp(cc2))
+ new_cc_state = PD_CC_DEBUG_ACC;
+ else if (cc_is_rp(cc1) || cc_is_rp(cc2))
+ new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ new_cc_state = PD_CC_NONE;
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+ tc[port].pd_debounce = get_time().val + PD_T_PD_DEBOUNCE;
+ tc[port].cc_state = new_cc_state;
+ return 0;
+ }
+
+ /*
+ * A DRP shall transition to Unattached.SNK when the state of both
+ * the CC1 and CC2 pins is SNK.Open for at least tPDDebounce.
+ */
+ if (new_cc_state == PD_CC_NONE &&
+ get_time().val > tc[port].pd_debounce) {
+ /* We are detached */
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_src);
+ }
+
+ /* Wait for CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * The port shall transition to Attached.SNK after the state of only one
+ * of the CC1 or CC2 pins is SNK.Rp for at least tCCDebounce and VBUS is
+ * detected.
+ *
+ * A DRP that strongly prefers the Source role may optionally transition
+ * to Try.SRC instead of Attached.SNK when the state of only one CC pin
+ * has been SNK.Rp for at least tCCDebounce and VBUS is detected.
+ *
+ * If the port supports Debug Accessory Mode, the port shall transition
+ * to DebugAccessory.SNK if the state of both the CC1 and CC2 pins is
+ * SNK.Rp for at least tCCDebounce and VBUS is detected.
+ */
+ if (pd_is_vbus_present(port)) {
+ if (new_cc_state == PD_CC_DFP_ATTACHED) {
+#ifdef CONFIG_USB_PD_TRY_SRC
+ if (pd_try_src_enable)
+ sm_set_state(port, TC_OBJ(port), tc_try_src);
+ else
+#endif
+ sm_set_state(port, TC_OBJ(port),
+ tc_attached_snk);
+ } else {
+ /* new_cc_state is PD_CC_DEBUG_ACC */
+ TC_SET_FLAG(port, TC_FLAGS_TS_DTS_PARTNER);
+ sm_set_state(port, TC_OBJ(port), tc_dbg_acc_snk);
+ }
+ }
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * Attached.SNK
+ */
+static int tc_attached_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attached_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_attached_snk_entry(int port)
+{
+ int cc1;
+ int cc2;
+
+ tc[port].state_id = TC_ATTACHED_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Get connector orientation */
+ tcpm_get_cc(port, &cc1, &cc2);
+ tc[port].polarity = get_snk_polarity(cc1, cc2);
+ set_polarity(port, tc[port].polarity);
+
+ /*
+ * Initial data role for sink is UFP
+ * This also sets the usb mux
+ */
+ tc_set_data_role(port, PD_ROLE_UFP);
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) {
+ tc[port].typec_curr =
+ get_typec_current_limit(tc[port].polarity, cc1, cc2);
+ typec_set_input_current_limit(port, tc[port].typec_curr,
+ TYPE_C_VOLTAGE);
+ charge_manager_update_dualrole(port, CAP_DEDICATED);
+ tc[port].cc_state = (tc[port].polarity) ? cc2 : cc1;
+ }
+
+ /* Apply Rd */
+ tcpm_set_cc(port, TYPEC_CC_RD);
+
+ tc[port].cc_debounce = 0;
+ return 0;
+}
+
+static int tc_attached_snk_run(int port)
+{
+ /* Detach detection */
+ if (!pd_is_vbus_present(port))
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ /* Run Sink Power Sub-State */
+ sink_power_sub_states(port);
+
+ return 0;
+}
+
+static int tc_attached_snk_exit(int port)
+{
+ /* Stop drawing power */
+ sink_stop_drawing_current(port);
+
+ return 0;
+}
+
+/**
+ * UnorientedDebugAccessory.SRC
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rp on CC
+ * Set power role to SOURCE
+ */
+static int tc_unoriented_dbg_acc_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_unoriented_dbg_acc_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rp);
+}
+
+static int tc_unoriented_dbg_acc_src_entry(int port)
+{
+ tc[port].state_id = TC_UNORIENTED_DEBUG_ACCESSORY_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable VBUS */
+ pd_set_power_supply_ready(port);
+
+ /* Any board specific unoriented debug setup should be added below */
+
+ return 0;
+}
+
+static int tc_unoriented_dbg_acc_src_run(int port)
+{
+ int cc1;
+ int cc2;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ /*
+ * A DRP, the port shall transition to Unattached.SNK when the
+ * SRC.Open state is detected on either the CC1 or CC2 pin.
+ */
+ if (cc1 == TYPEC_CC_VOLT_OPEN || cc2 == TYPEC_CC_VOLT_OPEN) {
+ /* Remove VBUS */
+ pd_power_supply_reset(port);
+ charge_manager_set_ceil(port, CEIL_REQUESTOR_PD,
+ CHARGE_CEIL_NONE);
+
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+ }
+
+ return 0;
+}
+
+/**
+ * Debug Accessory.SNK
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rd on CC
+ * Set power role to SINK
+ */
+static int tc_dbg_acc_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_dbg_acc_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rd);
+}
+
+static int tc_dbg_acc_snk_entry(int port)
+{
+ tc[port].state_id = TC_DEBUG_ACCESSORY_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /*
+ * TODO(b/137759869): Board specific debug accessory setup should
+ * be add here.
+ */
+
+ return 0;
+}
+
+static int tc_dbg_acc_snk_run(int port)
+{
+ if (!pd_is_vbus_present(port))
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * Unattached.SRC
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rp on CC
+ * Set power role to SOURCE
+ */
+static int tc_unattached_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_unattached_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rp);
+}
+
+static int tc_unattached_src_entry(int port)
+{
+ tc[port].state_id = TC_UNATTACHED_SRC;
+ if (tc[port].obj.last_state != tc_unattached_snk)
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ if (IS_ENABLED(CONFIG_USBC_PPC)) {
+ /* There is no sink connected. */
+ ppc_sink_is_connected(port, 0);
+
+ /*
+ * Clear the overcurrent event counter
+ * since we've detected a disconnect.
+ */
+ ppc_clear_oc_event_counter(port);
+ }
+
+ if (IS_ENABLED(CONFIG_CHARGE_MANAGER))
+ charge_manager_update_dualrole(port, CAP_UNKNOWN);
+
+ tc_set_data_role(port, PD_ROLE_DFP);
+
+ /*
+ * Indicate that the port is disconnected so the board
+ * can restore state from any previous data swap.
+ */
+ pd_execute_data_swap(port, PD_ROLE_DISCONNECTED);
+
+ tc[port].next_role_swap = get_time().val + PD_T_DRP_SRC;
+
+ return 0;
+}
+
+static int tc_unattached_src_run(int port)
+{
+ int cc1;
+ int cc2;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ /*
+ * Transition to AttachWait.SRC when VBUS is vSafe0V and:
+ * 1) The SRC.Rd state is detected on either CC1 or CC2 pin or
+ * 2) The SRC.Ra state is detected on both the CC1 and CC2 pins.
+ *
+ * A DRP shall transition to Unattached.SNK within tDRPTransition
+ * after dcSRC.DRP ∙ tDRP
+ */
+ if (cc_is_at_least_one_rd(cc1, cc2) || cc_is_audio_acc(cc1, cc2))
+ sm_set_state(port, TC_OBJ(port), tc_attach_wait_src);
+ else if (get_time().val > tc[port].next_role_swap)
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * AttachWait.SRC
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rp on CC
+ * Set power role to SOURCE
+ */
+static int tc_attach_wait_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attach_wait_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rp);
+}
+
+static int tc_attach_wait_src_entry(int port)
+{
+ tc[port].state_id = TC_ATTACH_WAIT_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_attach_wait_src_run(int port)
+{
+ int cc1;
+ int cc2;
+ enum pd_cc_states new_cc_state;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ /* Debug accessory */
+ if (cc_is_snk_dbg_acc(cc1, cc2)) {
+ /* Debug accessory */
+ new_cc_state = PD_CC_DEBUG_ACC;
+ } else if (cc_is_at_least_one_rd(cc1, cc2)) {
+ /* UFP attached */
+ new_cc_state = PD_CC_UFP_ATTACHED;
+ } else if (cc_is_audio_acc(cc1, cc2)) {
+ /* AUDIO Accessory not supported. Just ignore */
+ new_cc_state = PD_CC_AUDIO_ACC;
+ } else {
+ /* No UFP */
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+ }
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+ tc[port].cc_state = new_cc_state;
+ return 0;
+ }
+
+ /* Wait for CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * The port shall transition to Attached.SRC when VBUS is at vSafe0V
+ * and the SRC.Rd state is detected on exactly one of the CC1 or CC2
+ * pins for at least tCCDebounce.
+ *
+ * If the port supports Debug Accessory Mode, it shall transition to
+ * UnorientedDebugAccessory.SRC when VBUS is at vSafe0V and the SRC.Rd
+ * state is detected on both the CC1 and CC2 pins for at least
+ * tCCDebounce.
+ */
+ if (!pd_is_vbus_present(port)) {
+ if (new_cc_state == PD_CC_UFP_ATTACHED)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_attached_src);
+ else if (new_cc_state == PD_CC_DEBUG_ACC)
+ return sm_set_state(port, TC_OBJ(port),
+ tc_unoriented_dbg_acc_src);
+ }
+
+ return 0;
+}
+
+/**
+ * Attached.SRC
+ */
+static int tc_attached_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attached_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_attached_src_entry(int port)
+{
+ int cc1;
+ int cc2;
+
+ tc[port].state_id = TC_ATTACHED_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Get connector orientation */
+ tcpm_get_cc(port, &cc1, &cc2);
+ tc[port].polarity = (cc1 != TYPEC_CC_VOLT_RD);
+ set_polarity(port, tc[port].polarity);
+
+ /*
+ * Initial data role for sink is DFP
+ * This also sets the usb mux
+ */
+ tc_set_data_role(port, PD_ROLE_DFP);
+
+ /*
+ * Start sourcing Vconn before Vbus to ensure
+ * we are within USB Type-C Spec 1.4 tVconnON
+ */
+ if (IS_ENABLED(CONFIG_USBC_VCONN))
+ set_vconn(port, 1);
+
+ /* Enable VBUS */
+ if (pd_set_power_supply_ready(port)) {
+ /* Stop sourcing Vconn if Vbus failed */
+ if (IS_ENABLED(CONFIG_USBC_VCONN))
+ set_vconn(port, 0);
+
+ if (IS_ENABLED(CONFIG_USBC_SS_MUX))
+ usb_mux_set(port, TYPEC_MUX_NONE,
+ USB_SWITCH_DISCONNECT, tc[port].polarity);
+ }
+
+ /* Apply Rp */
+ tcpm_set_cc(port, TYPEC_CC_RP);
+
+ tc[port].cc_debounce = 0;
+ tc[port].cc_state = PD_CC_NONE;
+
+ /* Inform PPC that a sink is connected. */
+ if (IS_ENABLED(CONFIG_USBC_PPC))
+ ppc_sink_is_connected(port, 1);
+
+ return 0;
+}
+
+static int tc_attached_src_run(int port)
+{
+ int cc1;
+ int cc2;
+ enum pd_cc_states new_cc_state;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ if (tc[port].polarity)
+ cc1 = cc2;
+
+ if (cc1 == TYPEC_CC_VOLT_OPEN)
+ new_cc_state = PD_CC_NO_UFP;
+ else
+ new_cc_state = PD_CC_NONE;
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_SRC_DISCONNECT;
+ }
+
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * When the SRC.Open state is detected on the monitored CC pin, a DRP
+ * shall transition to Unattached.SNK unless it strongly prefers the
+ * Source role. In that case, it shall transition to TryWait.SNK.
+ * This transition to TryWait.SNK is needed so that two devices that
+ * both prefer the Source role do not loop endlessly between Source
+ * and Sink. In other words, a DRP that would enter Try.SRC from
+ * AttachWait.SNK shall enter TryWait.SNK for a Sink detach from
+ * Attached.SRC.
+ */
+ if (tc[port].cc_state == PD_CC_NO_UFP) {
+ if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC))
+ return sm_set_state(port, TC_OBJ(port),
+ tc_try_wait_snk);
+ else
+ return sm_set_state(port, TC_OBJ(port),
+ tc_unattached_snk);
+ }
+
+ return 0;
+}
+
+static int tc_attached_src_exit(int port)
+{
+ /*
+ * A port shall cease to supply VBUS within tVBUSOFF of exiting
+ * Attached.SRC.
+ */
+ tc_src_power_off(port);
+
+ return 0;
+}
+
+/**
+ * Try.SRC
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rp on CC
+ * Set power role to SOURCE
+ */
+#ifdef CONFIG_USB_PD_TRY_SRC
+static int tc_try_src(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_try_src_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rp);
+}
+
+static int tc_try_src_entry(int port)
+{
+ tc[port].state_id = TC_TRY_SRC;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].cc_state = PD_CC_UNSET;
+ tc[port].try_wait_debounce = get_time().val + PD_T_DRP_TRY;
+ tc[port].timeout = get_time().val + PD_T_TRY_TIMEOUT;
+ return 0;
+}
+
+static int tc_try_src_run(int port)
+{
+ int cc1;
+ int cc2;
+ enum pd_cc_states new_cc_state;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ if ((cc1 == TYPEC_CC_VOLT_RD && cc2 != TYPEC_CC_VOLT_RD) ||
+ (cc1 != TYPEC_CC_VOLT_RD && cc2 == TYPEC_CC_VOLT_RD))
+ new_cc_state = PD_CC_UFP_ATTACHED;
+ else
+ new_cc_state = PD_CC_NONE;
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+ }
+
+ /*
+ * The port shall transition to Attached.SRC when the SRC.Rd state is
+ * detected on exactly one of the CC1 or CC2 pins for at least
+ * tTryCCDebounce.
+ */
+ if (get_time().val > tc[port].cc_debounce) {
+ if (new_cc_state == PD_CC_UFP_ATTACHED)
+ sm_set_state(port, TC_OBJ(port), tc_attached_src);
+ }
+
+ /*
+ * The port shall transition to TryWait.SNK after tDRPTry and the
+ * SRC.Rd state has not been detected and VBUS is within vSafe0V,
+ * or after tTryTimeout and the SRC.Rd state has not been detected.
+ */
+ if (new_cc_state == PD_CC_NONE) {
+ if ((get_time().val > tc[port].try_wait_debounce &&
+ !pd_is_vbus_present(port)) ||
+ get_time().val > tc[port].timeout) {
+ sm_set_state(port, TC_OBJ(port), tc_try_wait_snk);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * TryWait.SNK
+ *
+ * Super State Entry Actions:
+ * Vconn Off
+ * Place Rd on CC
+ * Set power role to SINK
+ */
+static int tc_try_wait_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_try_wait_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_cc_rd);
+}
+
+static int tc_try_wait_snk_entry(int port)
+{
+ tc[port].state_id = TC_TRY_WAIT_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ tc[port].cc_state = PD_CC_UNSET;
+ tc[port].try_wait_debounce = get_time().val + PD_T_CC_DEBOUNCE;
+
+ return 0;
+}
+
+static int tc_try_wait_snk_run(int port)
+{
+ int cc1;
+ int cc2;
+ enum pd_cc_states new_cc_state;
+
+ /* Check for connection */
+ tcpm_get_cc(port, &cc1, &cc2);
+
+ /* We only care about CCs being open */
+ if (cc1 == TYPEC_CC_VOLT_OPEN && cc2 == TYPEC_CC_VOLT_OPEN)
+ new_cc_state = PD_CC_NONE;
+ else
+ new_cc_state = PD_CC_UNSET;
+
+ /* Debounce the cc state */
+ if (new_cc_state != tc[port].cc_state) {
+ tc[port].cc_state = new_cc_state;
+ tc[port].pd_debounce = get_time().val + PD_T_PD_DEBOUNCE;
+ }
+
+ /*
+ * The port shall transition to Unattached.SNK when the state of both
+ * of the CC1 and CC2 pins is SNK.Open for at least tPDDebounce.
+ */
+ if ((get_time().val > tc[port].pd_debounce) &&
+ (new_cc_state == PD_CC_NONE)) {
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+ }
+
+ /*
+ * The port shall transition to Attached.SNK after tCCDebounce if or
+ * when VBUS is detected.
+ */
+ if (get_time().val > tc[port].try_wait_debounce &&
+ pd_is_vbus_present(port))
+ sm_set_state(port, TC_OBJ(port), tc_attached_snk);
+
+ return 0;
+}
+
+#endif
+
+/**
+ * Super State CC_RD
+ */
+static int tc_cc_rd(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_cc_rd_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_cc_rd_entry(int port)
+{
+ /* Disable VCONN */
+ if (IS_ENABLED(CONFIG_USBC_VCONN))
+ set_vconn(port, 0);
+
+ /*
+ * Both CC1 and CC2 pins shall be independently terminated to
+ * ground through Rd.
+ */
+ tcpm_set_cc(port, TYPEC_CC_RD);
+
+ /* Set power role to sink */
+ tc_set_power_role(port, PD_ROLE_SINK);
+ tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role);
+
+ return 0;
+}
+
+/**
+ * Super State CC_RP
+ */
+static int tc_cc_rp(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_cc_rp_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_cc_rp_entry(int port)
+{
+ /* Disable VCONN */
+ if (IS_ENABLED(CONFIG_USBC_VCONN))
+ set_vconn(port, 0);
+
+ /* Set power role to source */
+ tc_set_power_role(port, PD_ROLE_SOURCE);
+ tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role);
+
+ /*
+ * Both CC1 and CC2 pins shall be independently pulled
+ * up through Rp.
+ */
+ tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP);
+ tcpm_set_cc(port, TYPEC_CC_RP);
+
+ return 0;
+}
+
+/**
+ * Super State CC_OPEN
+ */
+static int tc_cc_open(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_cc_open_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_cc_open_entry(int port)
+{
+ /* Disable VBUS */
+ pd_power_supply_reset(port);
+
+ /* Disable VCONN */
+ if (IS_ENABLED(CONFIG_USBC_VCONN))
+ set_vconn(port, 0);
+
+ /* Remove terminations from CC */
+ tcpm_set_cc(port, TYPEC_CC_OPEN);
+
+ if (IS_ENABLED(CONFIG_USBC_PPC)) {
+ /* There is no sink connected. */
+ ppc_sink_is_connected(port, 0);
+
+ /*
+ * Clear the overcurrent event counter
+ * since we've detected a disconnect.
+ */
+ ppc_clear_oc_event_counter(port);
+ }
+
+ return 0;
+}
diff --git a/common/usbc/usb_tc_vpd_sm.c b/common/usbc/usb_tc_vpd_sm.c
new file mode 100644
index 0000000000..b8002b0e2a
--- /dev/null
+++ b/common/usbc/usb_tc_vpd_sm.c
@@ -0,0 +1,335 @@
+/* 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 "common.h"
+#include "console.h"
+#include "system.h"
+#include "task.h"
+#include "tcpm.h"
+#include "usb_pd.h"
+#include "usb_tc_sm.h"
+#include "usb_tc_vpd_sm.h"
+#include "vpd_api.h"
+
+/* USB Type-C VCONN Powered Device module */
+
+#ifdef CONFIG_COMMON_RUNTIME
+#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
+#else /* CONFIG_COMMON_RUNTIME */
+#define CPRINTF(format, args...)
+#define CPRINTS(format, args...)
+#endif
+
+/* Type-C Layer Flags */
+#define TC_FLAGS_VCONN_ON BIT(0)
+
+/**
+ * This is the Type-C Port object that contains information needed to
+ * implement a VCONN Powered Device.
+ */
+struct type_c tc[CONFIG_USB_PD_PORT_COUNT];
+
+/* Type-C states */
+DECLARE_STATE(tc, disabled, WITH_RUN, WITH_EXIT);
+DECLARE_STATE(tc, unattached_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attach_wait_snk, WITH_RUN, NOOP);
+DECLARE_STATE(tc, attached_snk, WITH_RUN, WITH_EXIT);
+
+/* Super States */
+DECLARE_STATE(tc, host_rard, NOOP, NOOP);
+DECLARE_STATE(tc, host_open, NOOP, NOOP);
+DECLARE_STATE(tc, vbus_cc_iso, NOOP, NOOP);
+
+void tc_state_init(int port, enum typec_state_id start_state)
+{
+ int res = 0;
+ sm_state this_state;
+
+ res = tc_restart_tcpc(port);
+
+ CPRINTS("TCPC p%d init %s", port, res ? "failed" : "ready");
+ this_state = res ? tc_disabled : tc_unattached_snk;
+
+ /* Disable TCPC RX until connection is established */
+ tcpm_set_rx_enable(port, 0);
+
+ sm_init_state(port, TC_OBJ(port), this_state);
+
+ /* Disable pd state machines */
+ tc[port].pd_enable = 0;
+ tc[port].evt_timeout = 10*MSEC;
+ tc[port].power_role = PD_PLUG_CABLE_VPD;
+ tc[port].data_role = 0; /* Reserved for VPD */
+ tc[port].flags = 0;
+}
+
+void tc_event_check(int port, int evt)
+{
+ /* Do Nothing */
+}
+
+/**
+ * Disabled
+ *
+ * Super State Entries:
+ * Enable mcu communication
+ * Remove the terminations from Host CC
+ */
+static int tc_disabled(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_disabled_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_open);
+}
+
+static int tc_disabled_entry(int port)
+{
+ tc[port].state_id = TC_DISABLED;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ return 0;
+}
+
+static int tc_disabled_run(int port)
+{
+ task_wait_event(-1);
+
+ return SM_RUN_SUPER;
+}
+
+static int tc_disabled_exit(int port)
+{
+#ifndef CONFIG_USB_PD_TCPC
+ if (tc_restart_tcpc(port) != 0) {
+ CPRINTS("TCPC p%d restart failed!", port);
+ return 0;
+ }
+#endif
+ CPRINTS("TCPC p%d resumed!", port);
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * Unattached.SNK
+ *
+ * Super State Entry:
+ * Enable mcu communication
+ * Place Ra on VCONN and Rd on Host CC
+ */
+static int tc_unattached_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_unattached_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rard);
+}
+
+static int tc_unattached_snk_entry(int port)
+{
+ tc[port].state_id = TC_UNATTACHED_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ return 0;
+}
+
+static int tc_unattached_snk_run(int port)
+{
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ /*
+ * Transition to AttachWait.SNK when a Source connection is
+ * detected, as indicated by the SNK.Rp state on its Host-side
+ * port’s CC pin.
+ */
+ if (cc_is_rp(host_cc))
+ return sm_set_state(port, TC_OBJ(port), tc_attach_wait_snk);
+
+ return SM_RUN_SUPER;
+}
+
+/**
+ * AttachedWait.SNK
+ *
+ * Super State Entry:
+ * Enable mcu communication
+ * Place Ra on VCONN and Rd on Host CC
+ */
+static int tc_attach_wait_snk(int port, enum sm_signal sig)
+{
+ int ret = 0;
+
+ ret = (*tc_attach_wait_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_host_rard);
+}
+
+static int tc_attach_wait_snk_entry(int port)
+{
+ tc[port].state_id = TC_ATTACH_WAIT_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+ tc[port].host_cc_state = PD_CC_UNSET;
+
+ return 0;
+}
+
+static int tc_attach_wait_snk_run(int port)
+{
+ int host_new_cc_state;
+ int host_cc;
+
+ /* Check Host CC for connection */
+ vpd_host_get_cc(&host_cc);
+
+ if (cc_is_rp(host_cc))
+ host_new_cc_state = PD_CC_DFP_ATTACHED;
+ else
+ host_new_cc_state = PD_CC_NONE;
+
+ /* Debounce the Host CC state */
+ if (tc[port].host_cc_state != host_new_cc_state) {
+ tc[port].host_cc_state = host_new_cc_state;
+ if (host_new_cc_state == PD_CC_DFP_ATTACHED)
+ tc[port].cc_debounce = get_time().val +
+ PD_T_CC_DEBOUNCE;
+ else
+ tc[port].cc_debounce = get_time().val +
+ PD_T_PD_DEBOUNCE;
+
+ return 0;
+ }
+
+ /* Wait for Host CC debounce */
+ if (get_time().val < tc[port].cc_debounce)
+ return 0;
+
+ /*
+ * A VCONN-Powered USB Device shall transition to
+ * Attached.SNK after the state of the Host-side port’s CC pin is
+ * SNK.Rp for at least tCCDebounce and either host-side VCONN or
+ * VBUS is detected.
+ *
+ * Transition to Unattached.SNK when the state of both the CC1 and
+ * CC2 pins is SNK.Open for at least tPDDebounce.
+ */
+ if (tc[port].host_cc_state == PD_CC_DFP_ATTACHED &&
+ (vpd_is_vconn_present() || vpd_is_host_vbus_present()))
+ sm_set_state(port, TC_OBJ(port), tc_attached_snk);
+ else if (tc[port].host_cc_state == PD_CC_NONE)
+ sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ return 0;
+}
+
+/**
+ * Attached.SNK
+ */
+static int tc_attached_snk(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_attached_snk_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_attached_snk_entry(int port)
+{
+ tc[port].state_id = TC_ATTACHED_SNK;
+ CPRINTS("C%d: %s", port, tc_state_names[tc[port].state_id]);
+
+ /* Enable PD */
+ tc[port].pd_enable = 1;
+ set_polarity(port, 0);
+
+ return 0;
+}
+
+static int tc_attached_snk_run(int port)
+{
+ /* Has host vbus and vconn been removed */
+ if (!vpd_is_host_vbus_present() && !vpd_is_vconn_present())
+ return sm_set_state(port, TC_OBJ(port), tc_unattached_snk);
+
+ if (vpd_is_vconn_present()) {
+ if (!(tc[port].flags & TC_FLAGS_VCONN_ON)) {
+ /* VCONN detected. Remove RA */
+ vpd_host_set_pull(TYPEC_CC_RD, 0);
+ tc[port].flags |= TC_FLAGS_VCONN_ON;
+ }
+ }
+
+ return 0;
+}
+
+static int tc_attached_snk_exit(int port)
+{
+ /* Disable PD */
+ tc[port].pd_enable = 0;
+ tc[port].flags &= ~TC_FLAGS_VCONN_ON;
+
+ return 0;
+}
+
+/**
+ * Super State HOST_RARD
+ */
+static int tc_host_rard(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_rard_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_rard_entry(int port)
+{
+ /* Place Ra on VCONN and Rd on Host CC */
+ vpd_host_set_pull(TYPEC_CC_RA_RD, 0);
+
+ return 0;
+}
+
+/**
+ * Super State HOST_OPEN
+ */
+static int tc_host_open(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_host_open_sig[sig])(port);
+ return SM_SUPER(ret, sig, tc_vbus_cc_iso);
+}
+
+static int tc_host_open_entry(int port)
+{
+ /* Remove the terminations from Host CC */
+ vpd_host_set_pull(TYPEC_CC_OPEN, 0);
+
+ return 0;
+}
+
+/**
+ * Super State VBUS_CC_ISO
+ */
+static int tc_vbus_cc_iso(int port, enum sm_signal sig)
+{
+ int ret;
+
+ ret = (*tc_vbus_cc_iso_sig[sig])(port);
+ return SM_SUPER(ret, sig, 0);
+}
+
+static int tc_vbus_cc_iso_entry(int port)
+{
+ /* Enable mcu communication and cc */
+ vpd_mcu_cc_en(1);
+
+ return 0;
+}
diff --git a/common/usbc/usbc_task.c b/common/usbc/usbc_task.c
new file mode 100644
index 0000000000..7ff8bbf44f
--- /dev/null
+++ b/common/usbc/usbc_task.c
@@ -0,0 +1,298 @@
+/* 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 "ec_commands.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "registers.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+#include "usb_charge.h"
+#include "usb_mux.h"
+#include "usb_pd.h"
+#include "usb_prl_sm.h"
+#include "tcpm.h"
+#include "usb_pe_sm.h"
+#include "usb_prl_sm.h"
+#include "usb_sm.h"
+#include "usb_tc_sm.h"
+#include "usbc_ppc.h"
+#include "version.h"
+
+/* Include USB Type-C State Machine Header File */
+#if defined(CONFIG_USB_TYPEC_CTVPD)
+#include "usb_tc_ctvpd_sm.h"
+#elif defined(CONFIG_USB_TYPEC_VPD)
+#include "usb_tc_vpd_sm.h"
+#elif defined(CONFIG_USB_TYPEC_DRP_ACC_TRYSRC)
+#include "usb_tc_drp_acc_trysrc_sm.h"
+#else
+#error "A USB Type-C State Machine must be defined."
+#endif
+
+#ifdef CONFIG_COMMON_RUNTIME
+#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
+#else /* CONFIG_COMMON_RUNTIME */
+#define CPRINTF(format, args...)
+#define CPRINTS(format, args...)
+#endif
+
+#ifdef CONFIG_COMMON_RUNTIME
+const char * const tc_state_names[] = {
+ "Disabled",
+ "Unattached.SNK",
+ "AttachWait.SNK",
+ "Attached.SNK",
+#if !defined(CONFIG_USB_TYPEC_VPD)
+ "ErrorRecovery",
+ "Unattached.SRC",
+ "AttachWait.SRC",
+ "Attached.SRC",
+#endif
+#if !defined(CONFIG_USB_TYPEC_CTVPD) && !defined(CONFIG_USB_TYPEC_VPD)
+ "AudioAccessory",
+ "OrientedDebugAccessory.SRC",
+ "UnorientedDebugAccessory.SRC",
+ "DebugAccessory.SNK",
+ "Try.SRC",
+ "TryWait.SNK",
+ "CTUnattached.SNK",
+ "CTAttached.SNK",
+#endif
+#if defined(CONFIG_USB_TYPEC_CTVPD)
+ "CTTry.SNK",
+ "CTAttached.Unsupported",
+ "CTAttachWait.Unsupported",
+ "CTUnattached.Unsupported",
+ "CTUnattached.VPD",
+ "CTAttachWait.VPD",
+ "CTAttached.VPD",
+ "CTDisabled.VPD",
+ "Try.SNK",
+ "TryWait.SRC"
+#endif
+};
+BUILD_ASSERT(ARRAY_SIZE(tc_state_names) == TC_STATE_COUNT);
+#endif
+
+/* Public Functions */
+
+int tc_get_power_role(int port)
+{
+ return tc[port].power_role;
+}
+
+int tc_get_data_role(int port)
+{
+ return tc[port].data_role;
+}
+
+void tc_set_power_role(int port, int role)
+{
+ tc[port].power_role = role;
+}
+
+void tc_set_timeout(int port, uint64_t timeout)
+{
+ tc[port].evt_timeout = timeout;
+}
+
+enum typec_state_id get_typec_state_id(int port)
+{
+ return tc[port].state_id;
+}
+
+/*
+ * CC values for regular sources and Debug sources (aka DTS)
+ *
+ * Source type Mode of Operation CC1 CC2
+ * ---------------------------------------------
+ * Regular Default USB Power RpUSB Open
+ * Regular USB-C @ 1.5 A Rp1A5 Open
+ * Regular USB-C @ 3 A Rp3A0 Open
+ * DTS Default USB Power Rp3A0 Rp1A5
+ * DTS USB-C @ 1.5 A Rp1A5 RpUSB
+ * DTS USB-C @ 3 A Rp3A0 RpUSB
+ */
+
+inline enum pd_cc_polarity_type get_snk_polarity(int cc1, int cc2)
+{
+ /* the following assumes:
+ * TYPEC_CC_VOLT_RP_3_0 > TYPEC_CC_VOLT_RP_1_5
+ * TYPEC_CC_VOLT_RP_1_5 > TYPEC_CC_VOLT_RP_DEF
+ * TYPEC_CC_VOLT_RP_DEF > TYPEC_CC_VOLT_OPEN
+ */
+ return (cc2 > cc1) ? POLARITY_CC2 : POLARITY_CC1;
+}
+
+int tc_restart_tcpc(int port)
+{
+ return tcpm_init(port);
+}
+
+void set_polarity(int port, int polarity)
+{
+ tcpm_set_polarity(port, polarity);
+
+ if (IS_ENABLED(CONFIG_USBC_PPC_POLARITY))
+ ppc_set_polarity(port, polarity);
+}
+
+void set_usb_mux_with_current_data_role(int port)
+{
+#ifdef CONFIG_USBC_SS_MUX
+ /*
+ * If the SoC is down, then we disconnect the MUX to save power since
+ * no one cares about the data lines.
+ */
+#ifdef CONFIG_POWER_COMMON
+ if (chipset_in_or_transitioning_to_state(CHIPSET_STATE_ANY_OFF)) {
+ usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT,
+ tc[port].polarity);
+ return;
+ }
+#endif /* CONFIG_POWER_COMMON */
+
+ /*
+ * When PD stack is disconnected, then mux should be disconnected, which
+ * is also what happens in the set_state disconnection code. Once the
+ * PD state machine progresses out of disconnect, the MUX state will
+ * be set correctly again.
+ */
+ if (!pd_is_connected(port))
+ usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT,
+ tc[port].polarity);
+ /*
+ * If new data role isn't DFP and we only support DFP, also disconnect.
+ */
+ else if (IS_ENABLED(CONFIG_USBC_SS_MUX_DFP_ONLY) &&
+ tc[port].data_role != PD_ROLE_DFP)
+ usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT,
+ tc[port].polarity);
+ /*
+ * Otherwise connect mux since we are in S3+
+ */
+ else
+ usb_mux_set(port, TYPEC_MUX_USB, USB_SWITCH_CONNECT,
+ tc[port].polarity);
+#endif /* CONFIG_USBC_SS_MUX */
+}
+
+/* High-priority interrupt tasks implementations */
+#if defined(HAS_TASK_PD_INT_C0) || defined(HAS_TASK_PD_INT_C1) || \
+ defined(HAS_TASK_PD_INT_C2)
+
+/* Used to conditionally compile code in main pd task. */
+#define HAS_DEFFERED_INTERRUPT_HANDLER
+
+/* Events for pd_interrupt_handler_task */
+#define PD_PROCESS_INTERRUPT (1<<0)
+
+static uint8_t pd_int_task_id[CONFIG_USB_PD_PORT_COUNT];
+
+void schedule_deferred_pd_interrupt(const int port)
+{
+ task_set_event(pd_int_task_id[port], PD_PROCESS_INTERRUPT, 0);
+}
+
+/*
+ * Main task entry point that handles PD interrupts for a single port
+ *
+ * @param p The PD port number for which to handle interrupts (pointer is
+ * reinterpreted as an integer directly).
+ */
+void pd_interrupt_handler_task(void *p)
+{
+ const int port = (int) p;
+ const int port_mask = (PD_STATUS_TCPC_ALERT_0 << port);
+
+ ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_COUNT);
+
+ pd_int_task_id[port] = task_get_current();
+
+ while (1) {
+ const int evt = task_wait_event(-1);
+
+ if (evt & PD_PROCESS_INTERRUPT) {
+ /*
+ * While the interrupt signal is asserted; we have more
+ * work to do. This effectively makes the interrupt a
+ * level-interrupt instead of an edge-interrupt without
+ * having to enable/disable a real level-interrupt in
+ * multiple locations.
+ *
+ * Also, if the port is disabled do not process
+ * interrupts. Upon existing suspend, we schedule a
+ * PD_PROCESS_INTERRUPT to check if we missed anything.
+ */
+ while ((tcpc_get_alert_status() & port_mask) &&
+ pd_is_port_enabled(port))
+ tcpc_alert(port);
+ }
+ }
+}
+#endif /* HAS_TASK_PD_INT_C0 || HAS_TASK_PD_INT_C1 || HAS_TASK_PD_INT_C2 */
+
+void pd_task(void *u)
+{
+ int port = TASK_ID_TO_PD_PORT(task_get_current());
+
+ tc_state_init(port, TC_DEFAULT_STATE(port));
+
+ if (IS_ENABLED(CONFIG_USBC_PPC))
+ ppc_init(port);
+
+ /*
+ * Since most boards configure the TCPC interrupt as edge
+ * and it is possible that the interrupt line was asserted between init
+ * and calling set_state, we need to process any pending interrupts now.
+ * Otherwise future interrupts will never fire because another edge
+ * never happens. Note this needs to happen after set_state() is called.
+ */
+ if (IS_ENABLED(HAS_DEFFERED_INTERRUPT_HANDLER))
+ schedule_deferred_pd_interrupt(port);
+
+
+ while (1) {
+ /* wait for next event/packet or timeout expiration */
+ tc[port].evt = task_wait_event(tc[port].evt_timeout);
+
+ /* handle events that affect the state machine as a whole */
+ tc_event_check(port, tc[port].evt);
+
+#ifdef CONFIG_USB_PD_TCPC
+ /*
+ * run port controller task to check CC and/or read incoming
+ * messages
+ */
+ tcpc_run(port, tc[port].evt);
+#endif
+
+#ifdef CONFIG_USB_PE_SM
+ /* run policy engine state machine */
+ usbc_policy_engine(port, tc[port].evt, tc[port].pd_enable);
+#endif
+
+#ifdef CONFIG_USB_PRL_SM
+ /* run protocol state machine */
+ usbc_protocol_layer(port, tc[port].evt, tc[port].pd_enable);
+#endif
+
+ /* run typec state machine */
+ sm_run_state_machine(port, TC_OBJ(port), SM_RUN_SIG);
+ }
+}