/* Copyright 2019 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "builtin/assert.h" #include "common.h" #include "console.h" #include "system.h" #include "task.h" #include "tcpm/tcpm.h" #include "typec_control.h" #include "usb_pd.h" #include "usb_tc_sm.h" #include "usb_sm.h" #include "vpd_api.h" /* USB Type-C VCONN Powered Device module */ #ifdef CONFIG_COMMON_RUNTIME #define CPRINTF(format, args...) cprintf(CC_USB, format, ##args) #define CPRINTS(format, args...) cprints(CC_USB, format, ##args) #else /* CONFIG_COMMON_RUNTIME */ #define CPRINTF(format, args...) #define CPRINTS(format, args...) #endif /* Type-C Layer Flags */ #define TC_FLAGS_VCONN_ON BIT(0) /** * This is the Type-C Port object that contains information needed to * implement a VCONN Powered Device. */ static struct type_c { /* state machine context */ struct sm_ctx ctx; /* Higher-level power deliver state machines are enabled if true. */ uint8_t pd_enable; /* port flags, see TC_FLAGS_* */ uint32_t flags; /* Time a port shall wait before it can determine it is attached */ uint64_t cc_debounce; /* VPD host port cc state */ enum pd_cc_states host_cc_state; uint8_t ct_cc; } tc[CONFIG_USB_PD_PORT_MAX_COUNT]; /* List of all TypeC-level states */ enum usb_tc_state { /* Normal States */ TC_DISABLED, TC_UNATTACHED_SNK, TC_ATTACH_WAIT_SNK, TC_ATTACHED_SNK, /* Super States */ TC_VBUS_CC_ISO, TC_HOST_RARD, TC_HOST_OPEN, }; /* Forward declare the full list of states. This is indexed by usb_tc_state */ static const struct usb_state tc_states[]; /* List of human readable state names for console debugging */ __maybe_unused static const char *const tc_state_names[] = { #ifdef CONFIG_COMMON_RUNTIME [TC_DISABLED] = "Disabled", [TC_UNATTACHED_SNK] = "Unattached.SNK", [TC_ATTACH_WAIT_SNK] = "AttachWait.SNK", [TC_ATTACHED_SNK] = "Attached.SNK", #endif }; /* Forward declare private, common functions */ static void set_state_tc(const int port, enum usb_tc_state new_state); /* * TCPC CC/Rp management * * Stub for linking purposes. * This is not supported for vpd, it uses a different mechanism to update * cc values. */ void typec_select_pull(int port, enum tcpc_cc_pull pull) { } void typec_select_src_current_limit_rp(int port, enum tcpc_rp_value rp) { } void typec_select_src_collision_rp(int port, enum tcpc_rp_value rp) { } int typec_update_cc(int port) { return EC_SUCCESS; } /* Public TypeC functions */ void tc_state_init(int port) { int res = 0; res = tcpm_init(port); CPRINTS("C%d: init %s", port, res ? "failed" : "ready"); /* Disable TCPC RX until connection is established */ tcpm_set_rx_enable(port, 0); set_state_tc(port, res ? TC_DISABLED : TC_UNATTACHED_SNK); /* Disable pd state machines */ tc[port].pd_enable = 0; tc[port].flags = 0; } enum pd_power_role pd_get_power_role(int port) { /* Vconn power device is always the sink */ return PD_ROLE_SINK; } enum pd_cable_plug tc_get_cable_plug(int port) { /* Vconn power device is always the cable */ return PD_PLUG_FROM_CABLE; } enum pd_data_role pd_get_data_role(int port) { /* Vconn power device doesn't have a data role, but UFP match SNK */ return PD_ROLE_UFP; } /* Note tc_set_power_role and tc_set_data_role are unimplemented */ uint8_t tc_get_polarity(int port) { /* Does not track polarity yet */ return 0; } uint8_t tc_get_pd_enabled(int port) { return tc[port].pd_enable; } void tc_event_check(int port, int evt) { /* Do Nothing */ } /* * Private Functions */ /* Set the TypeC state machine to a new state. */ static void set_state_tc(const int port, const enum usb_tc_state new_state) { set_state(port, &tc[port].ctx, &tc_states[new_state]); } /* Get the current TypeC state. */ test_export_static enum usb_tc_state get_state_tc(const int port) { return tc[port].ctx.current - &tc_states[0]; } test_mockable_static void print_current_state(const int port) { CPRINTS("C%d: %s", port, tc_state_names[get_state_tc(port)]); } /** * Disabled * * Super State Entries: * Enable mcu communication * Remove the terminations from Host CC */ static void tc_disabled_entry(const int port) { print_current_state(port); } static void tc_disabled_run(const int port) { task_wait_event(-1); } void pd_set_suspend(int port, int suspend) { /* * This shouldn't happen. If it does, we need to send an event to the * PD task to put the SM into the disabled state. It is not safe to * directly set_state here since this may be in another task. */ assert(false); } static void tc_disabled_exit(const int port) { if (!IS_ENABLED(CONFIG_USB_PD_TCPC)) { if (tcpm_init(port) != 0) { CPRINTS("C%d: restart failed!", port); return; } } CPRINTS("C%d: resumed!", port); } /** * Unattached.SNK * * Super State Entry: * Enable mcu communication * Place Ra on VCONN and Rd on Host CC */ static void tc_unattached_snk_entry(const int port) { print_current_state(port); } static void tc_unattached_snk_run(const int port) { int host_cc; /* Check Host CC for connection */ vpd_host_get_cc(&host_cc); /* * Transition to AttachWait.SNK when a Source connection is * detected, as indicated by the SNK.Rp state on its Host-side * port’s CC pin. */ if (cc_is_rp(host_cc)) set_state_tc(port, TC_ATTACH_WAIT_SNK); } /** * AttachedWait.SNK * * Super State Entry: * Enable mcu communication * Place Ra on VCONN and Rd on Host CC */ static void tc_attach_wait_snk_entry(const int port) { print_current_state(port); /* Forces an initial debounce in run function */ tc[port].host_cc_state = -1; } static void tc_attach_wait_snk_run(const int port) { int host_new_cc_state; int host_cc; /* Check Host CC for connection */ vpd_host_get_cc(&host_cc); if (cc_is_rp(host_cc)) host_new_cc_state = PD_CC_DFP_ATTACHED; else host_new_cc_state = PD_CC_NONE; /* Debounce the Host CC state */ if (tc[port].host_cc_state != host_new_cc_state) { tc[port].host_cc_state = host_new_cc_state; if (host_new_cc_state == PD_CC_DFP_ATTACHED) tc[port].cc_debounce = get_time().val + PD_T_CC_DEBOUNCE; else tc[port].cc_debounce = get_time().val + PD_T_PD_DEBOUNCE; return; } /* Wait for Host CC debounce */ if (get_time().val < tc[port].cc_debounce) return; /* * A VCONN-Powered USB Device shall transition to * Attached.SNK after the state of the Host-side port’s CC pin is * SNK.Rp for at least tCCDebounce and either host-side VCONN or * VBUS is detected. * * Transition to Unattached.SNK when the state of both the CC1 and * CC2 pins is SNK.Open for at least tPDDebounce. */ if (tc[port].host_cc_state == PD_CC_DFP_ATTACHED && (vpd_is_vconn_present() || vpd_is_host_vbus_present())) set_state_tc(port, TC_ATTACHED_SNK); else if (tc[port].host_cc_state == PD_CC_NONE) set_state_tc(port, TC_UNATTACHED_SNK); } /** * Attached.SNK */ static void tc_attached_snk_entry(const int port) { print_current_state(port); /* Enable PD */ tc[port].pd_enable = 1; typec_set_polarity(port, 0); } static void tc_attached_snk_run(const int port) { /* Has host vbus and vconn been removed */ if (!vpd_is_host_vbus_present() && !vpd_is_vconn_present()) { set_state_tc(port, TC_UNATTACHED_SNK); return; } if (vpd_is_vconn_present()) { if (!(tc[port].flags & TC_FLAGS_VCONN_ON)) { /* VCONN detected. Remove RA */ vpd_host_set_pull(TYPEC_CC_RD, 0); tc[port].flags |= TC_FLAGS_VCONN_ON; } } } static void tc_attached_snk_exit(const int port) { /* Disable PD */ tc[port].pd_enable = 0; tc[port].flags &= ~TC_FLAGS_VCONN_ON; } /** * Super State HOST_RARD */ static void tc_host_rard_entry(const int port) { /* Place Ra on VCONN and Rd on Host CC */ vpd_host_set_pull(TYPEC_CC_RA_RD, 0); } /** * Super State HOST_OPEN */ static void tc_host_open_entry(const int port) { /* Remove the terminations from Host CC */ vpd_host_set_pull(TYPEC_CC_OPEN, 0); } /** * Super State VBUS_CC_ISO */ static void tc_vbus_cc_iso_entry(const int port) { /* Enable mcu communication and cc */ vpd_mcu_cc_en(1); } void tc_run(const int port) { run_state(port, &tc[port].ctx); } /* * Type-C State Hierarchy (Sub-States are listed inside the boxes) * * | TC_VBUS_CC_ISO ----------------------------------------| * | | * | | TC_HOST_RARD -----------| | TC_HOST_OPEN ---------| | * | | | | | | * | | TC_UNATTACHED_SNK | | TC_DISABLED | | * | | TC_ATTACH_WAIT_SNK | |-----------------------| | * | |-------------------------| | * |--------------------------------------------------------| * * TC_ATTACHED_SNK */ static const struct usb_state tc_states[] = { /* Super States */ [TC_VBUS_CC_ISO] = { .entry = tc_vbus_cc_iso_entry, }, [TC_HOST_RARD] = { .entry = tc_host_rard_entry, .parent = &tc_states[TC_VBUS_CC_ISO], }, [TC_HOST_OPEN] = { .entry = tc_host_open_entry, .parent = &tc_states[TC_VBUS_CC_ISO], }, /* Normal States */ [TC_DISABLED] = { .entry = tc_disabled_entry, .run = tc_disabled_run, .exit = tc_disabled_exit, .parent = &tc_states[TC_HOST_OPEN], }, [TC_UNATTACHED_SNK] = { .entry = tc_unattached_snk_entry, .run = tc_unattached_snk_run, .parent = &tc_states[TC_HOST_RARD], }, [TC_ATTACH_WAIT_SNK] = { .entry = tc_attach_wait_snk_entry, .run = tc_attach_wait_snk_run, .parent = &tc_states[TC_HOST_RARD], }, [TC_ATTACHED_SNK] = { .entry = tc_attached_snk_entry, .run = tc_attached_snk_run, .exit = tc_attached_snk_exit, }, }; #ifdef TEST_BUILD const struct test_sm_data test_tc_sm_data[] = { { .base = tc_states, .size = ARRAY_SIZE(tc_states), .names = tc_state_names, .names_size = ARRAY_SIZE(tc_state_names), }, }; const int test_tc_sm_data_size = ARRAY_SIZE(test_tc_sm_data); #endif