]> git.ipfire.org Git - thirdparty/linux.git/blobdiff - drivers/usb/typec/tcpm/tcpm.c
Merge tag 'usb-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
[thirdparty/linux.git] / drivers / usb / typec / tcpm / tcpm.c
index 0965972310275e1c4d82be94051573648175a4fe..ae2b6c94482d5fab23f26be2b12491a52b15419d 100644 (file)
        S(VCONN_SWAP_WAIT_FOR_VCONN),           \
        S(VCONN_SWAP_TURN_ON_VCONN),            \
        S(VCONN_SWAP_TURN_OFF_VCONN),           \
+       S(VCONN_SWAP_SEND_SOFT_RESET),          \
                                                \
        S(FR_SWAP_SEND),                        \
        S(FR_SWAP_SEND_TIMEOUT),                \
        S(PORT_RESET_WAIT_OFF),                 \
                                                \
        S(AMS_START),                           \
-       S(CHUNK_NOT_SUPP)
+       S(CHUNK_NOT_SUPP),                      \
+                                               \
+       S(SRC_VDM_IDENTITY_REQUEST)
 
 #define FOREACH_AMS(S)                         \
        S(NONE_AMS),                            \
@@ -327,6 +330,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 */
 
@@ -468,7 +477,9 @@ struct tcpm_port {
 
        /* Alternate mode data */
        struct pd_mode_data mode_data;
+       struct pd_mode_data mode_data_prime;
        struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX];
+       struct typec_altmode *plug_prime_altmode[ALTMODE_DISCOVERY_MAX];
        struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX];
 
        /* Deadline in jiffies to exit src_try_wait state */
@@ -505,6 +516,41 @@ struct tcpm_port {
         * transitions.
         */
        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
+        * tcpm_queue_message or messages that send after state changes,
+        * the tx_sop_type is set accordingly.
+        */
+       enum tcpm_transmit_type tx_sop_type;
+       /*
+        * Prior to discovering the port partner's Specification Revision, the
+        * Vconn source and cable plug will use the lower of their two revisions.
+        *
+        * When the port partner's Specification Revision is discovered, the following
+        * rules are put in place.
+        *      1. If the cable revision (1) is lower than the revision negotiated
+        * between the port and partner (2), the port and partner will communicate
+        * on revision (2), but the port and cable will communicate on revision (1).
+        *      2. If the cable revision (1) is higher than the revision negotiated
+        * between the port and partner (2), the port and partner will communicate
+        * on revision (2), and the port and cable will communicate on revision (2)
+        * as well.
+        */
+       unsigned int negotiated_rev_prime;
+       /*
+        * Each SOP* type must maintain their own tx and rx message IDs
+        */
+       unsigned int message_id_prime;
+       unsigned int rx_msgid_prime;
 #ifdef CONFIG_DEBUG_FS
        struct dentry *dentry;
        struct mutex logbuffer_lock;    /* log buffer access lock */
@@ -518,6 +564,7 @@ struct pd_rx_event {
        struct kthread_work work;
        struct tcpm_port *port;
        struct pd_message msg;
+       enum tcpm_transmit_type rx_sop_type;
 };
 
 static const char * const pd_rev[] = {
@@ -893,19 +940,30 @@ static void tcpm_ams_finish(struct tcpm_port *port)
 }
 
 static int tcpm_pd_transmit(struct tcpm_port *port,
-                           enum tcpm_transmit_type type,
+                           enum tcpm_transmit_type tx_sop_type,
                            const struct pd_message *msg)
 {
        unsigned long timeout;
        int ret;
+       unsigned int negotiated_rev;
+
+       switch (tx_sop_type) {
+       case TCPC_TX_SOP_PRIME:
+               negotiated_rev = port->negotiated_rev_prime;
+               break;
+       case TCPC_TX_SOP:
+       default:
+               negotiated_rev = port->negotiated_rev;
+               break;
+       }
 
        if (msg)
                tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header));
        else
-               tcpm_log(port, "PD TX, type: %#x", type);
+               tcpm_log(port, "PD TX, type: %#x", tx_sop_type);
 
        reinit_completion(&port->tx_complete);
-       ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev);
+       ret = port->tcpc->pd_transmit(port->tcpc, tx_sop_type, msg, negotiated_rev);
        if (ret < 0)
                return ret;
 
@@ -918,7 +976,17 @@ static int tcpm_pd_transmit(struct tcpm_port *port,
 
        switch (port->tx_status) {
        case TCPC_TX_SUCCESS:
-               port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK;
+               switch (tx_sop_type) {
+               case TCPC_TX_SOP_PRIME:
+                       port->message_id_prime = (port->message_id_prime + 1) &
+                                                PD_HEADER_ID_MASK;
+                       break;
+               case TCPC_TX_SOP:
+               default:
+                       port->message_id = (port->message_id + 1) &
+                                          PD_HEADER_ID_MASK;
+                       break;
+               }
                /*
                 * USB PD rev 2.0, 8.3.2.2.1:
                 * USB PD rev 3.0, 8.3.2.1.3:
@@ -1099,6 +1167,12 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached,
        if (ret < 0)
                return ret;
 
+       if (port->tcpc->set_orientation) {
+               ret = port->tcpc->set_orientation(port->tcpc, orientation);
+               if (ret < 0)
+                       return ret;
+       }
+
        port->pwr_role = role;
        port->data_role = data;
        typec_set_data_role(port->typec_port, data);
@@ -1456,7 +1530,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];
 
@@ -1464,7 +1538,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 */
@@ -1479,14 +1556,16 @@ 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);
 }
 
 static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header,
