samples: boards: Add sample application based on the STM32 USB TCPC Driver

This sample application uses the STM32 USB TCPC Driver to create a USBC
Sink Application for the STM32G081b_eval board.

Signed-off-by: Sam Hurst <sbh1187@gmail.com>
This commit is contained in:
Sam Hurst 2022-01-16 14:34:03 -08:00 committed by Carles Cufí
commit 986093c8cc
10 changed files with 1156 additions and 0 deletions

View file

@ -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)

View file

@ -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

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2022 The Chromium OS Authors.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
/ {
aliases {
tcpc = &ucpd2;
};
};

View file

@ -0,0 +1,4 @@
CONFIG_USBC_TCPC_DRIVER=y
CONFIG_SMF=y
CONFIG_LOG=y
CONFIG_ADC=y

View file

@ -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"

View file

@ -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__ */

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 The Chromium OS Authors.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <logging/log.h>
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);
}
}

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/adc.h>
#include <logging/log.h>
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;
}

View file

@ -0,0 +1,775 @@
/*
* Copyright (c) 2022 The Chromium OS Authors.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <smf.h>
#include <drivers/usbc/usbc_tcpc.h>
#include "board.h"
#include <logging/log.h>
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;
}

View file

@ -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__ */