blob: 23e8b0eb49c7fe65623ef078628dc887c1982220 [file] [log] [blame]
/*
* Copyright (c) 2022 The Chromium OS Authors.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/zephyr.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/smf.h>
#include <zephyr/drivers/usbc/usbc_tcpc.h>
#include "board.h"
#include <zephyr/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("\tUnchunked 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 Suspend: %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 cleared 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 Source Caps Received from 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 power 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;
}