-                                   const u32 *data, int cnt)
+                                   const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type)
 {
        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);
 }
 
@@ -1508,9 +1587,68 @@ static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt)
                 PD_PRODUCT_PID(product), product & 0xffff);
 }
 
-static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
+static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p, int cnt)
 {
-       struct pd_mode_data *pmdata = &port->mode_data;
+       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,
+                              enum tcpm_transmit_type rx_sop_type)
+{
+       struct pd_mode_data *pmdata = rx_sop_type == TCPC_TX_SOP_PRIME ?
+                                     &port->mode_data_prime : &port->mode_data;
        int i;
 
        for (i = 1; i < cnt; i++) {
@@ -1556,14 +1694,29 @@ abort:
        return false;
 }
 
-static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt)
+static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt,
+                              enum tcpm_transmit_type rx_sop_type)
 {
        struct pd_mode_data *pmdata = &port->mode_data;
        struct typec_altmode_desc *paltmode;
        int i;
 
-       if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) {
-               /* Already logged in svdm_consume_svids() */
+       switch (rx_sop_type) {
+       case TCPC_TX_SOP_PRIME:
+               pmdata = &port->mode_data_prime;
+               if (pmdata->altmodes >= ARRAY_SIZE(port->plug_prime_altmode)) {
+                       /* Already logged in svdm_consume_svids() */
+                       return;
+               }
+               break;
+       case TCPC_TX_SOP:
+               pmdata = &port->mode_data;
+               if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) {
+                       /* Already logged in svdm_consume_svids() */
+                       return;
+               }
+               break;
+       default:
                return;
        }
 
@@ -1601,20 +1754,129 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port)
        }
 }
 
+static void tcpm_register_plug_altmodes(struct tcpm_port *port)
+{
+       struct pd_mode_data *modep = &port->mode_data_prime;
+       struct typec_altmode *altmode;
+       int i;
+
+       typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes);
+
+       for (i = 0; i < modep->altmodes; i++) {
+               altmode = typec_plug_register_altmode(port->plug_prime,
+                                               &modep->altmode_desc[i]);
+               if (IS_ERR(altmode)) {
+                       tcpm_log(port, "Failed to register plug SVID 0x%04x",
+                                modep->altmode_desc[i].svid);
+                       altmode = NULL;
+               }
+               port->plug_prime_altmode[i] = altmode;
+       }
+}
+
 #define supports_modal(port)   PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
+#define supports_modal_cable(port)     PD_IDH_MODAL_SUPP((port)->cable_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
+ * current point in time.
+ */
+static bool tcpm_can_communicate_sop_prime(struct tcpm_port *port)
+{
+       /* Check to see if tcpc supports SOP' communication */
+       if (!port->tcpc->cable_comm_capable || !port->tcpc->cable_comm_capable(port->tcpc))
+               return false;
+       /*
+        * Power Delivery 2.0 Section 6.3.11
+        * Before communicating with a Cable Plug a Port Should ensure that it
+        * is the Vconn Source and that the Cable Plugs are powered by
+        * performing a Vconn swap if necessary. Since it cannot be guaranteed
+        * that the present Vconn Source is supplying Vconn, the only means to
+        * ensure that the Cable Plugs are powered is for a Port wishing to
+        * communicate with a Cable Plug is to become the Vconn Source.
+        *
+        * Power Delivery 3.0 Section 6.3.11
+        * Before communicating with a Cable Plug a Port Shall ensure that it
+        * is the Vconn source.
+        */
+       if (port->vconn_role != TYPEC_SOURCE)
+               return false;
+       /*
+        * Power Delivery 2.0 Section 2.4.4
+        * When no Contract or an Implicit Contract is in place the Source can
+        * communicate with a Cable Plug using SOP' packets in order to discover
+        * its characteristics.
+        *
+        * Power Delivery 3.0 Section 2.4.4
+        * When no Contract or an Implicit Contract is in place only the Source
+        * port that is supplying Vconn is allowed to send packets to a Cable
+        * Plug and is allowed to respond to packets from the Cable Plug.
+        */
+       if (!port->explicit_contract)
+               return port->pwr_role == TYPEC_SOURCE;
+       if (port->negotiated_rev == PD_REV30)
+               return true;
+       /*
+        * Power Delivery 2.0 Section 2.4.4
+        *
+        * When an Explicit Contract is in place the DFP (either the Source or
+        * the Sink) can communicate with the Cable Plug(s) using SOP’/SOP”
+        * Packets (see Figure 2-3).
+        */
+       if (port->negotiated_rev == PD_REV20)
+               return port->data_role == TYPEC_HOST;
+       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 bool tcpm_cable_vdm_supported(struct tcpm_port *port)
+{
+       return !IS_ERR_OR_NULL(port->cable) &&
+              typec_cable_is_active(port->cable) &&
+              supports_modal_cable(port) &&
+              tcpm_can_communicate_sop_prime(port);
+}
 
 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;
-       struct pd_mode_data *modep;
+       struct typec_altmode *pdev, *pdev_prime;
+       struct pd_mode_data *modep, *modep_prime;
        int svdm_version;
        int rlen = 0;
        int cmd_type;
        int cmd;
        int i;
+       int ret;
 
        cmd_type = PD_VDO_CMDT(p[0]);
        cmd = PD_VDO_CMD(p[0]);
@@ -1622,17 +1884,54 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
        tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d",
                 p[0], cmd_type, cmd, cnt);
 
