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

From: Heikki Krogerus
Date: Tue Dec 19 2023 - 09:26:42 EST


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