diff --git a/samples/boards/stm32/usbc/sink/CMakeLists.txt b/samples/boards/stm32/usbc/sink/CMakeLists.txt new file mode 100644 index 00000000000..469bb9b4951 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(usbc_sink) + +target_sources(app PRIVATE src/main.c) +target_sources(app PRIVATE src/usbc_snk.c) +target_sources(app PRIVATE src/stm32g081b_eval_board.c) diff --git a/samples/boards/stm32/usbc/sink/README.rst b/samples/boards/stm32/usbc/sink/README.rst new file mode 100644 index 00000000000..385d0a717b9 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/README.rst @@ -0,0 +1,86 @@ +.. _sink-sample: + +Basic USBC SINK +############### + +Overview +******** + +This example demonstrates how to create a USBC Power Delivery application +using a TypeC Port Controller (TCPC) driver. The application implements a +USBC Sink device. + +After the USBC Sink device is powered, an LED begins to blink and +when the USBC Sink device is plugged into a Power Delivery charger, it +negotiates with the charger to provide 5V@100mA and displays all +Power Delivery Objects (PDOs) provided by the charger. + +Please note that this example does not implement any of the features and +requiresments outlined in the USBC Specification needed to create a robust +USBC Sink device. It is meant for demonstration purposes only. + +.. _sink-sample-requirements: + +Requirements +************ + +The TCPC device that's used by the sample is specified by defining a devicetree +node label named ``tcpc``. +The sample has been tested on :ref:`stm32g081b_eval_board` and provides an +overlay file for both board. + +For the :ref:`stm32g081b_eval_board`, Port 2 is configured as a Sink and Port 1 +is unused. So, the charger must be plugged into Port 2. + +Building and Running +******************** + +Build and flash Sink as follows, changing ``stm32g081b_eval_board`` for your board: + +.. zephyr-app-commands:: + :zephyr-app: samples/boards/usbc/sink + :board: stm32g081b_eval_board + :goals: build flash + :compact: + +After flashing, the LED starts to blink. Connect a charger and see console output: + +Sample Output +============= + +.. code-block:: console + + UnAttached.SNK + AttachedWait.SNK + Attached.SNK + Got PD_CTRL_ACCEPT + Got PD_CTRL_PS_RDY + Source Caps: + PDO 0: + Type: FIXED + DRP: 0 + Suspend: 0 + UP: 1 + USB Comm: 0 + DRD: 0 + Voltage: 5000 mV + Current: 2400 mA + PDO 1: + Type: FIXED + DRP: 0 + Suspend: 0 + UP: 0 + USB Comm: 0 + DRD: 0 + Voltage: 9000 mV + Current: 3000 mA + PDO 2: + Type: FIXED + DRP: 0 + Suspend: 0 + UP: 0 + USB Comm: 0 + DRD: 0 + Voltage: 20000 mV + Current: 3000 mA + SNK_READY diff --git a/samples/boards/stm32/usbc/sink/boards/stm32g081b_eval.overlay b/samples/boards/stm32/usbc/sink/boards/stm32g081b_eval.overlay new file mode 100644 index 00000000000..e3884c1df15 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/boards/stm32g081b_eval.overlay @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +/ { + aliases { + tcpc = &ucpd2; + }; +}; diff --git a/samples/boards/stm32/usbc/sink/prj.conf b/samples/boards/stm32/usbc/sink/prj.conf new file mode 100644 index 00000000000..61df8a304d0 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/prj.conf @@ -0,0 +1,4 @@ +CONFIG_USBC_TCPC_DRIVER=y +CONFIG_SMF=y +CONFIG_LOG=y +CONFIG_ADC=y diff --git a/samples/boards/stm32/usbc/sink/sample.yaml b/samples/boards/stm32/usbc/sink/sample.yaml new file mode 100644 index 00000000000..34a8bc4470d --- /dev/null +++ b/samples/boards/stm32/usbc/sink/sample.yaml @@ -0,0 +1,12 @@ +sample: + name: USBC SINK +tests: + sample.usbc.sink: + depends_on: tcpc + tags: usbc + platform_allow: stm32g081b_eval + harness: console + harness_config: + type: one_line + regex: + - "Unattached.SNK" diff --git a/samples/boards/stm32/usbc/sink/src/board.h b/samples/boards/stm32/usbc/sink/src/board.h new file mode 100644 index 00000000000..90292c02e87 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/src/board.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __BOARD_H__ +#define __BOARD_H__ + +/** + * @brief Configure the board + */ +int board_config(void); + +/** + * @brief Control a board specific led + * + * @param val LED is off when 0, else on + */ +void board_led(int val); + +/** + * @brief Measure VBUS in a board specific way + * + * @param mv pointer where VBUS, in millivolts, is stored + * + * @return 0 on success + * @return -EINVAL on failure + */ +int board_vbus_meas(int *mv); + +/** + * @brief Discharge VBUS in a board specific way + * + * @param en VBUS is discharged when true, else it is not + * + * @return 0 on success + * @return -EIO on failure + * @return -ENOTSUP if not supported + */ +int board_discharge_vbus(int en); + +#endif /* __BOARD_H__ */ diff --git a/samples/boards/stm32/usbc/sink/src/main.c b/samples/boards/stm32/usbc/sink/src/main.c new file mode 100644 index 00000000000..322aa783a53 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/src/main.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); + +#include "usbc_snk.h" +#include "board.h" + +/* 1 s = 200 * 5 msec */ +#define LED_TOGGLE_TIME 200 + +void main(void) +{ + int ret; + int loop_interval; + int timer; + int led_is_on; + + /* Config the board */ + ret = board_config(); + if (ret) { + LOG_ERR("Could not configure board"); + return; + } + + /* Initialize the Sink State Machine */ + ret = sink_init(); + if (ret) { + LOG_ERR("Could not initialize sink state machine\n"); + return; + } + + led_is_on = 0; + timer = 0; + + /* Run Sink State Machine on 5 msec loop interval */ + while (1) { + /* Run Sink State Machine */ + loop_interval = sink_sm(); + + /* Update LED toggle timer */ + timer++; + + /* Toggle Board LED */ + if (timer == LED_TOGGLE_TIME) { + timer = 0; + board_led(led_is_on); + led_is_on = !led_is_on; + } + + k_msleep(loop_interval); + } +} diff --git a/samples/boards/stm32/usbc/sink/src/stm32g081b_eval_board.c b/samples/boards/stm32/usbc/sink/src/stm32g081b_eval_board.c new file mode 100644 index 00000000000..f657a7abd22 --- /dev/null +++ b/samples/boards/stm32/usbc/sink/src/stm32g081b_eval_board.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(board, LOG_LEVEL_DBG); + +#define VBUS DT_PATH(vbus) +#define DISCHARGE_VBUS_NODE DT_PATH(discharge_vbus2_config, discharge_vbus2) +#define LED0_NODE DT_ALIAS(led0) + +static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); +static const struct gpio_dt_spec discharge_vbus = GPIO_DT_SPEC_GET(DISCHARGE_VBUS_NODE, gpios); + +/* Common settings supported by most ADCs */ +#define ADC_RESOLUTION 12 +#define ADC_GAIN ADC_GAIN_1 +#define ADC_REFERENCE ADC_REF_INTERNAL +#define ADC_ACQUISITION_TIME ADC_ACQ_TIME_DEFAULT +#define ADC_REF_MV 3300 + +static const struct device *dev_adc; +static int16_t sample_buffer; + +static const uint32_t output_ohm = DT_PROP(VBUS, output_ohms); +static const uint32_t full_ohm = DT_PROP(VBUS, full_ohms); + +struct adc_channel_cfg channel_cfg = { + .gain = ADC_GAIN, + .reference = ADC_REFERENCE, + .acquisition_time = ADC_ACQUISITION_TIME, + .differential = 0 +}; + +struct adc_sequence sequence = { + .channels = BIT(3), + .buffer = &sample_buffer, + /* buffer size in bytes, not number of samples */ + .buffer_size = sizeof(sample_buffer), + .resolution = ADC_RESOLUTION, +}; + +/** + * @brief This function is used to measure the VBUS in millivolts + */ +int board_vbus_meas(int *mv) +{ + int32_t value; + int ret; + + ret = adc_read(dev_adc, &sequence); + if (ret != 0) { + printk("ADC reading failed with error %d.\n", ret); + return ret; + } + + value = sample_buffer; + ret = adc_raw_to_millivolts(ADC_REF_MV, ADC_GAIN, ADC_RESOLUTION, &value); + if (ret != 0) { + printk("Scaling ADC failed with error %d.\n", ret); + return ret; + } + + /* VBUS is scaled down though a voltage divider */ + *mv = (value * 1000) / ((output_ohm * 1000) / full_ohm); + + return 0; +} + +/** + * @brief This function discharges VBUS + */ +int board_discharge_vbus(int en) +{ + gpio_pin_set_dt(&discharge_vbus, en); + + return 0; +} + +void board_led(int val) +{ + gpio_pin_set_dt(&led, val); +} + +int board_config(void) +{ + int ret; + + dev_adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(VBUS)); + if (!device_is_ready(dev_adc)) { + printk("ADC device not found\n"); + return -ENODEV; + } + + if (!device_is_ready(led.port)) { + LOG_ERR("LED device not ready"); + return -ENODEV; + } + + if (!device_is_ready(discharge_vbus.port)) { + LOG_ERR("Discharge VBUS device not ready"); + return -ENODEV; + } + + adc_channel_setup(dev_adc, &channel_cfg); + + ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); + if (ret) { + LOG_ERR("Could not configure LED pin"); + return ret; + } + + ret = gpio_pin_configure_dt(&discharge_vbus, GPIO_OUTPUT_ACTIVE); + if (ret) { + LOG_ERR("Could not configure VBUS Discharge pin"); + return ret; + } + + return 0; +} diff --git a/samples/boards/stm32/usbc/sink/src/usbc_snk.c b/samples/boards/stm32/usbc/sink/src/usbc_snk.c new file mode 100644 index 00000000000..2d6d5d922cc --- /dev/null +++ b/samples/boards/stm32/usbc/sink/src/usbc_snk.c @@ -0,0 +1,775 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "board.h" + +#include +LOG_MODULE_REGISTER(snk, LOG_LEVEL_DBG); + +#define TCPC_NODE DT_ALIAS(tcpc) + +/** + * @brief Loop time: 5 ms + */ +#define LOOP_TIMEOUT_5MS 5 + +/** + * @brief CC debounce time converted to 5mS counts + */ +#define T_CC_DEBOUNCE (TC_T_CC_DEBOUNCE_MIN_MS / LOOP_TIMEOUT_5MS) + +/** + * @brief RP value change time converted to 5mS counts + */ +#define T_RP_VALUE_CHANGE (TC_T_RP_VALUE_CHANGE_MIN_MS / LOOP_TIMEOUT_5MS) + +/** + * @brief Sink wait for source cap time converted to 5mS counts + */ +#define T_SINK_WAIT_CAP (PD_T_TYPEC_SINK_WAIT_CAP_MAX_MS / LOOP_TIMEOUT_5MS) + + +/* PD message received flag*/ +#define FLAG_PDMSG_RECEIVED BIT(0) +/* Hard Reset message received flag */ +#define FLAG_HARD_RESET_RECEIVED BIT(1) +/* CC event flag */ +#define FLAG_CC_EVENT BIT(2) +/* PD Message Send Failed */ +#define FLAG_PDMSG_FAILED BIT(3) +/* PD Message Send was Discarded */ +#define FLAG_PDMSG_DISCARDED BIT(4) +/* PD Message Send was successful */ +#define FLAG_PDMSG_SENT BIT(5) + +/* + * USB Type-C Sink + */ + +/** + * @brief List of all TypeC-level states + */ +enum usb_tc_state { + /** Unattached Sink */ + TC_UNATTACHED_SNK, + /** Attach Wait Sink */ + TC_ATTACH_WAIT_SNK, + /** Attached Sink */ + TC_ATTACHED_SNK, +}; + +/** + * @brief Power Delivery states + */ +enum pd_states { + /** Sink wait for source caps */ + SNK_WAIT_FOR_CAPABILITIES, + /** Sink evaluate source caps */ + SNK_EVALUATE_CAPABILITY, + /** Sink select source caps */ + SNK_SELECT_CAPABILITY, + /** SinK transition sink */ + SNK_TRANSITION_SINK, + /** Sink ready */ + SNK_READY, +}; + +/** + * @brief Generic substates + */ +enum substate { + /** Generic substate 0 */ + SUB_STATE0, + /** Generic substate 1 */ + SUB_STATE1, + /** Generic substate 2 */ + SUB_STATE2, + /** Generic substate 3 */ + SUB_STATE3, +}; + +/* TypeC Only Power strings (No PD) */ +static const char *const pwr2_5_str = "5V/0.5A"; +static const char *const pwr7_5_str = "5V/1.5A"; +static const char *const pwr15_str = "5V/3A"; +static const char *const pwr_open_str = "Removed"; + +/** + * @brief TypeC PD object + */ +static struct tc_t { + /** state machine context */ + struct smf_ctx ctx; + /** TypeC Port Controller device */ + const struct device *tcpc_dev; + /** VBUS measurement device */ + const struct device *vbus_dev; + /** Port polarity */ + enum tc_cc_polarity cc_polarity; + /** Time a port shall wait before it can determine it is attached */ + uint32_t cc_debounce; + /** The cc state */ + enum tc_cc_states cc_state; + /** Voltage on CC pin */ + enum tc_cc_voltage_state cc_voltage; + /** Current CC1 value */ + enum tc_cc_voltage_state cc1; + /** Current CC2 value */ + enum tc_cc_voltage_state cc2; + /** PD message for RX and TX */ + struct pd_msg msg; + /** PD connection flag. Also used as PD contract in place flag */ + bool pd_connected; + /** PD state */ + enum pd_states pd_state; + /** Event flags */ + uint32_t flag; + /** Transmission message id count */ + uint32_t msg_id_cnt; + /** Receive message id */ + uint32_t msg_id; + /** General timer */ + uint32_t timer; + /** Sub state variable */ + enum substate sub_state; + /** Source caps */ + uint32_t src_caps[7]; + /** Source caps header */ + union pd_header src_caps_hdr; +} tc; + +static void set_state_tc(const enum usb_tc_state new_state); + +/** + * @brief Sink startup + */ +static void snk_startup(struct tc_t *tc) +{ + tc->flag = 0; + tc->msg_id_cnt = 0; + tc->msg_id = 0; + tc->timer = 0; + + /* Not currently PD connected */ + tc->pd_connected = false; + + /* Initialize PD state */ + tc->pd_state = SNK_WAIT_FOR_CAPABILITIES; +} + +/** + * @brief Sink power sub states. Only called if a PD contract is ot in place + */ +static void sink_power_sub_states(void) +{ + enum tc_cc_voltage_state cc; + enum tc_cc_voltage_state new_cc_voltage; + char const *pwr; + + cc = tc.cc_polarity ? tc.cc2 : tc.cc1; + + if (cc == TC_CC_VOLT_RP_DEF) { + new_cc_voltage = TC_CC_VOLT_RP_DEF; + pwr = pwr2_5_str; + } else if (cc == TC_CC_VOLT_RP_1A5) { + new_cc_voltage = TC_CC_VOLT_RP_1A5; + pwr = pwr7_5_str; + } else if (cc == TC_CC_VOLT_RP_3A0) { + new_cc_voltage = TC_CC_VOLT_RP_3A0; + pwr = pwr15_str; + } else { + new_cc_voltage = TC_CC_VOLT_OPEN; + pwr = pwr_open_str; + } + + /* Debounce the cc state */ + if (new_cc_voltage != tc.cc_voltage) { + tc.cc_voltage = new_cc_voltage; + tc.cc_debounce = T_RP_VALUE_CHANGE; + } else if (tc.cc_debounce) { + /* Update timer */ + tc.cc_debounce--; + } +} + +/* + * TYPE-C State Implementations + */ + +/** + * @brief Unattached.SNK + */ +static void tc_unattached_snk_entry(void *tc_obj) +{ + struct tc_t *tc = tc_obj; + + LOG_INF("UnAttached.SNK"); + + tcpc_set_cc(tc->tcpc_dev, TC_CC_RD); + tc->cc_voltage = TC_CC_VOLT_OPEN; +} + +static void tc_unattached_snk_run(void *tc_obj) +{ + struct tc_t *tc = tc_obj; + + /* + * 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. + */ + if (tcpc_is_cc_rp(tc->cc1) || tcpc_is_cc_rp(tc->cc2)) { + set_state_tc(TC_ATTACH_WAIT_SNK); + } +} + +/** + * @brief AttachWait.SNK + */ +static void tc_attach_wait_snk_entry(void *tc_obj) +{ + struct tc_t *tc = tc_obj; + + LOG_INF("AttachedWait.SNK"); + tc->cc_state = TC_CC_NONE; +} + +static void tc_attach_wait_snk_run(void *tc_obj) +{ + + struct tc_t *tc = tc_obj; + enum tc_cc_states new_cc_state; + + if (tcpc_is_cc_rp(tc->cc1) && tcpc_is_cc_rp(tc->cc2)) { + new_cc_state = TC_CC_DFP_DEBUG_ACC; + } else if (tcpc_is_cc_rp(tc->cc1) || tcpc_is_cc_rp(tc->cc2)) { + new_cc_state = TC_CC_DFP_ATTACHED; + } else { + new_cc_state = TC_CC_NONE; + } + + /* Debounce the cc state */ + if (new_cc_state != tc->cc_state) { + tc->cc_debounce = T_CC_DEBOUNCE; + tc->cc_state = new_cc_state; + } else if (tc->cc_debounce) { + /* Update timers */ + tc->cc_debounce--; + } + + /* Wait for CC debounce */ + if (tc->cc_debounce) { + return; + } + + /* + * 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. + */ + if (tcpc_check_vbus_level(tc->tcpc_dev, TC_VBUS_PRESENT) && + (new_cc_state == TC_CC_DFP_ATTACHED)) { + set_state_tc(TC_ATTACHED_SNK); + } else { + set_state_tc(TC_UNATTACHED_SNK); + } +} +/** + * @brief Display a single Power Delivery Object + */ +static void display_pdo(int idx, uint32_t pdo_value) +{ + union pd_fixed_supply_pdo_source pdo; + + /* Default to fixed supply pdo source until type is detected */ + pdo.raw_value = pdo_value; + + LOG_INF("PDO %d:", idx); + switch (pdo.type) { + case PDO_FIXED: { + LOG_INF("\tType: FIXED"); + LOG_INF("\tCurrent: %d", + PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(pdo.max_current)); + LOG_INF("\tVoltage: %d", + PD_CONVERT_FIXED_PDO_VOLTAGE_TO_MV(pdo.voltage)); + LOG_INF("\tPeak Current: %d", pdo.peak_current); + LOG_INF("\tUchunked Support: %d", + pdo.unchunked_ext_msg_supported); + LOG_INF("\tDual Role Data: %d", + pdo.dual_role_data); + LOG_INF("\tUSB Comms: %d", + pdo.usb_comms_capable); + LOG_INF("\tUnconstrained Pwr: %d", + pdo.unconstrained_power); + LOG_INF("\tUSB Susspend: %d", + pdo.usb_suspend_supported); + LOG_INF("\tDual Role Power: %d", + pdo.dual_role_power); + } + break; + case PDO_BATTERY: { + union pd_battery_supply_pdo_source pdo; + + pdo.raw_value = pdo_value; + LOG_INF("\tType: BATTERY"); + LOG_INF("\tMin Voltage: %d", + PD_CONVERT_BATTERY_PDO_VOLTAGE_TO_MV(pdo.min_voltage)); + LOG_INF("\tMax Voltage: %d", + PD_CONVERT_BATTERY_PDO_VOLTAGE_TO_MV(pdo.max_voltage)); + LOG_INF("\tMax Power: %d", + PD_CONVERT_BATTERY_PDO_POWER_TO_MW(pdo.max_power)); + } + break; + case PDO_VARIABLE: { + union pd_variable_supply_pdo_source pdo; + + pdo.raw_value = pdo_value; + LOG_INF("\tType: VARIABLE"); + LOG_INF("\tMin Voltage: %d", + PD_CONVERT_VARIABLE_PDO_VOLTAGE_TO_MV(pdo.min_voltage)); + LOG_INF("\tMax Voltage: %d", + PD_CONVERT_VARIABLE_PDO_VOLTAGE_TO_MV(pdo.max_voltage)); + LOG_INF("\tMax Current: %d", + PD_CONVERT_VARIABLE_PDO_CURRENT_TO_MA(pdo.max_current)); + } + break; + case PDO_AUGMENTED: { + union pd_augmented_supply_pdo_source pdo; + + pdo.raw_value = pdo_value; + LOG_INF("\tType: AUGMENTED"); + LOG_INF("\tMin Voltage: %d", + PD_CONVERT_AUGMENTED_PDO_VOLTAGE_TO_MV(pdo.min_voltage)); + LOG_INF("\tMax Voltage: %d", + PD_CONVERT_AUGMENTED_PDO_VOLTAGE_TO_MV(pdo.max_voltage)); + LOG_INF("\tMax Current: %d", + PD_CONVERT_AUGMENTED_PDO_CURRENT_TO_MA(pdo.max_current)); + LOG_INF("\tPPS Power Limited: %d", pdo.pps_power_limited); + } + break; + } +} + +/** + * @brief Display source caps + */ +static void display_source_caps(struct tc_t *tc) +{ + int pdo_num; + union pd_header header = tc->src_caps_hdr; + + /* Make sure the message is a source caps */ + if (header.number_of_data_objects == 0 || + header.message_type != PD_DATA_SOURCE_CAP) { + return; + } + + /* Get number of Power Data Objects (PDOs) */ + pdo_num = header.number_of_data_objects; + + LOG_INF("Source Caps:"); + for (int i = 0; i < pdo_num; i++) { + display_pdo(i, tc->src_caps[i]); + } +} + +/** + * @brief Attached.SNK + */ +static void tc_attached_snk_entry(void *tc_obj) +{ + struct tc_t *tc = tc_obj; + + LOG_INF("Attached.SNK"); + + snk_startup(tc); + + /* Set CC polarity */ + tcpc_set_cc_polarity(tc->tcpc_dev, tc->cc_polarity); + + /* Enable PD */ + tcpc_set_rx_enable(tc->tcpc_dev, true); +} + +static void tc_attached_snk_run(void *tc_obj) +{ + struct tc_t *tc = tc_obj; + union pd_rdo rdo; + + /* Detach detection */ + if (!tcpc_check_vbus_level(tc->tcpc_dev, TC_VBUS_PRESENT)) { + set_state_tc(TC_UNATTACHED_SNK); + return; + } + + /* Check if Hard Reset was received */ + if (tc->flag & FLAG_HARD_RESET_RECEIVED) { + /* Flag is cleard in snk_startup function */ + snk_startup(tc); + } + + switch (tc->pd_state) { + case SNK_WAIT_FOR_CAPABILITIES: + if (tc->timer == 0) { + tc->timer = T_SINK_WAIT_CAP; + } else { + /* Update SinkWaitCapTimer */ + tc->timer--; + if (tc->timer == 0) { + LOG_INF("Source Caps not received. Continuing to wait!"); + tc->timer = T_SINK_WAIT_CAP; + break; + } + } + + /* Check if a PD message was received */ + if (tc->flag & FLAG_PDMSG_RECEIVED) { + /* Clear flag */ + tc->flag &= ~FLAG_PDMSG_RECEIVED; + /* Get the message */ + if (!tcpc_receive_data(tc->tcpc_dev, &tc->msg)) { + /* Problem getting the message */ + break; + } + /* Check if the message is a source caps message */ + if (tc->msg.header.number_of_data_objects == 0 || + tc->msg.header.message_type != PD_DATA_SOURCE_CAP) { + /* Not a source caps message */ + break; + } + } else { + /* Message not received */ + break; + } + /* We got the source caps message */ + tc->pd_state = SNK_EVALUATE_CAPABILITY; + __fallthrough; + case SNK_EVALUATE_CAPABILITY: + /* Save source caps header and payload */ + tc->src_caps_hdr = tc->msg.header; + memcpy(tc->src_caps, tc->msg.data, + PD_CONVERT_PD_HEADER_COUNT_TO_BYTES(tc->msg.header.number_of_data_objects)); + + /* Move on to the select capabilities */ + tc->pd_state = SNK_SELECT_CAPABILITY; + tc->sub_state = SUB_STATE0; + __fallthrough; + case SNK_SELECT_CAPABILITY: + switch (tc->sub_state) { + case SUB_STATE0: + /* Select the 5V PDO at index 1 */ + + /* Create the PD header */ + + /* Request message */ + tc->msg.header.message_type = PD_DATA_REQUEST; + /* Always sink */ + tc->msg.header.port_power_role = TC_ROLE_SINK; + /* Always upstream facing port */ + tc->msg.header.port_data_role = TC_ROLE_UFP; + /* Message ID */ + tc->msg.header.message_id = tc->msg_id_cnt; + /* one 4-byte Request Data Object (RDO) */ + tc->msg.header.number_of_data_objects = 1; + /* PD Revision 2.0 */ + tc->msg.header.specification_revision = PD_REV20; + /* Not an extended message */ + tc->msg.header.extended = 0; + + /* Create the Request Data Object */ + + /* Maximum operating current 100mA (GIVEBACK = 0) */ + rdo.fixed.min_or_max_operating_current = + PD_CONVERT_MA_TO_FIXED_PDO_CURRENT(100); + /* Operating current 100mA */ + rdo.fixed.operating_current = PD_CONVERT_MA_TO_FIXED_PDO_CURRENT(100); + /* Unchunked Extended Messages Not Supported */ + rdo.fixed.unchunked_ext_msg_supported = 0; + /* No USB Suspend */ + rdo.fixed.no_usb_suspend = 1; + /* Not USB Communications Capable */ + rdo.fixed.usb_comm_capable = 0; + /* No capability mismatch */ + rdo.fixed.cap_mismatch = 0; + /* Don't giveback */ + rdo.fixed.giveback = 0; + /* Object position 1 (5V PDO) */ + rdo.fixed.object_pos = 1; + + /* Set message payload. Select the 5V PDO */ + *(uint32_t *)tc->msg.data = rdo.raw_value; + + /* Set packet type */ + tc->msg.type = PD_PACKET_SOP; + + /* Set message length */ + tc->msg.len = 4; + + /* Send the message */ + tcpc_transmit_data(tc->tcpc_dev, &tc->msg); + tc->sub_state = SUB_STATE1; + break; + case SUB_STATE1: + if (tc->flag & FLAG_PDMSG_SENT) { + tc->msg_id_cnt++; + tc->sub_state = SUB_STATE2; + } else if (tc->flag & + (FLAG_PDMSG_DISCARDED | FLAG_PDMSG_FAILED)) { + LOG_INF("Failed to send Request message"); + set_state_tc(TC_UNATTACHED_SNK); + } else { + /* Waiting for message to send */ + break; + } + __fallthrough; + case SUB_STATE2: + /* Check if a PD message was received */ + if (tc->flag & FLAG_PDMSG_RECEIVED) { + /* Clear flag */ + tc->flag &= ~FLAG_PDMSG_RECEIVED; + /* Get the message */ + if (!tcpc_receive_data(tc->tcpc_dev, + &tc->msg)) { + /* Problem getting the message */ + break; + } + /* Check if the message is an Accept message */ + if (tc->msg.header.number_of_data_objects != 0 || + tc->msg.header.message_type != PD_CTRL_ACCEPT) { + /* Not an Accept message */ + break; + } + } else { + /* Message not received */ + break; + } + LOG_INF("Received PD_CTRL_ACCEPT"); + tc->sub_state = SUB_STATE3; + break; + case SUB_STATE3: + /* Check if a PD message was received */ + if (tc->flag & FLAG_PDMSG_RECEIVED) { + /* Clear flag */ + tc->flag &= ~FLAG_PDMSG_RECEIVED; + /* Get the message */ + if (!tcpc_receive_data(tc->tcpc_dev, &tc->msg)) { + /* Problem getting the message */ + break; + } + /* Check if the message is a PS Ready message */ + if (tc->msg.header.number_of_data_objects != 0 || + tc->msg.header.message_type != PD_CTRL_PS_RDY) { + break; + } + } else { + break; + } + LOG_INF("Received PD_CTRL_PS_RDY"); + /* PD contract in place and PD connected */ + tc->pd_connected = true; + tc->pd_state = SNK_TRANSITION_SINK; + } + break; + case SNK_TRANSITION_SINK: + /* Display Soruce Caps Received form Source */ + display_source_caps(tc); + tc->sub_state = SUB_STATE0; + tc->pd_state = SNK_READY; + __fallthrough; + case SNK_READY: + switch (tc->sub_state) { + case SUB_STATE0: + LOG_INF("SNK_READY"); + tc->sub_state++; + __fallthrough; + case SUB_STATE1: + /* STAY HERE FOREVER AND REJECT ALL RECEIVED MESSAGES */ + + /* Check if a PD message was received */ + if (tc->flag & FLAG_PDMSG_RECEIVED) { + /* Clear flag */ + tc->flag &= ~FLAG_PDMSG_RECEIVED; + + /* Create the PD header */ + + /* Reject message */ + tc->msg.header.message_type = PD_CTRL_REJECT; + /* Always sink */ + tc->msg.header.port_power_role = TC_ROLE_SINK; + /* Always upstream facing port */ + tc->msg.header.port_data_role = TC_ROLE_UFP; + /* Message ID */ + tc->msg.header.message_id = tc->msg_id_cnt; + /* one 4-byte Request Data Object (RDO) */ + tc->msg.header.number_of_data_objects = 0; + /* PD Revision 2.0 */ + tc->msg.header.specification_revision = PD_REV20; + /* Not an extended message */ + tc->msg.header.extended = 0; + + /* Set packet type */ + tc->msg.type = PD_PACKET_SOP; + + /* Set message length */ + tc->msg.len = 0; + + /* Send the message */ + tcpc_transmit_data(tc->tcpc_dev, &tc->msg); + } + break; + default: + break; + } + break; + } + + /* Only the sink pwer sub states when we are not in a pd contract */ + if (!tc->pd_connected) { + /* Run Sink Power Sub-State */ + sink_power_sub_states(); + } +} + +static void tc_attached_snk_exit(void *tc_obj) +{ + struct tc_t *tc = tc_obj; + + tc->pd_connected = false; + /* Disable PD */ + tcpc_set_rx_enable(tc->tcpc_dev, false); +} + +/** + * @brief Type-C State Table + */ +static struct smf_state tc_states[] = { + /** Unattached Sink */ + [TC_UNATTACHED_SNK] = { + .entry = tc_unattached_snk_entry, + .run = tc_unattached_snk_run, + }, + /** Attach Wait Sink */ + [TC_ATTACH_WAIT_SNK] = { + .entry = tc_attach_wait_snk_entry, + .run = tc_attach_wait_snk_run, + }, + /** Attached Sink */ + [TC_ATTACHED_SNK] = { + .entry = tc_attached_snk_entry, + .run = tc_attached_snk_run, + .exit = tc_attached_snk_exit + }, +}; + +/** + * @brief Set the TypeC state machine to a new state + */ +static void set_state_tc(const enum usb_tc_state new_state) +{ + smf_set_state(SMF_CTX(&tc), &tc_states[new_state]); +} + +/** + * @brief Alert Handler called by the TCPC driver + */ +static void alert_handler(const struct device *dev, + void *data, enum tcpc_alert alert) +{ + struct tc_t *tc = data; + + switch (alert) { + case TCPC_ALERT_CC_STATUS: + tc->flag |= FLAG_CC_EVENT; + break; + case TCPC_ALERT_POWER_STATUS: + break; + case TCPC_ALERT_MSG_STATUS: + tc->flag |= FLAG_PDMSG_RECEIVED; + break; + case TCPC_ALERT_HARD_RESET_RECEIVED: + tc->flag |= FLAG_HARD_RESET_RECEIVED; + break; + case TCPC_ALERT_TRANSMIT_MSG_FAILED: + tc->flag |= FLAG_PDMSG_FAILED; + break; + case TCPC_ALERT_TRANSMIT_MSG_DISCARDED: + tc->flag |= FLAG_PDMSG_DISCARDED; + break; + case TCPC_ALERT_TRANSMIT_MSG_SUCCESS: + tc->flag |= FLAG_PDMSG_SENT; + break; + /* These alerts are ignored */ + default: + break; + } +} + +int discharge_vbus(const struct device *dev, bool en) +{ + return board_discharge_vbus(en); +} + +int vbus_meas(const struct device *dev, int *vbus_meas) +{ + int ret; + + ret = board_vbus_meas(vbus_meas); + if (ret != 0) { + *vbus_meas = 0; + } + + return 0; +} + +int sink_init(void) +{ + tc.tcpc_dev = DEVICE_DT_GET(TCPC_NODE); + if (!device_is_ready(tc.tcpc_dev)) { + LOG_ERR("TCPC device not ready"); + return -ENODEV; + } + + /* Set the Hard Reset Received callback */ + tcpc_set_alert_handler_cb(tc.tcpc_dev, alert_handler, &tc); + + /* Set the VBUS measurement callback */ + tcpc_set_vbus_measure_cb(tc.tcpc_dev, vbus_meas); + + /* Set the VBUS discharge callback */ + tcpc_set_discharge_vbus_cb(tc.tcpc_dev, discharge_vbus); + + /* Remove resistors from CC1 */ + tcpc_set_cc(tc.tcpc_dev, TC_CC_OPEN); + + /* Allow time for disconnection to be detected */ + k_msleep(100); + + /* Unattached.SNK is the default starting state. */ + smf_set_initial(SMF_CTX(&tc), &tc_states[TC_UNATTACHED_SNK]); + + return 0; +} + +int sink_sm(void) +{ + /* Sample CC lines */ + tcpc_get_cc(tc.tcpc_dev, &tc.cc1, &tc.cc2); + + /* Detect polarity */ + tc.cc_polarity = (tc.cc1 > tc.cc2) ? + TC_POLARITY_CC1 : TC_POLARITY_CC2; + + /* Run TypeC state machine */ + smf_run_state(SMF_CTX(&tc)); + + return LOOP_TIMEOUT_5MS; +} diff --git a/samples/boards/stm32/usbc/sink/src/usbc_snk.h b/samples/boards/stm32/usbc/sink/src/usbc_snk.h new file mode 100644 index 00000000000..da0b97a392b --- /dev/null +++ b/samples/boards/stm32/usbc/sink/src/usbc_snk.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __USBC_SINK_H__ +#define __USBC_SINK_H__ + +/** + * @brief Initialize the Sink state machine + * + * @return 0 on success + * @return -EIO on failure + */ +int sink_init(void); + +/** + * @brief Run the Sink state machine + * + * @return time to wait before calling again + */ +int sink_sm(void); + +#endif /* __USBC_SINK_H__ */