-       modep = &port->mode_data;
-
-       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:
+               modep_prime = &port->mode_data_prime;
+               pdev_prime = typec_match_altmode(port->plug_prime_altmode,
+                                                ALTMODE_DISCOVERY_MAX,
+                                                PD_VDO_VID(p[0]),
+                                                PD_VDO_OPOS(p[0]));
+               svdm_version = typec_get_cable_svdm_version(typec);
+               /*
+                * Update SVDM version if cable was discovered before port partner.
+                */
+               if (!IS_ERR_OR_NULL(port->cable) &&
+                   PD_VDO_SVDM_VER(p[0]) < svdm_version)
+                       typec_cable_set_svdm_version(port->cable, svdm_version);
+               break;
+       case TCPC_TX_SOP:
+               modep = &port->mode_data;
+               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;
+               break;
+       default:
+               modep = &port->mode_data;
+               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;
+               break;
+       }
 
        switch (cmd_type) {
        case CMDT_INIT:
+               /*
+                * Only the port or port partner is allowed to initialize SVDM
+                * commands over SOP'. In case the port partner initializes a
+                * sequence when it is not allowed to send SOP' messages, drop
+                * the message should the TCPM port try to process it.
+                */
+               if (rx_sop_type == TCPC_TX_SOP_PRIME)
+                       return 0;
+
                switch (cmd) {
                case CMD_DISCOVER_IDENT:
                        if (PD_VDO_VID(p[0]) != USB_SID_PD)
@@ -1699,55 +1998,186 @@ 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
+                * (Active Cables)
+                * 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;
+                                       /* Cannot perform Vconn swap */
+                                       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);
+
+                               /*
+                                * If received in SRC_VDM_IDENTITY_REQUEST, continue
+                                * to SRC_SEND_CAPABILITIES
+                                */
+                               if (port->state == SRC_VDM_IDENTITY_REQUEST) {
+                                       tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
+                                       return 0;
+                               }
+
+                               *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:
+                       *response_tx_sop_type = rx_sop_type;
                        /* 6.4.4.3.2 */
-                       if (svdm_consume_svids(port, p, cnt)) {
+                       if (svdm_consume_svids(port, p, cnt, rx_sop_type)) {
                                response[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID);
                                rlen = 1;
-                       } else if (modep->nsvids && supports_modal(port)) {
-                               response[0] = VDO(modep->svids[0], 1, svdm_version,
-                                                 CMD_DISCOVER_MODES);
-                               rlen = 1;
+                       } else {
+                               if (rx_sop_type == TCPC_TX_SOP) {
+                                       if (modep->nsvids && supports_modal(port)) {
+                                               response[0] = VDO(modep->svids[0], 1, svdm_version,
+                                                               CMD_DISCOVER_MODES);
+                                               rlen = 1;
+                                       }
+                               } else if (rx_sop_type == TCPC_TX_SOP_PRIME) {
+                                       if (modep_prime->nsvids) {
+                                               response[0] = VDO(modep_prime->svids[0], 1,
+                                                                 svdm_version, CMD_DISCOVER_MODES);
+                                               rlen = 1;
+                                       }
+                               }
                        }
                        break;
                case CMD_DISCOVER_MODES:
-                       /* 6.4.4.3.3 */
-                       svdm_consume_modes(port, p, cnt);
-                       modep->svid_index++;
-                       if (modep->svid_index < modep->nsvids) {
-                               u16 svid = modep->svids[modep->svid_index];
-                               response[0] = VDO(svid, 1, svdm_version, CMD_DISCOVER_MODES);
-                               rlen = 1;
-                       } else {
-                               tcpm_register_partner_altmodes(port);
+                       if (rx_sop_type == TCPC_TX_SOP) {
+                               /* 6.4.4.3.3 */
+                               svdm_consume_modes(port, p, cnt, rx_sop_type);
+                               modep->svid_index++;
+                               if (modep->svid_index < modep->nsvids) {
+                                       u16 svid = modep->svids[modep->svid_index];
+                                       *response_tx_sop_type = TCPC_TX_SOP;
+                                       response[0] = VDO(svid, 1, svdm_version,
+                                                         CMD_DISCOVER_MODES);
+                                       rlen = 1;
+                               } else if (tcpm_cable_vdm_supported(port)) {
+                                       *response_tx_sop_type = TCPC_TX_SOP_PRIME;
+                                       response[0] = VDO(USB_SID_PD, 1,
+                                                         typec_get_cable_svdm_version(typec),
+                                                         CMD_DISCOVER_SVID);
+                                       rlen = 1;
+                               } else {
+                                       tcpm_register_partner_altmodes(port);
+                               }
+                       } else if (rx_sop_type == TCPC_TX_SOP_PRIME) {
+                               /* 6.4.4.3.3 */
+                               svdm_consume_modes(port, p, cnt, rx_sop_type);
+                               modep_prime->svid_index++;
+                               if (modep_prime->svid_index < modep_prime->nsvids) {
+                                       u16 svid = modep_prime->svids[modep_prime->svid_index];
+                                       *response_tx_sop_type = TCPC_TX_SOP_PRIME;
+                                       response[0] = VDO(svid, 1,
+                                                         typec_get_cable_svdm_version(typec),
+                                                         CMD_DISCOVER_MODES);
+                                       rlen = 1;
+                               } else {
+                                       tcpm_register_plug_altmodes(port);
+                                       tcpm_register_partner_altmodes(port);
+                               }
                        }
                        break;
                case CMD_ENTER_MODE:
-                       if (adev && pdev)
-                               *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL;
+                       *response_tx_sop_type = rx_sop_type;
+                       if (rx_sop_type == TCPC_TX_SOP) {
+                               if (adev && pdev) {
+                                       typec_altmode_update_active(pdev, true);
+                                       *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL;
+                               }
+                       } else if (rx_sop_type == TCPC_TX_SOP_PRIME) {
+                               if (adev && pdev_prime) {
+                                       typec_altmode_update_active(pdev_prime, true);
+                                       *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL;
+                               }
+                       }
                        return 0;
                case CMD_EXIT_MODE:
-                       if (adev && pdev) {
-                               /* Back to USB Operation */
-                               *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM;
-                               return 0;
+                       *response_tx_sop_type = rx_sop_type;
+                       if (rx_sop_type == TCPC_TX_SOP) {
+                               if (adev && pdev) {
+                                       typec_altmode_update_active(pdev, false);
+                                       /* Back to USB Operation */
+                                       *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM;
+                                       return 0;
+                               }
                        }
                        break;
                case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15):
@@ -1800,13 +2230,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]);
@@ -1841,7 +2273,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);
@@ -1877,19 +2310,37 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
                        typec_altmode_vdm(adev, p[0], &p[1], cnt);
                        break;
                case ADEV_QUEUE_VDM:
