Re: [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP'

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Dec 14, 2023 at 11:08:52PM +0000, RD Babiera wrote:
> Add data message handling and Discover Identity SVDM over SOP'
> 
> This patch contains the following changes:
>     1. pd_vdo
> Add VDO indices for active and passive cables, documentation to reflect
> expected number of objects depending on PD Revision, and macro to indicate
> port parter is data host capable.
>     2. tcpm
> Add typec_cable and typec_plug to tcpm_port to maintain cable and plug
> information. tcpm_port also adds send_discover_prime to indicate that
> Discover Identity should be sent out of the ready state.
> 
> tcpm_queue_vdm and tcpm_send_vdm now take the SOP* type when transmitting
> messages. tcpm_handle_vdm_request and tcpm_pd_svdm also use the SOP* type.
> tcpm_pd_svdm handles Discover Identity messages for SOP and SOP'. In the
> SOP case, the port uses tcpm_attempt_vconn_swap_discovery to determine if
> a Vconn swap is needed for cable communication. Otherwise, the port will
> send Discover Identity on SOP' if it can, or default to Discover SVIDs.
> 
> svdm_consume_identity_sop_prime consumes the result of Discover Identity
> on SOP'. It fills out cable identity and description, and it registers
> the cable. The SOP' plug is registered as well.
> 
> The VDM state machine is adjusted to construct messages based on the SOP*
> type. If a transmission error occurs after the max number of retries for
> Discover Identity over SOP', then the port will send Discover SVIDs over
> SOP.
> 
> Signed-off-by: RD Babiera <rdbabiera@xxxxxxxxxx>

Reviewed-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>

> ---
> Changes since v1:
> * Moved typec_cable_set_svdm_version and typec_cable_get_svdm_version
>   symbols into independent patch.
> * Minor change to svdm_version handing for SOP' in tcpm_pd_svdm
> ---
>  drivers/usb/typec/tcpm/tcpm.c | 388 +++++++++++++++++++++++++++++-----
>  include/linux/usb/pd_vdo.h    |   8 +-
>  2 files changed, 347 insertions(+), 49 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index c1e1fd6bd60d..5924e359e14d 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -319,6 +319,12 @@ struct tcpm_port {
>  	struct typec_partner_desc partner_desc;
>  	struct typec_partner *partner;
>  
> +	struct usb_pd_identity cable_ident;
> +	struct typec_cable_desc cable_desc;
> +	struct typec_cable *cable;
> +	struct typec_plug_desc plug_prime_desc;
> +	struct typec_plug *plug_prime;
> +
>  	enum typec_cc_status cc_req;
>  	enum typec_cc_status src_rp;	/* work only if pd_supported == false */
>  
> @@ -496,6 +502,12 @@ struct tcpm_port {
>  	bool potential_contaminant;
>  
>  	/* SOP* Related Fields */
> +	/*
> +	 * Flag to determine if SOP' Discover Identity is available. The flag
> +	 * is set if Discover Identity on SOP' does not immediately follow
> +	 * Discover Identity on SOP.
> +	 */
> +	bool send_discover_prime;
>  	/*
>  	 * tx_sop_type determines which SOP* a message is being sent on.
>  	 * For messages that are queued and not sent immediately such as in
> @@ -1501,7 +1513,7 @@ static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams)
>   * VDM/VDO handling functions
>   */
>  static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
> -			   const u32 *data, int cnt)
> +			   const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type)
>  {
>  	u32 vdo_hdr = port->vdo_data[0];
>  
> @@ -1509,7 +1521,10 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
>  
>  	/* If is sending discover_identity, handle received message first */
>  	if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) {
> -		port->send_discover = true;
> +		if (tx_sop_type == TCPC_TX_SOP_PRIME)
> +			port->send_discover_prime = true;
> +		else
> +			port->send_discover = true;
>  		mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS);
>  	} else {
>  		/* Make sure we are not still processing a previous VDM packet */
> @@ -1524,6 +1539,8 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
>  	port->vdm_state = VDM_STATE_READY;
>  	port->vdm_sm_running = true;
>  
> +	port->tx_sop_type = tx_sop_type;
> +
>  	mod_vdm_delayed_work(port, 0);
>  }
>  
> @@ -1531,7 +1548,7 @@ static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header,
>  				    const u32 *data, int cnt)
>  {
>  	mutex_lock(&port->lock);
> -	tcpm_queue_vdm(port, header, data, cnt);
> +	tcpm_queue_vdm(port, header, data, cnt, TCPC_TX_SOP);
>  	mutex_unlock(&port->lock);
>  }
>  
> @@ -1553,6 +1570,63 @@ static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt)
>  		 PD_PRODUCT_PID(product), product & 0xffff);
>  }
>  
> +static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p, int cnt)
> +{
> +	u32 idh = p[VDO_INDEX_IDH];
> +	u32 product = p[VDO_INDEX_PRODUCT];
> +	int svdm_version;
> +
> +	/*
> +	 * Attempt to consume identity only if cable currently is not set
> +	 */
> +	if (!IS_ERR_OR_NULL(port->cable))
> +		goto register_plug;
> +
> +	/* Reset cable identity */
> +	memset(&port->cable_ident, 0, sizeof(port->cable_ident));
> +
> +	/* Fill out id header, cert, product, cable VDO 1 */
> +	port->cable_ident.id_header = idh;
> +	port->cable_ident.cert_stat = p[VDO_INDEX_CSTAT];
> +	port->cable_ident.product = product;
> +	port->cable_ident.vdo[0] = p[VDO_INDEX_CABLE_1];
> +
> +	/* Fill out cable desc, infer svdm_version from pd revision */
> +	port->cable_desc.type = (enum typec_plug_type) (VDO_TYPEC_CABLE_TYPE(p[VDO_INDEX_CABLE_1]) +
> +							USB_PLUG_TYPE_A);
> +	port->cable_desc.active = PD_IDH_PTYPE(idh) == IDH_PTYPE_ACABLE ? 1 : 0;
> +	/* Log PD Revision and additional cable VDO from negotiated revision */
> +	switch (port->negotiated_rev_prime) {
> +	case PD_REV30:
> +		port->cable_desc.pd_revision = 0x0300;
> +		if (port->cable_desc.active)
> +			port->cable_ident.vdo[1] = p[VDO_INDEX_CABLE_2];
> +		break;
> +	case PD_REV20:
> +		port->cable_desc.pd_revision = 0x0200;
> +		break;
> +	default:
> +		port->cable_desc.pd_revision = 0x0200;
> +		break;
> +	}
> +	port->cable_desc.identity = &port->cable_ident;
> +	/* Register Cable, set identity and svdm_version */
> +	port->cable = typec_register_cable(port->typec_port, &port->cable_desc);
> +	if (IS_ERR_OR_NULL(port->cable))
> +		return;
> +	typec_cable_set_identity(port->cable);
> +	/* Get SVDM version */
> +	svdm_version = PD_VDO_SVDM_VER(p[VDO_INDEX_HDR]);
> +	typec_cable_set_svdm_version(port->cable, svdm_version);
> +
> +register_plug:
> +	if (IS_ERR_OR_NULL(port->plug_prime)) {
> +		port->plug_prime_desc.index = TYPEC_PLUG_SOP_P;
> +		port->plug_prime = typec_register_plug(port->cable,
> +						       &port->plug_prime_desc);
> +	}
> +}
> +
>  static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
>  {
>  	struct pd_mode_data *pmdata = &port->mode_data;
> @@ -1647,6 +1721,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port)
>  }
>  
>  #define supports_modal(port)	PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
> +#define supports_host(port)    PD_IDH_HOST_SUPP((port->partner_ident.id_header))
>  
>  /*
>   * Helper to determine whether the port is capable of SOP' communication at the
> @@ -1699,9 +1774,35 @@ static bool tcpm_can_communicate_sop_prime(struct tcpm_port *port)
>  	return false;
>  }
>  
> +static bool tcpm_attempt_vconn_swap_discovery(struct tcpm_port *port)
> +{
> +	if (!port->tcpc->attempt_vconn_swap_discovery)
> +		return false;
> +
> +	/* Port is already source, no need to perform swap */
> +	if (port->vconn_role == TYPEC_SOURCE)
> +		return false;
> +
> +	/*
> +	 * Partner needs to support Alternate Modes with modal support. If
> +	 * partner is also capable of being a USB Host, it could be a device
> +	 * that supports Alternate Modes as the DFP.
> +	 */
> +	if (!supports_modal(port) || supports_host(port))
> +		return false;
> +
> +	if ((port->negotiated_rev == PD_REV20 && port->data_role == TYPEC_HOST) ||
> +	    port->negotiated_rev == PD_REV30)
> +		return port->tcpc->attempt_vconn_swap_discovery(port->tcpc);
> +
> +	return false;
> +}
> +
>  static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  			const u32 *p, int cnt, u32 *response,
> -			enum adev_actions *adev_action)
> +			enum adev_actions *adev_action,
> +			enum tcpm_transmit_type rx_sop_type,
> +			enum tcpm_transmit_type *response_tx_sop_type)
>  {
>  	struct typec_port *typec = port->typec_port;
>  	struct typec_altmode *pdev;
> @@ -1711,6 +1812,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  	int cmd_type;
>  	int cmd;
>  	int i;
> +	int ret;
>  
>  	cmd_type = PD_VDO_CMDT(p[0]);
>  	cmd = PD_VDO_CMD(p[0]);
> @@ -1723,9 +1825,25 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  	pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX,
>  				   PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
>  
> -	svdm_version = typec_get_negotiated_svdm_version(typec);
> -	if (svdm_version < 0)
> -		return 0;
> +	switch (rx_sop_type) {
> +	case TCPC_TX_SOP_PRIME:
> +		if (!IS_ERR_OR_NULL(port->cable)) {
> +			svdm_version = typec_get_cable_svdm_version(typec);
> +			if (PD_VDO_SVDM_VER(p[0]) < svdm_version)
> +				typec_cable_set_svdm_version(port->cable, svdm_version);
> +		}
> +		break;
> +	case TCPC_TX_SOP:
> +		svdm_version = typec_get_negotiated_svdm_version(typec);
> +		if (svdm_version < 0)
> +			return 0;
> +		break;
> +	default:
> +		svdm_version = typec_get_negotiated_svdm_version(typec);
> +		if (svdm_version < 0)
> +			return 0;
> +		break;
> +	}
>  
>  	switch (cmd_type) {
>  	case CMDT_INIT:
> @@ -1795,22 +1913,89 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  			      (VDO_SVDM_VERS(typec_get_negotiated_svdm_version(typec)));
>  		break;
>  	case CMDT_RSP_ACK:
> -		/* silently drop message if we are not connected */
> -		if (IS_ERR_OR_NULL(port->partner))
> +		/*
> +		 * Silently drop message if we are not connected, but can process
> +		 * if SOP' Discover Identity prior to explicit contract.
> +		 */
> +		if (IS_ERR_OR_NULL(port->partner) &&
> +		    !(rx_sop_type == TCPC_TX_SOP_PRIME && cmd == CMD_DISCOVER_IDENT))
>  			break;
>  
>  		tcpm_ams_finish(port);
>  
>  		switch (cmd) {
> +		/*
> +		 * SVDM Command Flow for SOP and SOP':
> +		 * SOP		Discover Identity
> +		 * SOP'		Discover Identity
> +		 * SOP		Discover SVIDs
> +		 *		Discover Modes
> +		 *
> +		 * Perform Discover SOP' if the port can communicate with cable
> +		 * plug.
> +		 */
>  		case CMD_DISCOVER_IDENT:
> -			if (PD_VDO_SVDM_VER(p[0]) < svdm_version)
> -				typec_partner_set_svdm_version(port->partner,
> -							       PD_VDO_SVDM_VER(p[0]));
> -			/* 6.4.4.3.1 */
> -			svdm_consume_identity(port, p, cnt);
> -			response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec),
> -					  CMD_DISCOVER_SVID);
> -			rlen = 1;
> +			switch (rx_sop_type) {
> +			case TCPC_TX_SOP:
> +				if (PD_VDO_SVDM_VER(p[0]) < svdm_version) {
> +					typec_partner_set_svdm_version(port->partner,
> +								       PD_VDO_SVDM_VER(p[0]));
> +					/* If cable is discovered before partner, downgrade svdm */
> +					if (!IS_ERR_OR_NULL(port->cable) &&
> +					    (typec_get_cable_svdm_version(port->typec_port) >
> +					    svdm_version))
> +						typec_cable_set_svdm_version(port->cable,
> +									     svdm_version);
> +				}
> +				/* 6.4.4.3.1 */
> +				svdm_consume_identity(port, p, cnt);
> +				/* Attempt Vconn swap, delay SOP' discovery if necessary */
> +				if (tcpm_attempt_vconn_swap_discovery(port)) {
> +					port->send_discover_prime = true;
> +					port->upcoming_state = VCONN_SWAP_SEND;
> +					ret = tcpm_ams_start(port, VCONN_SWAP);
> +					if (!ret)
> +						return 0;
> +					port->upcoming_state = INVALID_STATE;
> +					port->send_discover_prime = false;
> +				}
> +
> +				/*
> +				 * Attempt Discover Identity on SOP' if the
> +				 * cable was not discovered previously, and use
> +				 * the SVDM version of the partner to probe.
> +				 */
> +				if (IS_ERR_OR_NULL(port->cable) &&
> +				    tcpm_can_communicate_sop_prime(port)) {
> +					*response_tx_sop_type = TCPC_TX_SOP_PRIME;
> +					port->send_discover_prime = true;
> +					response[0] = VDO(USB_SID_PD, 1,
> +							  typec_get_negotiated_svdm_version(typec),
> +							  CMD_DISCOVER_IDENT);
> +					rlen = 1;
> +				} else {
> +					*response_tx_sop_type = TCPC_TX_SOP;
> +					response[0] = VDO(USB_SID_PD, 1,
> +							  typec_get_negotiated_svdm_version(typec),
> +							  CMD_DISCOVER_SVID);
> +					rlen = 1;
> +				}
> +				break;
> +			case TCPC_TX_SOP_PRIME:
> +				/*
> +				 * svdm_consume_identity_sop_prime will determine
> +				 * the svdm_version for the cable moving forward.
> +				 */
> +				svdm_consume_identity_sop_prime(port, p, cnt);
> +				*response_tx_sop_type = TCPC_TX_SOP;
> +				response[0] = VDO(USB_SID_PD, 1,
> +						  typec_get_negotiated_svdm_version(typec),
> +						  CMD_DISCOVER_SVID);
> +				rlen = 1;
> +				break;
> +			default:
> +				return 0;
> +			}
>  			break;
>  		case CMD_DISCOVER_SVID:
>  			/* 6.4.4.3.2 */
> @@ -1896,13 +2081,15 @@ static void tcpm_pd_handle_msg(struct tcpm_port *port,
>  			       enum tcpm_ams ams);
>  
>  static void tcpm_handle_vdm_request(struct tcpm_port *port,
> -				    const __le32 *payload, int cnt)
> +				    const __le32 *payload, int cnt,
> +				    enum tcpm_transmit_type rx_sop_type)
>  {
>  	enum adev_actions adev_action = ADEV_NONE;
>  	struct typec_altmode *adev;
>  	u32 p[PD_MAX_PAYLOAD];
>  	u32 response[8] = { };
>  	int i, rlen = 0;
> +	enum tcpm_transmit_type response_tx_sop_type = TCPC_TX_SOP;
>  
>  	for (i = 0; i < cnt; i++)
>  		p[i] = le32_to_cpu(payload[i]);
> @@ -1937,7 +2124,8 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
>  		 *  - We will send NAK and the flag will be cleared in the state machine.
>  		 */
>  		port->vdm_sm_running = true;
> -		rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action);
> +		rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action,
> +				    rx_sop_type, &response_tx_sop_type);
>  	} else {
>  		if (port->negotiated_rev >= PD_REV30)
>  			tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS);
> @@ -2005,19 +2193,38 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
>  	mutex_lock(&port->lock);
>  
>  	if (rlen > 0)
> -		tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);
> +		tcpm_queue_vdm(port, response[0], &response[1], rlen - 1, response_tx_sop_type);
>  	else
>  		port->vdm_sm_running = false;
>  }
>  
>  static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd,
> -			  const u32 *data, int count)
> +			  const u32 *data, int count, enum tcpm_transmit_type tx_sop_type)
>  {
> -	int svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
> +	int svdm_version;
>  	u32 header;
>  
> -	if (svdm_version < 0)
> -		return;
> +	switch (tx_sop_type) {
> +	case TCPC_TX_SOP_PRIME:
> +		/*
> +		 * If the port partner is discovered, then the port partner's
> +		 * SVDM Version will be returned
> +		 */
> +		svdm_version = typec_get_cable_svdm_version(port->typec_port);
> +		if (svdm_version < 0)
> +			svdm_version = SVDM_VER_MAX;
> +		break;
> +	case TCPC_TX_SOP:
> +		svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
> +		if (svdm_version < 0)
> +			return;
> +		break;
> +	default:
> +		svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
> +		if (svdm_version < 0)
> +			return;
> +		break;
> +	}
>  
>  	if (WARN_ON(count > VDO_MAX_SIZE - 1))
>  		count = VDO_MAX_SIZE - 1;
> @@ -2026,7 +2233,7 @@ static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd,
>  	header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ?
>  			1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION),
>  			svdm_version, cmd);
> -	tcpm_queue_vdm(port, header, data, count);
> +	tcpm_queue_vdm(port, header, data, count, tx_sop_type);
>  }
>  
>  static unsigned int vdm_ready_timeout(u32 vdm_hdr)
> @@ -2060,6 +2267,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
>  	struct pd_message msg;
>  	int i, res = 0;
>  	u32 vdo_hdr = port->vdo_data[0];
> +	u32 response[8] = { };
>  
>  	switch (port->vdm_state) {
>  	case VDM_STATE_READY:
> @@ -2084,7 +2292,17 @@ static void vdm_run_state_machine(struct tcpm_port *port)
>  			case CMD_DISCOVER_IDENT:
>  				res = tcpm_ams_start(port, DISCOVER_IDENTITY);
>  				if (res == 0) {
> -					port->send_discover = false;
> +					switch (port->tx_sop_type) {
> +					case TCPC_TX_SOP_PRIME:
> +						port->send_discover_prime = false;
> +						break;
> +					case TCPC_TX_SOP:
> +						port->send_discover = false;
> +						break;
> +					default:
> +						port->send_discover = false;
> +						break;
> +					}
>  				} else if (res == -EAGAIN) {
>  					port->vdo_data[0] = 0;
>  					mod_send_discover_delayed_work(port,
> @@ -2153,19 +2371,49 @@ static void vdm_run_state_machine(struct tcpm_port *port)
>  				tcpm_ams_finish(port);
>  		} else {
>  			tcpm_ams_finish(port);
> +			if (port->tx_sop_type == TCPC_TX_SOP)
> +				break;
> +			/* Handle SOP' Transmission Errors */
> +			switch (PD_VDO_CMD(vdo_hdr)) {
> +			/*
> +			 * If Discover Identity fails on SOP', then resume
> +			 * discovery process on SOP only.
> +			 */
> +			case CMD_DISCOVER_IDENT:
> +				port->vdo_data[0] = 0;
> +				response[0] = VDO(USB_SID_PD, 1,
> +						  typec_get_negotiated_svdm_version(
> +									port->typec_port),
> +						  CMD_DISCOVER_SVID);
> +				tcpm_queue_vdm(port, response[0], &response[1],
> +					       0, TCPC_TX_SOP);
> +				break;
> +			default:
> +				break;
> +			}
>  		}
>  		break;
>  	case VDM_STATE_SEND_MESSAGE:
>  		/* Prepare and send VDM */
>  		memset(&msg, 0, sizeof(msg));
> -		msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
> -					  port->pwr_role,
> -					  port->data_role,
> -					  port->negotiated_rev,
> -					  port->message_id, port->vdo_count);
> +		if (port->tx_sop_type == TCPC_TX_SOP_PRIME) {
> +			msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
> +						  0,	/* Cable Plug Indicator for DFP/UFP */
> +						  0,	/* Reserved */
> +						  port->negotiated_rev_prime,
> +						  port->message_id_prime,
> +						  port->vdo_count);
> +		} else {
> +			msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
> +						  port->pwr_role,
> +						  port->data_role,
> +						  port->negotiated_rev,
> +						  port->message_id,
> +						  port->vdo_count);
> +		}
>  		for (i = 0; i < port->vdo_count; i++)
>  			msg.payload[i] = cpu_to_le32(port->vdo_data[i]);
> -		res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
> +		res = tcpm_pd_transmit(port, port->tx_sop_type, &msg);
>  		if (res < 0) {
>  			port->vdm_state = VDM_STATE_ERR_SEND;
>  		} else {
> @@ -2552,7 +2800,8 @@ static int tcpm_register_sink_caps(struct tcpm_port *port)
>  }
>  
>  static void tcpm_pd_data_request(struct tcpm_port *port,
> -				 const struct pd_message *msg)
> +				 const struct pd_message *msg,
> +				 enum tcpm_transmit_type rx_sop_type)
>  {
>  	enum pd_data_msg_type type = pd_header_type_le(msg->header);
>  	unsigned int cnt = pd_header_cnt_le(msg->header);
> @@ -2593,8 +2842,11 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
>  			break;
>  		}
>  
> -		if (rev < PD_MAX_REV)
> +		if (rev < PD_MAX_REV) {
>  			port->negotiated_rev = rev;
> +			if (port->negotiated_rev_prime > port->negotiated_rev)
> +				port->negotiated_rev_prime = port->negotiated_rev;
> +		}
>  
>  		if (port->pwr_role == TYPEC_SOURCE) {
>  			if (port->ams == GET_SOURCE_CAPABILITIES)
> @@ -2645,8 +2897,11 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
>  			break;
>  		}
>  
> -		if (rev < PD_MAX_REV)
> +		if (rev < PD_MAX_REV) {
>  			port->negotiated_rev = rev;
> +			if (port->negotiated_rev_prime > port->negotiated_rev)
> +				port->negotiated_rev_prime = port->negotiated_rev;
> +		}
>  
>  		if (port->pwr_role != TYPEC_SOURCE || cnt != 1) {
>  			tcpm_pd_handle_msg(port,
> @@ -2702,7 +2957,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
>  					   NONE_AMS);
>  		break;
>  	case PD_DATA_VENDOR_DEF:
> -		tcpm_handle_vdm_request(port, msg->payload, cnt);
> +		tcpm_handle_vdm_request(port, msg->payload, cnt, rx_sop_type);
>  		break;
>  	case PD_DATA_BIST:
>  		port->bist_request = le32_to_cpu(msg->payload[0]);
> @@ -3151,7 +3406,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
>  			if (le16_to_cpu(msg->header) & PD_HEADER_EXT_HDR)
>  				tcpm_pd_ext_msg_request(port, msg);
>  			else if (cnt)
> -				tcpm_pd_data_request(port, msg);
> +				tcpm_pd_data_request(port, msg, rx_sop_type);
>  			else
>  				tcpm_pd_ctrl_request(port, msg, rx_sop_type);
>  		}
> @@ -3808,6 +4063,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
>  
>  	port->attached = true;
>  	port->send_discover = true;
> +	port->send_discover_prime = false;
>  
>  	return 0;
>  
> @@ -3824,6 +4080,15 @@ static int tcpm_src_attach(struct tcpm_port *port)
>  
>  static void tcpm_typec_disconnect(struct tcpm_port *port)
>  {
> +	/*
> +	 * Unregister plug/cable outside of port->connected because cable can
> +	 * be discovered before SRC_READY/SNK_READY states where port->connected
> +	 * is set.
> +	 */
> +	typec_unregister_plug(port->plug_prime);
> +	typec_unregister_cable(port->cable);
> +	port->plug_prime = NULL;
> +	port->cable = NULL;
>  	if (port->connected) {
>  		typec_partner_set_usb_power_delivery(port->partner, NULL);
>  		typec_unregister_partner(port->partner);
> @@ -3946,6 +4211,7 @@ static int tcpm_snk_attach(struct tcpm_port *port)
>  
>  	port->attached = true;
>  	port->send_discover = true;
> +	port->send_discover_prime = false;
>  
>  	return 0;
>  }
> @@ -4307,14 +4573,23 @@ static void run_state_machine(struct tcpm_port *port)
>  		 * 6.4.4.3.1 Discover Identity
>  		 * "The Discover Identity Command Shall only be sent to SOP when there is an
>  		 * Explicit Contract."
> -		 * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using
> -		 * port->explicit_contract to decide whether to send the command.
> +		 *
> +		 * Discover Identity on SOP' should be discovered prior to the
> +		 * ready state, but if done after a Vconn Swap following Discover
> +		 * Identity on SOP then the discovery process can be run here
> +		 * as well.
>  		 */
>  		if (port->explicit_contract) {
> -			tcpm_set_initial_svdm_version(port);
> +			if (port->send_discover_prime) {
> +				port->tx_sop_type = TCPC_TX_SOP_PRIME;
> +			} else {
> +				port->tx_sop_type = TCPC_TX_SOP;
> +				tcpm_set_initial_svdm_version(port);
> +			}
>  			mod_send_discover_delayed_work(port, 0);
>  		} else {
>  			port->send_discover = false;
> +			port->send_discover_prime = false;
>  		}
>  
>  		/*
> @@ -4605,14 +4880,23 @@ static void run_state_machine(struct tcpm_port *port)
>  		 * 6.4.4.3.1 Discover Identity
>  		 * "The Discover Identity Command Shall only be sent to SOP when there is an
>  		 * Explicit Contract."
> -		 * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using
> -		 * port->explicit_contract.
> +		 *
> +		 * Discover Identity on SOP' should be discovered prior to the
> +		 * ready state, but if done after a Vconn Swap following Discover
> +		 * Identity on SOP then the discovery process can be run here
> +		 * as well.
>  		 */
>  		if (port->explicit_contract) {
> -			tcpm_set_initial_svdm_version(port);
> +			if (port->send_discover_prime) {
> +				port->tx_sop_type = TCPC_TX_SOP_PRIME;
> +			} else {
> +				port->tx_sop_type = TCPC_TX_SOP;
> +				tcpm_set_initial_svdm_version(port);
> +			}
>  			mod_send_discover_delayed_work(port, 0);
>  		} else {
>  			port->send_discover = false;
> +			port->send_discover_prime = false;
>  		}
>  
>  		power_supply_changed(port->psy);
> @@ -4653,6 +4937,7 @@ static void run_state_machine(struct tcpm_port *port)
>  		tcpm_unregister_altmodes(port);
>  		port->nr_sink_caps = 0;
>  		port->send_discover = true;
> +		port->send_discover_prime = false;
>  		if (port->pwr_role == TYPEC_SOURCE)
>  			tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF,
>  				       PD_T_PS_HARD_RESET);
> @@ -4799,20 +5084,25 @@ static void run_state_machine(struct tcpm_port *port)
>  	/* DR_Swap states */
>  	case DR_SWAP_SEND:
>  		tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP);
> -		if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20)
> +		if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) {
>  			port->send_discover = true;
> +			port->send_discover_prime = false;
> +		}
>  		tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT,
>  				    PD_T_SENDER_RESPONSE);
>  		break;
>  	case DR_SWAP_ACCEPT:
>  		tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
> -		if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20)
> +		if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) {
>  			port->send_discover = true;
> +			port->send_discover_prime = false;
> +		}
>  		tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
>  		break;
>  	case DR_SWAP_SEND_TIMEOUT:
>  		tcpm_swap_complete(port, -ETIMEDOUT);
>  		port->send_discover = false;
> +		port->send_discover_prime = false;
>  		tcpm_ams_finish(port);
>  		tcpm_set_state(port, ready_state(port), 0);
>  		break;
> @@ -5794,7 +6084,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work)
>  		goto unlock;
>  
>  	/* Send when the state machine is idle */
> -	if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover)
> +	if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover ||
> +	    port->send_discover_prime)
>  		goto resched;
>  
>  	port->upcoming_state = GET_SINK_CAP;
> @@ -5817,11 +6108,12 @@ static void tcpm_send_discover_work(struct kthread_work *work)
>  
>  	mutex_lock(&port->lock);
>  	/* No need to send DISCOVER_IDENTITY anymore */
> -	if (!port->send_discover)
> +	if (!port->send_discover && !port->send_discover_prime)
>  		goto unlock;
>  
>  	if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) {
>  		port->send_discover = false;
> +		port->send_discover_prime = false;
>  		goto unlock;
>  	}
>  
> @@ -5831,7 +6123,7 @@ static void tcpm_send_discover_work(struct kthread_work *work)
>  		goto unlock;
>  	}
>  
> -	tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
> +	tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type);
>  
>  unlock:
>  	mutex_unlock(&port->lock);
> diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h
> index 3a747938cdab..c09c5a12e273 100644
> --- a/include/linux/usb/pd_vdo.h
> +++ b/include/linux/usb/pd_vdo.h
> @@ -86,12 +86,15 @@
>   *
>   * Request is simply properly formatted SVDM header
>   *
> - * Response is 4 data objects:
> + * Response is 4 data objects for Power Delivery 2.0 and Passive Cables for
> + * Power Delivery 3.0. Active Cables in Power Delivery 3.0 have 5 data objects.
>   * [0] :: SVDM header
>   * [1] :: Identitiy header
>   * [2] :: Cert Stat VDO
>   * [3] :: (Product | Cable) VDO
> + * [4] :: Cable VDO 1
>   * [4] :: AMA VDO
> + * [5] :: Cable VDO 2
>   *
>   */
>  #define VDO_INDEX_HDR		0
> @@ -100,6 +103,8 @@
>  #define VDO_INDEX_CABLE		3
>  #define VDO_INDEX_PRODUCT	3
>  #define VDO_INDEX_AMA		4
> +#define VDO_INDEX_CABLE_1	4
> +#define VDO_INDEX_CABLE_2	5
>  
>  /*
>   * SVDM Identity Header
> @@ -150,6 +155,7 @@
>  #define PD_IDH_MODAL_SUPP(vdo)	((vdo) & (1 << 26))
>  #define PD_IDH_DFP_PTYPE(vdo)	(((vdo) >> 23) & 0x7)
>  #define PD_IDH_CONN_TYPE(vdo)	(((vdo) >> 21) & 0x3)
> +#define PD_IDH_HOST_SUPP(vdo)  ((vdo) & (1 << 31))
>  
>  /*
>   * Cert Stat VDO
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux