diff options
author | Sam Hurst <shurst@google.com> | 2019-04-18 12:47:52 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2019-07-30 21:25:24 +0000 |
commit | 2e3109ec6b964776e7c5bd12f1fc058ca96d33f7 (patch) | |
tree | 23ea5eaf67e50094ae683f5d16f09a332a1303c4 /common/usbc | |
parent | b99b1b10c3f2070e3fbd8d5cb2d0927f04f642ea (diff) | |
download | chrome-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.mk | 22 | ||||
-rw-r--r-- | common/usbc/usb_pe_ctvpd_sm.c | 207 | ||||
-rw-r--r-- | common/usbc/usb_prl_sm.c | 1901 | ||||
-rw-r--r-- | common/usbc/usb_sm.c | 177 | ||||
-rw-r--r-- | common/usbc/usb_tc_ctvpd_sm.c | 1653 | ||||
-rw-r--r-- | common/usbc/usb_tc_drp_acc_trysrc_sm.c | 1724 | ||||
-rw-r--r-- | common/usbc/usb_tc_vpd_sm.c | 335 | ||||
-rw-r--r-- | common/usbc/usbc_task.c | 298 |
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); + } +} |