-                       typec_altmode_vdm(adev, p[0], &p[1], cnt);
+                       if (response_tx_sop_type == TCPC_TX_SOP_PRIME)
+                               typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, p[0], &p[1], cnt);
+                       else
+                               typec_altmode_vdm(adev, p[0], &p[1], cnt);
                        break;
                case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL:
-                       if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
-                               int svdm_version = typec_get_negotiated_svdm_version(
-                                                                       port->typec_port);
-                               if (svdm_version < 0)
-                                       break;
-
-                               response[0] = VDO(adev->svid, 1, svdm_version,
-                                                 CMD_EXIT_MODE);
-                               response[0] |= VDO_OPOS(adev->mode);
-                               rlen = 1;
+                       if (response_tx_sop_type == TCPC_TX_SOP_PRIME) {
+                               if (typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P,
+                                                           p[0], &p[1], cnt)) {
+                                       int svdm_version = typec_get_cable_svdm_version(
+                                                                               port->typec_port);
+                                       if (svdm_version < 0)
+                                               break;
+
+                                       response[0] = VDO(adev->svid, 1, svdm_version,
+                                                       CMD_EXIT_MODE);
+                                       response[0] |= VDO_OPOS(adev->mode);
+                                       rlen = 1;
+                               }
+                       } else {
+                               if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
+                                       int svdm_version = typec_get_negotiated_svdm_version(
+                                                                               port->typec_port);
+                                       if (svdm_version < 0)
+                                               break;
+
+                                       response[0] = VDO(adev->svid, 1, svdm_version,
+                                                       CMD_EXIT_MODE);
+                                       response[0] |= VDO_OPOS(adev->mode);
+                                       rlen = 1;
+                               }
                        }
                        break;
                case ADEV_ATTENTION:
@@ -1909,19 +2360,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;
@@ -1930,7 +2400,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)
@@ -1964,6 +2434,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:
@@ -1977,7 +2448,8 @@ static void vdm_run_state_machine(struct tcpm_port *port)
                 * if there's traffic or we're not in PDO ready state don't send
                 * a VDM.
                 */
-               if (port->state != SRC_READY && port->state != SNK_READY) {
+               if (port->state != SRC_READY && port->state != SNK_READY &&
+                   port->state != SRC_VDM_IDENTITY_REQUEST) {
                        port->vdm_sm_running = false;
                        break;
                }
@@ -1988,7 +2460,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,
@@ -2043,13 +2525,22 @@ static void vdm_run_state_machine(struct tcpm_port *port)
                        tcpm_ams_finish(port);
                break;
        case VDM_STATE_ERR_SEND:
+               /*
+                * When sending Discover Identity to SOP' before establishing an
+                * explicit contract, do not retry. Instead, weave sending
+                * Source_Capabilities over SOP and Discover Identity over SOP'.
+                */
+               if (port->state == SRC_VDM_IDENTITY_REQUEST) {
+                       tcpm_ams_finish(port);
+                       port->vdm_state = VDM_STATE_DONE;
+                       tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
                /*
                 * A partner which does not support USB PD will not reply,
                 * so this is not a fatal error. At the same time, some
                 * devices may not return GoodCRC under some circumstances,
                 * so we need to retry.
                 */
-               if (port->vdm_retries < 3) {
+               } else if (port->vdm_retries < 3) {
                        tcpm_log(port, "VDM Tx error, retry");
                        port->vdm_retries++;
                        port->vdm_state = VDM_STATE_READY;
@@ -2057,19 +2548,59 @@ 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;
+                       /*
+                        * If Discover SVIDs or Discover Modes fail, then
+                        * proceed with Alt Mode discovery process on SOP.
+                        */
+                       case CMD_DISCOVER_SVID:
+                               tcpm_register_partner_altmodes(port);
+                               break;
+                       case CMD_DISCOVER_MODES:
+                               tcpm_register_partner_altmodes(port);
+                               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 {
@@ -2244,7 +2775,7 @@ static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo)
        header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE);
        header |= VDO_OPOS(altmode->mode);
 
-       tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0);
+       tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP);
        return 0;
 }
 
@@ -2261,7 +2792,7 @@ static int tcpm_altmode_exit(struct typec_altmode *altmode)
        header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE);
        header |= VDO_OPOS(altmode->mode);
 
-       tcpm_queue_vdm_unlocked(port, header, NULL, 0);
+       tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP);
        return 0;
 }
 
