blob: ea0abda3357d45bd3a59a4ea946f71064089e929 [file] [log] [blame]
/*
* Copyright (c) 2022 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(usbc_stack, CONFIG_USBC_STACK_LOG_LEVEL);
#include "usbc_stack.h"
/**
* @brief Type-C Layer Flags
*/
enum tc_flags {
/**
* Flag to track Rp resistor change when the sink attached
* sub-state runs
*/
TC_FLAGS_RP_SUBSTATE_CHANGE = 0,
};
/**
* @brief Type-C States
*/
enum tc_state_t {
/** Super state that opens the CC lines */
TC_CC_OPEN_SUPER_STATE,
/** Super state that applies Rd to the CC lines */
TC_CC_RD_SUPER_STATE,
/** Disabled State */
TC_DISABLED_STATE,
/** Error Recovery State */
TC_ERROR_RECOVERY_STATE,
/** Unattached Sink State */
TC_UNATTACHED_SNK_STATE,
/** Attach Wait Sink State */
TC_ATTACH_WAIT_SNK_STATE,
/** Attached Sink State */
TC_ATTACHED_SNK_STATE,
};
static const struct smf_state tc_snk_states[];
static void tc_init(const struct device *dev);
static void tc_set_state(const struct device *dev,
const enum tc_state_t state);
static enum tc_state_t tc_get_state(const struct device *dev);
static void pd_enable(const struct device *dev,
const bool enable);
/**
* @brief Initializes the state machine and enters the Disabled state
*/
void tc_subsys_init(const struct device *dev)
{
struct usbc_port_data *data = dev->data;
struct tc_sm_t *tc = data->tc;
/* Save the port device object so states can access it */
tc->dev = dev;
/* Initialize the state machine */
smf_set_initial(SMF_CTX(tc), &tc_snk_states[TC_DISABLED_STATE]);
}
/**
* @brief Runs the Type-C layer
*/
void tc_run(const struct device *dev,
const int32_t dpm_request)
{
struct usbc_port_data *data = dev->data;
const struct device *tcpc = data->tcpc;
struct tc_sm_t *tc = data->tc;
/* These requests are implicitly set by the Device Policy Manager */
if (dpm_request == PRIV_PORT_REQUEST_START) {
data->tc_enabled = true;
} else if (dpm_request == PRIV_PORT_REQUEST_SUSPEND) {
data->tc_enabled = false;
tc_set_state(dev, TC_DISABLED_STATE);
}
switch (data->tc_sm_state) {
case SM_PAUSED:
if (data->tc_enabled == false) {
break;
}
/* fall through */
case SM_INIT:
/* Initialize the Type-C layer */
tc_init(dev);
data->tc_sm_state = SM_RUN;
/* fall through */
case SM_RUN:
if (data->tc_enabled == false) {
pd_enable(dev, false);
data->tc_sm_state = SM_PAUSED;
break;
}
/* Sample CC lines */
tcpc_get_cc(tcpc, &tc->cc1, &tc->cc2);
/* Detect polarity */
tc->cc_polarity = (tc->cc1 > tc->cc2) ?
TC_POLARITY_CC1 : TC_POLARITY_CC2;
/* Execute any asyncronous Device Policy Manager Requests */
if (dpm_request == REQUEST_TC_ERROR_RECOVERY) {
/* Transition to Error Recovery State */
tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
} else if (dpm_request == REQUEST_TC_DISABLED) {
/* Transition to Disabled State */
tc_set_state(dev, TC_DISABLED_STATE);
}
/* Run state machine */
smf_run_state(SMF_CTX(tc));
}
}
/**
* @brief Checks if the TC Layer is in an Attached state
*/
bool tc_is_in_attached_state(const struct device *dev)
{
return (tc_get_state(dev) == TC_ATTACHED_SNK_STATE);
}
/**
* @brief Initializes the Type-C layer
*/
static void tc_init(const struct device *dev)
{
struct usbc_port_data *data = dev->data;
struct tc_sm_t *tc = data->tc;
/* Initialize the timers */
usbc_timer_init(&tc->tc_t_error_recovery, TC_T_ERROR_RECOVERY_SOURCE_MIN_MS);
usbc_timer_init(&tc->tc_t_cc_debounce, TC_T_CC_DEBOUNCE_MAX_MS);
usbc_timer_init(&tc->tc_t_rp_value_change, TC_T_RP_VALUE_CHANGE_MAX_MS);
/* Clear the flags */
tc->flags = ATOMIC_INIT(0);
/* Initialize the TCPC */
tcpc_init(data->tcpc);
/* Initialize the state machine */
/*
* Start out in error recovery state so the CC lines are opened for a
* short while if this is a system reset.
*/
tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
}
/**
* @brief Sets a Type-C state
*/
static void tc_set_state(const struct device *dev,
const enum tc_state_t state)
{
struct usbc_port_data *data = dev->data;
struct tc_sm_t *tc = data->tc;
smf_set_state(SMF_CTX(tc), &tc_snk_states[state]);
}
/**
* @brief Get the Type-C current state
*/
static enum tc_state_t tc_get_state(const struct device *dev)
{
struct usbc_port_data *data = dev->data;
return data->tc->ctx.current - &tc_snk_states[0];
}
/**
* @brief Enable Power Delivery
*/
static void pd_enable(const struct device *dev,
const bool enable)
{
if (enable) {
prl_start(dev);
pe_start(dev);
} else {
prl_suspend(dev);
pe_suspend(dev);
}
}
/**
* @brief Sink power sub states. Only called if a PD contract is not in place
*/
static void sink_power_sub_states(const struct device *dev)
{
struct usbc_port_data *data = dev->data;
enum tc_cc_voltage_state cc;
enum tc_cc_voltage_state new_cc_voltage;
enum usbc_policy_check_t dpm_pwr_change_notify;
struct tc_sm_t *tc = data->tc;
/* Get the active CC line */
cc = tc->cc_polarity ? tc->cc2 : tc->cc1;
if (cc == TC_CC_VOLT_RP_DEF) {
/*
* This sub-state supports Sinks consuming current within the
* lowest range (default) of Source-supplied current.
*/
new_cc_voltage = TC_CC_VOLT_RP_DEF;
dpm_pwr_change_notify = POWER_CHANGE_DEF;
} else if (cc == TC_CC_VOLT_RP_1A5) {
/*
* This sub-state supports Sinks consuming current within the
* two lower ranges (default and 1.5 A) of Source-supplied
* current.
*/
new_cc_voltage = TC_CC_VOLT_RP_1A5;
dpm_pwr_change_notify = POWER_CHANGE_1A5;
} else if (cc == TC_CC_VOLT_RP_3A0) {
/*
* This sub-state supports Sinks consuming current within all
* three ranges (default, 1.5 A and 3.0 A) of Source-supplied
* current.
*/
new_cc_voltage = TC_CC_VOLT_RP_3A0;
dpm_pwr_change_notify = POWER_CHANGE_3A0;
} else {
/* Disconnect detected */
new_cc_voltage = TC_CC_VOLT_OPEN;
dpm_pwr_change_notify = POWER_CHANGE_0A0;
}
/* Debounce the Rp state */
if (new_cc_voltage != tc->cc_voltage) {
tc->cc_voltage = new_cc_voltage;
atomic_set_bit(&tc->flags, TC_FLAGS_RP_SUBSTATE_CHANGE);
usbc_timer_start(&tc->tc_t_rp_value_change);
}
/* Wait for Rp debounce */
if (usbc_timer_expired(&tc->tc_t_rp_value_change) == false) {
return;
}
/* Notify DPM of sink sub-state power change */
if (atomic_test_and_clear_bit(&tc->flags,
TC_FLAGS_RP_SUBSTATE_CHANGE)) {
if (data->policy_cb_notify) {
data->policy_cb_notify(dev, dpm_pwr_change_notify);
}
}
}
/**
* @brief Unattached.SNK Entry
*/
static void tc_unattached_snk_entry(void *obj)
{
LOG_INF("Unattached.SNK");
}
/**
* @brief Unattached.SNK Run
*/
static void tc_unattached_snk_run(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
/*
* Transition to AttachWait.SNK when the SNK.Rp state is present
* on at least one of its CC pins.
*/
if (tcpc_is_cc_rp(tc->cc1) || tcpc_is_cc_rp(tc->cc2)) {
tc_set_state(dev, TC_ATTACH_WAIT_SNK_STATE);
}
}
/**
* @brief AttachWait.SNK Entry
*/
static void tc_attach_wait_snk_entry(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
LOG_INF("AttachWait.SNK");
tc->cc_state = TC_CC_NONE;
}
/**
* @brief AttachWait.SNK Run
*/
static void tc_attach_wait_snk_run(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
struct usbc_port_data *data = dev->data;
const struct device *vbus = data->vbus;
enum tc_cc_states new_cc_state;
bool vbus_present;
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) {
usbc_timer_start(&tc->tc_t_cc_debounce);
tc->cc_state = new_cc_state;
}
/* Wait for CC debounce */
if (usbc_timer_running(&tc->tc_t_cc_debounce) &&
usbc_timer_expired(&tc->tc_t_cc_debounce) == false) {
return;
}
/* Transition to UnAttached.SNK if CC lines are open */
if (new_cc_state == TC_CC_NONE) {
tc_set_state(dev, TC_UNATTACHED_SNK_STATE);
}
/*
* 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.
*/
vbus_present = usbc_vbus_check_level(vbus, TC_VBUS_PRESENT);
if (vbus_present) {
tc_set_state(dev, TC_ATTACHED_SNK_STATE);
}
}
static void tc_attach_wait_snk_exit(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
usbc_timer_stop(&tc->tc_t_cc_debounce);
}
/**
* @brief Attached.SNK Entry
*/
static void tc_attached_snk_entry(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
struct usbc_port_data *data = dev->data;
const struct device *tcpc = data->tcpc;
LOG_INF("Attached.SNK");
/* Set CC polarity */
tcpc_set_cc_polarity(tcpc, tc->cc_polarity);
/* Enable PD */
pd_enable(dev, true);
}
/**
* @brief Attached.SNK and DebugAccessory.SNK Run
*/
static void tc_attached_snk_run(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
struct usbc_port_data *data = dev->data;
const struct device *vbus = data->vbus;
/* Detach detection */
if (usbc_vbus_check_level(vbus, TC_VBUS_PRESENT) == false) {
tc_set_state(dev, TC_UNATTACHED_SNK_STATE);
return;
}
/* Run Sink Power Sub-State if not in an explicit contract */
if (pe_is_explicit_contract(dev) == false) {
sink_power_sub_states(dev);
}
}
/**
* @brief Attached.SNK and DebugAccessory.SNK Exit
*/
static void tc_attached_snk_exit(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
/* Disable PD */
pd_enable(dev, false);
}
/**
* @brief CC Open Entry
*/
static void tc_cc_open_entry(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
struct usbc_port_data *data = dev->data;
const struct device *tcpc = data->tcpc;
tc->cc_voltage = TC_CC_VOLT_OPEN;
/* Disable VCONN */
tcpc_set_vconn(tcpc, false);
/* Open CC lines */
tcpc_set_cc(tcpc, TC_CC_OPEN);
}
/**
* @brief Rd on CC lines Entry
*/
static void tc_cc_rd_entry(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
struct usbc_port_data *data = dev->data;
const struct device *tcpc = data->tcpc;
tcpc_set_cc(tcpc, TC_CC_RD);
}
/**
* @brief Disabled Entry
*/
static void tc_disabled_entry(void *obj)
{
LOG_INF("Disabled");
}
/**
* @brief Disabled Run
*/
static void tc_disabled_run(void *obj)
{
/* Do nothing */
}
/**
* @brief ErrorRecovery Entry
*/
static void tc_error_recovery_entry(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
LOG_INF("ErrorRecovery");
/* Start tErrorRecovery timer */
usbc_timer_start(&tc->tc_t_error_recovery);
}
/**
* @brief ErrorRecovery Run
*/
static void tc_error_recovery_run(void *obj)
{
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
const struct device *dev = tc->dev;
/* Wait for expiry */
if (usbc_timer_expired(&tc->tc_t_error_recovery) == false) {
return;
}
/* Transition to Unattached.SNK */
tc_set_state(dev, TC_UNATTACHED_SNK_STATE);
}
/**
* @brief Type-C State Table
*/
static const struct smf_state tc_snk_states[] = {
/* Super States */
[TC_CC_OPEN_SUPER_STATE] = SMF_CREATE_STATE(
tc_cc_open_entry,
NULL,
NULL,
NULL),
[TC_CC_RD_SUPER_STATE] = SMF_CREATE_STATE(
tc_cc_rd_entry,
NULL,
NULL,
NULL),
/* Normal States */
[TC_UNATTACHED_SNK_STATE] = SMF_CREATE_STATE(
tc_unattached_snk_entry,
tc_unattached_snk_run,
NULL,
&tc_snk_states[TC_CC_RD_SUPER_STATE]),
[TC_ATTACH_WAIT_SNK_STATE] = SMF_CREATE_STATE(
tc_attach_wait_snk_entry,
tc_attach_wait_snk_run,
tc_attach_wait_snk_exit,
&tc_snk_states[TC_CC_RD_SUPER_STATE]),
[TC_ATTACHED_SNK_STATE] = SMF_CREATE_STATE(
tc_attached_snk_entry,
tc_attached_snk_run,
tc_attached_snk_exit,
NULL),
[TC_DISABLED_STATE] = SMF_CREATE_STATE(
tc_disabled_entry,
tc_disabled_run,
NULL,
&tc_snk_states[TC_CC_OPEN_SUPER_STATE]),
[TC_ERROR_RECOVERY_STATE] = SMF_CREATE_STATE(
tc_error_recovery_entry,
tc_error_recovery_run,
NULL,
&tc_snk_states[TC_CC_OPEN_SUPER_STATE]),
};