diff options
Diffstat (limited to 'common/usbc/usb_tc_drp_acc_trysrc_sm.c')
-rw-r--r-- | common/usbc/usb_tc_drp_acc_trysrc_sm.c | 1389 |
1 files changed, 1331 insertions, 58 deletions
diff --git a/common/usbc/usb_tc_drp_acc_trysrc_sm.c b/common/usbc/usb_tc_drp_acc_trysrc_sm.c index 896ffffdd1..44cb8bf9d4 100644 --- a/common/usbc/usb_tc_drp_acc_trysrc_sm.c +++ b/common/usbc/usb_tc_drp_acc_trysrc_sm.c @@ -14,6 +14,8 @@ #include "usb_common.h" #include "usb_mux.h" #include "usb_pd.h" +#include "usb_pe_sm.h" +#include "usb_prl_sm.h" #include "usb_sm.h" #include "usb_tc_sm.h" #include "usbc_ppc.h" @@ -38,6 +40,29 @@ #define TC_FLAGS_LPM_TRANSITION BIT(3) #define TC_FLAGS_LPM_ENGAGED BIT(4) #define TC_FLAGS_LPM_REQUESTED BIT(5) +#define TC_FLAGS_CTVPD_DETECTED BIT(6) +#define TC_FLAGS_REQUEST_VC_SWAP_ON BIT(7) +#define TC_FLAGS_REQUEST_VC_SWAP_OFF BIT(8) +#define TC_FLAGS_REJECT_VCONN_SWAP BIT(9) +#define TC_FLAGS_REQUEST_PR_SWAP BIT(10) +#define TC_FLAGS_REQUEST_DR_SWAP BIT(11) +#define TC_FLAGS_POWER_OFF_SNK BIT(12) +#define TC_FLAGS_PARTNER_EXTPOWER BIT(13) +#define TC_FLAGS_PARTNER_DR_DATA BIT(14) +#define TC_FLAGS_PARTNER_DR_POWER BIT(15) +#define TC_FLAGS_PARTNER_PD_CAPABLE BIT(16) +#define TC_FLAGS_HARD_RESET BIT(17) +#define TC_FLAGS_PARTNER_USB_COMM BIT(18) +#define TC_FLAGS_PR_SWAP_IN_PROGRESS BIT(19) +#define TC_FLAGS_DO_PR_SWAP BIT(20) +#define TC_FLAGS_DISC_IDENT_IN_PROGRESS BIT(21) + +enum ps_reset_sequence { + PS_STATE0, + PS_STATE1, + PS_STATE2, + PS_STATE3 +}; /* List of all TypeC-level states */ enum usb_tc_state { @@ -54,6 +79,10 @@ enum usb_tc_state { TC_ATTACHED_SRC, TC_TRY_SRC, TC_TRY_WAIT_SNK, +#ifdef CONFIG_USB_PE_SM + TC_CT_UNATTACHED_SNK, + TC_CT_ATTACHED_SNK, +#endif /* Super States */ TC_CC_OPEN, TC_CC_RD, @@ -95,6 +124,10 @@ static struct type_c { uint8_t data_role; /* Higher-level power deliver state machines are enabled if true. */ uint8_t pd_enable; +#ifdef CONFIG_USB_PE_SM + /* Power supply reset sequence during a hard reset */ + enum ps_reset_sequence ps_reset_state; +#endif /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ uint8_t polarity; /* port flags, see TC_FLAGS_* */ @@ -148,6 +181,17 @@ static enum pd_dual_role_states drp_state[CONFIG_USB_PD_PORT_COUNT] = { [0 ... (CONFIG_USB_PD_PORT_COUNT - 1)] = CONFIG_USB_PD_INITIAL_DRP_STATE}; +#ifdef CONFIG_USBC_VCONN +static void set_vconn(int port, int enable); +#endif + +#ifdef CONFIG_USB_PE_SM + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +/* Tracker for which task is waiting on sysjump prep to finish */ +static volatile task_id_t sysjump_task_waiting = TASK_ID_INVALID; +#endif + /* * 4 entry rw_hash table of type-C devices that AP has firmware updates for. */ @@ -157,7 +201,22 @@ static struct ec_params_usb_pd_rw_hash_entry rw_hash_table[RW_HASH_ENTRIES]; #endif /* Forward declare common, private functions */ -static void tc_set_data_role(int port, int role); +#ifdef CONFIG_USB_PD_TCPC_LOW_POWER +static void exit_low_power_mode(int port); +static void handle_device_access(int port); +static int pd_device_in_low_power(int port); +static void pd_wait_for_wakeup(int port); +static int reset_device_and_notify(int port); +#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ + +#ifdef CONFIG_POWER_COMMON +static void handle_new_power_state(int port); +#endif /* CONFIG_POWER_COMMON */ + +static void pd_update_dual_role_config(int port); +#endif /* CONFIG_USB_PE_SM */ + +/* Forward declare common, private functions */ static void set_state_tc(const int port, const enum usb_tc_state new_state); test_export_static enum usb_tc_state get_state_tc(const int port); @@ -167,6 +226,8 @@ static uint8_t pd_try_src_enable; static void pd_update_try_source(void); #endif +static void sink_stop_drawing_current(int port); + /* * Public Functions * @@ -201,21 +262,299 @@ uint16_t pd_get_identity_vid(int port) return 0; } -#endif /* !defined(CONFIG_USB_PRL_SM) */ +#endif /* !CONFIG_USB_PRL_SM */ void pd_update_contract(int port) { - /* DO NOTHING */ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + /* Must be in Attached.SRC when this function is called */ + if (get_state_tc(port) == TC_ATTACHED_SRC) + pe_dpm_request(port, DPM_REQUEST_SRC_CAP_CHANGE); + } +} + +void pd_request_source_voltage(int port, int mv) +{ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + pd_set_max_voltage(mv); + + /* Must be in Attached.SNK when this function is called */ + if (get_state_tc(port) == TC_ATTACHED_SNK) + pe_dpm_request(port, DPM_REQUEST_NEW_POWER_LEVEL); + else + TC_SET_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP); + + task_wake(PD_PORT_TO_TASK_ID(port)); + } +} + +void pd_set_external_voltage_limit(int port, int mv) +{ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + pd_set_max_voltage(mv); + + /* Must be in Attached.SNK when this function is called */ + if (get_state_tc(port) == TC_ATTACHED_SNK) + pe_dpm_request(port, DPM_REQUEST_NEW_POWER_LEVEL); + + task_wake(PD_PORT_TO_TASK_ID(port)); + } } void pd_set_new_power_request(int port) { - /* DO NOTHING */ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + /* Must be in Attached.SNK when this function is called */ + if (get_state_tc(port) == TC_ATTACHED_SNK) + pe_dpm_request(port, DPM_REQUEST_NEW_POWER_LEVEL); + } } void pd_request_power_swap(int port) { - /* DO NOTHING */ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + /* + * Must be in Attached.SRC or Attached.SNK when this function + * is called + */ + if (get_state_tc(port) == TC_ATTACHED_SRC || + get_state_tc(port) == TC_ATTACHED_SNK) { + TC_SET_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS); + } + } +} + +#ifdef CONFIG_USB_PE_SM +void pd_set_dual_role(int port, enum pd_dual_role_states state) +{ + drp_state[port] = state; + + if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) + pd_update_try_source(); +} + +int pd_get_partner_data_swap_capable(int port) +{ + /* return data swap capable status of port partner */ + return TC_CHK_FLAG(port, TC_FLAGS_PARTNER_DR_DATA); +} + +int pd_comm_is_enabled(int port) +{ + return tc[port].pd_enable; +} + +void pd_send_vdm(int port, uint32_t vid, int cmd, const uint32_t *data, + int count) +{ + pe_send_vdm(port, vid, cmd, data, count); +} + +void pd_request_data_swap(int port) +{ + /* + * Must be in Attached.SRC or Attached.SNK when this function + * is called + */ + if (get_state_tc(port) == TC_ATTACHED_SRC || + get_state_tc(port) == TC_ATTACHED_SNK) { + TC_SET_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP); + task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0); + } +} + +/* + * Return true if partner port is a DTS or TS capable of entering debug + * mode (eg. is presenting Rp/Rp or Rd/Rd). + */ +int pd_ts_dts_plugged(int port) +{ + return TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER); +} + +/* Return true if partner port is known to be PD capable. */ +int pd_capable(int port) +{ + return TC_CHK_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE); +} + +/* + * Return true if partner port is capable of communication over USB data + * lines. + */ +int pd_get_partner_usb_comm_capable(int port) +{ + return TC_CHK_FLAG(port, TC_FLAGS_PARTNER_USB_COMM); +} + +enum pd_dual_role_states pd_get_dual_role(int port) +{ + return drp_state[port]; +} + +int pd_dev_store_rw_hash(int port, uint16_t dev_id, uint32_t *rw_hash, + uint32_t current_image) +{ + int i; + + tc[port].dev_id = dev_id; + memcpy(tc[port].dev_rw_hash, rw_hash, PD_RW_HASH_SIZE); +#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO + if (debug_level >= 2) + pd_dev_dump_info(dev_id, (uint8_t *)rw_hash); +#endif + tc[port].current_image = current_image; + + /* Search table for matching device / hash */ + for (i = 0; i < RW_HASH_ENTRIES; i++) + if (dev_id == rw_hash_table[i].dev_id) + return !memcmp(rw_hash, + rw_hash_table[i].dev_rw_hash, + PD_RW_HASH_SIZE); + return 0; +} + +int tc_is_attached_src(int port) +{ + return get_state_tc(port) == TC_ATTACHED_SRC; +} + +int tc_is_attached_snk(int port) +{ + return get_state_tc(port) == TC_ATTACHED_SNK; +} + +void tc_partner_dr_power(int port, int en) +{ + if (en) + TC_SET_FLAG(port, TC_FLAGS_PARTNER_DR_POWER); + else + TC_CLR_FLAG(port, TC_FLAGS_PARTNER_DR_POWER); +} + +void tc_partner_extpower(int port, int en) +{ + if (en) + TC_SET_FLAG(port, TC_FLAGS_PARTNER_EXTPOWER); + else + TC_CLR_FLAG(port, TC_FLAGS_PARTNER_EXTPOWER); +} + +void tc_partner_usb_comm(int port, int en) +{ + if (en) + TC_SET_FLAG(port, TC_FLAGS_PARTNER_USB_COMM); + else + TC_CLR_FLAG(port, TC_FLAGS_PARTNER_USB_COMM); +} + +void tc_partner_dr_data(int port, int en) +{ + if (en) + TC_SET_FLAG(port, TC_FLAGS_PARTNER_DR_DATA); + else + TC_CLR_FLAG(port, TC_FLAGS_PARTNER_DR_DATA); +} + +void tc_pd_connection(int port, int en) +{ + if (en) + TC_SET_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE); + else + TC_CLR_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE); +} + +void tc_ctvpd_detected(int port) +{ + TC_SET_FLAG(port, TC_FLAGS_CTVPD_DETECTED); +} + +void tc_vconn_on(int port) +{ + set_vconn(port, 1); +} + +int tc_check_vconn_swap(int port) +{ +#ifdef CONFIG_USBC_VCONN + if (TC_CHK_FLAG(port, TC_FLAGS_REJECT_VCONN_SWAP)) + return 0; + + return pd_check_vconn_swap(port); +#else + return 0; +#endif +} + +void tc_pr_swap_complete(int port) +{ + TC_CLR_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS); +} + +void tc_prs_src_snk_assert_rd(int port) +{ + /* Must be in Attached.SRC when this function is called */ + if (get_state_tc(port) == TC_ATTACHED_SRC) { + /* Transition to Attached.SNK to assert Rd */ + TC_SET_FLAG(port, TC_FLAGS_DO_PR_SWAP); + task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0); + } +} + +void tc_prs_snk_src_assert_rp(int port) +{ + /* Must be in Attached.SNK when this function is called */ + if (get_state_tc(port) == TC_ATTACHED_SNK) { + /* Transition to Attached.SRC to assert Rp */ + TC_SET_FLAG(port, TC_FLAGS_DO_PR_SWAP); + task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0); + } +} + +void tc_hard_reset(int port) +{ + TC_SET_FLAG(port, TC_FLAGS_HARD_RESET); + task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SM, 0); +} + +void tc_disc_ident_in_progress(int port) +{ + TC_SET_FLAG(port, TC_FLAGS_DISC_IDENT_IN_PROGRESS); +} + +void tc_disc_ident_complete(int port) +{ + TC_CLR_FLAG(port, TC_FLAGS_DISC_IDENT_IN_PROGRESS); +} +#endif /* CONFIG_USB_PE_SM */ + +void tc_snk_power_off(int port) +{ + if (get_state_tc(port) == TC_ATTACHED_SNK) { + TC_SET_FLAG(port, TC_FLAGS_POWER_OFF_SNK); + sink_stop_drawing_current(port); + } +} + +int tc_src_power_on(int port) +{ + if (get_state_tc(port) == TC_ATTACHED_SRC) + return pd_set_power_supply_ready(port); + + return 0; +} + +void tc_src_power_off(int port) +{ + if (get_state_tc(port) == TC_ATTACHED_SRC) { + /* Remove VBUS */ + pd_power_supply_reset(port); + + if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { + charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, + CHARGE_CEIL_NONE); + } + } } void pd_set_suspend(int port, int enable) @@ -234,6 +573,9 @@ int pd_is_port_enabled(int port) int pd_fetch_acc_log_entry(int port) { + if (IS_ENABLED(CONFIG_USB_PE_SM)) + pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_GET_LOG, NULL, 0); + return EC_RES_SUCCESS; } @@ -273,25 +615,30 @@ int pd_is_connected(int port) */ void pd_prepare_sysjump(void) { - /* - * We can't be in an alternate mode since PD comm is disabled, so - * no need to send the event - */ -} -#endif - -void tc_src_power_off(int port) -{ - if (get_state_tc(port) == TC_ATTACHED_SRC) { - /* Remove VBUS */ - pd_power_supply_reset(port); + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + int i; - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { - charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, - CHARGE_CEIL_NONE); + /* + * Exit modes before sysjump so we can cleanly enter again + * later + */ + for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { + /* + * We can't be in an alternate mode if PD comm is + * disabled, so no need to send the event + */ + if (!pd_comm_is_enabled(i)) + continue; + + sysjump_task_waiting = task_get_current(); + task_set_event(PD_PORT_TO_TASK_ID(i), + PD_EVENT_SYSJUMP, 0); + task_wait_event_mask(TASK_EVENT_SYSJUMP_READY, -1); + sysjump_task_waiting = TASK_ID_INVALID; } } } +#endif void tc_start_error_recovery(int port) { @@ -329,6 +676,11 @@ static void restart_tc_sm(int port, enum usb_tc_state start_state) tc[port].flags = 0; tc[port].evt_timeout = 5*MSEC; + +#ifdef CONFIG_USB_PE_SM + tc[port].pd_enable = 0; + tc[port].ps_reset_state = PS_STATE0; +#endif } void tc_state_init(int port) @@ -372,11 +724,6 @@ void tc_set_timeout(int port, uint64_t timeout) tc[port].evt_timeout = timeout; } -void tc_event_check(int port, int evt) -{ - /* NO EVENTS TO CHECK */ -} - /* * Private Functions */ @@ -404,6 +751,41 @@ static void print_current_state(const int port) CPRINTS("C%d: %s", port, tc_state_names[get_state_tc(port)]); } +#ifdef CONFIG_USB_PE_SM +#ifdef CONFIG_USB_PD_TCPC_LOW_POWER +/* This is only called from the PD tasks that owns the port. */ +static void exit_low_power_mode(int port) +{ + if (TC_CHK_FLAG(port, TC_FLAGS_LPM_ENGAGED)) + reset_device_and_notify(port); + else + TC_CLR_FLAG(port, TC_FLAGS_LPM_REQUESTED); +} +#endif +#endif + +void tc_event_check(int port, int evt) +{ +#ifdef CONFIG_USB_PE_SM + if (IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER)) { + if (evt & PD_EXIT_LOW_POWER_EVENT_MASK) + exit_low_power_mode(port); + + if (evt & PD_EVENT_DEVICE_ACCESSED) + handle_device_access(port); + } + + if (IS_ENABLED(CONFIG_POWER_COMMON)) { + if (evt & PD_EVENT_POWER_STATE_CHANGE) + handle_new_power_state(port); + } + + if (evt & PD_EVENT_UPDATE_DUAL_ROLE) + pd_update_dual_role_config(port); +#endif + +} + /* * CC values for regular sources and Debug sources (aka DTS) * @@ -417,7 +799,7 @@ static void print_current_state(const int port) * DTS USB-C @ 3 A Rp3A0 RpUSB */ -static void tc_set_data_role(int port, int role) +void tc_set_data_role(int port, int role) { tc[port].data_role = role; @@ -544,9 +926,54 @@ void pd_deferred_resume(int port) } #endif /* CONFIG_USB_PD_DEFERRED_RESUME */ +#ifdef CONFIG_USB_PE_SM + +/* This must only be called from the PD task */ +static void pd_update_dual_role_config(int port) +{ + /* + * Change to sink if port is currently a source AND (new DRP + * state is force sink OR new DRP state is either toggle off + * or debug accessory toggle only and we are in the source + * disconnected state). + */ + if (tc[port].power_role == PD_ROLE_SOURCE && + ((drp_state[port] == PD_DRP_FORCE_SINK && + !pd_ts_dts_plugged(port)) || + (drp_state[port] == PD_DRP_TOGGLE_OFF && + get_state_tc(port) == TC_UNATTACHED_SRC))) { + set_state_tc(port, TC_UNATTACHED_SNK); + } else if (tc[port].power_role == PD_ROLE_SINK && + drp_state[port] == PD_DRP_FORCE_SOURCE) { + /* + * Change to source if port is currently a sink and the + * new DRP state is force source. + */ + set_state_tc(port, TC_UNATTACHED_SRC); + } +} + +#ifdef CONFIG_POWER_COMMON +static void handle_new_power_state(int port) +{ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + if (chipset_in_or_transitioning_to_state(CHIPSET_STATE_ANY_OFF)) + /* + * The SoC will negotiated DP mode again when it + * boots up + */ + pe_exit_dp_mode(port); + } + + /* Ensure mux is set properly after chipset transition */ + set_usb_mux_with_current_data_role(port); +} +#endif /* CONFIG_POWER_COMMON */ + /* * HOST COMMANDS */ +#ifdef HAS_TASK_HOSTCMD static int hc_pd_ports(struct host_cmd_handler_args *args) { struct ec_response_usb_pd_ports *r = args->response; @@ -559,12 +986,167 @@ static int hc_pd_ports(struct host_cmd_handler_args *args) DECLARE_HOST_COMMAND(EC_CMD_USB_PD_PORTS, hc_pd_ports, EC_VER_MASK(0)); +static const enum pd_dual_role_states dual_role_map[USB_PD_CTRL_ROLE_COUNT] = { + [USB_PD_CTRL_ROLE_TOGGLE_ON] = PD_DRP_TOGGLE_ON, + [USB_PD_CTRL_ROLE_TOGGLE_OFF] = PD_DRP_TOGGLE_OFF, + [USB_PD_CTRL_ROLE_FORCE_SINK] = PD_DRP_FORCE_SINK, + [USB_PD_CTRL_ROLE_FORCE_SOURCE] = PD_DRP_FORCE_SOURCE, + [USB_PD_CTRL_ROLE_FREEZE] = PD_DRP_FREEZE, +}; + +#ifdef CONFIG_USBC_SS_MUX +static const enum typec_mux typec_mux_map[USB_PD_CTRL_MUX_COUNT] = { + [USB_PD_CTRL_MUX_NONE] = TYPEC_MUX_NONE, + [USB_PD_CTRL_MUX_USB] = TYPEC_MUX_USB, + [USB_PD_CTRL_MUX_AUTO] = TYPEC_MUX_DP, + [USB_PD_CTRL_MUX_DP] = TYPEC_MUX_DP, + [USB_PD_CTRL_MUX_DOCK] = TYPEC_MUX_DOCK, +}; +#endif + +static int hc_usb_pd_control(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_pd_control *p = args->params; + struct ec_response_usb_pd_control_v1 *r_v1 = args->response; + struct ec_response_usb_pd_control *r = args->response; + + if (p->port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + if (p->role >= USB_PD_CTRL_ROLE_COUNT || + p->mux >= USB_PD_CTRL_MUX_COUNT) + return EC_RES_INVALID_PARAM; + + if (p->role != USB_PD_CTRL_ROLE_NO_CHANGE) + pd_set_dual_role(p->port, dual_role_map[p->role]); + +#ifdef CONFIG_USBC_SS_MUX + if (p->mux != USB_PD_CTRL_MUX_NO_CHANGE) + usb_mux_set(p->port, typec_mux_map[p->mux], + typec_mux_map[p->mux] == TYPEC_MUX_NONE ? + USB_SWITCH_DISCONNECT : + USB_SWITCH_CONNECT, + pd_get_polarity(p->port)); +#endif /* CONFIG_USBC_SS_MUX */ + + if (p->swap == USB_PD_CTRL_SWAP_DATA) + pd_request_data_swap(p->port); + else if (p->swap == USB_PD_CTRL_SWAP_POWER) + pd_request_power_swap(p->port); +#ifdef CONFIG_USBC_VCONN_SWAP + else if (p->swap == USB_PD_CTRL_SWAP_VCONN) + pe_dpm_request(p->port, DPM_REQUEST_VCONN_SWAP); +#endif + + if (args->version == 0) { + r->enabled = pd_comm_is_enabled(p->port); + r->role = tc[p->port].power_role; + r->polarity = tc[p->port].polarity; + r->state = get_state_tc(p->port); + args->response_size = sizeof(*r); + } else { + r_v1->enabled = + (pd_comm_is_enabled(p->port) ? + PD_CTRL_RESP_ENABLED_COMMS : 0) | + (pd_is_connected(p->port) ? + PD_CTRL_RESP_ENABLED_CONNECTED : 0) | + (TC_CHK_FLAG(p->port, TC_FLAGS_PARTNER_PD_CAPABLE) ? + PD_CTRL_RESP_ENABLED_PD_CAPABLE : 0); + r_v1->role = + (tc[p->port].power_role ? PD_CTRL_RESP_ROLE_POWER : 0) | + (tc[p->port].data_role ? PD_CTRL_RESP_ROLE_DATA : 0) | + (TC_CHK_FLAG(p->port, TC_FLAGS_VCONN_ON) ? + PD_CTRL_RESP_ROLE_VCONN : 0) | + (TC_CHK_FLAG(p->port, TC_FLAGS_PARTNER_DR_POWER) ? + PD_CTRL_RESP_ROLE_DR_POWER : 0) | + (TC_CHK_FLAG(p->port, TC_FLAGS_PARTNER_DR_DATA) ? + PD_CTRL_RESP_ROLE_DR_DATA : 0) | + (TC_CHK_FLAG(p->port, TC_FLAGS_PARTNER_USB_COMM) ? + PD_CTRL_RESP_ROLE_USB_COMM : 0) | + (TC_CHK_FLAG(p->port, TC_FLAGS_PARTNER_EXTPOWER) ? + PD_CTRL_RESP_ROLE_EXT_POWERED : 0); + r_v1->polarity = tc[p->port].polarity; + strzcpy(r_v1->state, tc_state_names[get_state_tc(p->port)], + sizeof(r_v1->state)); + args->response_size = sizeof(*r_v1); + } + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_CONTROL, + hc_usb_pd_control, + EC_VER_MASK(0) | EC_VER_MASK(1)); + +static int hc_remote_flash(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_pd_fw_update *p = args->params; + int port = p->port; + int rv = EC_RES_SUCCESS; + const uint32_t *data = &(p->size) + 1; + int i, size; + + if (port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + if (p->size + sizeof(*p) > args->params_size) + return EC_RES_INVALID_PARAM; + +#if defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \ +defined(CONFIG_BATTERY_PRESENT_GPIO) + /* + * Do not allow PD firmware update if no battery and this port + * is sinking power, because we will lose power. + */ + if (battery_is_present() != BP_YES && + charge_manager_get_active_charge_port() == port) + return EC_RES_UNAVAILABLE; +#endif + + switch (p->cmd) { + case USB_PD_FW_REBOOT: + pe_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_REBOOT, NULL, 0); + /* + * Return immediately to free pending i2c bus. Host needs to + * manage this delay. + */ + return EC_RES_SUCCESS; + + case USB_PD_FW_FLASH_ERASE: + pe_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_ERASE, NULL, 0); + /* + * Return immediately. Host needs to manage delays here which + * can be as long as 1.2 seconds on 64KB RW flash. + */ + return EC_RES_SUCCESS; + + case USB_PD_FW_ERASE_SIG: + pe_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_ERASE_SIG, NULL, 0); + break; + + case USB_PD_FW_FLASH_WRITE: + /* Data size must be a multiple of 4 */ + if (!p->size || p->size % 4) + return EC_RES_INVALID_PARAM; + + size = p->size / 4; + for (i = 0; i < size; i += VDO_MAX_SIZE - 1) { + pe_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_WRITE, + data + i, MIN(size - i, VDO_MAX_SIZE - 1)); + } + return EC_RES_SUCCESS; + + default: + return EC_RES_INVALID_PARAM; + } + + return rv; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_FW_UPDATE, + hc_remote_flash, + EC_VER_MASK(0)); static int hc_remote_rw_hash_entry(struct host_cmd_handler_args *args) { - int i; - int idx = 0; - int found = 0; + int i, idx = 0, found = 0; const struct ec_params_usb_pd_rw_hash_entry *p = args->params; static int rw_hash_next_idx; @@ -585,7 +1167,6 @@ static int hc_remote_rw_hash_entry(struct host_cmd_handler_args *args) if (rw_hash_next_idx == RW_HASH_ENTRIES) rw_hash_next_idx = 0; } - memcpy(&rw_hash_table[idx], p, sizeof(*p)); return EC_RES_SUCCESS; @@ -617,6 +1198,143 @@ DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DEV_INFO, hc_remote_pd_dev_info, EC_VER_MASK(0)); +#ifndef CONFIG_USB_PD_TCPC +#ifdef CONFIG_EC_CMD_PD_CHIP_INFO +static int hc_remote_pd_chip_info(struct host_cmd_handler_args *args) +{ + const struct ec_params_pd_chip_info *p = args->params; + struct ec_response_pd_chip_info_v1 *info; + + if (p->port >= CONFIG_USB_PD_PORT_COUNT) + return EC_RES_INVALID_PARAM; + + if (tcpm_get_chip_info(p->port, p->live, &info)) + return EC_RES_ERROR; + + /* + * Take advantage of the fact that v0 and v1 structs have the + * same layout for v0 data. (v1 just appends data) + */ + args->response_size = + args->version ? sizeof(struct ec_response_pd_chip_info_v1) + : sizeof(struct ec_response_pd_chip_info); + + memcpy(args->response, info, args->response_size); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_PD_CHIP_INFO, + hc_remote_pd_chip_info, + EC_VER_MASK(0) | EC_VER_MASK(1)); +#endif /* CONFIG_EC_CMD_PD_CHIP_INFO */ +#endif /* !CONFIG_USB_PD_TCPC */ + +#ifdef CONFIG_HOSTCMD_EVENTS +void pd_notify_dp_alt_mode_entry(void) +{ + /* + * Note: EC_HOST_EVENT_PD_MCU may be a more appropriate host event to + * send, but we do not send that here because there are other cases + * where we send EC_HOST_EVENT_PD_MCU such as charger insertion or + * removal. Currently, those do not wake the system up, but + * EC_HOST_EVENT_MODE_CHANGE does. If we made the system wake up on + * EC_HOST_EVENT_PD_MCU, we would be turning the internal display on on + * every charger insertion/removal, which is not desired. + */ + CPRINTS("Notifying AP of DP Alt Mode Entry..."); + host_set_single_event(EC_HOST_EVENT_MODE_CHANGE); +} +#endif /* CONFIG_HOSTCMD_EVENTS */ + +#ifdef CONFIG_USB_PD_ALT_MODE_DFP +static int hc_remote_pd_set_amode(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_pd_set_mode_request *p = args->params; + + if ((p->port >= CONFIG_USB_PD_PORT_COUNT) || (!p->svid) || (!p->opos)) + return EC_RES_INVALID_PARAM; + + switch (p->cmd) { + case PD_EXIT_MODE: + if (pd_dfp_exit_mode(p->port, p->svid, p->opos)) + pd_send_vdm(p->port, p->svid, + CMD_EXIT_MODE | VDO_OPOS(p->opos), NULL, 0); + else { + CPRINTF("Failed exit mode\n"); + return EC_RES_ERROR; + } + break; + case PD_ENTER_MODE: + if (pd_dfp_enter_mode(p->port, p->svid, p->opos)) + pd_send_vdm(p->port, p->svid, CMD_ENTER_MODE | + VDO_OPOS(p->opos), NULL, 0); + break; + default: + return EC_RES_INVALID_PARAM; + } + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_PD_SET_AMODE, + hc_remote_pd_set_amode, + EC_VER_MASK(0)); +#endif /* CONFIG_USB_PD_ALT_MODE_DFP */ +#endif /* HAS_TASK_HOSTCMD */ + +#if defined(CONFIG_USB_PD_ALT_MODE) && !defined(CONFIG_USB_PD_ALT_MODE_DFP) +void pd_send_hpd(int port, enum hpd_event hpd) +{ + uint32_t data[1]; + int opos = pd_alt_mode(port, USB_SID_DISPLAYPORT); + + if (!opos) + return; + + data[0] = + VDO_DP_STATUS((hpd == hpd_irq), /* IRQ_HPD */ + (hpd != hpd_low), /* HPD_HI|LOW */ + 0, /* request exit DP */ + 0, /* request exit USB */ + 0, /* MF pref */ + 1, /* enabled */ + 0, /* power low */ + 0x2); + pd_send_vdm(port, USB_SID_DISPLAYPORT, + VDO_OPOS(opos) | CMD_ATTENTION, data, 1); +} +#endif +#endif /* CONFIG_USB_PE_SM */ + +#ifdef CONFIG_USBC_VCONN_SWAP +void pd_request_vconn_swap_off(int port) +{ + if (get_state_tc(port) == TC_ATTACHED_SRC || + get_state_tc(port) == TC_ATTACHED_SNK) { + TC_SET_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF); + task_wake(PD_PORT_TO_TASK_ID(port)); + } +} + +void pd_request_vconn_swap_on(int port) +{ + if (get_state_tc(port) == TC_ATTACHED_SRC || + get_state_tc(port) == TC_ATTACHED_SNK) { + TC_SET_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON); + task_wake(PD_PORT_TO_TASK_ID(port)); + } +} +#endif + +#ifdef CONFIG_USBC_VCONN +int tc_is_vconn_src(int port) +{ + if (get_state_tc(port) == TC_ATTACHED_SRC || + get_state_tc(port) == TC_ATTACHED_SNK) + return TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON); + else + return -1; +} +#endif + #ifdef CONFIG_USBC_PPC static void pd_send_hard_reset(int port) { @@ -956,6 +1674,11 @@ static void tc_unattached_snk_entry(const int port) */ pd_execute_data_swap(port, PD_ROLE_DISCONNECTED); tc[port].next_role_swap = get_time().val + PD_T_DRP_SNK; + + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + tc[port].flags = 0; + tc[port].pd_enable = 0; + } } static void tc_unattached_snk_run(const int port) @@ -967,6 +1690,15 @@ static void tc_unattached_snk_run(const int port) * status after role changes */ + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET)) { + TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET); + tc_set_data_role(port, PD_ROLE_UFP); + /* Inform Policy Engine that hard reset is complete */ + pe_ps_reset_complete(port); + } + } + /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); @@ -1034,6 +1766,11 @@ static void tc_attach_wait_snk_run(const int port) */ if (new_cc_state == PD_CC_NONE && get_time().val > tc[port].pd_debounce) { + if (IS_ENABLED(CONFIG_USB_PE_SM) && + IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) { + pd_dfp_exit_mode(port, 0, 0); + } + /* We are detached */ set_state_tc(port, TC_UNATTACHED_SRC); return; @@ -1044,13 +1781,14 @@ static void tc_attach_wait_snk_run(const int port) 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. + * 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. * - * A DRP that strongly prefers the Source role may optionally transition - * to Try.SRC instead of Attached.SNK when the state of only one CC pin - * has been SNK.Rp for at least tCCDebounce and VBUS is detected. + * A DRP that strongly prefers the Source role may optionally + * transition to Try.SRC instead of Attached.SNK when the state of only + * one CC pin has been SNK.Rp for at least tCCDebounce and VBUS is + * detected. * * If the port supports Debug Accessory Mode, the port shall transition * to DebugAccessory.SNK if the state of both the CC1 and CC2 pins is @@ -1069,6 +1807,12 @@ static void tc_attach_wait_snk_run(const int port) TC_SET_FLAG(port, TC_FLAGS_TS_DTS_PARTNER); set_state_tc(port, TC_DBG_ACC_SNK); } + + if (IS_ENABLED(CONFIG_USB_PE_SM) && + IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) { + hook_call_deferred(&pd_usb_billboard_deferred_data, + PD_T_AME); + } } } @@ -1081,46 +1825,192 @@ static void tc_attached_snk_entry(const int port) print_current_state(port); - /* Get connector orientation */ - tcpm_get_cc(port, &cc1, &cc2); - tc[port].polarity = get_snk_polarity(cc1, cc2); - set_polarity(port, tc[port].polarity); +#ifdef CONFIG_USB_PE_SM + if (TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { + /* + * Both CC1 and CC2 pins shall be independently terminated to + * ground through Rd. + */ + tcpm_set_cc(port, TYPEC_CC_RD); - /* - * Initial data role for sink is UFP - * This also sets the usb mux - */ - tc_set_data_role(port, PD_ROLE_UFP); + /* Change role to sink */ + tc_set_power_role(port, PD_ROLE_SINK); + tcpm_set_msg_header(port, tc[port].power_role, + tc[port].data_role); - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { - tc[port].typec_curr = usb_get_typec_current_limit( - tc[port].polarity, cc1, cc2); - typec_set_input_current_limit(port, tc[port].typec_curr, - TYPE_C_VOLTAGE); - charge_manager_update_dualrole(port, CAP_DEDICATED); - tc[port].cc_state = (tc[port].polarity) ? cc2 : cc1; + /* + * Maintain VCONN supply state, whether ON or OFF, and its + * data role / usb mux connections. + */ + } else +#endif + { + /* Get connector orientation */ + tcpm_get_cc(port, &cc1, &cc2); + tc[port].polarity = get_snk_polarity(cc1, cc2); + set_polarity(port, tc[port].polarity); + + /* + * Initial data role for sink is UFP + * This also sets the usb mux + */ + tc_set_data_role(port, PD_ROLE_UFP); + + if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { + tc[port].typec_curr = + usb_get_typec_current_limit(tc[port].polarity, + cc1, cc2); + typec_set_input_current_limit(port, + tc[port].typec_curr, TYPE_C_VOLTAGE); + charge_manager_update_dualrole(port, CAP_DEDICATED); + tc[port].cc_state = (tc[port].polarity) ? cc2 : cc1; + } } /* Apply Rd */ tcpm_set_cc(port, TYPEC_CC_RD); tc[port].cc_debounce = 0; + + /* Enable PD */ + if (IS_ENABLED(CONFIG_USB_PE_SM)) + tc[port].pd_enable = 1; } static void tc_attached_snk_run(const int port) { - /* Detach detection */ - if (!pd_is_vbus_present(port)) { - set_state_tc(port, TC_UNATTACHED_SNK); - return; +#ifdef CONFIG_USB_PE_SM + /* + * Perform Hard Reset + */ + if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET)) { + TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET); + + tc_set_data_role(port, PD_ROLE_UFP); + /* Clear the input current limit */ + sink_stop_drawing_current(port); + + /* + * When VCONN is supported, the Hard Reset Shall cause + * the Port with the Rd resistor asserted to turn off + * VCONN. + */ + if (IS_ENABLED(CONFIG_USBC_VCONN)) + if (TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON)) + set_vconn(port, 0); + + /* + * Inform policy engine that power supply + * reset is complete + */ + pe_ps_reset_complete(port); } + /* + * The sink will be powered off during a power role swap but we don't + * want to trigger a disconnect + */ + if (!TC_CHK_FLAG(port, TC_FLAGS_POWER_OFF_SNK) && + !TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { + /* Detach detection */ + if (!pd_is_vbus_present(port)) { + if (IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) + pd_dfp_exit_mode(port, 0, 0); + + return set_state_tc(port, TC_UNATTACHED_SNK); + } + + if (!pe_is_explicit_contract(port)) + sink_power_sub_states(port); + } + + /* + * PD swap commands + */ + if (tc[port].pd_enable && prl_is_running(port)) { + /* + * Power Role Swap + */ + if (TC_CHK_FLAG(port, TC_FLAGS_DO_PR_SWAP)) { + TC_CLR_FLAG(port, TC_FLAGS_DO_PR_SWAP); + return set_state_tc(port, TC_ATTACHED_SRC); + } + + /* + * Data Role Swap + */ + if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP)) { + TC_CLR_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP); + + /* Perform Data Role Swap */ + tc_set_data_role(port, !tc[port].data_role); + } + +#ifdef CONFIG_USBC_VCONN + /* + * VCONN Swap + */ + if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON)) { + TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON); + + set_vconn(port, 1); + /* Inform policy engine that vconn swap is complete */ + pe_vconn_swap_complete(port); + } else if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF)) { + TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF); + + set_vconn(port, 0); + /* Inform policy engine that vconn swap is complete */ + pe_vconn_swap_complete(port); + } +#endif + /* + * If the port supports Charge-Through VCONN-Powered USB + * devices, and an explicit PD contract has failed to be + * negotiated, the port shall query the identity of the + * cable via USB PD on SOP’ + */ + if (!pe_is_explicit_contract(port) && + TC_CHK_FLAG(port, TC_FLAGS_CTVPD_DETECTED)) { + /* + * A port that via SOP’ has detected an attached + * Charge-Through VCONN-Powered USB device shall + * transition to Unattached.SRC if an explicit PD + * contract has failed to be negotiated. + */ + /* CTVPD detected */ + set_state_tc(port, TC_UNATTACHED_SRC); + } + } + +#else /* CONFIG_USB_PE_SM */ + + /* Detach detection */ + if (!pd_is_vbus_present(port)) + return set_state_tc(port, TC_UNATTACHED_SNK); + /* Run Sink Power Sub-State */ sink_power_sub_states(port); +#endif /* CONFIG_USB_PE_SM */ } static void tc_attached_snk_exit(const int port) { + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + TC_CLR_FLAG(port, TC_FLAGS_POWER_OFF_SNK); + + if (IS_ENABLED(CONFIG_USBC_VCONN)) { + /* + * If supplying VCONN, the port shall cease to supply + * it within tVCONNOFF of exiting Attached.SNK. + */ + if (!TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS) && + TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON)) { + set_vconn(port, 0); + } + } + } + /* Stop drawing power */ sink_stop_drawing_current(port); } @@ -1184,8 +2074,14 @@ static void tc_dbg_acc_snk_entry(const int port) static void tc_dbg_acc_snk_run(const int port) { - if (!pd_is_vbus_present(port)) + if (!pd_is_vbus_present(port)) { + if (IS_ENABLED(CONFIG_USB_PE_SM) && + IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) { + pd_dfp_exit_mode(port, 0, 0); + } + set_state_tc(port, TC_UNATTACHED_SNK); + } } /** @@ -1223,6 +2119,11 @@ static void tc_unattached_src_entry(const int port) */ pd_execute_data_swap(port, PD_ROLE_DISCONNECTED); + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + tc[port].flags = 0; + tc[port].pd_enable = 0; + } + tc[port].next_role_swap = get_time().val + PD_T_DRP_SRC; } @@ -1230,6 +2131,24 @@ static void tc_unattached_src_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; + if (IS_ENABLED(CONFIG_USB_PE_SM)) { + if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET)) { + TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET); + tc_set_data_role(port, PD_ROLE_DFP); + /* Inform Policy Engine that hard reset is complete */ + pe_ps_reset_complete(port); + } + } + + if (IS_ENABLED(CONFIG_USBC_PPC)) { + /* + * If the port is latched off, just continue to + * monitor for a detach. + */ + if (ppc_is_port_latched_off(port)) + return; + } + /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); @@ -1328,6 +2247,61 @@ static void tc_attached_src_entry(const int port) print_current_state(port); +#if defined(CONFIG_USB_PE_SM) + if (TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { + /* Change role to source */ + tc_set_power_role(port, PD_ROLE_SOURCE); + tcpm_set_msg_header(port, + tc[port].power_role, tc[port].data_role); + + /* + * Both CC1 and CC2 pins shall be independently terminated to + * ground through Rp. + */ + tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); + + /* Enable VBUS */ + pd_set_power_supply_ready(port); + + /* + * Maintain VCONN supply state, whether ON or OFF, and its + * data role / usb mux connections. + */ + } else { + /* + * Start sourcing Vconn before Vbus to ensure + * we are within USB Type-C Spec 1.4 tVconnON + */ + if (IS_ENABLED(CONFIG_USBC_VCONN)) + set_vconn(port, 1); + + /* Enable VBUS */ + if (pd_set_power_supply_ready(port)) { + /* Stop sourcing Vconn if Vbus failed */ + if (IS_ENABLED(CONFIG_USBC_VCONN)) + set_vconn(port, 0); + + if (IS_ENABLED(CONFIG_USBC_SS_MUX)) + usb_mux_set(port, TYPEC_MUX_NONE, + USB_SWITCH_DISCONNECT, tc[port].polarity); + } + + /* Get connector orientation */ + tcpm_get_cc(port, &cc1, &cc2); + tc[port].polarity = (cc1 != TYPEC_CC_VOLT_RD); + set_polarity(port, tc[port].polarity); + + /* + * Initial data role for sink is DFP + * This also sets the usb mux + */ + tc_set_data_role(port, PD_ROLE_DFP); + + tc[port].pd_enable = 0; + tc[port].timeout = get_time().val + + PD_POWER_SUPPLY_TURN_ON_DELAY + PD_T_VCONN_STABLE; + } +#else /* Get connector orientation */ tcpm_get_cc(port, &cc1, &cc2); tc[port].polarity = (cc1 != TYPEC_CC_VOLT_RD); @@ -1356,6 +2330,7 @@ static void tc_attached_src_entry(const int port) usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT, tc[port].polarity); } +#endif /* CONFIG_USB_PE_SM */ /* Apply Rp */ tcpm_set_cc(port, TYPEC_CC_RP); @@ -1373,6 +2348,72 @@ static void tc_attached_src_run(const int port) enum tcpc_cc_voltage_status cc1, cc2; enum pd_cc_states new_cc_state; +#ifdef CONFIG_USB_PE_SM + /* Enable PD communications after power supply has fully turned on */ + if (tc[port].pd_enable == 0 && + get_time().val > tc[port].timeout) { + + tc[port].pd_enable = 1; + tc[port].timeout = 0; + } + + if (tc[port].pd_enable == 0) + return; + + /* + * Handle Hard Reset from Policy Engine + */ + if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET)) { + if (get_time().val < tc[port].timeout) + return; + + switch (tc[port].ps_reset_state) { + case PS_STATE0: + /* Remove VBUS */ + tc_src_power_off(port); + + /* Set role to DFP */ + tc_set_data_role(port, PD_ROLE_DFP); + + /* Turn off VCONN */ + if (IS_ENABLED(CONFIG_USBC_VCONN)) + set_vconn(port, 0); + + /* Remove Rp */ + tcpm_set_cc(port, TYPEC_CC_OPEN); + + tc[port].ps_reset_state = PS_STATE1; + tc[port].timeout = get_time().val + PD_T_SRC_RECOVER; + return; + case PS_STATE1: + /* Enable VBUS */ + pd_set_power_supply_ready(port); + + /* Apply Rp */ + tcpm_set_cc(port, TYPEC_CC_RP); + + tc[port].ps_reset_state = PS_STATE2; + tc[port].timeout = get_time().val + + PD_POWER_SUPPLY_TURN_ON_DELAY; + return; + case PS_STATE2: + /* Turn on VCONN */ + if (IS_ENABLED(CONFIG_USBC_VCONN)) + set_vconn(port, 1); + + tc[port].ps_reset_state = PS_STATE3; + return; + case PS_STATE3: + /* Tell Policy Engine Hard Reset is complete */ + pe_ps_reset_complete(port); + + TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET); + tc[port].ps_reset_state = PS_STATE0; + return; + } + } +#endif + /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); @@ -1403,10 +2444,86 @@ static void tc_attached_src_run(const int port) * AttachWait.SNK shall enter TryWait.SNK for a Sink detach from * Attached.SRC. */ - if (tc[port].cc_state == PD_CC_NO_UFP) { + if (tc[port].cc_state == PD_CC_NO_UFP && + !TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS) && + !TC_CHK_FLAG(port, TC_FLAGS_DISC_IDENT_IN_PROGRESS)) { + + if (IS_ENABLED(CONFIG_USB_PE_SM)) + if (IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) + pd_dfp_exit_mode(port, 0, 0); + + tc[port].pd_enable = 0; set_state_tc(port, IS_ENABLED(CONFIG_USB_PD_TRY_SRC) ? TC_TRY_WAIT_SNK : TC_UNATTACHED_SNK); } + +#ifdef CONFIG_USB_PE_SM + /* + * PD swap commands + */ + if (tc[port].pd_enable && prl_is_running(port)) { + /* + * Power Role Swap Request + */ + if (TC_CHK_FLAG(port, TC_FLAGS_DO_PR_SWAP)) { + TC_CLR_FLAG(port, TC_FLAGS_DO_PR_SWAP); + return set_state_tc(port, TC_ATTACHED_SNK); + } + + /* + * Data Role Swap Request + */ + if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP)) { + TC_CLR_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP); + + /* Perform Data Role Swap */ + tc_set_data_role(port, !tc[port].data_role); + } + + if (IS_ENABLED(CONFIG_USBC_VCONN)) { + /* + * VCONN Swap Request + */ + if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON)) { + TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON); + set_vconn(port, 1); + pe_vconn_swap_complete(port); + } else if (TC_CHK_FLAG(port, + TC_FLAGS_REQUEST_VC_SWAP_OFF)) { + TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF); + set_vconn(port, 0); + pe_vconn_swap_complete(port); + } + } + + /* + * A DRP that supports Charge-Through VCONN-Powered USB Devices + * shall transition to CTUnattached.SNK if the connected device + * identifies itself as a Charge-Through VCONN-Powered USB + * Device in its Discover Identity Command response. + */ + + /* + * A DRP that supports Charge-Through VCONN-Powered USB Devices + * shall transition to CTUnattached.SNK if the connected device + * identifies itself as a Charge-Through VCONN-Powered USB + * Device in its Discover Identity Command response. + * + * If it detects that it is connected to a VCONN-Powered USB + * Device, the port may remove VBUS and discharge it to + * vSafe0V, while continuing to remain in this state with VCONN + * applied. + */ + if (TC_CHK_FLAG(port, TC_FLAGS_CTVPD_DETECTED)) { + TC_CLR_FLAG(port, TC_FLAGS_CTVPD_DETECTED); + + /* Clear TC_FLAGS_DISC_IDENT_IN_PROGRESS */ + TC_CLR_FLAG(port, TC_FLAGS_DISC_IDENT_IN_PROGRESS); + + set_state_tc(port, TC_CT_UNATTACHED_SNK); + } + } +#endif } static void tc_attached_src_exit(const int port) @@ -1537,6 +2654,150 @@ static void tc_try_wait_snk_run(const int port) #endif +#if defined(CONFIG_USB_PE_SM) +/* + * CTUnattached.SNK + */ +static void tc_ct_unattached_snk_entry(int port) +{ + print_current_state(port); + + /* + * Both CC1 and CC2 pins shall be independently terminated to + * ground through Rd. + */ + tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); + tcpm_set_cc(port, TYPEC_CC_RD); + tc[port].cc_state = PD_CC_UNSET; + + /* Set power role to sink */ + tc_set_power_role(port, PD_ROLE_SINK); + tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); + + /* + * The policy engine is in the disabled state. Disable PD and + * re-enable it + */ + tc[port].pd_enable = 0; + + tc[port].timeout = get_time().val + PD_POWER_SUPPLY_TURN_ON_DELAY; +} + +static void tc_ct_unattached_snk_run(int port) +{ + enum tcpc_cc_voltage_status cc1; + enum tcpc_cc_voltage_status cc2; + enum pd_cc_states new_cc_state; + + if (tc[port].timeout > 0 && get_time().val > tc[port].timeout) { + tc[port].pd_enable = 1; + tc[port].timeout = 0; + } + + if (tc[port].timeout > 0) + return; + + /* Wait until Protocol Layer is ready */ + if (!prl_is_running(port)) + return; + + /* + * Hard Reset is sent when the PE layer is disabled due to a + * CTVPD connection. + */ + if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET)) { + TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET); + /* Nothing to do. Just signal hard reset completion */ + pe_ps_reset_complete(port); + } + + /* Check for connection */ + tcpm_get_cc(port, &cc1, &cc2); + + /* We only care about CCs being open */ + if (cc1 == TYPEC_CC_VOLT_OPEN && cc2 == TYPEC_CC_VOLT_OPEN) + new_cc_state = PD_CC_NONE; + else + new_cc_state = PD_CC_UNSET; + + /* Debounce the cc state */ + if (new_cc_state != tc[port].cc_state) { + tc[port].cc_state = new_cc_state; + tc[port].cc_debounce = get_time().val + PD_T_VPDDETACH; + } + + /* + * The port shall transition to Unattached.SNK if the state of + * the CC pin is SNK.Open for tVPDDetach after VBUS is vSafe0V. + */ + if (get_time().val > tc[port].cc_debounce) { + if (new_cc_state == PD_CC_NONE && !pd_is_vbus_present(port)) { + if (IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) + pd_dfp_exit_mode(port, 0, 0); + + set_state_tc(port, TC_UNATTACHED_SNK); + return; + } + } + + /* + * The port shall transition to CTAttached.SNK when VBUS is detected. + */ + if (pd_is_vbus_present(port)) + set_state_tc(port, TC_CT_ATTACHED_SNK); +} + +/** + * CTAttached.SNK + */ +static void tc_ct_attached_snk_entry(int port) +{ + print_current_state(port); + + /* The port shall reject a VCONN swap request. */ + TC_SET_FLAG(port, TC_FLAGS_REJECT_VCONN_SWAP); +} + +static void tc_ct_attached_snk_run(int port) +{ + /* + * Hard Reset is sent when the PE layer is disabled due to a + * CTVPD connection. + */ + if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET)) { + TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET); + /* Nothing to do. Just signal hard reset completion */ + pe_ps_reset_complete(port); + } + + /* + * A port that is not in the process of a USB PD Hard Reset shall + * transition to CTUnattached.SNK within tSinkDisconnect when VBUS + * falls below vSinkDisconnect + */ + if (!pd_is_vbus_present(port)) { + set_state_tc(port, TC_CT_UNATTACHED_SNK); + return; + } + + /* + * The port shall operate in one of the Sink Power Sub-States + * and remain within the Sink Power Sub-States, until either VBUS is + * removed or a USB PD contract is established with the source. + */ + if (!pe_is_explicit_contract(port)) + sink_power_sub_states(port); +} + +static void tc_ct_attached_snk_exit(int port) +{ + /* Stop drawing power */ + sink_stop_drawing_current(port); + + TC_CLR_FLAG(port, TC_FLAGS_REJECT_VCONN_SWAP); +} +#endif /* CONFIG_USB_PE_SM */ + /** * Super State CC_RD */ @@ -1557,6 +2818,7 @@ static void tc_cc_rd_entry(const int port) tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); } + /** * Super State CC_RP */ @@ -1705,6 +2967,17 @@ static const struct usb_state tc_states[] = { .parent = &tc_states[TC_CC_RD], }, #endif /* CONFIG_USB_PD_TRY_SRC */ +#ifdef CONFIG_USB_PE_SM + [TC_CT_UNATTACHED_SNK] = { + .entry = tc_ct_unattached_snk_entry, + .run = tc_ct_unattached_snk_run, + }, + [TC_CT_ATTACHED_SNK] = { + .entry = tc_ct_attached_snk_entry, + .run = tc_ct_attached_snk_run, + .exit = tc_ct_attached_snk_exit, + }, +#endif }; #ifdef TEST_BUILD |