@@ -2270,7 +2801,7 @@ static int tcpm_altmode_vdm(struct typec_altmode *altmode,
 {
        struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
 
-       tcpm_queue_vdm_unlocked(port, header, data, count - 1);
+       tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP);
 
        return 0;
 }
@@ -2281,6 +2812,58 @@ static const struct typec_altmode_ops tcpm_altmode_ops = {
        .vdm = tcpm_altmode_vdm,
 };
 
+
+static int tcpm_cable_altmode_enter(struct typec_altmode *altmode, enum typec_plug_index sop,
+                                   u32 *vdo)
+{
+       struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+       int svdm_version;
+       u32 header;
+
+       svdm_version = typec_get_cable_svdm_version(port->typec_port);
+       if (svdm_version < 0)
+               return svdm_version;
+
+       header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE);
+       header |= VDO_OPOS(altmode->mode);
+
+       tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP_PRIME);
+       return 0;
+}
+
+static int tcpm_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_index sop)
+{
+       struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+       int svdm_version;
+       u32 header;
+
+       svdm_version = typec_get_cable_svdm_version(port->typec_port);
+       if (svdm_version < 0)
+               return svdm_version;
+
+       header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE);
+       header |= VDO_OPOS(altmode->mode);
+
+       tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP_PRIME);
+       return 0;
+}
+
+static int tcpm_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop,
+                                 u32 header, const u32 *data, int count)
+{
+       struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+
+       tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP_PRIME);
+
+       return 0;
+}
+
+static const struct typec_cable_ops tcpm_cable_ops = {
+       .enter = tcpm_cable_altmode_enter,
+       .exit = tcpm_cable_altmode_exit,
+       .vdm = tcpm_cable_altmode_vdm,
+};
+
 /*
  * PD (data, control) command handling functions
  */
@@ -2293,7 +2876,8 @@ static inline enum tcpm_state ready_state(struct tcpm_port *port)
 }
 
 static int tcpm_pd_send_control(struct tcpm_port *port,
-                               enum pd_ctrl_msg_type type);
+                               enum pd_ctrl_msg_type type,
+                               enum tcpm_transmit_type tx_sop_type);
 
 static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload,
                              int cnt)
@@ -2455,7 +3039,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);
@@ -2496,8 +3081,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)
@@ -2548,8 +3136,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,
@@ -2605,7 +3196,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]);
@@ -2647,10 +3238,12 @@ static void tcpm_pps_complete(struct tcpm_port *port, int result)
 }
 
 static void tcpm_pd_ctrl_request(struct tcpm_port *port,
-                                const struct pd_message *msg)
+                                const struct pd_message *msg,
+                                enum tcpm_transmit_type rx_sop_type)
 {
        enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
        enum tcpm_state next_state;
+       unsigned int rev = pd_header_rev_le(msg->header);
 
        /*
         * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in
@@ -2815,6 +3408,16 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
                case SOFT_RESET_SEND:
                        if (port->ams == SOFT_RESET_AMS)
                                tcpm_ams_finish(port);
+                       /*
+                        * SOP' Soft Reset is done after Vconn Swap,
+                        * which returns to ready state
+                        */
+                       if (rx_sop_type == TCPC_TX_SOP_PRIME) {
+                               if (rev < port->negotiated_rev_prime)
+                                       port->negotiated_rev_prime = rev;
+                               tcpm_set_state(port, ready_state(port), 0);
+                               break;
+                       }
                        if (port->pwr_role == TYPEC_SOURCE) {
                                port->upcoming_state = SRC_SEND_CAPABILITIES;
                                tcpm_ams_start(port, POWER_NEGOTIATION);
@@ -2981,6 +3584,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
        const struct pd_message *msg = &event->msg;
        unsigned int cnt = pd_header_cnt_le(msg->header);
        struct tcpm_port *port = event->port;
+       enum tcpm_transmit_type rx_sop_type = event->rx_sop_type;
 
        mutex_lock(&port->lock);
 
@@ -2991,6 +3595,14 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
                enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
                unsigned int msgid = pd_header_msgid_le(msg->header);
 
+               /*
+                * Drop SOP' messages if cannot receive via
+                * tcpm_can_communicate_sop_prime
+                */
+               if (rx_sop_type == TCPC_TX_SOP_PRIME &&
+                   !tcpm_can_communicate_sop_prime(port))
+                       goto done;
+
                /*
                 * USB PD standard, 6.6.1.2:
                 * "... if MessageID value in a received Message is the
@@ -3000,16 +3612,26 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
                 * Message). Note: this shall not apply to the Soft_Reset
                 * Message which always has a MessageID value of zero."
                 */
-               if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET)
-                       goto done;
-               port->rx_msgid = msgid;
+               switch (rx_sop_type) {
+               case TCPC_TX_SOP_PRIME:
+                       if (msgid == port->rx_msgid_prime)
+                               goto done;
+                       port->rx_msgid_prime = msgid;
+                       break;
+               case TCPC_TX_SOP:
+               default:
+                       if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET)
+                               goto done;
+                       port->rx_msgid = msgid;
+                       break;
+               }
 
                /*
                 * If both ends believe to be DFP/host, we have a data role
                 * mismatch.
                 */
                if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) ==
-                   (port->data_role == TYPEC_HOST)) {
+                   (port->data_role == TYPEC_HOST) && rx_sop_type == TCPC_TX_SOP) {
                        tcpm_log(port,
                                 "Data role mismatch, initiating error recovery");
                        tcpm_set_state(port, ERROR_RECOVERY, 0);
@@ -3017,9 +3639,9 @@ 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);
+                               tcpm_pd_ctrl_request(port, msg, rx_sop_type);
                }
        }
 
