As the original USB-C Power Delivery (PD) solution for the ChromeOS Embedded Controller has aged, it has grown to the point where it is difficult to add new features and address bugs. A new PD stack (generally referred to as TCPMv2) has been introduced to the codebase for use moving forward. It implements a layered, state-based design which tracks more closely with the USB Type-C and USB PD specifications.
Boards may enable TCPMv2 by adding the following defines:
CONFIG_USB_PD_TCPMV2Configures the board to use the new stack.CONFIG_USB_DRP_ACC_TRYSRC: Configures the type of state machine to run (in this case, a DRP which performs Try.SRC behavior). Currently available are DRP and charge-through Vconn-powered device optionsCONFIG_USB_PD_DECODE_SOP: Sets up messaging for SOP’ and SOP’’, which is strongly encouraged in the TCPMv2 code moving forwardCONFIG_USB_PID 0x1234: Sets the USB Product Identifier. This will be shared for all boards within one reference design, and new PIDs may be requested by sending an email to the ChromeOS FW Team.CONFIG_USB_PD_REV30: The TCPMv2 stack defaults to PD2.0 operation but defining this macro enables PD3.0 functionality.Other configurations to specify behaviors within the task still apply (ex. CONFIG_USB_PD_ALT_MODE_DFP and CONFIG_USB_PD_TCPC_LOW_POWER).
The basis of the TCPMv2 state machines is a generic state machine framework. This framework can be found in common/usbc/usb_sm.c. For each state, there may be defined:
All fields are optional and may be set to NULL. A new state is transitioned to with a call into set_state(), which exits the old state and parents and enters the new parents and state. States may be changed with set_state() in any entry or run function, but any call from an exit function is ignored since exit is only called when a change is already in progress. While in a state, run_state() executes the run function for the current state and parents. If set_state() is called from either an entry function or a run function, the remaining run or entry functions are stopped.
Below is a graphical example of what some states may look like. States 1 and 2 share Parent State 1, while State 3 has Parent State of 2.
Translated into code, this would be something like below (note it is not necessary that the states be a part of an array, but the TCPMv2 code generally organizes the states in this way):
static const struct usb_state test_states[] = {
[PARENT_1] = {
.entry = parent_1_entry,
.run = parent_1_run,
.exit = parent_1_exit,
},
[PARENT_2] = {
.entry = parent_2_entry,
.run = parent_2_run,
.exit = parent_2_exit,
},
[STATE_1] = {
.entry = state_1_entry,
.run = state_1_run,
.exit = state_1_exit,
.parent = &test_states[PARENT_1],
},
[STATE_2] = {
.entry = state_2_entry,
.run = state_2_run,
.exit = state_2_exit,
.parent = &test_states[PARENT_1],
},
[STATE_3] = {
.entry = state_3_entry,
.run = state_3_run,
.exit = state_3_exit,
.parent = &test_states[PARENT_2],
},
};
For this example, each test state is written simply to produce a print of its function name. The two exceptions are:
Graphically, this is represented below:
And the following code output is produced:
Calling run_state() state_1_run parent_1_run Calling run_state() state_1_run state_1_run calling set_state() to state 2 state_1_exit state_2_entry state_2_entry calling set_state() to state 3 state_2_exit parent_1_exit parent_2_entry state_3_entry Calling run_state() state_3_run parent_2_run
The TCPMv2 PD task is built upon state machines using the above framework and is located in common/usbc/usbc_task.c. It is separated into three layers which track with the USB Type-C and USB PD specification states and run in a loop with 5 ms periods between executions. A graphical representation of these layers is below.
The task is designed such that the Type-C (TC) layer could run independently for any application which doesn’t wish to enable PD messaging. Boards define their appropriate Policy Engine (PE) and TC state machines through their selection of a CONFIG_USB_* define, with current options supporting both Dual-Role Ports (DRPs) and Charge-Through Vconn-Powered Device (CTVPD). All boards use the same Protocol Layer (PRL) code.
The three state machines mentioned above interact with each other and the EC drivers in order to orchestrate all Type-C connection behavior. Graphically, they are represented below.
Layers communicate with each other using atomic operations on flags and shared buffers. Functions calling into each layer are clearly named to indicate the layer they are acting on, and anything calling into the PD task should be doing so through pd_* named functions.
Some specific examples of how this communication works between layers is below. If a port partner sends in a Vconn_swap request, then:
Defined in the USB Type-C specification, this layer is responsible for basic connection. It takes care of setting the CC lines, detecting and debouncing the partner CC lines, and performs most of the interactions needed with the PPC and USB mux. Once the TC layer has gotten the connection to the point of being Attached.SNK or Attached.SRC, it will enable the PRL and PE layers accordingly.
A part of the USB PD specification, the protocol layer is responsible for the actual sending and receiving of PD messages with the TCPCs. The layer is actually composed of 4 separate state machines running one after the other. These state machines are:
The PE layer states are defined as a part of the USB PD specification. State names are meant to track very closely with the specification so they can be easily searchable and understood. The PE’s primary responsibility is to send and process PD messages in order to implement the port’s policy.
The USB PD specification defines some responsibilities of the DPM, e.g. selecting Source Capabilities to offer and alternate modes to enter. Broadly, the DPM is supposed to make discretionary decisions about the behavior of the PE, i.e. policy decisions. However, the spec is relatively silent on the structure and scope of the DPM.
In TCPMv2, the DPM consists of a central code module in usb_pd_dpm.c and several code modules for supported alternate or USB modes. The mode entry and exit logic in the DPM takes the form of a state machine, and each mode has its own state machine to control the specific mode entry and exit steps. The DPM also contains logic to choose which Source PDO to offer in Source Capabilities.
The DPM state machines do not currently use the USB state machine framework. Additionally, there are policy decisions encoded in the lower layers and in board code that ought to be made by the DPM. An effort to centralize policy decisions and improve DPM code organization is ongoing.
TCPMv2 supports several modes of operation besides USB 3.2. To determine which modes the port partner and cable support, the TCPM sends SOP and SOP' Vendor-Defined Messages, specifically Discover Identity, Discover SVIDs, and Discover Modes. The responses to these messages list supported modes. The AP may query this information via EC_CMD_TYPEC_DISCOVERY.
Based on the discovery responses, the DPM decides which mode to enter (if any) and drives mode entry. If CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY is enabled, the AP directs the DPM to enter and exit modes via EC_CMD_TYPEC_CONTROL.
DisplayPort alternate mode repurposes the SuperSpeed lanes as video data lanes, all flowing from the host to the monitor. DP mode can use 2 lanes, allowing simultaneous USB traffic on the cable, or it can use all 4 lanes, supporting greater display bandwidth at the expense of USB.
The sequence to enter DP alternate mode (v. 1.4) is
To exit DP alternate mode, the TCPM sends an SOP VDM Exit Mode REQ.
dp_alt_mode.c and usb_pd_alt_mode_dfp.c contain the majority of the DP entry and exit logic.
TBT3 alternate mode repurposes all 4 SuperSpeed lanes for Thunderbolt 3, which is itself a bidirectional tunneling protocol supporting various types of hardware, most commonly docks.
The sequence to enter TBT3 alternate mode depends on the cable used:
To exit TBT3 alternate mode, the TCPM sends VDM Exit Mode REQs to SOP, SOP'', and SOP', as appropriate.
tbt_alt_mode.c and usb_pd_alt_mode_dfp.c contain the TBT entry and exit logic.
USB4 is not an alternate mode, but it does repurpose the SuperSpeed lines (using different signaling and routing than USB 3.2), so the TCPM must explicitly enter USB4.
The sequence to enter USB4 is conceptually similar to that for TBT3:
To exit USB4 (returning to a USB 3.2 mux configuration), the TCPM performs a Data Reset sequence.
usb_mode.c and usb_pd_alt_mode_dfp.c contain the USB4 entry and exit logic.