diff options
Diffstat (limited to 'drivers/staging/typec/tcpm.c')
-rw-r--r-- | drivers/staging/typec/tcpm.c | 471 |
1 files changed, 294 insertions, 177 deletions
diff --git a/drivers/staging/typec/tcpm.c b/drivers/staging/typec/tcpm.c index 20eb4ebcf8c3..8af62e74d54c 100644 --- a/drivers/staging/typec/tcpm.c +++ b/drivers/staging/typec/tcpm.c @@ -17,6 +17,7 @@ #include <linux/completion.h> #include <linux/debugfs.h> #include <linux/device.h> +#include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> @@ -89,9 +90,11 @@ S(PR_SWAP_START), \ S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ S(PR_SWAP_SRC_SNK_SINK_ON), \ S(PR_SWAP_SNK_SRC_SINK_OFF), \ S(PR_SWAP_SNK_SRC_SOURCE_ON), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ \ S(VCONN_SWAP_ACCEPT), \ S(VCONN_SWAP_SEND), \ @@ -104,10 +107,14 @@ \ S(SNK_TRY), \ S(SNK_TRY_WAIT), \ + S(SNK_TRY_WAIT_DEBOUNCE), \ + S(SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ S(SRC_TRYWAIT), \ + S(SRC_TRYWAIT_DEBOUNCE), \ S(SRC_TRYWAIT_UNATTACHED), \ \ S(SRC_TRY), \ + S(SRC_TRY_WAIT), \ S(SRC_TRY_DEBOUNCE), \ S(SNK_TRYWAIT), \ S(SNK_TRYWAIT_DEBOUNCE), \ @@ -115,7 +122,8 @@ S(BIST_RX), \ \ S(ERROR_RECOVERY), \ - S(ERROR_RECOVERY_WAIT_OFF) + S(PORT_RESET), \ + S(PORT_RESET_WAIT_OFF) #define GENERATE_ENUM(e) e #define GENERATE_STRING(s) #s @@ -196,6 +204,7 @@ struct tcpm_port { bool attached; bool connected; + enum typec_port_type port_type; bool vbus_present; bool vbus_never_low; bool vbus_source; @@ -230,6 +239,7 @@ struct tcpm_port { struct mutex swap_lock; /* swap command lock */ bool swap_pending; + bool non_pd_role_swap; struct completion swap_complete; int swap_status; @@ -281,6 +291,9 @@ struct tcpm_port { struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; + /* Deadline in jiffies to exit src_try_wait state */ + unsigned long max_wait; + #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -325,19 +338,26 @@ struct pd_rx_event { (tcpm_cc_is_audio((port)->cc2) && tcpm_cc_is_open((port)->cc1))) #define tcpm_try_snk(port) \ - ((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK) + ((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK && \ + (port)->port_type == TYPEC_PORT_DRP) #define tcpm_try_src(port) \ - ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE) + ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \ + (port)->port_type == TYPEC_PORT_DRP) static enum tcpm_state tcpm_default_state(struct tcpm_port *port) { - if (port->try_role == TYPEC_SINK) - return SNK_UNATTACHED; - else if (port->try_role == TYPEC_SOURCE) - return SRC_UNATTACHED; - else if (port->tcpc->config->default_role == TYPEC_SINK) + if (port->port_type == TYPEC_PORT_DRP) { + if (port->try_role == TYPEC_SINK) + return SNK_UNATTACHED; + else if (port->try_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else if (port->tcpc->config->default_role == TYPEC_SINK) + return SNK_UNATTACHED; + /* Fall through to return SRC_UNATTACHED */ + } else if (port->port_type == TYPEC_PORT_UFP) { return SNK_UNATTACHED; + } return SRC_UNATTACHED; } @@ -369,6 +389,7 @@ static bool tcpm_log_full(struct tcpm_port *port) (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; } +__printf(2, 0) static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) { char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; @@ -415,6 +436,7 @@ abort: mutex_unlock(&port->logbuffer_lock); } +__printf(2, 3) static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) { va_list args; @@ -430,6 +452,7 @@ static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) va_end(args); } +__printf(2, 3) static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { va_list args; @@ -546,7 +569,9 @@ static void tcpm_debugfs_exit(struct tcpm_port *port) #else +__printf(2, 3) static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { } +__printf(2, 3) static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { } static void tcpm_log_source_caps(struct tcpm_port *port) { } static int tcpm_debugfs_init(const struct tcpm_port *port) { return 0; } @@ -660,7 +685,10 @@ static u32 tcpm_get_current_limit(struct tcpm_port *port) break; case TYPEC_CC_RP_DEF: default: - limit = 0; + if (port->tcpc->get_current_limit) + limit = port->tcpc->get_current_limit(port->tcpc); + else + limit = 0; break; } @@ -1015,8 +1043,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, if (port->data_role == TYPEC_DEVICE && port->nr_snk_vdo) { for (i = 0; i < port->nr_snk_vdo; i++) - response[i + 1] - = cpu_to_le32(port->snk_vdo[i]); + response[i + 1] = port->snk_vdo[i]; rlen = port->nr_snk_vdo + 1; } break; @@ -1367,6 +1394,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_current_limit(port, port->current_limit, port->supply_voltage); + port->explicit_contract = true; tcpm_set_state(port, SNK_READY, 0); } else { /* @@ -1377,7 +1405,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, SNK_TRANSITION_SINK_VBUS, 0); } break; - case PR_SWAP_SRC_SNK_SOURCE_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0); break; case PR_SWAP_SNK_SRC_SINK_OFF: @@ -1451,7 +1479,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_state(port, SOFT_RESET, 0); break; case PD_CTRL_DR_SWAP: - if (port->typec_caps.type != TYPEC_PORT_DRP) { + if (port->port_type != TYPEC_PORT_DRP) { tcpm_queue_message(port, PD_MSG_CTRL_REJECT); break; } @@ -1471,7 +1499,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, } break; case PD_CTRL_PR_SWAP: - if (port->typec_caps.type != TYPEC_PORT_DRP) { + if (port->port_type != TYPEC_PORT_DRP) { tcpm_queue_message(port, PD_MSG_CTRL_REJECT); break; } @@ -1846,7 +1874,7 @@ static bool tcpm_start_drp_toggling(struct tcpm_port *port) int ret; if (port->tcpc->start_drp_toggling && - port->typec_caps.type == TYPEC_PORT_DRP) { + port->port_type == TYPEC_PORT_DRP) { tcpm_log_force(port, "Start DRP toggling"); ret = port->tcpc->start_drp_toggling(port->tcpc, tcpm_rp_cc(port)); @@ -2099,10 +2127,16 @@ static inline enum tcpm_state ready_state(struct tcpm_port *port) static inline enum tcpm_state unattached_state(struct tcpm_port *port) { - if (port->pwr_role == TYPEC_SOURCE) + if (port->port_type == TYPEC_PORT_DRP) { + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else + return SNK_UNATTACHED; + } else if (port->port_type == TYPEC_PORT_DFP) { return SRC_UNATTACHED; - else - return SNK_UNATTACHED; + } + + return SNK_UNATTACHED; } static void tcpm_check_send_discover(struct tcpm_port *port) @@ -2119,13 +2153,29 @@ static void tcpm_swap_complete(struct tcpm_port *port, int result) if (port->swap_pending) { port->swap_status = result; port->swap_pending = false; + port->non_pd_role_swap = false; complete(&port->swap_complete); } } +static enum typec_pwr_opmode tcpm_get_pwr_opmode(enum typec_cc_status cc) +{ + switch (cc) { + case TYPEC_CC_RP_1_5: + return TYPEC_PWR_MODE_1_5A; + case TYPEC_CC_RP_3_0: + return TYPEC_PWR_MODE_3_0A; + case TYPEC_CC_RP_DEF: + default: + return TYPEC_PWR_MODE_USB; + } +} + static void run_state_machine(struct tcpm_port *port) { int ret; + enum typec_pwr_opmode opmode; + unsigned int msecs; port->enter_state = port->state; switch (port->state) { @@ -2133,14 +2183,15 @@ static void run_state_machine(struct tcpm_port *port) break; /* SRC states */ case SRC_UNATTACHED: - tcpm_swap_complete(port, -ENOTCONN); + if (!port->non_pd_role_swap) + tcpm_swap_complete(port, -ENOTCONN); tcpm_src_detach(port); if (tcpm_start_drp_toggling(port)) { tcpm_set_state(port, DRP_TOGGLING, 0); break; } tcpm_set_cc(port, tcpm_rp_cc(port)); - if (port->typec_caps.type == TYPEC_PORT_DRP) + if (port->port_type == TYPEC_PORT_DRP) tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK); break; case SRC_ATTACH_WAIT: @@ -2171,25 +2222,43 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY); break; case SNK_TRY_WAIT: + if (tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE, 0); + } else { + tcpm_set_state(port, SRC_TRYWAIT, 0); + port->max_wait = 0; + } + break; + case SNK_TRY_WAIT_DEBOUNCE: + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, + PD_T_PD_DEBOUNCE); + break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: if (port->vbus_present && tcpm_port_is_sink(port)) { tcpm_set_state(port, SNK_ATTACHED, 0); - break; - } - if (!tcpm_port_is_sink(port)) { - tcpm_set_state(port, SRC_TRYWAIT, - PD_T_PD_DEBOUNCE); - break; + } else { + tcpm_set_state(port, SRC_TRYWAIT, 0); + port->max_wait = 0; } - /* No vbus, cc state is sink or open */ - tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, PD_T_DRP_TRYWAIT); break; case SRC_TRYWAIT: tcpm_set_cc(port, tcpm_rp_cc(port)); - if (!port->vbus_present && tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE); - else + if (port->max_wait == 0) { + port->max_wait = jiffies + + msecs_to_jiffies(PD_T_DRP_TRY); tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, PD_T_DRP_TRY); + } else { + if (time_is_after_jiffies(port->max_wait)) + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, + jiffies_to_msecs(port->max_wait - + jiffies)); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); + } + break; + case SRC_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE); break; case SRC_TRYWAIT_UNATTACHED: tcpm_set_state(port, SNK_UNATTACHED, 0); @@ -2201,7 +2270,8 @@ static void run_state_machine(struct tcpm_port *port) ret < 0 ? 0 : PD_T_PS_SOURCE_ON); break; case SRC_STARTUP: - typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB); + opmode = tcpm_get_pwr_opmode(tcpm_rp_cc(port)); + typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; port->caps_count = 0; port->message_id = 0; @@ -2262,8 +2332,8 @@ static void run_state_machine(struct tcpm_port *port) #endif port->try_src_count = 0; + tcpm_swap_complete(port, 0); tcpm_typec_connect(port); - tcpm_check_send_discover(port); /* * 6.3.5 @@ -2273,14 +2343,11 @@ static void run_state_machine(struct tcpm_port *port) * - The system is not operating in PD mode * or * - Both partners are connected using a Type-C connector - * XXX How do we know that ? + * + * There is no actual need to send PD messages since the local + * port type-c and the spec does not clearly say whether PD is + * possible when type-c is connected to Type-A/B */ - if (port->pwr_opmode == TYPEC_PWR_MODE_PD && - !port->op_vsafe5v) { - tcpm_pd_send_control(port, PD_CTRL_PING); - tcpm_set_state_cond(port, SRC_READY, - PD_T_SOURCE_ACTIVITY); - } break; case SRC_WAIT_NEW_CAPABILITIES: /* Nothing to do... */ @@ -2288,14 +2355,15 @@ static void run_state_machine(struct tcpm_port *port) /* SNK states */ case SNK_UNATTACHED: - tcpm_swap_complete(port, -ENOTCONN); + if (!port->non_pd_role_swap) + tcpm_swap_complete(port, -ENOTCONN); tcpm_snk_detach(port); if (tcpm_start_drp_toggling(port)) { tcpm_set_state(port, DRP_TOGGLING, 0); break; } tcpm_set_cc(port, TYPEC_CC_RD); - if (port->typec_caps.type == TYPEC_PORT_DRP) + if (port->port_type == TYPEC_PORT_DRP) tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); break; case SNK_ATTACH_WAIT: @@ -2320,39 +2388,52 @@ static void run_state_machine(struct tcpm_port *port) 0); else /* Wait for VBUS, but not forever */ - tcpm_set_state(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); + tcpm_set_state(port, PORT_RESET, PD_T_PS_SOURCE_ON); break; case SRC_TRY: port->try_src_count++; tcpm_set_cc(port, tcpm_rp_cc(port)); - tcpm_set_state(port, SNK_TRYWAIT, PD_T_DRP_TRY); + port->max_wait = 0; + tcpm_set_state(port, SRC_TRY_WAIT, 0); + break; + case SRC_TRY_WAIT: + if (port->max_wait == 0) { + port->max_wait = jiffies + + msecs_to_jiffies(PD_T_DRP_TRY); + msecs = PD_T_DRP_TRY; + } else { + if (time_is_after_jiffies(port->max_wait)) + msecs = jiffies_to_msecs(port->max_wait - + jiffies); + else + msecs = 0; + } + tcpm_set_state(port, SNK_TRYWAIT, msecs); break; case SRC_TRY_DEBOUNCE: tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE); break; case SNK_TRYWAIT: tcpm_set_cc(port, TYPEC_CC_RD); - tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, PD_T_CC_DEBOUNCE); + tcpm_set_state(port, SNK_TRYWAIT_VBUS, PD_T_CC_DEBOUNCE); break; - case SNK_TRYWAIT_DEBOUNCE: - if (port->vbus_present) { + case SNK_TRYWAIT_VBUS: + /* + * TCPM stays in this state indefinitely until VBUS + * is detected as long as Rp is not detected for + * more than a time period of tPDDebounce. + */ + if (port->vbus_present && tcpm_port_is_sink(port)) { tcpm_set_state(port, SNK_ATTACHED, 0); break; } - if (tcpm_port_is_disconnected(port)) { - tcpm_set_state(port, SNK_UNATTACHED, - PD_T_PD_DEBOUNCE); - break; - } - if (tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_ATTACHED, 0); - /* XXX Are we supposed to stay in this state ? */ + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); break; - case SNK_TRYWAIT_VBUS: - tcpm_set_state(port, SNK_ATTACHED, PD_T_CC_DEBOUNCE); + case SNK_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SNK_UNATTACHED, PD_T_PD_DEBOUNCE); break; - case SNK_ATTACHED: ret = tcpm_snk_attach(port); if (ret < 0) @@ -2362,7 +2443,9 @@ static void run_state_machine(struct tcpm_port *port) break; case SNK_STARTUP: /* XXX: callback into infrastructure */ - typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB); + opmode = tcpm_get_pwr_opmode(port->polarity ? + port->cc2 : port->cc1); + typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; port->message_id = 0; port->rx_msgid = -1; @@ -2384,7 +2467,7 @@ static void run_state_machine(struct tcpm_port *port) * see USB power delivery specification, section 8.3.3.6.1.5.1). */ tcpm_set_state(port, hard_reset_state(port), - port->typec_caps.type == TYPEC_PORT_DRP ? + port->port_type == TYPEC_PORT_DRP ? PD_T_DB_DETECT : PD_T_NO_RESPONSE); break; case SNK_DISCOVERY_DEBOUNCE: @@ -2441,12 +2524,14 @@ static void run_state_machine(struct tcpm_port *port) break; case SNK_READY: port->try_snk_count = 0; - port->explicit_contract = true; - typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); - port->pwr_opmode = TYPEC_PWR_MODE_PD; + if (port->explicit_contract) { + typec_set_pwr_opmode(port->typec_port, + TYPEC_PWR_MODE_PD); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + } + tcpm_swap_complete(port, 0); tcpm_typec_connect(port); - tcpm_check_send_discover(port); break; @@ -2574,7 +2659,6 @@ static void run_state_machine(struct tcpm_port *port) TYPEC_HOST); port->send_discover = true; } - tcpm_swap_complete(port, 0); tcpm_set_state(port, ready_state(port), 0); break; @@ -2602,11 +2686,17 @@ static void run_state_machine(struct tcpm_port *port) case PR_SWAP_SRC_SNK_TRANSITION_OFF: tcpm_set_vbus(port, false); port->explicit_contract = false; + /* allow time for Vbus discharge, must be < tSrcSwapStdby */ tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, - PD_T_PS_SOURCE_OFF); + PD_T_SRCSWAPSTDBY); break; case PR_SWAP_SRC_SNK_SOURCE_OFF: tcpm_set_cc(port, TYPEC_CC_RD); + /* allow CC debounce */ + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED, + PD_T_CC_DEBOUNCE); + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: /* * USB-PD standard, 6.2.1.4, Port Power Role: * "During the Power Role Swap Sequence, for the initial Source @@ -2622,7 +2712,6 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); break; case PR_SWAP_SRC_SNK_SINK_ON: - tcpm_swap_complete(port, 0); tcpm_set_state(port, SNK_STARTUP, 0); break; case PR_SWAP_SNK_SRC_SINK_OFF: @@ -2634,6 +2723,15 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_cc(port, tcpm_rp_cc(port)); tcpm_set_vbus(port, true); /* + * allow time VBUS ramp-up, must be < tNewSrc + * Also, this window overlaps with CC debounce as well. + * So, Wait for the max of two which is PD_T_NEWSRC + */ + tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP, + PD_T_NEWSRC); + break; + case PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP: + /* * USB PD standard, 6.2.1.4: * "Subsequent Messages initiated by the Policy Engine, * such as the PS_RDY Message sent to indicate that Vbus @@ -2642,7 +2740,6 @@ static void run_state_machine(struct tcpm_port *port) */ tcpm_set_pwr_role(port, TYPEC_SOURCE); tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_swap_complete(port, 0); tcpm_set_state(port, SRC_STARTUP, 0); break; @@ -2672,12 +2769,10 @@ static void run_state_machine(struct tcpm_port *port) case VCONN_SWAP_TURN_ON_VCONN: tcpm_set_vconn(port, true); tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_swap_complete(port, 0); tcpm_set_state(port, ready_state(port), 0); break; case VCONN_SWAP_TURN_OFF_VCONN: tcpm_set_vconn(port, false); - tcpm_swap_complete(port, 0); tcpm_set_state(port, ready_state(port), 0); break; @@ -2704,13 +2799,15 @@ static void run_state_machine(struct tcpm_port *port) break; case ERROR_RECOVERY: tcpm_swap_complete(port, -EPROTO); + tcpm_set_state(port, PORT_RESET, 0); + break; + case PORT_RESET: tcpm_reset_port(port); - tcpm_set_cc(port, TYPEC_CC_OPEN); - tcpm_set_state(port, ERROR_RECOVERY_WAIT_OFF, + tcpm_set_state(port, PORT_RESET_WAIT_OFF, PD_T_ERROR_RECOVERY); break; - case ERROR_RECOVERY_WAIT_OFF: + case PORT_RESET_WAIT_OFF: tcpm_set_state(port, tcpm_default_state(port), port->vbus_present ? PD_T_PS_SOURCE_OFF : 0); @@ -2799,10 +2896,12 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, tcpm_set_state(port, SRC_ATTACH_WAIT, 0); break; case SRC_ATTACHED: - if (tcpm_port_is_disconnected(port)) + case SRC_SEND_CAPABILITIES: + case SRC_READY: + if (tcpm_port_is_disconnected(port) || + !tcpm_port_is_source(port)) tcpm_set_state(port, SRC_UNATTACHED, 0); break; - case SNK_UNATTACHED: if (tcpm_port_is_sink(port)) tcpm_set_state(port, SNK_ATTACH_WAIT, 0); @@ -2869,52 +2968,43 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, case SRC_TRYWAIT: /* Hand over to state machine if needed */ if (!port->vbus_present && tcpm_port_is_source(port)) - new_state = SRC_ATTACHED; - else - new_state = SRC_TRYWAIT_UNATTACHED; - - if (new_state != port->delayed_state) + tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); + break; + case SRC_TRYWAIT_DEBOUNCE: + if (port->vbus_present || !tcpm_port_is_source(port)) tcpm_set_state(port, SRC_TRYWAIT, 0); break; - case SNK_TRY_WAIT: - if (port->vbus_present && tcpm_port_is_sink(port)) { - tcpm_set_state(port, SNK_ATTACHED, 0); - break; + case SNK_TRY_WAIT_DEBOUNCE: + if (!tcpm_port_is_sink(port)) { + port->max_wait = 0; + tcpm_set_state(port, SRC_TRYWAIT, 0); } - if (!tcpm_port_is_sink(port)) - new_state = SRC_TRYWAIT; - else - new_state = SRC_TRYWAIT_UNATTACHED; - - if (new_state != port->delayed_state) - tcpm_set_state(port, SNK_TRY_WAIT, 0); break; - - case SRC_TRY: - tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0); + case SRC_TRY_WAIT: + if (tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0); break; case SRC_TRY_DEBOUNCE: - tcpm_set_state(port, SRC_TRY, 0); + tcpm_set_state(port, SRC_TRY_WAIT, 0); break; case SNK_TRYWAIT_DEBOUNCE: - if (port->vbus_present) { - tcpm_set_state(port, SNK_ATTACHED, 0); - break; - } - if (tcpm_port_is_source(port)) { - tcpm_set_state(port, SRC_ATTACHED, 0); - break; - } - if (tcpm_port_is_disconnected(port) && - port->delayed_state != SNK_UNATTACHED) + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0); + break; + case SNK_TRYWAIT_VBUS: + if (!tcpm_port_is_sink(port)) tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); break; - + case SNK_TRYWAIT: + /* Do nothing, waiting for tCCDebounce */ + break; case PR_SWAP_SNK_SRC_SINK_OFF: case PR_SWAP_SRC_SNK_TRANSITION_OFF: case PR_SWAP_SRC_SNK_SOURCE_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + case PR_SWAP_SNK_SRC_SOURCE_ON: /* - * CC state change is expected here; we just turned off power. + * CC state change is expected in PR_SWAP * Ignore it. */ break; @@ -2928,12 +3018,11 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, static void _tcpm_pd_vbus_on(struct tcpm_port *port) { - enum tcpm_state new_state; - tcpm_log_force(port, "VBUS on"); port->vbus_present = true; switch (port->state) { case SNK_TRANSITION_SINK_VBUS: + port->explicit_contract = true; tcpm_set_state(port, SNK_READY, 0); break; case SNK_DISCOVERY: @@ -2959,27 +3048,28 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) /* Do nothing, waiting for timeout */ break; case SRC_TRYWAIT: - /* Hand over to state machine if needed */ - if (port->delayed_state != SRC_TRYWAIT_UNATTACHED) - tcpm_set_state(port, SRC_TRYWAIT, 0); + /* Do nothing, Waiting for Rd to be detected */ break; - case SNK_TRY_WAIT: - if (tcpm_port_is_sink(port)) { - tcpm_set_state(port, SNK_ATTACHED, 0); - break; - } - if (!tcpm_port_is_sink(port)) - new_state = SRC_TRYWAIT; - else - new_state = SRC_TRYWAIT_UNATTACHED; - - if (new_state != port->delayed_state) - tcpm_set_state(port, SNK_TRY_WAIT, 0); + case SRC_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + /* Do nothing, waiting for PD_DEBOUNCE to do be done */ break; case SNK_TRYWAIT: - tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0); + /* Do nothing, waiting for tCCDebounce */ + break; + case SNK_TRYWAIT_VBUS: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + /* Do nothing, waiting for Rp */ + break; + case SRC_TRY_WAIT: + case SRC_TRY_DEBOUNCE: + /* Do nothing, waiting for sink detection */ break; - default: break; } @@ -2987,8 +3077,6 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) static void _tcpm_pd_vbus_off(struct tcpm_port *port) { - enum tcpm_state new_state; - tcpm_log_force(port, "VBUS off"); port->vbus_present = false; port->vbus_never_low = false; @@ -3008,25 +3096,15 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) case SRC_TRYWAIT: /* Hand over to state machine if needed */ if (tcpm_port_is_source(port)) - new_state = SRC_ATTACHED; - else - new_state = SRC_TRYWAIT_UNATTACHED; - if (new_state != port->delayed_state) - tcpm_set_state(port, SRC_TRYWAIT, 0); + tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); break; - case SNK_TRY_WAIT: - if (!tcpm_port_is_sink(port)) - new_state = SRC_TRYWAIT; - else - new_state = SRC_TRYWAIT_UNATTACHED; - - if (new_state != port->delayed_state) - tcpm_set_state(port, SNK_TRY_WAIT, 0); + case SNK_TRY_WAIT_DEBOUNCE: + /* Do nothing, waiting for PD_DEBOUNCE to do be done */ break; + case SNK_TRYWAIT: case SNK_TRYWAIT_VBUS: - tcpm_set_state(port, SNK_TRYWAIT, 0); + case SNK_TRYWAIT_DEBOUNCE: break; - case SNK_ATTACH_WAIT: tcpm_set_state(port, SNK_UNATTACHED, 0); break; @@ -3042,13 +3120,13 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) /* Do nothing, expected */ break; - case ERROR_RECOVERY_WAIT_OFF: - tcpm_set_state(port, - port->pwr_role == TYPEC_SOURCE ? - SRC_UNATTACHED : SNK_UNATTACHED, - 0); + case PORT_RESET_WAIT_OFF: + tcpm_set_state(port, tcpm_default_state(port), 0); + break; + case SRC_TRY_WAIT: + case SRC_TRY_DEBOUNCE: + /* Do nothing, waiting for sink detection */ break; - default: if (port->pwr_role == TYPEC_SINK && port->attached) @@ -3142,7 +3220,7 @@ static int tcpm_dr_set(const struct typec_capability *cap, mutex_lock(&port->swap_lock); mutex_lock(&port->lock); - if (port->typec_caps.type != TYPEC_PORT_DRP || !port->pd_capable) { + if (port->port_type != TYPEC_PORT_DRP) { ret = -EINVAL; goto port_unlock; } @@ -3163,15 +3241,35 @@ static int tcpm_dr_set(const struct typec_capability *cap, * Reject data role swap request in this case. */ + if (!port->pd_capable) { + /* + * If the partner is not PD capable, reset the port to + * trigger a role change. This can only work if a preferred + * role is configured, and if it matches the requested role. + */ + if (port->try_role == TYPEC_NO_PREFERRED_ROLE || + port->try_role == port->pwr_role) { + ret = -EINVAL; + goto port_unlock; + } + port->non_pd_role_swap = true; + tcpm_set_state(port, PORT_RESET, 0); + } else { + tcpm_set_state(port, DR_SWAP_SEND, 0); + } + port->swap_status = 0; port->swap_pending = true; reinit_completion(&port->swap_complete); - tcpm_set_state(port, DR_SWAP_SEND, 0); mutex_unlock(&port->lock); - wait_for_completion(&port->swap_complete); + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; - ret = port->swap_status; + port->non_pd_role_swap = false; goto swap_unlock; port_unlock: @@ -3190,7 +3288,7 @@ static int tcpm_pr_set(const struct typec_capability *cap, mutex_lock(&port->swap_lock); mutex_lock(&port->lock); - if (port->typec_caps.type != TYPEC_PORT_DRP) { + if (port->port_type != TYPEC_PORT_DRP) { ret = -EINVAL; goto port_unlock; } @@ -3204,31 +3302,18 @@ static int tcpm_pr_set(const struct typec_capability *cap, goto port_unlock; } - if (!port->pd_capable) { - /* - * If the partner is not PD capable, reset the port to - * trigger a role change. This can only work if a preferred - * role is configured, and if it matches the requested role. - */ - if (port->try_role == TYPEC_NO_PREFERRED_ROLE || - port->try_role == port->pwr_role) { - ret = -EINVAL; - goto port_unlock; - } - tcpm_set_state(port, HARD_RESET_SEND, 0); - ret = 0; - goto port_unlock; - } - port->swap_status = 0; port->swap_pending = true; reinit_completion(&port->swap_complete); tcpm_set_state(port, PR_SWAP_SEND, 0); mutex_unlock(&port->lock); - wait_for_completion(&port->swap_complete); + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; - ret = port->swap_status; goto swap_unlock; port_unlock: @@ -3263,9 +3348,12 @@ static int tcpm_vconn_set(const struct typec_capability *cap, tcpm_set_state(port, VCONN_SWAP_SEND, 0); mutex_unlock(&port->lock); - wait_for_completion(&port->swap_complete); + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; - ret = port->swap_status; goto swap_unlock; port_unlock: @@ -3319,7 +3407,35 @@ static void tcpm_init(struct tcpm_port *port) * Some adapters need a clean slate at startup, and won't recover * otherwise. So do not try to be fancy and force a clean disconnect. */ - tcpm_set_state(port, ERROR_RECOVERY, 0); + tcpm_set_state(port, PORT_RESET, 0); +} + +static int tcpm_port_type_set(const struct typec_capability *cap, + enum typec_port_type type) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + + mutex_lock(&port->lock); + if (type == port->port_type) + goto port_unlock; + + port->port_type = type; + + if (!port->connected) { + tcpm_set_state(port, PORT_RESET, 0); + } else if (type == TYPEC_PORT_UFP) { + if (!(port->pwr_role == TYPEC_SINK && + port->data_role == TYPEC_DEVICE)) + tcpm_set_state(port, PORT_RESET, 0); + } else if (type == TYPEC_PORT_DFP) { + if (!(port->pwr_role == TYPEC_SOURCE && + port->data_role == TYPEC_HOST)) + tcpm_set_state(port, PORT_RESET, 0); + } + +port_unlock: + mutex_unlock(&port->lock); + return 0; } void tcpm_tcpc_reset(struct tcpm_port *port) @@ -3469,9 +3585,10 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.pr_set = tcpm_pr_set; port->typec_caps.vconn_set = tcpm_vconn_set; port->typec_caps.try_role = tcpm_try_role; + port->typec_caps.port_type_set = tcpm_port_type_set; port->partner_desc.identity = &port->partner_ident; - + port->port_type = tcpc->config->type; /* * TODO: * - alt_modes, set_alt_mode @@ -3485,7 +3602,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) } if (tcpc->config->alt_modes) { - struct typec_altmode_desc *paltmode = tcpc->config->alt_modes; + const struct typec_altmode_desc *paltmode = tcpc->config->alt_modes; i = 0; while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) { |