@@ -3028,7 +3650,8 @@ done:
        kfree(event);
 }
 
-void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg)
+void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg,
+                    enum tcpm_transmit_type rx_sop_type)
 {
        struct pd_rx_event *event;
 
@@ -3038,23 +3661,47 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg)
 
        kthread_init_work(&event->work, tcpm_pd_rx_handler);
        event->port = port;
+       event->rx_sop_type = rx_sop_type;
        memcpy(&event->msg, msg, sizeof(*msg));
        kthread_queue_work(port->wq, &event->work);
 }
 EXPORT_SYMBOL_GPL(tcpm_pd_receive);
 
 static int tcpm_pd_send_control(struct tcpm_port *port,
-                               enum pd_ctrl_msg_type type)
+                               enum pd_ctrl_msg_type type,
+                               enum tcpm_transmit_type tx_sop_type)
 {
        struct pd_message msg;
 
        memset(&msg, 0, sizeof(msg));
-       msg.header = PD_HEADER_LE(type, port->pwr_role,
-                                 port->data_role,
-                                 port->negotiated_rev,
-                                 port->message_id, 0);
+       switch (tx_sop_type) {
+       case TCPC_TX_SOP_PRIME:
+               msg.header = PD_HEADER_LE(type,
+                                         0,    /* Cable Plug Indicator for DFP/UFP */
+                                         0,    /* Reserved */
+                                         port->negotiated_rev,
+                                         port->message_id_prime,
+                                         0);
+               break;
+       case TCPC_TX_SOP:
+               msg.header = PD_HEADER_LE(type,
+                                         port->pwr_role,
+                                         port->data_role,
+                                         port->negotiated_rev,
+                                         port->message_id,
+                                         0);
+               break;
+       default:
+               msg.header = PD_HEADER_LE(type,
+                                         port->pwr_role,
+                                         port->data_role,
+                                         port->negotiated_rev,
+                                         port->message_id,
+                                         0);
+               break;
+       }
 
-       return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+       return tcpm_pd_transmit(port, tx_sop_type, &msg);
 }
 
 /*
@@ -3073,13 +3720,13 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
 
                switch (queued_message) {
                case PD_MSG_CTRL_WAIT:
-                       tcpm_pd_send_control(port, PD_CTRL_WAIT);
+                       tcpm_pd_send_control(port, PD_CTRL_WAIT, TCPC_TX_SOP);
                        break;
                case PD_MSG_CTRL_REJECT:
-                       tcpm_pd_send_control(port, PD_CTRL_REJECT);
+                       tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP);
                        break;
                case PD_MSG_CTRL_NOT_SUPP:
-                       tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP);
+                       tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP);
                        break;
                case PD_MSG_DATA_SINK_CAP:
                        ret = tcpm_pd_send_sink_caps(port);
@@ -3649,6 +4296,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
 
        port->attached = true;
        port->send_discover = true;
+       port->send_discover_prime = false;
 
        return 0;
 
@@ -3665,6 +4313,15 @@ out_disable_mux:
 
 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);
@@ -3676,14 +4333,20 @@ static void tcpm_typec_disconnect(struct tcpm_port *port)
 static void tcpm_unregister_altmodes(struct tcpm_port *port)
 {
        struct pd_mode_data *modep = &port->mode_data;
+       struct pd_mode_data *modep_prime = &port->mode_data_prime;
        int i;
 
        for (i = 0; i < modep->altmodes; i++) {
                typec_unregister_altmode(port->partner_altmode[i]);
                port->partner_altmode[i] = NULL;
        }
+       for (i = 0; i < modep_prime->altmodes; i++) {
+               typec_unregister_altmode(port->plug_prime_altmode[i]);
+               port->plug_prime_altmode[i] = NULL;
+       }
 
        memset(modep, 0, sizeof(*modep));
+       memset(modep_prime, 0, sizeof(*modep_prime));
 }
 
 static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable)
@@ -3712,6 +4375,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
         * we can check tcpm_pd_rx_handler() if we had seen it before.
         */
        port->rx_msgid = -1;
+       port->rx_msgid_prime = -1;
 
        port->tcpc->set_pd_rx(port->tcpc, false);
        tcpm_init_vbus(port);   /* also disables charging */
@@ -3783,6 +4447,7 @@ static int tcpm_snk_attach(struct tcpm_port *port)
 
        port->attached = true;
        port->send_discover = true;
+       port->send_discover_prime = false;
 
        return 0;
 }
@@ -4023,8 +4688,11 @@ static void run_state_machine(struct tcpm_port *port)
                port->pwr_opmode = TYPEC_PWR_MODE_USB;
                port->caps_count = 0;
                port->negotiated_rev = PD_MAX_REV;
+               port->negotiated_rev_prime = PD_MAX_REV;
                port->message_id = 0;
+               port->message_id_prime = 0;
                port->rx_msgid = -1;
