366 lines
10 KiB
C
366 lines
10 KiB
C
/*
|
|
* Copyright (c) 2023 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"
|
|
#include "usbc_tc_src_states_internal.h"
|
|
#include <zephyr/drivers/usb_c/usbc_ppc.h>
|
|
|
|
/**
|
|
* @brief Spec. Release 1.3, section 4.5.2.2.7 Unattached.SRC State
|
|
*
|
|
* When in the Unattached.SRC state, the port is waiting to detect the
|
|
* presence of a Sink or an Accessory.
|
|
*
|
|
* Requirements:
|
|
* 1: The port shall not drive VBUS or VCONN.
|
|
* NOTE: Implemented in the tc_attached_src_exit
|
|
* function and initially set the tc_init function.
|
|
*
|
|
* 2: The port shall provide a separate Rp termination on the CC1 and
|
|
* CC2 pins.
|
|
* NOTE: Implemented in the tc_cc_rp super state.
|
|
*/
|
|
|
|
void tc_unattached_src_entry(void *obj)
|
|
{
|
|
LOG_INF("Unattached.SRC");
|
|
}
|
|
|
|
void tc_unattached_src_run(void *obj)
|
|
{
|
|
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
|
|
const struct device *dev = tc->dev;
|
|
|
|
/*
|
|
* Transition to AttachWait.SRC when:
|
|
* The SRC.Rd is detected on either CC1 or CC2 pin or
|
|
* SRC.Ra is detected on both CC1 and CC2 pins.
|
|
* NOTE: Audio Adapter Accessory Mode is not supported, so
|
|
* SRC.Ra will not be checked.
|
|
*/
|
|
if (tcpc_is_cc_at_least_one_rd(tc->cc1, tc->cc2)) {
|
|
tc_set_state(dev, TC_ATTACH_WAIT_SRC_STATE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Spec. Release 1.3, section 4.5.2.2.6 UnattachedWait.SRC State
|
|
*
|
|
* When in the UnattachedWait.SRC state, the port is discharging the CC pin
|
|
* that was providing VCONN in the previous Attached.SRC state.
|
|
*
|
|
* Requirements:
|
|
* 1: The port shall not enable VBUS or VCONN.
|
|
* NOTE: Implemented in tc_attached_src_exit
|
|
*
|
|
* 2: The port shall continue to provide an Rp termination on the CC pin not
|
|
* being discharged.
|
|
* NOTE: Implemented in TC_CC_RP_SUPER_STATE super state.
|
|
*
|
|
* 3: The port shall provide an Rdch termination on the CC pin being
|
|
* discharged.
|
|
* NOTE: Implemented in tc_unattached_wait_src_entry
|
|
*/
|
|
|
|
void tc_unattached_wait_src_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("UnattachedWait.SRC");
|
|
|
|
/* Start discharging VCONN */
|
|
tcpc_vconn_discharge(tcpc, true);
|
|
|
|
/* Start VCONN off timer */
|
|
usbc_timer_start(&tc->tc_t_vconn_off);
|
|
}
|
|
|
|
void tc_unattached_wait_src_run(void *obj)
|
|
{
|
|
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
|
|
const struct device *dev = tc->dev;
|
|
|
|
/* CC Debounce time should be enough time for VCONN to discharge */
|
|
if (usbc_timer_expired(&tc->tc_t_vconn_off)) {
|
|
tc_set_state(dev, TC_UNATTACHED_SRC_STATE);
|
|
}
|
|
}
|
|
|
|
void tc_unattached_wait_src_exit(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;
|
|
|
|
/* Stop discharging VCONN */
|
|
tcpc_vconn_discharge(tcpc, false);
|
|
|
|
/* Stop timer */
|
|
usbc_timer_stop(&tc->tc_t_vconn_off);
|
|
}
|
|
|
|
/**
|
|
* @brief Spec. Release 1.3, section 4.5.2.2.8 AttachWait.SRC State
|
|
*
|
|
* The AttachWait.SRC state is used to ensure that the state of both of
|
|
* the CC1 and CC2 pins is stable after a Sink is connected.
|
|
*
|
|
* Requirements:
|
|
* The requirements for this state are identical to Unattached.SRC.
|
|
*/
|
|
|
|
void tc_attach_wait_src_entry(void *obj)
|
|
{
|
|
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
|
|
|
|
LOG_INF("AttachWait.SRC");
|
|
|
|
/* Initialize the cc state to open */
|
|
tc->cc_state = TC_CC_NONE;
|
|
}
|
|
|
|
void tc_attach_wait_src_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;
|
|
|
|
/* Is a connection detected? */
|
|
if (tcpc_is_cc_at_least_one_rd(tc->cc1, tc->cc2)) {
|
|
/* UFP attached */
|
|
new_cc_state = TC_CC_UFP_ATTACHED;
|
|
} else {
|
|
/* No UFP */
|
|
tc_set_state(dev, TC_UNATTACHED_SRC_STATE);
|
|
return;
|
|
}
|
|
|
|
/* Debounce the cc state */
|
|
if (new_cc_state != tc->cc_state) {
|
|
/* Start debouce timer */
|
|
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)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The port shall transition to Attached.SRC when VBUS is at vSafe0V
|
|
* and the SRC.Rd state is detected on exactly one of the CC1 or CC2
|
|
* pins for at least tCCDebounce.
|
|
*/
|
|
if (usbc_vbus_check_level(vbus, TC_VBUS_SAFE0V)) {
|
|
if (new_cc_state == TC_CC_UFP_ATTACHED) {
|
|
tc_set_state(dev, TC_ATTACHED_SRC_STATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tc_attach_wait_src_exit(void *obj)
|
|
{
|
|
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
|
|
|
|
/* Stop debounce timer */
|
|
usbc_timer_stop(&tc->tc_t_cc_debounce);
|
|
}
|
|
|
|
/**
|
|
* @brief Spec. Release 1.3, section 4.5.2.2.9 Attached.SRC State
|
|
*
|
|
* When in the Attached.SRC state, the port is attached and operating as a
|
|
* Source. When the port initially enters this state it is also operating
|
|
* as a DFP. Subsequently, the initial power and data roles can be changed
|
|
* using USB PD commands.
|
|
*
|
|
* Requirements:
|
|
* 1: If the port needs to determine the orientation of the connector, it
|
|
* shall do so only upon entry to the Attached.SRC state by detecting
|
|
* which of the CC1 or CC2 pins is connected through the
|
|
* cable, i.e., which CC pin is in the SRC.Rd state.
|
|
* NOTE: Implemented in tc_attached_src_entry.
|
|
*
|
|
* 2: If the port has entered this state from the AttachWait.SRC state,
|
|
* the SRC.Rd state will be on only one of the CC1 or CC2 pins. The
|
|
* port shall source current on this CC pin and monitor its state.
|
|
* NOTE: Implemented in the super state of AttachWait.SRC.
|
|
*
|
|
* 3: The port shall provide an Rp
|
|
* NOTE: Implemented in the super state of AttachWait.SRC.
|
|
*
|
|
* 5: The port shall supply VBUS current at the level it advertises on Rp.
|
|
* NOTE: Implemented in tc_attached_src_entry.
|
|
*
|
|
* 7: The port shall not initiate any USB PD communications until VBUS
|
|
* reaches vSafe5V.
|
|
* NOTE: Implemented in tc_attached_src_run.
|
|
*
|
|
* 8: The port may negotiate a USB PD PR_Swap, DR_Swap or VCONN_Swap.
|
|
* NOTE: Implemented in tc_attached_src_run.
|
|
*
|
|
* 9: If the port supplies VCONN, it shall do so within t_VCONN_ON.
|
|
* NOTE: Implemented in tc_attached_src_entry.
|
|
*/
|
|
|
|
void tc_attached_src_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;
|
|
int ret;
|
|
|
|
LOG_INF("Attached.SRC");
|
|
|
|
/* Initial data role for source is DFP */
|
|
tcpc_set_roles(tcpc, TC_ROLE_SOURCE, TC_ROLE_DFP);
|
|
|
|
/* Set cc polarity */
|
|
ret = tcpc_set_cc_polarity(tcpc, tc->cc_polarity);
|
|
if (ret != 0) {
|
|
LOG_ERR("Couldn't set CC polarity to %d: %d", tc->cc_polarity, ret);
|
|
tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
|
|
return;
|
|
}
|
|
|
|
/* Start sourcing VBUS */
|
|
if (usbc_policy_src_en(dev, tcpc, true) == 0) {
|
|
/* Start sourcing VCONN */
|
|
if (policy_check(dev, CHECK_VCONN_CONTROL)) {
|
|
if (tcpc_set_vconn(tcpc, true) == 0) {
|
|
atomic_set_bit(&tc->flags, TC_FLAGS_VCONN_ON);
|
|
} else {
|
|
LOG_ERR("VCONN can't be enabled\n");
|
|
}
|
|
}
|
|
} else {
|
|
LOG_ERR("Power Supply can't be enabled\n");
|
|
}
|
|
|
|
/* Enable PD */
|
|
tc_pd_enable(dev, true);
|
|
|
|
/* Enable the VBUS sourcing by the PPC */
|
|
if (data->ppc != NULL) {
|
|
ret = ppc_set_src_ctrl(data->ppc, true);
|
|
if (ret < 0 && ret != -ENOSYS) {
|
|
LOG_ERR("Couldn't disable PPC source");
|
|
}
|
|
}
|
|
}
|
|
|
|
void tc_attached_src_run(void *obj)
|
|
{
|
|
struct tc_sm_t *tc = (struct tc_sm_t *)obj;
|
|
const struct device *dev = tc->dev;
|
|
|
|
/* Monitor for CC disconnection */
|
|
if (tcpc_is_cc_open(tc->cc1, tc->cc2)) {
|
|
/*
|
|
* A Source that is supplying VCONN or has yielded VCONN source
|
|
* responsibility to the Sink through USBPD VCONN_Swap messaging
|
|
* shall transition to UnattachedWait.SRC when the SRC.Open state
|
|
* is detected on the monitored CC pin. The Source shall detect
|
|
* the SRC.Open state within tSRCDisconnect, but should detect
|
|
* it as quickly as possible.
|
|
*/
|
|
if (atomic_test_and_clear_bit(&tc->flags, TC_FLAGS_VCONN_ON)) {
|
|
tc_set_state(dev, TC_UNATTACHED_WAIT_SRC_STATE);
|
|
}
|
|
/*
|
|
* A Source that is not supplying VCONN and has not yielded
|
|
* VCONN responsibility to the Sink through USBPD VCONN_Swap
|
|
* messaging shall transition to Unattached.SRC when the
|
|
* SRC.Open state is detected on the monitored CC pin. The
|
|
* Source shall detect the SRC.Open state within tSRCDisconnect,
|
|
* but should detect it as quickly as possible.
|
|
*/
|
|
else {
|
|
tc_set_state(dev, TC_UNATTACHED_SRC_STATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tc_attached_src_exit(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;
|
|
int ret;
|
|
|
|
/* Disable PD */
|
|
tc_pd_enable(dev, false);
|
|
|
|
/* Stop sourcing VBUS */
|
|
if (usbc_policy_src_en(dev, tcpc, false) != 0) {
|
|
LOG_ERR("Couldn't disable VBUS source");
|
|
}
|
|
|
|
/* Disable the VBUS sourcing by the PPC */
|
|
if (data->ppc != NULL) {
|
|
ret = ppc_set_src_ctrl(data->ppc, false);
|
|
if (ret < 0 && ret != -ENOSYS) {
|
|
LOG_ERR("Couldn't disable PPC source");
|
|
}
|
|
}
|
|
|
|
/* Stop sourcing VCONN */
|
|
ret = tcpc_set_vconn(tcpc, false);
|
|
if (ret != 0 && ret != -ENOSYS) {
|
|
LOG_ERR("Couldn't disable VCONN source");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief This is a super state for Source States that
|
|
* requirement the Rp value placed on the CC lines.
|
|
*/
|
|
void tc_cc_rp_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;
|
|
enum tc_rp_value rp = TC_RP_USB;
|
|
int ret;
|
|
|
|
/*
|
|
* Get initial Rp value from Device Policy Manager or use
|
|
* default TC_RP_USB.
|
|
*/
|
|
if (data->policy_cb_get_src_rp) {
|
|
data->policy_cb_get_src_rp(dev, &rp);
|
|
}
|
|
|
|
/* Select Rp value */
|
|
ret = tcpc_select_rp_value(tcpc, rp);
|
|
if (ret != 0 && ret != -ENOTSUP) {
|
|
LOG_ERR("Couldn't set Rp value to %d: %d", rp, ret);
|
|
tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
|
|
return;
|
|
}
|
|
|
|
/* Place Rp on CC lines */
|
|
ret = tcpc_set_cc(tcpc, TC_CC_RP);
|
|
if (ret != 0) {
|
|
LOG_ERR("Couldn't set CC lines to Rp: %d", ret);
|
|
tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
|
|
}
|
|
}
|