| /* |
| * 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" |
| #include "usbc_tc_snk_states_internal.h" |
| #include "usbc_tc_src_states_internal.h" |
| #include "usbc_tc_common_internal.h" |
| #include <zephyr/drivers/usb_c/usbc_ppc.h> |
| |
| static const struct smf_state tc_states[TC_STATE_COUNT]; |
| static int tc_init(const struct device *dev); |
| |
| /** |
| * @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_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; |
| int ret; |
| |
| /* 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 */ |
| ret = tc_init(dev); |
| if (ret != 0 && ret != -EAGAIN) { |
| /* Transition to Disabled State */ |
| LOG_ERR("Disabling the Type-C Layer"); |
| data->tc_enabled = false; |
| tc_set_state(dev, TC_DISABLED_STATE); |
| } |
| |
| if (ret != 0) { |
| break; |
| } |
| |
| data->tc_sm_state = SM_RUN; |
| /* fall through */ |
| case SM_RUN: |
| if (data->tc_enabled == false) { |
| tc_pd_enable(dev, false); |
| data->tc_sm_state = SM_PAUSED; |
| break; |
| } |
| |
| /* Sample CC lines */ |
| if (tcpc_get_cc(tcpc, &tc->cc1, &tc->cc2) != 0) { |
| /* If this function fails, it may mean that the TCPC is in sleep mode or |
| * the communication with TCPC has failed, so we can assume that the CC |
| * lines are open or existing connection is faulty. |
| */ |
| tc->cc1 = TC_CC_VOLT_OPEN; |
| tc->cc2 = TC_CC_VOLT_OPEN; |
| } |
| |
| /* 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) |
| { |
| #ifdef CONFIG_USBC_CSM_SINK_ONLY |
| return (tc_get_state(dev) == TC_ATTACHED_SNK_STATE); |
| #else |
| return (tc_get_state(dev) == TC_ATTACHED_SRC_STATE); |
| #endif |
| } |
| |
| /** |
| * @brief Initializes the Type-C layer |
| */ |
| static int tc_init(const struct device *dev) |
| { |
| struct usbc_port_data *data = dev->data; |
| struct tc_sm_t *tc = data->tc; |
| const struct device *tcpc = data->tcpc; |
| int ret; |
| |
| /* 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); |
| #ifdef CONFIG_USBC_CSM_SOURCE_ONLY |
| usbc_timer_init(&tc->tc_t_vconn_off, TC_T_VCONN_OFF_MAX_MS); |
| #endif |
| |
| /* Clear the flags */ |
| tc->flags = ATOMIC_INIT(0); |
| |
| /* Initialize the TCPC */ |
| ret = tcpc_init(tcpc); |
| if (ret != 0) { |
| LOG_ERR("TCPC initialization failed: %d", ret); |
| return ret; |
| } |
| |
| #ifdef CONFIG_USBC_CSM_SOURCE_ONLY |
| /* Stop sourcing VBUS by policy callback and/or TCPC */ |
| ret = usbc_policy_src_en(dev, tcpc, false); |
| if (ret != 0) { |
| LOG_ERR("Couldn't disable vbus sourcing: %d", ret); |
| return ret; |
| } |
| |
| /* Disable VBUS sourcing by the PPC */ |
| if (data->ppc != NULL) { |
| ppc_set_src_ctrl(data->ppc, false); |
| } |
| |
| /* Stop sourcing VCONN */ |
| ret = tcpc_set_vconn(tcpc, false); |
| if (ret != 0 && ret != -ENOTSUP) { |
| LOG_ERR("Couldn't disable vconn: %d", ret); |
| return ret; |
| } |
| #endif |
| |
| /* 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); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Sets a Type-C state |
| */ |
| 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; |
| |
| __ASSERT(state < ARRAY_SIZE(tc_states), "invalid tc_state %d", state); |
| smf_set_state(SMF_CTX(tc), &tc_states[state]); |
| } |
| |
| /** |
| * @brief Get the Type-C current state |
| */ |
| enum tc_state_t tc_get_state(const struct device *dev) |
| { |
| struct usbc_port_data *data = dev->data; |
| |
| return data->tc->ctx.current - &tc_states[0]; |
| } |
| |
| /** |
| * @brief Enable Power Delivery |
| */ |
| void tc_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 TCPC CC/Rp management |
| */ |
| void tc_select_src_collision_rp(const struct device *dev, enum tc_rp_value rp) |
| { |
| struct usbc_port_data *data = dev->data; |
| const struct device *tcpc = data->tcpc; |
| int ret; |
| |
| /* 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); |
| } |
| } |
| |
| /** |
| * @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; |
| int ret; |
| |
| tc->cc_voltage = TC_CC_VOLT_OPEN; |
| |
| /* Disable VCONN */ |
| ret = tcpc_set_vconn(tcpc, false); |
| if (ret != 0 && ret != -ENOSYS) { |
| LOG_ERR("Couldn't disable vconn: %d", ret); |
| tc_set_state(dev, TC_ERROR_RECOVERY_STATE); |
| return; |
| } |
| |
| /* Open CC lines */ |
| ret = tcpc_set_cc(tcpc, TC_CC_OPEN); |
| if (ret != 0) { |
| LOG_ERR("Couldn't set CC lines to open: %d", ret); |
| tc_set_state(dev, TC_ERROR_RECOVERY_STATE); |
| } |
| } |
| |
| /** |
| * @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; |
| } |
| |
| #ifdef CONFIG_USBC_CSM_SINK_ONLY |
| /* Transition to Unattached.SNK */ |
| tc_set_state(dev, TC_UNATTACHED_SNK_STATE); |
| #else |
| /* Transition to Unattached.SRC */ |
| tc_set_state(dev, TC_UNATTACHED_SRC_STATE); |
| #endif |
| } |
| |
| /** |
| * @brief Type-C State Table |
| */ |
| static const struct smf_state tc_states[TC_STATE_COUNT] = { |
| /* Super States */ |
| [TC_CC_OPEN_SUPER_STATE] = SMF_CREATE_STATE( |
| tc_cc_open_entry, |
| NULL, |
| NULL, |
| NULL, |
| NULL), |
| #ifdef CONFIG_USBC_CSM_SINK_ONLY |
| [TC_CC_RD_SUPER_STATE] = SMF_CREATE_STATE( |
| tc_cc_rd_entry, |
| NULL, |
| NULL, |
| NULL, |
| NULL), |
| #else |
| [TC_CC_RP_SUPER_STATE] = SMF_CREATE_STATE( |
| tc_cc_rp_entry, |
| NULL, |
| NULL, |
| NULL, |
| NULL), |
| #endif |
| /* Normal States */ |
| #ifdef CONFIG_USBC_CSM_SINK_ONLY |
| [TC_UNATTACHED_SNK_STATE] = SMF_CREATE_STATE( |
| tc_unattached_snk_entry, |
| tc_unattached_snk_run, |
| NULL, |
| &tc_states[TC_CC_RD_SUPER_STATE], |
| NULL), |
| [TC_ATTACH_WAIT_SNK_STATE] = SMF_CREATE_STATE( |
| tc_attach_wait_snk_entry, |
| tc_attach_wait_snk_run, |
| tc_attach_wait_snk_exit, |
| &tc_states[TC_CC_RD_SUPER_STATE], |
| NULL), |
| [TC_ATTACHED_SNK_STATE] = SMF_CREATE_STATE( |
| tc_attached_snk_entry, |
| tc_attached_snk_run, |
| tc_attached_snk_exit, |
| NULL, |
| NULL), |
| #else |
| [TC_UNATTACHED_SRC_STATE] = SMF_CREATE_STATE( |
| tc_unattached_src_entry, |
| tc_unattached_src_run, |
| NULL, |
| &tc_states[TC_CC_RP_SUPER_STATE], |
| NULL), |
| [TC_UNATTACHED_WAIT_SRC_STATE] = SMF_CREATE_STATE( |
| tc_unattached_wait_src_entry, |
| tc_unattached_wait_src_run, |
| tc_unattached_wait_src_exit, |
| NULL, |
| NULL), |
| [TC_ATTACH_WAIT_SRC_STATE] = SMF_CREATE_STATE( |
| tc_attach_wait_src_entry, |
| tc_attach_wait_src_run, |
| tc_attach_wait_src_exit, |
| &tc_states[TC_CC_RP_SUPER_STATE], |
| NULL), |
| [TC_ATTACHED_SRC_STATE] = SMF_CREATE_STATE( |
| tc_attached_src_entry, |
| tc_attached_src_run, |
| tc_attached_src_exit, |
| NULL, |
| NULL), |
| #endif |
| [TC_DISABLED_STATE] = SMF_CREATE_STATE( |
| tc_disabled_entry, |
| tc_disabled_run, |
| NULL, |
| &tc_states[TC_CC_OPEN_SUPER_STATE], |
| NULL), |
| [TC_ERROR_RECOVERY_STATE] = SMF_CREATE_STATE( |
| tc_error_recovery_entry, |
| tc_error_recovery_run, |
| NULL, |
| &tc_states[TC_CC_OPEN_SUPER_STATE], |
| NULL), |
| }; |
| BUILD_ASSERT(ARRAY_SIZE(tc_states) == TC_STATE_COUNT); |