+               port->rx_msgid_prime = -1;
                port->explicit_contract = false;
                /* SNK -> SRC POWER/FAST_ROLE_SWAP finished */
                if (port->ams == POWER_ROLE_SWAP ||
@@ -4045,8 +4713,12 @@ static void run_state_machine(struct tcpm_port *port)
                }
                ret = tcpm_pd_send_source_caps(port);
                if (ret < 0) {
-                       tcpm_set_state(port, SRC_SEND_CAPABILITIES,
-                                      PD_T_SEND_SOURCE_CAP);
+                       if (tcpm_can_communicate_sop_prime(port) &&
+                           IS_ERR_OR_NULL(port->cable))
+                               tcpm_set_state(port, SRC_VDM_IDENTITY_REQUEST, 0);
+                       else
+                               tcpm_set_state(port, SRC_SEND_CAPABILITIES,
+                                              PD_T_SEND_SOURCE_CAP);
                } else {
                        /*
                         * Per standard, we should clear the reset counter here.
@@ -4087,7 +4759,7 @@ static void run_state_machine(struct tcpm_port *port)
        case SRC_NEGOTIATE_CAPABILITIES:
                ret = tcpm_pd_check_request(port);
                if (ret < 0) {
-                       tcpm_pd_send_control(port, PD_CTRL_REJECT);
+                       tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP);
                        if (!port->explicit_contract) {
                                tcpm_set_state(port,
                                               SRC_WAIT_NEW_CAPABILITIES, 0);
@@ -4095,7 +4767,7 @@ static void run_state_machine(struct tcpm_port *port)
                                tcpm_set_state(port, SRC_READY, 0);
                        }
                } else {
-                       tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+                       tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
                        tcpm_set_partner_usb_comm_capable(port,
                                                          !!(port->sink_request & RDO_USB_COMM));
                        tcpm_set_state(port, SRC_TRANSITION_SUPPLY,
@@ -4104,7 +4776,7 @@ static void run_state_machine(struct tcpm_port *port)
                break;
        case SRC_TRANSITION_SUPPLY:
                /* XXX: regulator_set_voltage(vbus, ...) */
-               tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+               tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP);
                port->explicit_contract = true;
                typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD);
                port->pwr_opmode = TYPEC_PWR_MODE_PD;
@@ -4141,14 +4813,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;
                }
 
                /*
@@ -4264,8 +4945,11 @@ static void run_state_machine(struct tcpm_port *port)
                typec_set_pwr_opmode(port->typec_port, opmode);
                port->pwr_opmode = TYPEC_PWR_MODE_USB;
                port->negotiated_rev = PD_MAX_REV;
+               port->negotiated_rev_prime = PD_MAX_REV;
                port->message_id = 0;
+               port->message_id_prime = 0;
                port->rx_msgid = -1;
+               port->rx_msgid_prime = -1;
                port->explicit_contract = false;
 
                if (port->ams == POWER_ROLE_SWAP ||
@@ -4437,14 +5121,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);
@@ -4485,6 +5178,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);
@@ -4586,7 +5280,7 @@ static void run_state_machine(struct tcpm_port *port)
                /* remove existing capabilities */
                usb_power_delivery_unregister_capabilities(port->partner_source_caps);
                port->partner_source_caps = NULL;
-               tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+               tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
                tcpm_ams_finish(port);
                if (port->pwr_role == TYPEC_SOURCE) {
                        port->upcoming_state = SRC_SEND_CAPABILITIES;
@@ -4603,35 +5297,53 @@ static void run_state_machine(struct tcpm_port *port)
                tcpm_ams_start(port, SOFT_RESET_AMS);
                break;
        case SOFT_RESET_SEND:
-               port->message_id = 0;
-               port->rx_msgid = -1;
-               /* remove existing capabilities */
-               usb_power_delivery_unregister_capabilities(port->partner_source_caps);
-               port->partner_source_caps = NULL;
-               if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET))
-                       tcpm_set_state_cond(port, hard_reset_state(port), 0);
-               else
-                       tcpm_set_state_cond(port, hard_reset_state(port),
-                                           PD_T_SENDER_RESPONSE);
+               /*
+                * Power Delivery 3.0 Section 6.3.13
+                *
+                * A Soft_Reset Message Shall be targeted at a specific entity
+                * depending on the type of SOP* packet used.
+                */
+               if (port->tx_sop_type == TCPC_TX_SOP_PRIME) {
+                       port->message_id_prime = 0;
+                       port->rx_msgid_prime = -1;
+                       tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP_PRIME);
+                       tcpm_set_state_cond(port, ready_state(port), PD_T_SENDER_RESPONSE);
+               } else {
+                       port->message_id = 0;
+                       port->rx_msgid = -1;
+                       /* remove existing capabilities */
+                       usb_power_delivery_unregister_capabilities(port->partner_source_caps);
+                       port->partner_source_caps = NULL;
+                       if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP))
+                               tcpm_set_state_cond(port, hard_reset_state(port), 0);
+                       else
+                               tcpm_set_state_cond(port, hard_reset_state(port),
+                                                   PD_T_SENDER_RESPONSE);
+               }
                break;
 
        /* DR_Swap states */
        case DR_SWAP_SEND:
-               tcpm_pd_send_control(port, PD_CTRL_DR_SWAP);
-               if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20)
+               tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP);
+               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);
-               if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20)
+               tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
+               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;
@@ -4648,7 +5360,7 @@ static void run_state_machine(struct tcpm_port *port)
                break;
 
        case FR_SWAP_SEND:
-               if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP)) {
+               if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP, TCPC_TX_SOP)) {
                        tcpm_set_state(port, ERROR_RECOVERY, 0);
                        break;
                }
@@ -4668,7 +5380,7 @@ static void run_state_machine(struct tcpm_port *port)
                break;
        case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
                tcpm_set_pwr_role(port, TYPEC_SOURCE);
-               if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) {
+               if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) {
                        tcpm_set_state(port, ERROR_RECOVERY, 0);
                        break;
                }
@@ -4678,11 +5390,11 @@ static void run_state_machine(struct tcpm_port *port)
 
        /* PR_Swap states */
        case PR_SWAP_ACCEPT:
-               tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+               tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
                tcpm_set_state(port, PR_SWAP_START, 0);
                break;
        case PR_SWAP_SEND:
-               tcpm_pd_send_control(port, PD_CTRL_PR_SWAP);
+               tcpm_pd_send_control(port, PD_CTRL_PR_SWAP, TCPC_TX_SOP);
                tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT,
                                    PD_T_SENDER_RESPONSE);
                break;
@@ -4724,7 +5436,7 @@ static void run_state_machine(struct tcpm_port *port)
                 * supply is turned off"
                 */
                tcpm_set_pwr_role(port, TYPEC_SINK);
-               if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) {
+               if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) {
                        tcpm_set_state(port, ERROR_RECOVERY, 0);
                        break;
                }
@@ -4771,17 +5483,17 @@ static void run_state_machine(struct tcpm_port *port)
                 * Source."
                 */
                tcpm_set_pwr_role(port, TYPEC_SOURCE);
-               tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+               tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP);
                tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START);
                break;
 
        case VCONN_SWAP_ACCEPT:
-               tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+               tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
                tcpm_ams_finish(port);
                tcpm_set_state(port, VCONN_SWAP_START, 0);
                break;
        case VCONN_SWAP_SEND:
-               tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP);
+               tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP, TCPC_TX_SOP);
                tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT,
                               PD_T_SENDER_RESPONSE);
                break;
@@ -4800,14 +5512,34 @@ static void run_state_machine(struct tcpm_port *port)
                               PD_T_VCONN_SOURCE_ON);
                break;
        case VCONN_SWAP_TURN_ON_VCONN:
-               tcpm_set_vconn(port, true);
-               tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
-               tcpm_set_state(port, ready_state(port), 0);
+               ret = tcpm_set_vconn(port, true);
+               tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP);
+               /*
+                * USB PD 3.0 Section 6.4.4.3.1
+                *
+                * Note that a Cable Plug or VPD will not be ready for PD
+                * Communication until tVCONNStable after VCONN has been applied
+                */
+               if (!ret)
+                       tcpm_set_state(port, VCONN_SWAP_SEND_SOFT_RESET,
+                                      PD_T_VCONN_STABLE);
+               else
+                       tcpm_set_state(port, ready_state(port), 0);
                break;
        case VCONN_SWAP_TURN_OFF_VCONN:
                tcpm_set_vconn(port, false);
                tcpm_set_state(port, ready_state(port), 0);
                break;
+       case VCONN_SWAP_SEND_SOFT_RESET:
+               tcpm_swap_complete(port, port->swap_status);
+               if (tcpm_can_communicate_sop_prime(port)) {
+                       port->tx_sop_type = TCPC_TX_SOP_PRIME;
+                       port->upcoming_state = SOFT_RESET_SEND;
+                       tcpm_ams_start(port, SOFT_RESET_AMS);
+               } else {
+                       tcpm_set_state(port, ready_state(port), 0);
+               }
+               break;
 
        case DR_SWAP_CANCEL:
        case PR_SWAP_CANCEL:
@@ -4843,7 +5575,7 @@ static void run_state_machine(struct tcpm_port *port)
                }
                break;
        case GET_STATUS_SEND:
-               tcpm_pd_send_control(port, PD_CTRL_GET_STATUS);
+               tcpm_pd_send_control(port, PD_CTRL_GET_STATUS, TCPC_TX_SOP);
                tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT,
                               PD_T_SENDER_RESPONSE);
                break;
@@ -4851,7 +5583,7 @@ static void run_state_machine(struct tcpm_port *port)
                tcpm_set_state(port, ready_state(port), 0);
                break;
        case GET_PPS_STATUS_SEND:
-               tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS);
+               tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS, TCPC_TX_SOP);
                tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT,
                               PD_T_SENDER_RESPONSE);
                break;
@@ -4859,7 +5591,7 @@ static void run_state_machine(struct tcpm_port *port)
                tcpm_set_state(port, ready_state(port), 0);
                break;
        case GET_SINK_CAP:
-               tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP);
+               tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP, TCPC_TX_SOP);
                tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE);
                break;
        case GET_SINK_CAP_TIMEOUT:
@@ -4902,9 +5634,18 @@ static void run_state_machine(struct tcpm_port *port)
 
        /* Chunk state */
        case CHUNK_NOT_SUPP:
-               tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP);
+               tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP);
                tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? SRC_READY : SNK_READY, 0);
                break;
+
+       /* Cable states */
+       case SRC_VDM_IDENTITY_REQUEST:
+               port->send_discover_prime = true;
+               port->tx_sop_type = TCPC_TX_SOP_PRIME;
+               mod_send_discover_delayed_work(port, 0);
+               port->upcoming_state = SRC_SEND_CAPABILITIES;
+               break;
+
        default:
                WARN(1, "Unexpected port state %d\n", port->state);
                break;
@@ -5596,7 +6337,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;
@@ -5619,21 +6361,23 @@ 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;
        }
 
        /* Retry if the port is not idle */
-       if ((port->state != SRC_READY && port->state != SNK_READY) || port->vdm_sm_running) {
+       if ((port->state != SRC_READY && port->state != SNK_READY &&
+            port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) {
                mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS);
                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);
@@ -6860,6 +7604,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
        typec_port_register_altmodes(port->typec_port,
                                     &tcpm_altmode_ops, port,
                                     port->port_altmode, ALTMODE_DISCOVERY_MAX);
+       typec_port_register_cable_ops(port->port_altmode, ARRAY_SIZE(port->port_altmode),
+                                     &tcpm_cable_ops);
        port->registered = true;
 
        mutex_lock(&port->lock);