linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers
@ 2023-12-14 23:08 RD Babiera
  2023-12-14 23:08 ` [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode RD Babiera
                   ` (11 more replies)
  0 siblings, 12 replies; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Extend the TCPM's functionality to include support for SOP' messages.
This feature is opt-in: TCPCI chip drivers opt into sending and receiving
SOP' messages. TCPCI drivers will also be expected to take the SOP frame
type in order to process SOP' messages within the TCPM. Specifically,
the exisiting API tcpm_pd_receive now takes tcpm_transmit_type as input.
The Maxim TCPCI implements this in Patch 4.

Discover Identity, Discover SVIDs, Discover Modes, and Alt Mode SVDM
support are included within the patchset. Because the port is expected to
be the Vconn source in order to communicate with the cable, TCPCI chip
drivers opt into performing a Vconn swap after Discover Identity on SOP
before performing Discover Identity on SOP'.

typec_cable_ops are defined to facilitate communication between the alt
mode drivers and the cable plugs. 2 new apis allow the alt mode drivers
to enter and exit mode on active cable plugs. A third is used by alt mode
drivers to send VDMs to the cable plugs or by the TCPM to return the
resulting VDM from the cable plug to the alt mode drivers.
---
Changes since v1:
* Add typec_cable_ops as replacement for changing typec_altmode_ops
  interface. Displayport driver patch now reflects this
* Separate patch for cable SVDM versioning.
* Separate patch for tcpm_pd_receive() api changes and cable_comm_capable
  addition to tcpci.
* Separate patches for Discover SVIDs/Discover Modes and Alt Mode
  operations on SOP'.

RD Babiera (12):
  usb: typec: altmodes: add typec_cable_ops to typec_altmode
  usb: typec: altmodes: add svdm version info for typec cables
  usb: typec: tcpci: add cable_comm_capable attribute
  usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive
  usb: typec: tcpm: process receive and transmission of sop' messages
  usb: typec: tcpm: add control message support to sop'
  usb: typec: tcpci: add attempt_vconn_swap_discovery callback
  usb: typec: tcpm: add discover identity support for SOP'
  usb: typec: tcpm: add state machine support for
    SRC_VDM_IDENTITY_REQUEST
  usb: typec: tcpm: add discover svids and discover modes support for
    sop'
  usb: typec: tcpm: add alt mode enter/exit/vdm support for sop'
  usb: typec: altmodes/displayport: add SOP' support

 drivers/usb/typec/altmodes/displayport.c      |  161 ++-
 drivers/usb/typec/bus.c                       |  102 ++
 drivers/usb/typec/class.c                     |   59 +
 drivers/usb/typec/class.h                     |    1 +
 drivers/usb/typec/tcpm/fusb302.c              |    2 +-
 .../typec/tcpm/qcom/qcom_pmic_typec_pdphy.c   |    2 +-
 drivers/usb/typec/tcpm/tcpci.c                |   26 +-
 drivers/usb/typec/tcpm/tcpci_maxim.h          |    1 +
 drivers/usb/typec/tcpm/tcpci_maxim_core.c     |   38 +-
 drivers/usb/typec/tcpm/tcpm.c                 | 1043 ++++++++++++++---
 drivers/usb/typec/tcpm/wcove.c                |    2 +-
 include/linux/usb/pd.h                        |    1 +
 include/linux/usb/pd_vdo.h                    |    8 +-
 include/linux/usb/tcpci.h                     |   13 +
 include/linux/usb/tcpm.h                      |   16 +-
 include/linux/usb/typec.h                     |    7 +
 include/linux/usb/typec_altmode.h             |   30 +
 17 files changed, 1346 insertions(+), 166 deletions(-)

-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-16  2:21   ` kernel test robot
  2023-12-17 15:11   ` kernel test robot
  2023-12-14 23:08 ` [PATCH v2 02/12] usb: typec: altmodes: add svdm version info for typec cables RD Babiera
                   ` (10 subsequent siblings)
  11 siblings, 2 replies; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add typec_cable_ops struct for enter, exit, and vdm. The struct is added
to typec_altmode so port alt modes can have access to partner and cable
specific callbacks, and alt mode drivers can specify operations over SOP'
and SOP'' without modifying the existing API.

typec_port_register_cable_ops is added as a new symbol for port drivers
to use to register cable operations to their registered port alt modes.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/bus.c           | 102 ++++++++++++++++++++++++++++++
 drivers/usb/typec/class.c         |  19 ++++++
 include/linux/usb/typec.h         |   4 ++
 include/linux/usb/typec_altmode.h |  20 ++++++
 4 files changed, 145 insertions(+)

diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c
index e95ec7e382bb..009447082fd5 100644
--- a/drivers/usb/typec/bus.c
+++ b/drivers/usb/typec/bus.c
@@ -244,6 +244,108 @@ typec_altmode_get_partner(struct typec_altmode *adev)
 }
 EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
 
+/* -------------------------------------------------------------------------- */
+/* API for cable alternate modes */
+
+/**
+ * typec_altmode_enter - Enter Mode
+ * @adev: The alternate mode
+ * @sop: Cable plug target for Enter Mode command
+ * @vdo: VDO for the Enter Mode command
+ *
+ * Alternate mode drivers use this function to enter mode on the cable plug.
+ * If the alternate mode does not require VDO, @vdo must be NULL.
+ */
+int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo)
+{
+	struct altmode *partner = to_altmode(adev)->partner;
+	struct typec_altmode *pdev;
+
+	if (!adev || adev->active)
+		return 0;
+
+	if (!partner)
+		return -ENODEV;
+
+	pdev = &partner->adev;
+
+	if (!pdev->active)
+		return -EPERM;
+
+	if (!pdev->cable_ops || !pdev->cable_ops->enter)
+		return -EOPNOTSUPP;
+
+	return pdev->cable_ops->enter(pdev, sop, vdo);
+}
+EXPORT_SYMBOL_GPL(typec_cable_altmode_enter);
+
+/**
+ * typec_altmode_exit - Exit Mode
+ * @adev: The alternate mode
+ * @sop: Cable plug target for Exit Mode command
+ *
+ * The alternate mode drivers use this function to exit mode on the cable plug.
+ */
+int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop)
+{
+	struct altmode *partner = to_altmode(adev)->partner;
+	struct typec_altmode *pdev;
+
+	if (!adev || !adev->active)
+		return 0;
+
+	if (!partner)
+		return -ENODEV;
+
+	pdev = &partner->adev;
+
+	if (!pdev->cable_ops || !pdev->cable_ops->exit)
+		return -EOPNOTSUPP;
+
+	return pdev->cable_ops->exit(pdev, sop);
+}
+EXPORT_SYMBOL_GPL(typec_cable_altmode_exit);
+
+/**
+ * typec_altmode_vdm - Send Vendor Defined Messages (VDM) between the cable plug and port.
+ * @adev: Alternate mode handle
+ * @sop: Cable plug target for VDM
+ * @header: VDM Header
+ * @vdo: Array of Vendor Defined Data Objects
+ * @count: Number of Data Objects
+ *
+ * The alternate mode drivers use this function for SVID specific communication
+ * with the cable plugs. The port drivers use it to deliver the Structured VDMs
+ * received from the cable plugs to the alternate mode drivers.
+ */
+int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index sop,
+			    const u32 header, const u32 *vdo, int count)
+{
+	struct altmode *altmode;
+	struct typec_altmode *pdev;
+
+	if (!adev)
+		return 0;
+
+	altmode = to_altmode(adev);
+
+	if (is_typec_plug(adev->dev.parent)) {
+		if (!altmode->partner)
+			return -ENODEV;
+		pdev = &altmode->partner->adev;
+	} else {
+		if (!altmode->plug[sop])
+			return -ENODEV;
+		pdev = &altmode->plug[sop]->adev;
+	}
+
+	if (!pdev->cable_ops || !pdev->cable_ops->vdm)
+		return -EOPNOTSUPP;
+
+	return pdev->cable_ops->vdm(pdev, sop, header, vdo, count);
+}
+EXPORT_SYMBOL_GPL(typec_cable_altmode_vdm);
+
 /* -------------------------------------------------------------------------- */
 /* API for the alternate mode drivers */
 
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 16a670828dde..86b5a8414b89 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -2277,6 +2277,25 @@ void typec_port_register_altmodes(struct typec_port *port,
 }
 EXPORT_SYMBOL_GPL(typec_port_register_altmodes);
 
+/**
+ * typec_port_register_cable_ops - Register typec_cable_ops to port altmodes
+ * @altmodes: USB Type-C Port's altmode vector
+ * @max_altmodes: The maximum number of alt modes supported by the port
+ * @ops: Cable alternate mode vector
+ */
+void typec_port_register_cable_ops(struct typec_altmode **altmodes, int max_altmodes,
+				   const struct typec_cable_ops *ops)
+{
+	int i;
+
+	for (i = 0; i < max_altmodes; i++) {
+		if (!altmodes[i])
+			return;
+		altmodes[i]->cable_ops = ops;
+	}
+}
+EXPORT_SYMBOL_GPL(typec_port_register_cable_ops);
+
 /**
  * typec_register_port - Register a USB Type-C Port
  * @parent: Parent device
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index a05d6f6f2536..38f93d72fd1b 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -18,6 +18,7 @@ struct typec_cable;
 struct typec_plug;
 struct typec_port;
 struct typec_altmode_ops;
+struct typec_cable_ops;
 
 struct fwnode_handle;
 struct device;
@@ -157,6 +158,9 @@ void typec_port_register_altmodes(struct typec_port *port,
 	const struct typec_altmode_ops *ops, void *drvdata,
 	struct typec_altmode **altmodes, size_t n);
 
+void typec_port_register_cable_ops(struct typec_altmode **altmodes, int max_altmodes,
+				   const struct typec_cable_ops *ops);
+
 void typec_unregister_altmode(struct typec_altmode *altmode);
 
 struct typec_port *typec_altmode2port(struct typec_altmode *alt);
diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h
index 28aeef8f9e7b..72ec8058543a 100644
--- a/include/linux/usb/typec_altmode.h
+++ b/include/linux/usb/typec_altmode.h
@@ -20,6 +20,7 @@ struct typec_altmode_ops;
  * @active: Tells has the mode been entered or not
  * @desc: Optional human readable description of the mode
  * @ops: Operations vector from the driver
+ * @cable_ops: Cable operations vector from the driver.
  */
 struct typec_altmode {
 	struct device			dev;
@@ -30,6 +31,7 @@ struct typec_altmode {
 
 	char				*desc;
 	const struct typec_altmode_ops	*ops;
+	const struct typec_cable_ops	*cable_ops;
 };
 
 #define to_typec_altmode(d) container_of(d, struct typec_altmode, dev)
@@ -75,6 +77,24 @@ int typec_altmode_notify(struct typec_altmode *altmode, unsigned long conf,
 const struct typec_altmode *
 typec_altmode_get_partner(struct typec_altmode *altmode);
 
+/**
+ * struct typec_cable_ops - Cable alternate mode operations vector
+ * @enter: Operations to be executed with Enter Mode Command
+ * @exit: Operations to be executed with Exit Mode Command
+ * @vdm: Callback for SVID specific commands
+ */
+struct typec_cable_ops {
+	int (*enter)(struct typec_altmode *altmode, enum typec_plug_index sop, u32 *vdo);
+	int (*exit)(struct typec_altmode *altmode, enum typec_plug_index sop);
+	int (*vdm)(struct typec_altmode *altmode, enum typec_plug_index sop,
+		   const u32 hdr, const u32 *vdo, int cnt);
+};
+
+int typec_cable_altmode_enter(struct typec_altmode *altmode, enum typec_plug_index sop, u32 *vdo);
+int typec_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_index sop);
+int typec_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop,
+			    const u32 header, const u32 *vdo, int count);
+
 /*
  * These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C
  * Specification. SVID specific connector states are expected to follow and
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 02/12] usb: typec: altmodes: add svdm version info for typec cables
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
  2023-12-14 23:08 ` [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 13:54   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 03/12] usb: typec: tcpci: add cable_comm_capable attribute RD Babiera
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add typec_cable_set_svdm_version and typec_get_cable_svdm version symbols.
Cables can operate under a lower PD revision than the port partner, and the
max SVDM version is tied to the PD revision. So, typec_cable maintains its
own svdm_version.

Add typec_altmode_get_cable_svdm_version to return the cable's negotiated
svdm_version for altmode drivers to use.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/class.c         | 40 +++++++++++++++++++++++++++++++
 drivers/usb/typec/class.h         |  1 +
 include/linux/usb/typec.h         |  3 +++
 include/linux/usb/typec_altmode.h | 10 ++++++++
 4 files changed, 54 insertions(+)

diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 86b5a8414b89..038c6498e683 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -2129,6 +2129,46 @@ int typec_get_negotiated_svdm_version(struct typec_port *port)
 }
 EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version);
 
+/**
+ * typec_get_cable_svdm_version - Get cable negotiated SVDM Version
+ * @port: USB Type-C Port.
+ *
+ * Get the negotiated SVDM Version for the cable. The Version is set to the port
+ * default value based on the PD Revision during cable registration, and updated
+ * after a successful Discover Identity if the negotiated value is less than the
+ * default.
+ *
+ * Returns usb_pd_svdm_ver if the cable has been registered otherwise -ENODEV.
+ */
+int typec_get_cable_svdm_version(struct typec_port *port)
+{
+	enum usb_pd_svdm_ver svdm_version;
+	struct device *cable_dev;
+
+	cable_dev = device_find_child(&port->dev, NULL, cable_match);
+	if (!cable_dev)
+		return -ENODEV;
+
+	svdm_version = to_typec_cable(cable_dev)->svdm_version;
+	put_device(cable_dev);
+
+	return svdm_version;
+}
+EXPORT_SYMBOL_GPL(typec_get_cable_svdm_version);
+
+/**
+ * typec_cable_set_svdm_version - Set negotiated Structured VDM (SVDM) Version
+ * @cable: USB Type-C Active Cable that supports SVDM
+ * @svdm_version: Negotiated SVDM Version
+ *
+ * This routine is used to save the negotiated SVDM Version.
+ */
+void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version)
+{
+	cable->svdm_version = svdm_version;
+}
+EXPORT_SYMBOL_GPL(typec_cable_set_svdm_version);
+
 /**
  * typec_get_drvdata - Return private driver data pointer
  * @port: USB Type-C port
diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h
index c36761ba3f59..759b98355eeb 100644
--- a/drivers/usb/typec/class.h
+++ b/drivers/usb/typec/class.h
@@ -23,6 +23,7 @@ struct typec_cable {
 	struct usb_pd_identity		*identity;
 	unsigned int			active:1;
 	u16				pd_revision; /* 0300H = "3.0" */
+	enum usb_pd_svdm_ver		svdm_version;
 };
 
 struct typec_partner {
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 38f93d72fd1b..b35b427561ab 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -337,6 +337,9 @@ void typec_partner_set_svdm_version(struct typec_partner *partner,
 				    enum usb_pd_svdm_ver svdm_version);
 int typec_get_negotiated_svdm_version(struct typec_port *port);
 
+int typec_get_cable_svdm_version(struct typec_port *port);
+void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version);
+
 struct usb_power_delivery *typec_partner_usb_power_delivery_register(struct typec_partner *partner,
 							struct usb_power_delivery_desc *desc);
 
diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h
index 72ec8058543a..b3c0866ea70f 100644
--- a/include/linux/usb/typec_altmode.h
+++ b/include/linux/usb/typec_altmode.h
@@ -95,6 +95,16 @@ int typec_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_inde
 int typec_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop,
 			    const u32 header, const u32 *vdo, int count);
 
+/**
+ * typec_altmode_get_cable_svdm_version - Get negotiated SVDM version for cable plug
+ * @altmode: Handle to the alternate mode
+ */
+static inline int
+typec_altmode_get_cable_svdm_version(struct typec_altmode *altmode)
+{
+	return typec_get_cable_svdm_version(typec_altmode2port(altmode));
+}
+
 /*
  * These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C
  * Specification. SVID specific connector states are expected to follow and
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 03/12] usb: typec: tcpci: add cable_comm_capable attribute
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
  2023-12-14 23:08 ` [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode RD Babiera
  2023-12-14 23:08 ` [PATCH v2 02/12] usb: typec: altmodes: add svdm version info for typec cables RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 13:55   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 04/12] usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive RD Babiera
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add cable_comm_capable to tcpci_data for tcpci drivers to indicate that
the port tcpc is capable of communicating to cables over SOP. A
corresponding tcpci callback tcpci_cable_comm_capable returns this value.
The tcpm will primarily use this in later patches to determine if the port
can transmit and receive SOP' messages.

Maxim based tcpci drivers are capable of SOP' communication, so the
cable_comm_capable flag is set to true.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
Changes since v1:
* Moved tcpm_pd_receive changes to separate patch
---
 drivers/usb/typec/tcpm/tcpci.c            | 8 ++++++++
 drivers/usb/typec/tcpm/tcpci_maxim_core.c | 1 +
 include/linux/usb/tcpci.h                 | 3 +++
 include/linux/usb/tcpm.h                  | 4 ++++
 4 files changed, 16 insertions(+)

diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index 0ee3e6e29bb1..1ededbcecc09 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -584,6 +584,13 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type
 	return 0;
 }
 
+static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+
+	return tcpci->data->cable_comm_capable;
+}
+
 static int tcpci_init(struct tcpc_dev *tcpc)
 {
 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -793,6 +800,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
 	tcpci->tcpc.enable_frs = tcpci_enable_frs;
 	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
 	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
+	tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable;
 
 	if (tcpci->data->check_contaminant)
 		tcpci->tcpc.check_contaminant = tcpci_check_contaminant;
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
index 7fb966fd639b..7b2d4e6e52a2 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
+++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
@@ -478,6 +478,7 @@ static int max_tcpci_probe(struct i2c_client *client)
 	chip->data.vbus_vsafe0v = true;
 	chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable;
 	chip->data.check_contaminant = max_tcpci_check_contaminant;
+	chip->data.cable_comm_capable = true;
 
 	max_tcpci_init_regs(chip);
 	chip->tcpci = tcpci_register_port(chip->dev, &chip->data);
diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
index 467e8045e9f8..1d0b849defd0 100644
--- a/include/linux/usb/tcpci.h
+++ b/include/linux/usb/tcpci.h
@@ -198,12 +198,15 @@ struct tcpci;
  *		Chip level drivers are expected to check for contaminant and call
  *		tcpm_clean_port when the port is clean to put the port back into
  *		toggling state.
+ * @cable_comm_capable
+ *		optional; Set when TCPC can communicate with cable plugs over SOP'
  */
 struct tcpci_data {
 	struct regmap *regmap;
 	unsigned char TX_BUF_BYTE_x_hidden:1;
 	unsigned char auto_discharge_disconnect:1;
 	unsigned char vbus_vsafe0v:1;
+	unsigned char cable_comm_capable:1;
 
 	int (*init)(struct tcpci *tcpci, struct tcpci_data *data);
 	int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data,
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 65fac5e1f317..430fa3ec69bb 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -119,6 +119,9 @@ enum tcpm_transmit_type {
  *		at the end of the deboumce period or when the port is still
  *		toggling. Chip level drivers are expected to check for contaminant
  *		and call tcpm_clean_port when the port is clean.
+ * @cable_comm_capable
+ *		Optional; Returns whether cable communication over SOP' is supported
+ *		by the tcpc
  */
 struct tcpc_dev {
 	struct fwnode_handle *fwnode;
@@ -154,6 +157,7 @@ struct tcpc_dev {
 	bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev);
 	void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable);
 	void (*check_contaminant)(struct tcpc_dev *dev);
+	bool (*cable_comm_capable)(struct tcpc_dev *dev);
 };
 
 struct tcpm_port;
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 04/12] usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (2 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 03/12] usb: typec: tcpci: add cable_comm_capable attribute RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 13:59   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages RD Babiera
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

tcpm_pd_receive adds the SOP type as a parameter, and passes it within the
pd_rx_event struct for tcpm_pd_rx_handler to use. For now, the handler
drops all SOP' messages.

Maxim based tcpci drivers are capable of SOP' communication, so process_rx
now takes the SOP type into account and passes the value to
tcpm_pd_receive.

tcpci_set_pd_rx now utilizes the cable_comm_capable flag to determine if
TCPC_RX_DETECT_SOP1 should be added to the bitfield when enabling PD
message reception.

For all other consumers of tcpm_pd_receive, default the new field to
TCPC_TX_SOP.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/tcpm/fusb302.c              |  2 +-
 .../typec/tcpm/qcom/qcom_pmic_typec_pdphy.c   |  2 +-
 drivers/usb/typec/tcpm/tcpci.c                |  7 +++++--
 drivers/usb/typec/tcpm/tcpci_maxim_core.c     | 20 ++++++++++++++++---
 drivers/usb/typec/tcpm/tcpm.c                 | 10 +++++++++-
 drivers/usb/typec/tcpm/wcove.c                |  2 +-
 include/linux/usb/tcpci.h                     |  1 +
 include/linux/usb/tcpm.h                      |  3 ++-
 8 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
index bc21006e979c..ef18a448b740 100644
--- a/drivers/usb/typec/tcpm/fusb302.c
+++ b/drivers/usb/typec/tcpm/fusb302.c
@@ -1467,7 +1467,7 @@ static int fusb302_pd_read_message(struct fusb302_chip *chip,
 	if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC))
 		tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS);
 	else
-		tcpm_pd_receive(chip->tcpm_port, msg);
+		tcpm_pd_receive(chip->tcpm_port, msg, TCPC_TX_SOP);
 
 	return ret;
 }
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
index 52c81378e36e..a3154085ae32 100644
--- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
@@ -299,7 +299,7 @@ static void qcom_pmic_typec_pdphy_pd_receive(struct pmic_typec_pdphy *pmic_typec
 
 	if (!ret) {
 		dev_vdbg(dev, "pd_receive: handing %d bytes to tcpm\n", size);
-		tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg);
+		tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg, TCPC_TX_SOP);
 	}
 }
 
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index 1ededbcecc09..8ea4ed159a13 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -445,8 +445,11 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
 	unsigned int reg = 0;
 	int ret;
 
-	if (enable)
+	if (enable) {
 		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
+		if (tcpci->data->cable_comm_capable)
+			reg |= TCPC_RX_DETECT_SOP1;
+	}
 	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
 	if (ret < 0)
 		return ret;
@@ -719,7 +722,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
 		/* Read complete, clear RX status alert bit */
 		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
 
-		tcpm_pd_receive(tcpci->port, &msg);
+		tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP);
 	}
 
 	if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) {
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
index 7b2d4e6e52a2..f9f838df43f7 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
+++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
@@ -128,6 +128,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status)
 	u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN];
 	int ret, payload_index;
 	u8 *rx_buf_ptr;
+	enum tcpm_transmit_type rx_type;
 
 	/*
 	 * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers
@@ -143,10 +144,23 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status)
 	count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET];
 	frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET];
 
-	if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) {
+	switch (frame_type) {
+	case TCPC_RX_BUF_FRAME_TYPE_SOP1:
+		rx_type = TCPC_TX_SOP_PRIME;
+		break;
+	case TCPC_RX_BUF_FRAME_TYPE_SOP:
+		rx_type = TCPC_TX_SOP;
+		break;
+	default:
+		rx_type = TCPC_TX_SOP;
+		break;
+	}
+
+	if (count == 0 || (frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP &&
+	    frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP1)) {
 		max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
 		dev_err(chip->dev, "%s\n", count ==  0 ? "error: count is 0" :
-			"error frame_type is not SOP");
+			"error frame_type is not SOP/SOP'");
 		return;
 	}
 
@@ -183,7 +197,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status)
 	if (ret < 0)
 		return;
 
-	tcpm_pd_receive(chip->port, &msg);
+	tcpm_pd_receive(chip->port, &msg, rx_type);
 }
 
 static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink)
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index ff67553b6932..b05325dcd7ac 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -506,6 +506,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[] = {
@@ -2969,12 +2970,17 @@ 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);
 
 	tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header),
 		 port->attached);
 
+	/* Ignore SOP' for now */
+	if (rx_sop_type == TCPC_TX_SOP_PRIME)
+		goto done;
+
 	if (port->attached) {
 		enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
 		unsigned int msgid = pd_header_msgid_le(msg->header);
@@ -3016,7 +3022,8 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
 	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;
 
@@ -3026,6 +3033,7 @@ 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);
 }
diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c
index 87d4abde0ea2..cf719307b3f6 100644
--- a/drivers/usb/typec/tcpm/wcove.c
+++ b/drivers/usb/typec/tcpm/wcove.c
@@ -535,7 +535,7 @@ static irqreturn_t wcove_typec_irq(int irq, void *data)
 				goto err;
 			}
 
-			tcpm_pd_receive(wcove->tcpm, &msg);
+			tcpm_pd_receive(wcove->tcpm, &msg, TCPC_TX_SOP);
 
 			ret = regmap_read(wcove->regmap, USBC_RXSTATUS,
 					  &status);
diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
index 1d0b849defd0..9ed6d62c9c5f 100644
--- a/include/linux/usb/tcpci.h
+++ b/include/linux/usb/tcpci.h
@@ -145,6 +145,7 @@
 #define TCPC_RX_BYTE_CNT		0x30
 #define TCPC_RX_BUF_FRAME_TYPE		0x31
 #define TCPC_RX_BUF_FRAME_TYPE_SOP	0
+#define TCPC_RX_BUF_FRAME_TYPE_SOP1	1
 #define TCPC_RX_HDR			0x32
 #define TCPC_RX_DATA			0x34 /* through 0x4f */
 
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 430fa3ec69bb..41d1ac9c8bbf 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -170,7 +170,8 @@ void tcpm_cc_change(struct tcpm_port *port);
 void tcpm_sink_frs(struct tcpm_port *port);
 void tcpm_sourcing_vbus(struct tcpm_port *port);
 void tcpm_pd_receive(struct tcpm_port *port,
-		     const struct pd_message *msg);
+		     const struct pd_message *msg,
+		     enum tcpm_transmit_type rx_sop_type);
 void tcpm_pd_transmit_complete(struct tcpm_port *port,
 			       enum tcpm_transmit_status status);
 void tcpm_pd_hard_reset(struct tcpm_port *port);
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (3 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 04/12] usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 14:07   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 06/12] usb: typec: tcpm: add control message support to sop' RD Babiera
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add negotiated revision and tx/rx message ids to tcpm_port specific to
SOP'. tx_sop_type is added to the tcpm_port to determine whether the
current constructed message will be sent over SOP or SOP' if not
sent immediately.

tcpm_pd_rx_handler updates the received message ids. SOP* messages are not
processed afterwards. The handler also calls tcpm_can_communicate_sop_prime
to determine if a SOP' message is directed towards the port, and drops SOP'
messages it should not respond to.

tcpm_can_communicate_sop_prime is added as a helper to determine whether
the port is capable of communicating over SOP' at a given moment. Being
the Vconn source is a requirement in Power Delivery 3.0 but only a
recommendation in Power Delviery 2.0. Because the port should ensure that
the cable is powered before communication, always enforce the port is the
Vconn source regardless of revision.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/tcpm/tcpm.c | 156 +++++++++++++++++++++++++++++++---
 1 file changed, 145 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index b05325dcd7ac..bc6c5f04e62f 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -493,6 +493,35 @@ struct tcpm_port {
 	 * transitions.
 	 */
 	bool potential_contaminant;
+
+	/* SOP* Related Fields */
+	/*
+	 * 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 */
@@ -882,19 +911,32 @@ 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:
+		negotiated_rev = port->negotiated_rev;
+		break;
+	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;
 
@@ -907,7 +949,20 @@ 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:
+			port->message_id = (port->message_id + 1) &
+					   PD_HEADER_ID_MASK;
+			break;
+		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:
@@ -1592,6 +1647,57 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port)
 
 #define supports_modal(port)	PD_IDH_MODAL_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 int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 			const u32 *p, int cnt, u32 *response,
 			enum adev_actions *adev_action)
@@ -2977,14 +3083,18 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
 	tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header),
 		 port->attached);
 
-	/* Ignore SOP' for now */
-	if (rx_sop_type == TCPC_TX_SOP_PRIME)
-		goto done;
-
 	if (port->attached) {
 		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
@@ -2994,16 +3104,33 @@ 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)
+		switch (rx_sop_type) {
+		case TCPC_TX_SOP_PRIME:
+			if (msgid == port->rx_msgid_prime)
+				goto done;
+			port->rx_msgid_prime = msgid;
+			/* Ignore SOP' for now */
 			goto done;
-		port->rx_msgid = msgid;
+		case TCPC_TX_SOP:
+			if (msgid == port->rx_msgid &&
+			    type != PD_CTRL_SOFT_RESET)
+				goto done;
+			port->rx_msgid = msgid;
+			break;
+		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);
@@ -3708,6 +3835,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 */
@@ -4022,8 +4150,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 ||
@@ -4263,8 +4394,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 ||
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 06/12] usb: typec: tcpm: add control message support to sop'
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (4 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 14:12   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 07/12] usb: typec: tcpci: add attempt_vconn_swap_discovery callback RD Babiera
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add tx_sop_type to tcpm_pd_send_control and rx_sop_type to
tcpm_pd_ctrl_request. TCPC_TX_SOP is added to all pd_send_control calls,
but TCPC_TX_SOP_PRIME is added to pd_send_control for a SOFT_RESET message
sent after a Vconn swap that makes the Port the Vconn source. Likewise,
tcpm_pd_ctrl_request resets the proper protocol layer depending on
rx_sop_type for SOFT_RESET.

VCONN_SWAP_TURN_ON_VCONN now moves to a new state,
VCONN_SWAP_SEND_SOFT_RESET. This state sends SOFT_RESET over SOP' before
transitioning to the ready state if applicable. It transitions after
PD_T_VCONN_STABLE, definied in pd.h as the time required for Vconn to be
on before transmitting messages.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/tcpm/tcpm.c | 159 ++++++++++++++++++++++++----------
 include/linux/usb/pd.h        |   1 +
 2 files changed, 115 insertions(+), 45 deletions(-)

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index bc6c5f04e62f..c1e1fd6bd60d 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -108,6 +108,7 @@
 	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),		\
@@ -2388,7 +2389,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)
@@ -2742,10 +2744,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
@@ -2910,6 +2914,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);
@@ -3109,8 +3123,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
 			if (msgid == port->rx_msgid_prime)
 				goto done;
 			port->rx_msgid_prime = msgid;
-			/* Ignore SOP' for now */
-			goto done;
+			break;
 		case TCPC_TX_SOP:
 			if (msgid == port->rx_msgid &&
 			    type != PD_CTRL_SOFT_RESET)
@@ -3140,7 +3153,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
 			else if (cnt)
 				tcpm_pd_data_request(port, msg);
 			else
-				tcpm_pd_ctrl_request(port, msg);
+				tcpm_pd_ctrl_request(port, msg, rx_sop_type);
 		}
 	}
 
@@ -3167,17 +3180,40 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg,
 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);
 }
 
 /*
@@ -3196,13 +3232,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);
@@ -4217,7 +4253,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);
@@ -4225,7 +4261,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,
@@ -4234,7 +4270,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;
@@ -4718,7 +4754,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;
@@ -4735,28 +4771,41 @@ 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);
+		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;
 		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);
+		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;
 		tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
@@ -4780,7 +4829,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;
 		}
@@ -4800,7 +4849,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;
 		}
@@ -4810,11 +4859,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;
@@ -4856,7 +4905,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;
 		}
@@ -4903,17 +4952,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;
@@ -4932,14 +4981,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:
@@ -4975,7 +5044,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;
@@ -4983,7 +5052,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;
@@ -4991,7 +5060,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:
@@ -5031,7 +5100,7 @@ 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;
 	default:
diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index eb626af0e4e7..d50098fb16b5 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -483,6 +483,7 @@ static inline unsigned int rdo_max_power(u32 rdo)
 #define PD_T_BIST_CONT_MODE	50	/* 30 - 60 ms */
 #define PD_T_SINK_TX		16	/* 16 - 20 ms */
 #define PD_T_CHUNK_NOT_SUPP	42	/* 40 - 50 ms */
+#define PD_T_VCONN_STABLE	50
 
 #define PD_T_DRP_TRY		100	/* 75 - 150 ms */
 #define PD_T_DRP_TRYWAIT	600	/* 400 - 800 ms */
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 07/12] usb: typec: tcpci: add attempt_vconn_swap_discovery callback
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (5 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 06/12] usb: typec: tcpm: add control message support to sop' RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 14:17   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP' RD Babiera
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add attempt_vconn_swap_discovery callback to determine whether the TCPM
should perform a Vconn swap following Discover Identity on SOP. The tcpci
will return false unless chip level drivers implement the callback.

Maxim based TCPCs will return true unless the last connection resulted in
a Vconn Over Current Fault, which may be the result of the Vconn swap. In
addition to the port resetting, the TCPCI will veto the next Vconn swap
from occurring.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/tcpm/tcpci.c            | 11 +++++++++++
 drivers/usb/typec/tcpm/tcpci_maxim.h      |  1 +
 drivers/usb/typec/tcpm/tcpci_maxim_core.c | 17 ++++++++++++++++-
 include/linux/usb/tcpci.h                 |  9 +++++++++
 include/linux/usb/tcpm.h                  |  9 +++++++++
 5 files changed, 46 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index 8ea4ed159a13..40c7b6224c74 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -594,6 +594,16 @@ static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc)
 	return tcpci->data->cable_comm_capable;
 }
 
+static bool tcpci_attempt_vconn_swap_discovery(struct tcpc_dev *tcpc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+
+	if (tcpci->data->attempt_vconn_swap_discovery)
+		return tcpci->data->attempt_vconn_swap_discovery(tcpci, tcpci->data);
+
+	return false;
+}
+
 static int tcpci_init(struct tcpc_dev *tcpc)
 {
 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -804,6 +814,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
 	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
 	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
 	tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable;
+	tcpci->tcpc.attempt_vconn_swap_discovery = tcpci_attempt_vconn_swap_discovery;
 
 	if (tcpci->data->check_contaminant)
 		tcpci->tcpc.check_contaminant = tcpci_check_contaminant;
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h
index 2c1c4d161b0d..78ff3b73ee7e 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim.h
+++ b/drivers/usb/typec/tcpm/tcpci_maxim.h
@@ -62,6 +62,7 @@ struct max_tcpci_chip {
 	struct i2c_client *client;
 	struct tcpm_port *port;
 	enum contamiant_state contaminant_state;
+	bool veto_vconn_swap;
 };
 
 static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val)
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
index f9f838df43f7..eec3bcec119c 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
+++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
@@ -323,8 +323,10 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status)
 		if (ret < 0)
 			return ret;
 
-		if (reg_status & TCPC_FAULT_STATUS_VCONN_OC)
+		if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) {
+			chip->veto_vconn_swap = true;
 			tcpm_port_error_recovery(chip->port);
+		}
 	}
 
 	if (status & TCPC_ALERT_EXTND) {
@@ -458,6 +460,18 @@ static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data *
 		tcpm_port_clean(chip->port);
 }
 
+static bool max_tcpci_attempt_vconn_swap_discovery(struct tcpci *tcpci, struct tcpci_data *tdata)
+{
+	struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata);
+
+	if (chip->veto_vconn_swap) {
+		chip->veto_vconn_swap = false;
+		return false;
+	}
+
+	return true;
+}
+
 static int max_tcpci_probe(struct i2c_client *client)
 {
 	int ret;
@@ -493,6 +507,7 @@ static int max_tcpci_probe(struct i2c_client *client)
 	chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable;
 	chip->data.check_contaminant = max_tcpci_check_contaminant;
 	chip->data.cable_comm_capable = true;
+	chip->data.attempt_vconn_swap_discovery = max_tcpci_attempt_vconn_swap_discovery;
 
 	max_tcpci_init_regs(chip);
 	chip->tcpci = tcpci_register_port(chip->dev, &chip->data);
diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
index 9ed6d62c9c5f..47a86b8a4a50 100644
--- a/include/linux/usb/tcpci.h
+++ b/include/linux/usb/tcpci.h
@@ -201,6 +201,14 @@ struct tcpci;
  *		toggling state.
  * @cable_comm_capable
  *		optional; Set when TCPC can communicate with cable plugs over SOP'
+ * @attempt_vconn_swap_discovery:
+ *		Optional; The callback is called by the TCPM when the result of
+ *		a Discover Identity request indicates that the port partner is
+ *		a receptacle capable of modal operation. Chip level TCPCI drivers
+ *		can implement their own policy to determine if and when a Vconn
+ *		swap following Discover Identity on SOP' occurs.
+ *		Return true when the TCPM is allowed to request a Vconn swap
+ *		after Discovery Identity on SOP.
  */
 struct tcpci_data {
 	struct regmap *regmap;
@@ -219,6 +227,7 @@ struct tcpci_data {
 	void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data,
 					     bool capable);
 	void (*check_contaminant)(struct tcpci *tcpci, struct tcpci_data *data);
+	bool (*attempt_vconn_swap_discovery)(struct tcpci *tcpci, struct tcpci_data *data);
 };
 
 struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data);
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 41d1ac9c8bbf..6671427f7eeb 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -122,6 +122,14 @@ enum tcpm_transmit_type {
  * @cable_comm_capable
  *		Optional; Returns whether cable communication over SOP' is supported
  *		by the tcpc
+ * @attempt_vconn_swap_discovery:
+ *		Optional; The callback is called by the TCPM when the result of
+ *		a Discover Identity request indicates that the port partner is
+ *		a receptacle capable of modal operation. Chip level TCPCI drivers
+ *		can implement their own policy to determine if and when a Vconn
+ *		swap following Discover Identity on SOP' occurs.
+ *		Return true when the TCPM is allowed to request a Vconn swap
+ *		after Discovery Identity on SOP.
  */
 struct tcpc_dev {
 	struct fwnode_handle *fwnode;
@@ -158,6 +166,7 @@ struct tcpc_dev {
 	void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable);
 	void (*check_contaminant)(struct tcpc_dev *dev);
 	bool (*cable_comm_capable)(struct tcpc_dev *dev);
+	bool (*attempt_vconn_swap_discovery)(struct tcpc_dev *dev);
 };
 
 struct tcpm_port;
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP'
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (6 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 07/12] usb: typec: tcpci: add attempt_vconn_swap_discovery callback RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 14:26   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 09/12] usb: typec: tcpm: add state machine support for SRC_VDM_IDENTITY_REQUEST RD Babiera
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

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@google.com>
---
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


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 09/12] usb: typec: tcpm: add state machine support for SRC_VDM_IDENTITY_REQUEST
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (7 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP' RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 14:26   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop' RD Babiera
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add SRC_VDM_IDENTITY_REQUEST state which first enters after SRC_STARTUP.
The state sends Discover Identity on SOP' and transitions to
SRC_SEND_CAPABILITIES. SRC_SEND_CAPABILITIES will transition back into
SRC_VDM_IDENTITY_REQUEST instead of retrying immediately.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/tcpm/tcpm.c | 49 ++++++++++++++++++++++++++++++-----
 1 file changed, 43 insertions(+), 6 deletions(-)

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 5924e359e14d..e21bc2eea3fc 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -146,7 +146,9 @@
 	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),				\
@@ -1956,6 +1958,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 					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;
 				}
@@ -1987,6 +1990,16 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 				 * 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),
@@ -2281,7 +2294,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;
 		}
@@ -2357,13 +2371,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;
@@ -4477,8 +4500,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.
@@ -5393,6 +5420,15 @@ static void run_state_machine(struct tcpm_port *port)
 		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;
@@ -6118,7 +6154,8 @@ static void tcpm_send_discover_work(struct kthread_work *work)
 	}
 
 	/* 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;
 	}
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop'
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (8 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 09/12] usb: typec: tcpm: add state machine support for SRC_VDM_IDENTITY_REQUEST RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-19 14:32   ` Heikki Krogerus
  2023-12-14 23:08 ` [PATCH v2 11/12] usb: typec: tcpm: add alt mode enter/exit/vdm " RD Babiera
  2023-12-14 23:08 ` [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support RD Babiera
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Adds Discover SVIDs and Discover Modes support for SOP' and Alt Mode
SVDM support over SOP'. tcpm_port adds separate Alt Mode data for SOP'.

svdm_consume_svids and svdm_consume_modes take the received SVDM's SOP*
type to store svids/modes separately, and tcpm_register_plug_altmodes
registers the active cable's alt modes.

In tcpm_pd_svdm, the port will send Discover SVIDs to SOP' after Discover
Modes on SOP if the connected cable is an active cable. Discover Modes on
SOP' is sent following Discover SVIDs on SOP. Registering partner alt modes
is delayed when an active cable is present until Discover Modes completes
on SOP', or if the Discover SVIDs/Discover Modes request on SOP' encounters
a transmission error.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
Changes since v1:
* Changes to tcpm_altmode_enter/exit/vdm are moved to next patch
* adev_action changes are moved to next patch
---
 drivers/usb/typec/tcpm/tcpm.c | 163 +++++++++++++++++++++++++++++-----
 1 file changed, 139 insertions(+), 24 deletions(-)

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index e21bc2eea3fc..61433dc4c917 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -465,7 +465,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 */
@@ -1629,9 +1631,11 @@ static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p
 	}
 }
 
-static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
+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 = &port->mode_data;
+	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++) {
@@ -1677,14 +1681,29 @@ static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
 	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;
 	}
 
@@ -1722,7 +1741,28 @@ 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))
 
 /*
@@ -1800,6 +1840,15 @@ static bool tcpm_attempt_vconn_swap_discovery(struct tcpm_port *port)
 	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,
@@ -1807,8 +1856,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 			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;
@@ -1829,6 +1878,11 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 
 	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]));
 		if (!IS_ERR_OR_NULL(port->cable)) {
 			svdm_version = typec_get_cable_svdm_version(typec);
 			if (PD_VDO_SVDM_VER(p[0]) < svdm_version)
@@ -1836,11 +1890,21 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 		}
 		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;
@@ -1932,6 +1996,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 		 * 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.
@@ -2011,26 +2078,62 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 			}
 			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:
@@ -2411,6 +2514,16 @@ static void vdm_run_state_machine(struct tcpm_port *port)
 				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;
 			}
@@ -4123,14 +4236,16 @@ 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)
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 11/12] usb: typec: tcpm: add alt mode enter/exit/vdm support for sop'
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (9 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop' RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2023-12-14 23:08 ` [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support RD Babiera
  11 siblings, 0 replies; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Add tcpm_cable_ops for enter, exit, and vdm to the tcpm, which are
registered after registering port alt modes through
typec_port_register_cable_ops. Enter Mode on SOP' now sends Exit Mode upon
failure to report to the driver.

tcpm_queue_vdm_unlocked now takes sop type as input. Proper adev_actions
in tcpm_pd_svdm are selected for SOP' messages.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
 drivers/usb/typec/tcpm/tcpm.c | 126 ++++++++++++++++++++++++++++------
 1 file changed, 106 insertions(+), 20 deletions(-)

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 61433dc4c917..aa3351328e7f 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -1549,7 +1549,7 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
 }
 
 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, TCPC_TX_SOP);
@@ -2137,14 +2137,28 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
 			}
 			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):
@@ -2277,19 +2291,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;
+			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;
+					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:
@@ -2724,7 +2756,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;
 }
 
@@ -2741,7 +2773,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;
 }
 
@@ -2750,7 +2782,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;
 }
@@ -2761,6 +2793,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
  */
@@ -7295,6 +7379,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);
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support
  2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
                   ` (10 preceding siblings ...)
  2023-12-14 23:08 ` [PATCH v2 11/12] usb: typec: tcpm: add alt mode enter/exit/vdm " RD Babiera
@ 2023-12-14 23:08 ` RD Babiera
  2024-01-02 14:02   ` Dan Carpenter
  11 siblings, 1 reply; 27+ messages in thread
From: RD Babiera @ 2023-12-14 23:08 UTC (permalink / raw)
  To: rdbabiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Implement active cable VDM support for SOP' according to the DisplayPort
Alt Mode 2.0 specification.

When probing the DisplayPort driver, the state machine will transition to
Enter Mode on SOP' if an active cable altmode is detected. The SVDM flow
is as followed:
    (1) Enter Mode     SOP'
    (2) Enter Mode     SOP
    (3) Status Update  SOP
    (4) Configure      SOP'
    (5) Configure      SOP

Status Update on SOP' after Enter Mode is optional and not implemented for
now. When exiting the alt mode, send Exit Mode over SOP' after SOP.

Should an altmode vdm fail on SOP', the DisplayPort driver will drop its
reference to the plug and attempt to continue in SOP operation.

Add new dp_state enums DP_STATE_ENTER_PRIME, DP_STATE_CONFIGURE_PRIME, and
DP_STATE_EXIT_PRIME. dp_altmode adds typec_displayport_data for the cable
plug to store the plug configuration and adds a typec_altmode reference
for the cable plug.

dp_altmode_configure takes the cable pin assignment capabilities into
account when deciding on pin configuration. dp_altmode_configure_vdm_cable
sends the configure message on SOP'.

dp_altmode_activate now attempts to enter on SOP' if applicable, and will
attempt to enter on SOP on failure.

dp_cable_altmode_vdm handles VDMs passed to the DisplayPort driver from
the tcpm.

Signed-off-by: RD Babiera <rdbabiera@google.com>
---
Changes since v1:
* dp_altmode_configure_vdm no longer handles sop', now handled by
  dp_altmode_configure_vdm_cable
* dp_exit_mode_handler deleted
* dp_altmode_vdm no longer handles sop', now handled by
  dp_cable_altmode_vdm as typec_cable_ops callback assigned to plug_prime
  if it exists.
* driver data registered to plug_prime if applicable.
---
 drivers/usb/typec/altmodes/displayport.c | 161 ++++++++++++++++++++++-
 1 file changed, 157 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index f81bec0c7b86..06ea63bc536e 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -50,13 +50,17 @@ enum {
 enum dp_state {
 	DP_STATE_IDLE,
 	DP_STATE_ENTER,
+	DP_STATE_ENTER_PRIME,
 	DP_STATE_UPDATE,
 	DP_STATE_CONFIGURE,
+	DP_STATE_CONFIGURE_PRIME,
 	DP_STATE_EXIT,
+	DP_STATE_EXIT_PRIME,
 };
 
 struct dp_altmode {
 	struct typec_displayport_data data;
+	struct typec_displayport_data data_prime;
 
 	enum dp_state state;
 	bool hpd;
@@ -67,6 +71,7 @@ struct dp_altmode {
 	struct typec_altmode *alt;
 	const struct typec_altmode *port;
 	struct fwnode_handle *connector_fwnode;
+	struct typec_altmode *plug_prime;
 };
 
 static int dp_altmode_notify(struct dp_altmode *dp)
@@ -99,12 +104,18 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
 		conf |= DP_CONF_UFP_U_AS_DFP_D;
 		pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
 			     DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo);
+		/* Account for active cable capabilities */
+		if (dp->plug_prime)
+			pin_assign &= DP_CAP_DFP_D_PIN_ASSIGN(dp->plug_prime->vdo);
 		break;
 	case DP_STATUS_CON_UFP_D:
 	case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
 		conf |= DP_CONF_UFP_U_AS_UFP_D;
 		pin_assign = DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo) &
 				 DP_CAP_PIN_ASSIGN_DFP_D(dp->port->vdo);
+		/* Account for active cable capabilities */
+		if (dp->plug_prime)
+			pin_assign &= DP_CAP_UFP_D_PIN_ASSIGN(dp->plug_prime->vdo);
 		break;
 	default:
 		break;
@@ -130,6 +141,8 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
 	}
 
 	dp->data.conf = conf;
+	if (dp->plug_prime)
+		dp->data_prime.conf = conf;
 
 	return 0;
 }
@@ -143,7 +156,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
 
 	if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
 		dp->data.conf = 0;
-		dp->state = DP_STATE_CONFIGURE;
+		dp->data_prime.conf = 0;
+		dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME :
+					     DP_STATE_CONFIGURE;
 	} else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
 		dp->state = DP_STATE_EXIT;
 	} else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
@@ -209,6 +224,19 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
 	return ret;
 }
 
+static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf)
+{
+	int svdm_version = typec_altmode_get_cable_svdm_version(dp->plug_prime);
+	u32 header;
+
+	if (svdm_version < 0)
+		return svdm_version;
+
+	header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE);
+
+	return typec_cable_altmode_vdm(dp->plug_prime, TYPEC_PLUG_SOP_P, header, &conf, 2);
+}
+
 static void dp_altmode_work(struct work_struct *work)
 {
 	struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
@@ -225,6 +253,19 @@ static void dp_altmode_work(struct work_struct *work)
 		if (ret && ret != -EBUSY)
 			dev_err(&dp->alt->dev, "failed to enter mode\n");
 		break;
+	case DP_STATE_ENTER_PRIME:
+		ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL);
+		/*
+		 * If we fail to enter Alt Mode on SOP', then we should drop the
+		 * plug from the driver and attempt to run the driver without
+		 * it.
+		 */
+		if (ret && ret != -EBUSY) {
+			dev_err(&dp->alt->dev, "plug failed to enter mode\n");
+			dp->state = DP_STATE_ENTER;
+			goto disable_prime;
+		}
+		break;
 	case DP_STATE_UPDATE:
 		svdm_version = typec_altmode_get_svdm_version(dp->alt);
 		if (svdm_version < 0)
@@ -243,10 +284,24 @@ static void dp_altmode_work(struct work_struct *work)
 			dev_err(&dp->alt->dev,
 				"unable to send Configure command (%d)\n", ret);
 		break;
+	case DP_STATE_CONFIGURE_PRIME:
+		ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf);
+		if (ret) {
+			dev_err(&dp->plug_prime->dev,
+				"unable to send Configure command (%d)\n",
+				ret);
+			dp->state = DP_STATE_CONFIGURE;
+			goto disable_prime;
+		}
+		break;
 	case DP_STATE_EXIT:
 		if (typec_altmode_exit(dp->alt))
 			dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
 		break;
+	case DP_STATE_EXIT_PRIME:
+		if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P))
+			dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n");
+		break;
 	default:
 		break;
 	}
@@ -254,6 +309,12 @@ static void dp_altmode_work(struct work_struct *work)
 	dp->state = DP_STATE_IDLE;
 
 	mutex_unlock(&dp->lock);
+	return;
+
+disable_prime:
+	typec_altmode_put_plug(dp->plug_prime);
+	dp->plug_prime = NULL;
+	schedule_work(&dp->work);
 }
 
 static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
@@ -314,6 +375,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
 				dp->hpd = false;
 				sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
 			}
+			if (dp->plug_prime)
+				dp->state = DP_STATE_EXIT_PRIME;
 			break;
 		case DP_CMD_STATUS_UPDATE:
 			dp->data.status = *vdo;
@@ -348,10 +411,84 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
 	return ret;
 }
 
+static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop,
+				const u32 hdr, const u32 *vdo, int count)
+{
+	struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
+	int cmd_type = PD_VDO_CMDT(hdr);
+	int cmd = PD_VDO_CMD(hdr);
+	int ret = 0;
+
+	mutex_lock(&dp->lock);
+
+	if (dp->state != DP_STATE_IDLE) {
+		ret = -EBUSY;
+		goto err_unlock;
+	}
+
+	switch (cmd_type) {
+	case CMDT_RSP_ACK:
+		switch (cmd) {
+		case CMD_ENTER_MODE:
+			typec_altmode_update_active(dp->plug_prime, true);
+			dp->state = DP_STATE_ENTER;
+			break;
+		case CMD_EXIT_MODE:
+			dp->data_prime.status = 0;
+			dp->data_prime.conf = 0;
+			typec_altmode_update_active(dp->plug_prime, false);
+			break;
+		case DP_CMD_CONFIGURE:
+			dp->state = DP_STATE_CONFIGURE;
+			break;
+		default:
+			break;
+		}
+		break;
+	case CMDT_RSP_NAK:
+		switch (cmd) {
+		case DP_CMD_CONFIGURE:
+			dp->data_prime.conf = 0;
+			/* Attempt to configure on SOP, drop plug */
+			typec_altmode_put_plug(dp->plug_prime);
+			dp->plug_prime = NULL;
+			dp->state = DP_STATE_CONFIGURE;
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (dp->state != DP_STATE_IDLE)
+		schedule_work(&dp->work);
+
+err_unlock:
+	mutex_unlock(&dp->lock);
+	return ret;
+}
+
 static int dp_altmode_activate(struct typec_altmode *alt, int activate)
 {
-	return activate ? typec_altmode_enter(alt, NULL) :
-			  typec_altmode_exit(alt);
+	struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
+	int ret;
+
+	if (activate) {
+		if (dp->plug_prime) {
+			ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL);
+			if (ret < 0) {
+				typec_altmode_put_plug(dp->plug_prime);
+				dp->plug_prime = NULL;
+			} else {
+				return ret;
+			}
+		}
+		return typec_altmode_enter(alt, NULL);
+	} else {
+		return typec_altmode_exit(alt);
+	}
 }
 
 static const struct typec_altmode_ops dp_altmode_ops = {
@@ -360,6 +497,10 @@ static const struct typec_altmode_ops dp_altmode_ops = {
 	.activate = dp_altmode_activate,
 };
 
+static const struct typec_cable_ops dp_cable_ops = {
+	.vdm = dp_cable_altmode_vdm,
+};
+
 static const char * const configurations[] = {
 	[DP_CONF_USB]	= "USB",
 	[DP_CONF_DFP_D]	= "source",
@@ -501,6 +642,7 @@ pin_assignment_store(struct device *dev, struct device_attribute *attr,
 
 	/* Only send Configure command if a configuration has been set */
 	if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
+		/* todo: send manual configure over SOP'*/
 		ret = dp_altmode_configure_vdm(dp, conf);
 		if (ret)
 			goto out_unlock;
@@ -574,6 +716,7 @@ static const struct attribute_group dp_altmode_group = {
 int dp_altmode_probe(struct typec_altmode *alt)
 {
 	const struct typec_altmode *port = typec_altmode_get_partner(alt);
+	struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P);
 	struct fwnode_handle *fwnode;
 	struct dp_altmode *dp;
 	int ret;
@@ -603,6 +746,13 @@ int dp_altmode_probe(struct typec_altmode *alt)
 	alt->desc = "DisplayPort";
 	alt->ops = &dp_altmode_ops;
 
+	if (plug) {
+		plug->desc = "Displayport";
+		plug->cable_ops = &dp_cable_ops;
+	}
+
+	dp->plug_prime = plug;
+
 	fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */
 	if (fwnode_property_present(fwnode, "displayport"))
 		dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0);
@@ -612,8 +762,10 @@ int dp_altmode_probe(struct typec_altmode *alt)
 		dp->connector_fwnode = NULL;
 
 	typec_altmode_set_drvdata(alt, dp);
+	if (plug)
+		typec_altmode_set_drvdata(plug, dp);
 
-	dp->state = DP_STATE_ENTER;
+	dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER;
 	schedule_work(&dp->work);
 
 	return 0;
@@ -626,6 +778,7 @@ void dp_altmode_remove(struct typec_altmode *alt)
 
 	sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
 	cancel_work_sync(&dp->work);
+	typec_altmode_put_plug(dp->plug_prime);
 
 	if (dp->connector_fwnode) {
 		drm_connector_oob_hotplug_event(dp->connector_fwnode,
-- 
2.43.0.472.g3155946c3a-goog


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode
  2023-12-14 23:08 ` [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode RD Babiera
@ 2023-12-16  2:21   ` kernel test robot
  2023-12-17 15:11   ` kernel test robot
  1 sibling, 0 replies; 27+ messages in thread
From: kernel test robot @ 2023-12-16  2:21 UTC (permalink / raw)
  To: RD Babiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: oe-kbuild-all, badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Hi RD,

kernel test robot noticed the following build warnings:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.7-rc5 next-20231215]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/RD-Babiera/usb-typec-altmodes-add-typec_cable_ops-to-typec_altmode/20231215-071339
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
patch link:    https://lore.kernel.org/r/20231214230850.379863-15-rdbabiera%40google.com
patch subject: [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode
config: alpha-randconfig-r071-20231216 (https://download.01.org/0day-ci/archive/20231216/202312161038.P8GnRTgA-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231216/202312161038.P8GnRTgA-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202312161038.P8GnRTgA-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/usb/typec/bus.c:260: warning: expecting prototype for typec_altmode_enter(). Prototype was for typec_cable_altmode_enter() instead
>> drivers/usb/typec/bus.c:290: warning: expecting prototype for typec_altmode_exit(). Prototype was for typec_cable_altmode_exit() instead
>> drivers/usb/typec/bus.c:323: warning: expecting prototype for typec_altmode_vdm(). Prototype was for typec_cable_altmode_vdm() instead


vim +260 drivers/usb/typec/bus.c

   249	
   250	/**
   251	 * typec_altmode_enter - Enter Mode
   252	 * @adev: The alternate mode
   253	 * @sop: Cable plug target for Enter Mode command
   254	 * @vdo: VDO for the Enter Mode command
   255	 *
   256	 * Alternate mode drivers use this function to enter mode on the cable plug.
   257	 * If the alternate mode does not require VDO, @vdo must be NULL.
   258	 */
   259	int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo)
 > 260	{
   261		struct altmode *partner = to_altmode(adev)->partner;
   262		struct typec_altmode *pdev;
   263	
   264		if (!adev || adev->active)
   265			return 0;
   266	
   267		if (!partner)
   268			return -ENODEV;
   269	
   270		pdev = &partner->adev;
   271	
   272		if (!pdev->active)
   273			return -EPERM;
   274	
   275		if (!pdev->cable_ops || !pdev->cable_ops->enter)
   276			return -EOPNOTSUPP;
   277	
   278		return pdev->cable_ops->enter(pdev, sop, vdo);
   279	}
   280	EXPORT_SYMBOL_GPL(typec_cable_altmode_enter);
   281	
   282	/**
   283	 * typec_altmode_exit - Exit Mode
   284	 * @adev: The alternate mode
   285	 * @sop: Cable plug target for Exit Mode command
   286	 *
   287	 * The alternate mode drivers use this function to exit mode on the cable plug.
   288	 */
   289	int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop)
 > 290	{
   291		struct altmode *partner = to_altmode(adev)->partner;
   292		struct typec_altmode *pdev;
   293	
   294		if (!adev || !adev->active)
   295			return 0;
   296	
   297		if (!partner)
   298			return -ENODEV;
   299	
   300		pdev = &partner->adev;
   301	
   302		if (!pdev->cable_ops || !pdev->cable_ops->exit)
   303			return -EOPNOTSUPP;
   304	
   305		return pdev->cable_ops->exit(pdev, sop);
   306	}
   307	EXPORT_SYMBOL_GPL(typec_cable_altmode_exit);
   308	
   309	/**
   310	 * typec_altmode_vdm - Send Vendor Defined Messages (VDM) between the cable plug and port.
   311	 * @adev: Alternate mode handle
   312	 * @sop: Cable plug target for VDM
   313	 * @header: VDM Header
   314	 * @vdo: Array of Vendor Defined Data Objects
   315	 * @count: Number of Data Objects
   316	 *
   317	 * The alternate mode drivers use this function for SVID specific communication
   318	 * with the cable plugs. The port drivers use it to deliver the Structured VDMs
   319	 * received from the cable plugs to the alternate mode drivers.
   320	 */
   321	int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index sop,
   322				    const u32 header, const u32 *vdo, int count)
 > 323	{
   324		struct altmode *altmode;
   325		struct typec_altmode *pdev;
   326	
   327		if (!adev)
   328			return 0;
   329	
   330		altmode = to_altmode(adev);
   331	
   332		if (is_typec_plug(adev->dev.parent)) {
   333			if (!altmode->partner)
   334				return -ENODEV;
   335			pdev = &altmode->partner->adev;
   336		} else {
   337			if (!altmode->plug[sop])
   338				return -ENODEV;
   339			pdev = &altmode->plug[sop]->adev;
   340		}
   341	
   342		if (!pdev->cable_ops || !pdev->cable_ops->vdm)
   343			return -EOPNOTSUPP;
   344	
   345		return pdev->cable_ops->vdm(pdev, sop, header, vdo, count);
   346	}
   347	EXPORT_SYMBOL_GPL(typec_cable_altmode_vdm);
   348	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode
  2023-12-14 23:08 ` [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode RD Babiera
  2023-12-16  2:21   ` kernel test robot
@ 2023-12-17 15:11   ` kernel test robot
  1 sibling, 0 replies; 27+ messages in thread
From: kernel test robot @ 2023-12-17 15:11 UTC (permalink / raw)
  To: RD Babiera, heikki.krogerus, linux, gregkh, linux-kernel, linux-usb
  Cc: oe-kbuild-all, badhri, bryan.odonoghue, agross, andersson, konrad.dybcio

Hi RD,

kernel test robot noticed the following build warnings:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.7-rc5 next-20231215]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/RD-Babiera/usb-typec-altmodes-add-typec_cable_ops-to-typec_altmode/20231215-071339
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
patch link:    https://lore.kernel.org/r/20231214230850.379863-15-rdbabiera%40google.com
patch subject: [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode
config: arc-randconfig-001-20231215 (https://download.01.org/0day-ci/archive/20231217/202312172234.6in6om5p-lkp@intel.com/config)
compiler: arc-elf-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231217/202312172234.6in6om5p-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202312172234.6in6om5p-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> scripts/kernel-doc: drivers/usb/typec/bus.c:260: warning: expecting prototype for typec_altmode_enter(). Prototype was for typec_cable_altmode_enter() instead
>> scripts/kernel-doc: drivers/usb/typec/bus.c:290: warning: expecting prototype for typec_altmode_exit(). Prototype was for typec_cable_altmode_exit() instead
>> scripts/kernel-doc: drivers/usb/typec/bus.c:323: warning: expecting prototype for typec_altmode_vdm(). Prototype was for typec_cable_altmode_vdm() instead

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 02/12] usb: typec: altmodes: add svdm version info for typec cables
  2023-12-14 23:08 ` [PATCH v2 02/12] usb: typec: altmodes: add svdm version info for typec cables RD Babiera
@ 2023-12-19 13:54   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 13:54 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:46PM +0000, RD Babiera wrote:
> Add typec_cable_set_svdm_version and typec_get_cable_svdm version symbols.
> Cables can operate under a lower PD revision than the port partner, and the
> max SVDM version is tied to the PD revision. So, typec_cable maintains its
> own svdm_version.
> 
> Add typec_altmode_get_cable_svdm_version to return the cable's negotiated
> svdm_version for altmode drivers to use.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
>  drivers/usb/typec/class.c         | 40 +++++++++++++++++++++++++++++++
>  drivers/usb/typec/class.h         |  1 +
>  include/linux/usb/typec.h         |  3 +++
>  include/linux/usb/typec_altmode.h | 10 ++++++++
>  4 files changed, 54 insertions(+)
> 
> diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
> index 86b5a8414b89..038c6498e683 100644
> --- a/drivers/usb/typec/class.c
> +++ b/drivers/usb/typec/class.c
> @@ -2129,6 +2129,46 @@ int typec_get_negotiated_svdm_version(struct typec_port *port)
>  }
>  EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version);
>  
> +/**
> + * typec_get_cable_svdm_version - Get cable negotiated SVDM Version
> + * @port: USB Type-C Port.
> + *
> + * Get the negotiated SVDM Version for the cable. The Version is set to the port
> + * default value based on the PD Revision during cable registration, and updated
> + * after a successful Discover Identity if the negotiated value is less than the
> + * default.
> + *
> + * Returns usb_pd_svdm_ver if the cable has been registered otherwise -ENODEV.
> + */
> +int typec_get_cable_svdm_version(struct typec_port *port)
> +{
> +	enum usb_pd_svdm_ver svdm_version;
> +	struct device *cable_dev;
> +
> +	cable_dev = device_find_child(&port->dev, NULL, cable_match);
> +	if (!cable_dev)
> +		return -ENODEV;
> +
> +	svdm_version = to_typec_cable(cable_dev)->svdm_version;
> +	put_device(cable_dev);
> +
> +	return svdm_version;
> +}
> +EXPORT_SYMBOL_GPL(typec_get_cable_svdm_version);
> +
> +/**
> + * typec_cable_set_svdm_version - Set negotiated Structured VDM (SVDM) Version
> + * @cable: USB Type-C Active Cable that supports SVDM
> + * @svdm_version: Negotiated SVDM Version
> + *
> + * This routine is used to save the negotiated SVDM Version.
> + */
> +void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version)
> +{
> +	cable->svdm_version = svdm_version;
> +}
> +EXPORT_SYMBOL_GPL(typec_cable_set_svdm_version);
> +
>  /**
>   * typec_get_drvdata - Return private driver data pointer
>   * @port: USB Type-C port
> diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h
> index c36761ba3f59..759b98355eeb 100644
> --- a/drivers/usb/typec/class.h
> +++ b/drivers/usb/typec/class.h
> @@ -23,6 +23,7 @@ struct typec_cable {
>  	struct usb_pd_identity		*identity;
>  	unsigned int			active:1;
>  	u16				pd_revision; /* 0300H = "3.0" */
> +	enum usb_pd_svdm_ver		svdm_version;
>  };
>  
>  struct typec_partner {
> diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> index 38f93d72fd1b..b35b427561ab 100644
> --- a/include/linux/usb/typec.h
> +++ b/include/linux/usb/typec.h
> @@ -337,6 +337,9 @@ void typec_partner_set_svdm_version(struct typec_partner *partner,
>  				    enum usb_pd_svdm_ver svdm_version);
>  int typec_get_negotiated_svdm_version(struct typec_port *port);
>  
> +int typec_get_cable_svdm_version(struct typec_port *port);
> +void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version);
> +
>  struct usb_power_delivery *typec_partner_usb_power_delivery_register(struct typec_partner *partner,
>  							struct usb_power_delivery_desc *desc);
>  
> diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h
> index 72ec8058543a..b3c0866ea70f 100644
> --- a/include/linux/usb/typec_altmode.h
> +++ b/include/linux/usb/typec_altmode.h
> @@ -95,6 +95,16 @@ int typec_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_inde
>  int typec_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop,
>  			    const u32 header, const u32 *vdo, int count);
>  
> +/**
> + * typec_altmode_get_cable_svdm_version - Get negotiated SVDM version for cable plug
> + * @altmode: Handle to the alternate mode
> + */
> +static inline int
> +typec_altmode_get_cable_svdm_version(struct typec_altmode *altmode)
> +{
> +	return typec_get_cable_svdm_version(typec_altmode2port(altmode));
> +}
> +
>  /*
>   * These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C
>   * Specification. SVID specific connector states are expected to follow and
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 03/12] usb: typec: tcpci: add cable_comm_capable attribute
  2023-12-14 23:08 ` [PATCH v2 03/12] usb: typec: tcpci: add cable_comm_capable attribute RD Babiera
@ 2023-12-19 13:55   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 13:55 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:47PM +0000, RD Babiera wrote:
> Add cable_comm_capable to tcpci_data for tcpci drivers to indicate that
> the port tcpc is capable of communicating to cables over SOP. A
> corresponding tcpci callback tcpci_cable_comm_capable returns this value.
> The tcpm will primarily use this in later patches to determine if the port
> can transmit and receive SOP' messages.
> 
> Maxim based tcpci drivers are capable of SOP' communication, so the
> cable_comm_capable flag is set to true.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
> Changes since v1:
> * Moved tcpm_pd_receive changes to separate patch
> ---
>  drivers/usb/typec/tcpm/tcpci.c            | 8 ++++++++
>  drivers/usb/typec/tcpm/tcpci_maxim_core.c | 1 +
>  include/linux/usb/tcpci.h                 | 3 +++
>  include/linux/usb/tcpm.h                  | 4 ++++
>  4 files changed, 16 insertions(+)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
> index 0ee3e6e29bb1..1ededbcecc09 100644
> --- a/drivers/usb/typec/tcpm/tcpci.c
> +++ b/drivers/usb/typec/tcpm/tcpci.c
> @@ -584,6 +584,13 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type
>  	return 0;
>  }
>  
> +static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc)
> +{
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +
> +	return tcpci->data->cable_comm_capable;
> +}
> +
>  static int tcpci_init(struct tcpc_dev *tcpc)
>  {
>  	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> @@ -793,6 +800,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
>  	tcpci->tcpc.enable_frs = tcpci_enable_frs;
>  	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
>  	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
> +	tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable;
>  
>  	if (tcpci->data->check_contaminant)
>  		tcpci->tcpc.check_contaminant = tcpci_check_contaminant;
> diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> index 7fb966fd639b..7b2d4e6e52a2 100644
> --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> @@ -478,6 +478,7 @@ static int max_tcpci_probe(struct i2c_client *client)
>  	chip->data.vbus_vsafe0v = true;
>  	chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable;
>  	chip->data.check_contaminant = max_tcpci_check_contaminant;
> +	chip->data.cable_comm_capable = true;
>  
>  	max_tcpci_init_regs(chip);
>  	chip->tcpci = tcpci_register_port(chip->dev, &chip->data);
> diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
> index 467e8045e9f8..1d0b849defd0 100644
> --- a/include/linux/usb/tcpci.h
> +++ b/include/linux/usb/tcpci.h
> @@ -198,12 +198,15 @@ struct tcpci;
>   *		Chip level drivers are expected to check for contaminant and call
>   *		tcpm_clean_port when the port is clean to put the port back into
>   *		toggling state.
> + * @cable_comm_capable
> + *		optional; Set when TCPC can communicate with cable plugs over SOP'
>   */
>  struct tcpci_data {
>  	struct regmap *regmap;
>  	unsigned char TX_BUF_BYTE_x_hidden:1;
>  	unsigned char auto_discharge_disconnect:1;
>  	unsigned char vbus_vsafe0v:1;
> +	unsigned char cable_comm_capable:1;
>  
>  	int (*init)(struct tcpci *tcpci, struct tcpci_data *data);
>  	int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data,
> diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
> index 65fac5e1f317..430fa3ec69bb 100644
> --- a/include/linux/usb/tcpm.h
> +++ b/include/linux/usb/tcpm.h
> @@ -119,6 +119,9 @@ enum tcpm_transmit_type {
>   *		at the end of the deboumce period or when the port is still
>   *		toggling. Chip level drivers are expected to check for contaminant
>   *		and call tcpm_clean_port when the port is clean.
> + * @cable_comm_capable
> + *		Optional; Returns whether cable communication over SOP' is supported
> + *		by the tcpc
>   */
>  struct tcpc_dev {
>  	struct fwnode_handle *fwnode;
> @@ -154,6 +157,7 @@ struct tcpc_dev {
>  	bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev);
>  	void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable);
>  	void (*check_contaminant)(struct tcpc_dev *dev);
> +	bool (*cable_comm_capable)(struct tcpc_dev *dev);
>  };
>  
>  struct tcpm_port;
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 04/12] usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive
  2023-12-14 23:08 ` [PATCH v2 04/12] usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive RD Babiera
@ 2023-12-19 13:59   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 13:59 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:48PM +0000, RD Babiera wrote:
> tcpm_pd_receive adds the SOP type as a parameter, and passes it within the
> pd_rx_event struct for tcpm_pd_rx_handler to use. For now, the handler
> drops all SOP' messages.
> 
> Maxim based tcpci drivers are capable of SOP' communication, so process_rx
> now takes the SOP type into account and passes the value to
> tcpm_pd_receive.
> 
> tcpci_set_pd_rx now utilizes the cable_comm_capable flag to determine if
> TCPC_RX_DETECT_SOP1 should be added to the bitfield when enabling PD
> message reception.
> 
> For all other consumers of tcpm_pd_receive, default the new field to
> TCPC_TX_SOP.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
>  drivers/usb/typec/tcpm/fusb302.c              |  2 +-
>  .../typec/tcpm/qcom/qcom_pmic_typec_pdphy.c   |  2 +-
>  drivers/usb/typec/tcpm/tcpci.c                |  7 +++++--
>  drivers/usb/typec/tcpm/tcpci_maxim_core.c     | 20 ++++++++++++++++---
>  drivers/usb/typec/tcpm/tcpm.c                 | 10 +++++++++-
>  drivers/usb/typec/tcpm/wcove.c                |  2 +-
>  include/linux/usb/tcpci.h                     |  1 +
>  include/linux/usb/tcpm.h                      |  3 ++-
>  8 files changed, 37 insertions(+), 10 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
> index bc21006e979c..ef18a448b740 100644
> --- a/drivers/usb/typec/tcpm/fusb302.c
> +++ b/drivers/usb/typec/tcpm/fusb302.c
> @@ -1467,7 +1467,7 @@ static int fusb302_pd_read_message(struct fusb302_chip *chip,
>  	if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC))
>  		tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS);
>  	else
> -		tcpm_pd_receive(chip->tcpm_port, msg);
> +		tcpm_pd_receive(chip->tcpm_port, msg, TCPC_TX_SOP);
>  
>  	return ret;
>  }
> diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
> index 52c81378e36e..a3154085ae32 100644
> --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
> +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
> @@ -299,7 +299,7 @@ static void qcom_pmic_typec_pdphy_pd_receive(struct pmic_typec_pdphy *pmic_typec
>  
>  	if (!ret) {
>  		dev_vdbg(dev, "pd_receive: handing %d bytes to tcpm\n", size);
> -		tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg);
> +		tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg, TCPC_TX_SOP);
>  	}
>  }
>  
> diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
> index 1ededbcecc09..8ea4ed159a13 100644
> --- a/drivers/usb/typec/tcpm/tcpci.c
> +++ b/drivers/usb/typec/tcpm/tcpci.c
> @@ -445,8 +445,11 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
>  	unsigned int reg = 0;
>  	int ret;
>  
> -	if (enable)
> +	if (enable) {
>  		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
> +		if (tcpci->data->cable_comm_capable)
> +			reg |= TCPC_RX_DETECT_SOP1;
> +	}
>  	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
>  	if (ret < 0)
>  		return ret;
> @@ -719,7 +722,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
>  		/* Read complete, clear RX status alert bit */
>  		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
>  
> -		tcpm_pd_receive(tcpci->port, &msg);
> +		tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP);
>  	}
>  
>  	if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) {
> diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> index 7b2d4e6e52a2..f9f838df43f7 100644
> --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> @@ -128,6 +128,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status)
>  	u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN];
>  	int ret, payload_index;
>  	u8 *rx_buf_ptr;
> +	enum tcpm_transmit_type rx_type;
>  
>  	/*
>  	 * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers
> @@ -143,10 +144,23 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status)
>  	count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET];
>  	frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET];
>  
> -	if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) {
> +	switch (frame_type) {
> +	case TCPC_RX_BUF_FRAME_TYPE_SOP1:
> +		rx_type = TCPC_TX_SOP_PRIME;
> +		break;
> +	case TCPC_RX_BUF_FRAME_TYPE_SOP:
> +		rx_type = TCPC_TX_SOP;
> +		break;
> +	default:
> +		rx_type = TCPC_TX_SOP;
> +		break;
> +	}
> +
> +	if (count == 0 || (frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP &&
> +	    frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP1)) {
>  		max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
>  		dev_err(chip->dev, "%s\n", count ==  0 ? "error: count is 0" :
> -			"error frame_type is not SOP");
> +			"error frame_type is not SOP/SOP'");
>  		return;
>  	}
>  
> @@ -183,7 +197,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status)
>  	if (ret < 0)
>  		return;
>  
> -	tcpm_pd_receive(chip->port, &msg);
> +	tcpm_pd_receive(chip->port, &msg, rx_type);
>  }
>  
>  static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink)
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index ff67553b6932..b05325dcd7ac 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -506,6 +506,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[] = {
> @@ -2969,12 +2970,17 @@ 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);
>  
>  	tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header),
>  		 port->attached);
>  
> +	/* Ignore SOP' for now */
> +	if (rx_sop_type == TCPC_TX_SOP_PRIME)
> +		goto done;
> +
>  	if (port->attached) {
>  		enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
>  		unsigned int msgid = pd_header_msgid_le(msg->header);
> @@ -3016,7 +3022,8 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
>  	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;
>  
> @@ -3026,6 +3033,7 @@ 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);
>  }
> diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c
> index 87d4abde0ea2..cf719307b3f6 100644
> --- a/drivers/usb/typec/tcpm/wcove.c
> +++ b/drivers/usb/typec/tcpm/wcove.c
> @@ -535,7 +535,7 @@ static irqreturn_t wcove_typec_irq(int irq, void *data)
>  				goto err;
>  			}
>  
> -			tcpm_pd_receive(wcove->tcpm, &msg);
> +			tcpm_pd_receive(wcove->tcpm, &msg, TCPC_TX_SOP);
>  
>  			ret = regmap_read(wcove->regmap, USBC_RXSTATUS,
>  					  &status);
> diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
> index 1d0b849defd0..9ed6d62c9c5f 100644
> --- a/include/linux/usb/tcpci.h
> +++ b/include/linux/usb/tcpci.h
> @@ -145,6 +145,7 @@
>  #define TCPC_RX_BYTE_CNT		0x30
>  #define TCPC_RX_BUF_FRAME_TYPE		0x31
>  #define TCPC_RX_BUF_FRAME_TYPE_SOP	0
> +#define TCPC_RX_BUF_FRAME_TYPE_SOP1	1
>  #define TCPC_RX_HDR			0x32
>  #define TCPC_RX_DATA			0x34 /* through 0x4f */
>  
> diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
> index 430fa3ec69bb..41d1ac9c8bbf 100644
> --- a/include/linux/usb/tcpm.h
> +++ b/include/linux/usb/tcpm.h
> @@ -170,7 +170,8 @@ void tcpm_cc_change(struct tcpm_port *port);
>  void tcpm_sink_frs(struct tcpm_port *port);
>  void tcpm_sourcing_vbus(struct tcpm_port *port);
>  void tcpm_pd_receive(struct tcpm_port *port,
> -		     const struct pd_message *msg);
> +		     const struct pd_message *msg,
> +		     enum tcpm_transmit_type rx_sop_type);
>  void tcpm_pd_transmit_complete(struct tcpm_port *port,
>  			       enum tcpm_transmit_status status);
>  void tcpm_pd_hard_reset(struct tcpm_port *port);
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages
  2023-12-14 23:08 ` [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages RD Babiera
@ 2023-12-19 14:07   ` Heikki Krogerus
  2024-01-08 19:13     ` RD Babiera
  0 siblings, 1 reply; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 14:07 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:49PM +0000, RD Babiera wrote:
> Add negotiated revision and tx/rx message ids to tcpm_port specific to
> SOP'. tx_sop_type is added to the tcpm_port to determine whether the
> current constructed message will be sent over SOP or SOP' if not
> sent immediately.
> 
> tcpm_pd_rx_handler updates the received message ids. SOP* messages are not
> processed afterwards. The handler also calls tcpm_can_communicate_sop_prime
> to determine if a SOP' message is directed towards the port, and drops SOP'
> messages it should not respond to.
> 
> tcpm_can_communicate_sop_prime is added as a helper to determine whether
> the port is capable of communicating over SOP' at a given moment. Being
> the Vconn source is a requirement in Power Delivery 3.0 but only a
> recommendation in Power Delviery 2.0. Because the port should ensure that
> the cable is powered before communication, always enforce the port is the
> Vconn source regardless of revision.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>
> ---
>  drivers/usb/typec/tcpm/tcpm.c | 156 +++++++++++++++++++++++++++++++---
>  1 file changed, 145 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index b05325dcd7ac..bc6c5f04e62f 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -493,6 +493,35 @@ struct tcpm_port {
>  	 * transitions.
>  	 */
>  	bool potential_contaminant;
> +
> +	/* SOP* Related Fields */
> +	/*
> +	 * 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 */
> @@ -882,19 +911,32 @@ 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:
> +		negotiated_rev = port->negotiated_rev;
> +		break;
> +	default:
> +		negotiated_rev = port->negotiated_rev;
> +		break;

Why not just fall through?

	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;
>  
> @@ -907,7 +949,20 @@ 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:
> +			port->message_id = (port->message_id + 1) &
> +					   PD_HEADER_ID_MASK;
> +			break;

Ditto.

> +		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:
> @@ -1592,6 +1647,57 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port)
>  
>  #define supports_modal(port)	PD_IDH_MODAL_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 int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  			const u32 *p, int cnt, u32 *response,
>  			enum adev_actions *adev_action)
> @@ -2977,14 +3083,18 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
>  	tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header),
>  		 port->attached);
>  
> -	/* Ignore SOP' for now */
> -	if (rx_sop_type == TCPC_TX_SOP_PRIME)
> -		goto done;
> -
>  	if (port->attached) {
>  		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
> @@ -2994,16 +3104,33 @@ 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)
> +		switch (rx_sop_type) {
> +		case TCPC_TX_SOP_PRIME:
> +			if (msgid == port->rx_msgid_prime)
> +				goto done;
> +			port->rx_msgid_prime = msgid;
> +			/* Ignore SOP' for now */
>  			goto done;
> -		port->rx_msgid = msgid;
> +		case TCPC_TX_SOP:
> +			if (msgid == port->rx_msgid &&
> +			    type != PD_CTRL_SOFT_RESET)
> +				goto done;
> +			port->rx_msgid = msgid;
> +			break;

Ditto.

> +		default:
> +			if (msgid == port->rx_msgid &&
> +			    type != PD_CTRL_SOFT_RESET)

And that fits on one line.

> +				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);
> @@ -3708,6 +3835,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 */
> @@ -4022,8 +4150,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 ||
> @@ -4263,8 +4394,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 ||
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 06/12] usb: typec: tcpm: add control message support to sop'
  2023-12-14 23:08 ` [PATCH v2 06/12] usb: typec: tcpm: add control message support to sop' RD Babiera
@ 2023-12-19 14:12   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 14:12 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:50PM +0000, RD Babiera wrote:
> Add tx_sop_type to tcpm_pd_send_control and rx_sop_type to
> tcpm_pd_ctrl_request. TCPC_TX_SOP is added to all pd_send_control calls,
> but TCPC_TX_SOP_PRIME is added to pd_send_control for a SOFT_RESET message
> sent after a Vconn swap that makes the Port the Vconn source. Likewise,
> tcpm_pd_ctrl_request resets the proper protocol layer depending on
> rx_sop_type for SOFT_RESET.
> 
> VCONN_SWAP_TURN_ON_VCONN now moves to a new state,
> VCONN_SWAP_SEND_SOFT_RESET. This state sends SOFT_RESET over SOP' before
> transitioning to the ready state if applicable. It transitions after
> PD_T_VCONN_STABLE, definied in pd.h as the time required for Vconn to be
> on before transmitting messages.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
>  drivers/usb/typec/tcpm/tcpm.c | 159 ++++++++++++++++++++++++----------
>  include/linux/usb/pd.h        |   1 +
>  2 files changed, 115 insertions(+), 45 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index bc6c5f04e62f..c1e1fd6bd60d 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -108,6 +108,7 @@
>  	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),		\
> @@ -2388,7 +2389,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)
> @@ -2742,10 +2744,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
> @@ -2910,6 +2914,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);
> @@ -3109,8 +3123,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
>  			if (msgid == port->rx_msgid_prime)
>  				goto done;
>  			port->rx_msgid_prime = msgid;
> -			/* Ignore SOP' for now */
> -			goto done;
> +			break;
>  		case TCPC_TX_SOP:
>  			if (msgid == port->rx_msgid &&
>  			    type != PD_CTRL_SOFT_RESET)
> @@ -3140,7 +3153,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work)
>  			else if (cnt)
>  				tcpm_pd_data_request(port, msg);
>  			else
> -				tcpm_pd_ctrl_request(port, msg);
> +				tcpm_pd_ctrl_request(port, msg, rx_sop_type);
>  		}
>  	}
>  
> @@ -3167,17 +3180,40 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg,
>  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);
>  }
>  
>  /*
> @@ -3196,13 +3232,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);
> @@ -4217,7 +4253,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);
> @@ -4225,7 +4261,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,
> @@ -4234,7 +4270,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;
> @@ -4718,7 +4754,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;
> @@ -4735,28 +4771,41 @@ 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);
> +		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;
>  		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);
> +		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;
>  		tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
> @@ -4780,7 +4829,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;
>  		}
> @@ -4800,7 +4849,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;
>  		}
> @@ -4810,11 +4859,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;
> @@ -4856,7 +4905,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;
>  		}
> @@ -4903,17 +4952,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;
> @@ -4932,14 +4981,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:
> @@ -4975,7 +5044,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;
> @@ -4983,7 +5052,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;
> @@ -4991,7 +5060,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:
> @@ -5031,7 +5100,7 @@ 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;
>  	default:
> diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
> index eb626af0e4e7..d50098fb16b5 100644
> --- a/include/linux/usb/pd.h
> +++ b/include/linux/usb/pd.h
> @@ -483,6 +483,7 @@ static inline unsigned int rdo_max_power(u32 rdo)
>  #define PD_T_BIST_CONT_MODE	50	/* 30 - 60 ms */
>  #define PD_T_SINK_TX		16	/* 16 - 20 ms */
>  #define PD_T_CHUNK_NOT_SUPP	42	/* 40 - 50 ms */
> +#define PD_T_VCONN_STABLE	50
>  
>  #define PD_T_DRP_TRY		100	/* 75 - 150 ms */
>  #define PD_T_DRP_TRYWAIT	600	/* 400 - 800 ms */
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 07/12] usb: typec: tcpci: add attempt_vconn_swap_discovery callback
  2023-12-14 23:08 ` [PATCH v2 07/12] usb: typec: tcpci: add attempt_vconn_swap_discovery callback RD Babiera
@ 2023-12-19 14:17   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 14:17 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:51PM +0000, RD Babiera wrote:
> Add attempt_vconn_swap_discovery callback to determine whether the TCPM
> should perform a Vconn swap following Discover Identity on SOP. The tcpci
> will return false unless chip level drivers implement the callback.
> 
> Maxim based TCPCs will return true unless the last connection resulted in
> a Vconn Over Current Fault, which may be the result of the Vconn swap. In
> addition to the port resetting, the TCPCI will veto the next Vconn swap
> from occurring.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
>  drivers/usb/typec/tcpm/tcpci.c            | 11 +++++++++++
>  drivers/usb/typec/tcpm/tcpci_maxim.h      |  1 +
>  drivers/usb/typec/tcpm/tcpci_maxim_core.c | 17 ++++++++++++++++-
>  include/linux/usb/tcpci.h                 |  9 +++++++++
>  include/linux/usb/tcpm.h                  |  9 +++++++++
>  5 files changed, 46 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
> index 8ea4ed159a13..40c7b6224c74 100644
> --- a/drivers/usb/typec/tcpm/tcpci.c
> +++ b/drivers/usb/typec/tcpm/tcpci.c
> @@ -594,6 +594,16 @@ static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc)
>  	return tcpci->data->cable_comm_capable;
>  }
>  
> +static bool tcpci_attempt_vconn_swap_discovery(struct tcpc_dev *tcpc)
> +{
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +
> +	if (tcpci->data->attempt_vconn_swap_discovery)
> +		return tcpci->data->attempt_vconn_swap_discovery(tcpci, tcpci->data);
> +
> +	return false;
> +}
> +
>  static int tcpci_init(struct tcpc_dev *tcpc)
>  {
>  	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> @@ -804,6 +814,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
>  	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
>  	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
>  	tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable;
> +	tcpci->tcpc.attempt_vconn_swap_discovery = tcpci_attempt_vconn_swap_discovery;
>  
>  	if (tcpci->data->check_contaminant)
>  		tcpci->tcpc.check_contaminant = tcpci_check_contaminant;
> diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h
> index 2c1c4d161b0d..78ff3b73ee7e 100644
> --- a/drivers/usb/typec/tcpm/tcpci_maxim.h
> +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h
> @@ -62,6 +62,7 @@ struct max_tcpci_chip {
>  	struct i2c_client *client;
>  	struct tcpm_port *port;
>  	enum contamiant_state contaminant_state;
> +	bool veto_vconn_swap;
>  };
>  
>  static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val)
> diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> index f9f838df43f7..eec3bcec119c 100644
> --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
> @@ -323,8 +323,10 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status)
>  		if (ret < 0)
>  			return ret;
>  
> -		if (reg_status & TCPC_FAULT_STATUS_VCONN_OC)
> +		if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) {
> +			chip->veto_vconn_swap = true;
>  			tcpm_port_error_recovery(chip->port);
> +		}
>  	}
>  
>  	if (status & TCPC_ALERT_EXTND) {
> @@ -458,6 +460,18 @@ static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data *
>  		tcpm_port_clean(chip->port);
>  }
>  
> +static bool max_tcpci_attempt_vconn_swap_discovery(struct tcpci *tcpci, struct tcpci_data *tdata)
> +{
> +	struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata);
> +
> +	if (chip->veto_vconn_swap) {
> +		chip->veto_vconn_swap = false;
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
>  static int max_tcpci_probe(struct i2c_client *client)
>  {
>  	int ret;
> @@ -493,6 +507,7 @@ static int max_tcpci_probe(struct i2c_client *client)
>  	chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable;
>  	chip->data.check_contaminant = max_tcpci_check_contaminant;
>  	chip->data.cable_comm_capable = true;
> +	chip->data.attempt_vconn_swap_discovery = max_tcpci_attempt_vconn_swap_discovery;
>  
>  	max_tcpci_init_regs(chip);
>  	chip->tcpci = tcpci_register_port(chip->dev, &chip->data);
> diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
> index 9ed6d62c9c5f..47a86b8a4a50 100644
> --- a/include/linux/usb/tcpci.h
> +++ b/include/linux/usb/tcpci.h
> @@ -201,6 +201,14 @@ struct tcpci;
>   *		toggling state.
>   * @cable_comm_capable
>   *		optional; Set when TCPC can communicate with cable plugs over SOP'
> + * @attempt_vconn_swap_discovery:
> + *		Optional; The callback is called by the TCPM when the result of
> + *		a Discover Identity request indicates that the port partner is
> + *		a receptacle capable of modal operation. Chip level TCPCI drivers
> + *		can implement their own policy to determine if and when a Vconn
> + *		swap following Discover Identity on SOP' occurs.
> + *		Return true when the TCPM is allowed to request a Vconn swap
> + *		after Discovery Identity on SOP.
>   */
>  struct tcpci_data {
>  	struct regmap *regmap;
> @@ -219,6 +227,7 @@ struct tcpci_data {
>  	void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data,
>  					     bool capable);
>  	void (*check_contaminant)(struct tcpci *tcpci, struct tcpci_data *data);
> +	bool (*attempt_vconn_swap_discovery)(struct tcpci *tcpci, struct tcpci_data *data);
>  };
>  
>  struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data);
> diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
> index 41d1ac9c8bbf..6671427f7eeb 100644
> --- a/include/linux/usb/tcpm.h
> +++ b/include/linux/usb/tcpm.h
> @@ -122,6 +122,14 @@ enum tcpm_transmit_type {
>   * @cable_comm_capable
>   *		Optional; Returns whether cable communication over SOP' is supported
>   *		by the tcpc
> + * @attempt_vconn_swap_discovery:
> + *		Optional; The callback is called by the TCPM when the result of
> + *		a Discover Identity request indicates that the port partner is
> + *		a receptacle capable of modal operation. Chip level TCPCI drivers
> + *		can implement their own policy to determine if and when a Vconn
> + *		swap following Discover Identity on SOP' occurs.
> + *		Return true when the TCPM is allowed to request a Vconn swap
> + *		after Discovery Identity on SOP.
>   */
>  struct tcpc_dev {
>  	struct fwnode_handle *fwnode;
> @@ -158,6 +166,7 @@ struct tcpc_dev {
>  	void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable);
>  	void (*check_contaminant)(struct tcpc_dev *dev);
>  	bool (*cable_comm_capable)(struct tcpc_dev *dev);
> +	bool (*attempt_vconn_swap_discovery)(struct tcpc_dev *dev);
>  };
>  
>  struct tcpm_port;
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP'
  2023-12-14 23:08 ` [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP' RD Babiera
@ 2023-12-19 14:26   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 14:26 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

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@google.com>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
> 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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 09/12] usb: typec: tcpm: add state machine support for SRC_VDM_IDENTITY_REQUEST
  2023-12-14 23:08 ` [PATCH v2 09/12] usb: typec: tcpm: add state machine support for SRC_VDM_IDENTITY_REQUEST RD Babiera
@ 2023-12-19 14:26   ` Heikki Krogerus
  0 siblings, 0 replies; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 14:26 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:53PM +0000, RD Babiera wrote:
> Add SRC_VDM_IDENTITY_REQUEST state which first enters after SRC_STARTUP.
> The state sends Discover Identity on SOP' and transitions to
> SRC_SEND_CAPABILITIES. SRC_SEND_CAPABILITIES will transition back into
> SRC_VDM_IDENTITY_REQUEST instead of retrying immediately.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>
> ---

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

>  drivers/usb/typec/tcpm/tcpm.c | 49 ++++++++++++++++++++++++++++++-----
>  1 file changed, 43 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index 5924e359e14d..e21bc2eea3fc 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -146,7 +146,9 @@
>  	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),				\
> @@ -1956,6 +1958,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  					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;
>  				}
> @@ -1987,6 +1990,16 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  				 * 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),
> @@ -2281,7 +2294,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;
>  		}
> @@ -2357,13 +2371,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;
> @@ -4477,8 +4500,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.
> @@ -5393,6 +5420,15 @@ static void run_state_machine(struct tcpm_port *port)
>  		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;
> @@ -6118,7 +6154,8 @@ static void tcpm_send_discover_work(struct kthread_work *work)
>  	}
>  
>  	/* 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;
>  	}
> -- 
> 2.43.0.472.g3155946c3a-goog

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop'
  2023-12-14 23:08 ` [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop' RD Babiera
@ 2023-12-19 14:32   ` Heikki Krogerus
  2024-01-08 19:02     ` RD Babiera
  0 siblings, 1 reply; 27+ messages in thread
From: Heikki Krogerus @ 2023-12-19 14:32 UTC (permalink / raw)
  To: RD Babiera
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Thu, Dec 14, 2023 at 11:08:54PM +0000, RD Babiera wrote:
> Adds Discover SVIDs and Discover Modes support for SOP' and Alt Mode
> SVDM support over SOP'. tcpm_port adds separate Alt Mode data for SOP'.
> 
> svdm_consume_svids and svdm_consume_modes take the received SVDM's SOP*
> type to store svids/modes separately, and tcpm_register_plug_altmodes
> registers the active cable's alt modes.
> 
> In tcpm_pd_svdm, the port will send Discover SVIDs to SOP' after Discover
> Modes on SOP if the connected cable is an active cable. Discover Modes on
> SOP' is sent following Discover SVIDs on SOP. Registering partner alt modes
> is delayed when an active cable is present until Discover Modes completes
> on SOP', or if the Discover SVIDs/Discover Modes request on SOP' encounters
> a transmission error.
> 
> Signed-off-by: RD Babiera <rdbabiera@google.com>
> ---
> Changes since v1:
> * Changes to tcpm_altmode_enter/exit/vdm are moved to next patch
> * adev_action changes are moved to next patch
> ---
>  drivers/usb/typec/tcpm/tcpm.c | 163 +++++++++++++++++++++++++++++-----
>  1 file changed, 139 insertions(+), 24 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index e21bc2eea3fc..61433dc4c917 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -465,7 +465,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 */
> @@ -1629,9 +1631,11 @@ static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p
>  	}
>  }
>  
> -static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
> +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 = &port->mode_data;
> +	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++) {
> @@ -1677,14 +1681,29 @@ static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
>  	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;
>  	}
>  
> @@ -1722,7 +1741,28 @@ 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))
>  
>  /*
> @@ -1800,6 +1840,15 @@ static bool tcpm_attempt_vconn_swap_discovery(struct tcpm_port *port)
>  	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,
> @@ -1807,8 +1856,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  			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;
> @@ -1829,6 +1878,11 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  
>  	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]));
>  		if (!IS_ERR_OR_NULL(port->cable)) {
>  			svdm_version = typec_get_cable_svdm_version(typec);
>  			if (PD_VDO_SVDM_VER(p[0]) < svdm_version)
> @@ -1836,11 +1890,21 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  		}
>  		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;
> @@ -1932,6 +1996,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  		 * 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.
> @@ -2011,26 +2078,62 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
>  			}
>  			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:
> @@ -2411,6 +2514,16 @@ static void vdm_run_state_machine(struct tcpm_port *port)
>  				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;
>  			}
> @@ -4123,14 +4236,16 @@ 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;
>  	}

I'm probable missing something, but where are the partner altmodes now
unregistered?

>  	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)

thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support
  2023-12-14 23:08 ` [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support RD Babiera
@ 2024-01-02 14:02   ` Dan Carpenter
  0 siblings, 0 replies; 27+ messages in thread
From: Dan Carpenter @ 2024-01-02 14:02 UTC (permalink / raw)
  To: oe-kbuild, RD Babiera, heikki.krogerus, linux, gregkh,
	linux-kernel, linux-usb
  Cc: lkp, oe-kbuild-all, badhri, bryan.odonoghue, agross, andersson,
	konrad.dybcio

Hi RD,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/RD-Babiera/usb-typec-altmodes-add-typec_cable_ops-to-typec_altmode/20231215-071339
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
patch link:    https://lore.kernel.org/r/20231214230850.379863-26-rdbabiera%40google.com
patch subject: [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support
config: riscv-randconfig-r081-20231216 (https://download.01.org/0day-ci/archive/20231216/202312161205.sNH5M6Pz-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project.git 4a5ac14ee968ff0ad5d2cc1ffa0299048db4c88a)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202312161205.sNH5M6Pz-lkp@intel.com/

smatch warnings:
drivers/usb/typec/altmodes/displayport.c:317 dp_altmode_work() warn: inconsistent returns '&dp->lock'.

vim +317 drivers/usb/typec/altmodes/displayport.c

0e3bb7d6894d9b Heikki Krogerus 2018-06-27  240  static void dp_altmode_work(struct work_struct *work)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  241  {
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  242  	struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
4c93cad8cc78bd Kyle Tso        2021-02-05  243  	int svdm_version;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  244  	u32 header;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  245  	u32 vdo;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  246  	int ret;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  247  
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  248  	mutex_lock(&dp->lock);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  249  
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  250  	switch (dp->state) {
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  251  	case DP_STATE_ENTER:
8face9aa57c833 Heikki Krogerus 2019-12-30  252  		ret = typec_altmode_enter(dp->alt, NULL);
5789051fc57bb6 Heikki Krogerus 2020-09-28  253  		if (ret && ret != -EBUSY)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  254  			dev_err(&dp->alt->dev, "failed to enter mode\n");
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  255  		break;
92483f2f3092f0 RD Babiera      2023-12-14  256  	case DP_STATE_ENTER_PRIME:
92483f2f3092f0 RD Babiera      2023-12-14  257  		ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL);
92483f2f3092f0 RD Babiera      2023-12-14  258  		/*
92483f2f3092f0 RD Babiera      2023-12-14  259  		 * If we fail to enter Alt Mode on SOP', then we should drop the
92483f2f3092f0 RD Babiera      2023-12-14  260  		 * plug from the driver and attempt to run the driver without
92483f2f3092f0 RD Babiera      2023-12-14  261  		 * it.
92483f2f3092f0 RD Babiera      2023-12-14  262  		 */
92483f2f3092f0 RD Babiera      2023-12-14  263  		if (ret && ret != -EBUSY) {
92483f2f3092f0 RD Babiera      2023-12-14  264  			dev_err(&dp->alt->dev, "plug failed to enter mode\n");
92483f2f3092f0 RD Babiera      2023-12-14  265  			dp->state = DP_STATE_ENTER;
92483f2f3092f0 RD Babiera      2023-12-14  266  			goto disable_prime;
92483f2f3092f0 RD Babiera      2023-12-14  267  		}
92483f2f3092f0 RD Babiera      2023-12-14  268  		break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  269  	case DP_STATE_UPDATE:
4c93cad8cc78bd Kyle Tso        2021-02-05  270  		svdm_version = typec_altmode_get_svdm_version(dp->alt);
4c93cad8cc78bd Kyle Tso        2021-02-05  271  		if (svdm_version < 0)
4c93cad8cc78bd Kyle Tso        2021-02-05  272  			break;
4c93cad8cc78bd Kyle Tso        2021-02-05  273  		header = DP_HEADER(dp, svdm_version, DP_CMD_STATUS_UPDATE);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  274  		vdo = 1;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  275  		ret = typec_altmode_vdm(dp->alt, header, &vdo, 2);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  276  		if (ret)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  277  			dev_err(&dp->alt->dev,
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  278  				"unable to send Status Update command (%d)\n",
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  279  				ret);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  280  		break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  281  	case DP_STATE_CONFIGURE:
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  282  		ret = dp_altmode_configure_vdm(dp, dp->data.conf);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  283  		if (ret)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  284  			dev_err(&dp->alt->dev,
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  285  				"unable to send Configure command (%d)\n", ret);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  286  		break;
92483f2f3092f0 RD Babiera      2023-12-14  287  	case DP_STATE_CONFIGURE_PRIME:
92483f2f3092f0 RD Babiera      2023-12-14  288  		ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf);
92483f2f3092f0 RD Babiera      2023-12-14  289  		if (ret) {
92483f2f3092f0 RD Babiera      2023-12-14  290  			dev_err(&dp->plug_prime->dev,
92483f2f3092f0 RD Babiera      2023-12-14  291  				"unable to send Configure command (%d)\n",
92483f2f3092f0 RD Babiera      2023-12-14  292  				ret);
92483f2f3092f0 RD Babiera      2023-12-14  293  			dp->state = DP_STATE_CONFIGURE;
92483f2f3092f0 RD Babiera      2023-12-14  294  			goto disable_prime;
92483f2f3092f0 RD Babiera      2023-12-14  295  		}
92483f2f3092f0 RD Babiera      2023-12-14  296  		break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  297  	case DP_STATE_EXIT:
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  298  		if (typec_altmode_exit(dp->alt))
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  299  			dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  300  		break;
92483f2f3092f0 RD Babiera      2023-12-14  301  	case DP_STATE_EXIT_PRIME:
92483f2f3092f0 RD Babiera      2023-12-14  302  		if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P))
92483f2f3092f0 RD Babiera      2023-12-14  303  			dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n");
92483f2f3092f0 RD Babiera      2023-12-14  304  		break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  305  	default:
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  306  		break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  307  	}
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  308  
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  309  	dp->state = DP_STATE_IDLE;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  310  
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  311  	mutex_unlock(&dp->lock);
92483f2f3092f0 RD Babiera      2023-12-14  312  	return;
92483f2f3092f0 RD Babiera      2023-12-14  313  
92483f2f3092f0 RD Babiera      2023-12-14  314  disable_prime:
92483f2f3092f0 RD Babiera      2023-12-14  315  	typec_altmode_put_plug(dp->plug_prime);
92483f2f3092f0 RD Babiera      2023-12-14  316  	dp->plug_prime = NULL;

We need a mutex_unlock(&dp->lock); somewhere here.

92483f2f3092f0 RD Babiera      2023-12-14 @317  	schedule_work(&dp->work);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27  318  }

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop'
  2023-12-19 14:32   ` Heikki Krogerus
@ 2024-01-08 19:02     ` RD Babiera
  0 siblings, 0 replies; 27+ messages in thread
From: RD Babiera @ 2024-01-08 19:02 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

I must've replaced the partner unregistration instead of adding the
cable one when
splitting the patchset, sorry about that. Thanks for the catch.

On Tue, Dec 19, 2023 at 6:32 AM Heikki Krogerus
<heikki.krogerus@linux.intel.com> wrote:
> I'm probable missing something, but where are the partner altmodes now
> unregistered?
>
> >       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)
>

best,
rd

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages
  2023-12-19 14:07   ` Heikki Krogerus
@ 2024-01-08 19:13     ` RD Babiera
  0 siblings, 0 replies; 27+ messages in thread
From: RD Babiera @ 2024-01-08 19:13 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: linux, gregkh, linux-kernel, linux-usb, badhri, bryan.odonoghue,
	agross, andersson, konrad.dybcio

On Tue, Dec 19, 2023 at 6:07 AM Heikki Krogerus
<heikki.krogerus@linux.intel.com> wrote:
> Why not just fall through?
>
>         case TCPC_TX_SOP:
>         default:
>                 negotiated_rev = port->negotiated_rev;
>                 break;
>

Will do, thanks for the advice!

best,
rd

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2024-01-08 19:13 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-12-14 23:08 [PATCH v2 00/12] usb: typec: add SOP' support to the tcpm and alt mode drivers RD Babiera
2023-12-14 23:08 ` [PATCH v2 01/12] usb: typec: altmodes: add typec_cable_ops to typec_altmode RD Babiera
2023-12-16  2:21   ` kernel test robot
2023-12-17 15:11   ` kernel test robot
2023-12-14 23:08 ` [PATCH v2 02/12] usb: typec: altmodes: add svdm version info for typec cables RD Babiera
2023-12-19 13:54   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 03/12] usb: typec: tcpci: add cable_comm_capable attribute RD Babiera
2023-12-19 13:55   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 04/12] usb: typec: tcpci: add tcpm_transmit_type to tcpm_pd_receive RD Babiera
2023-12-19 13:59   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 05/12] usb: typec: tcpm: process receive and transmission of sop' messages RD Babiera
2023-12-19 14:07   ` Heikki Krogerus
2024-01-08 19:13     ` RD Babiera
2023-12-14 23:08 ` [PATCH v2 06/12] usb: typec: tcpm: add control message support to sop' RD Babiera
2023-12-19 14:12   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 07/12] usb: typec: tcpci: add attempt_vconn_swap_discovery callback RD Babiera
2023-12-19 14:17   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 08/12] usb: typec: tcpm: add discover identity support for SOP' RD Babiera
2023-12-19 14:26   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 09/12] usb: typec: tcpm: add state machine support for SRC_VDM_IDENTITY_REQUEST RD Babiera
2023-12-19 14:26   ` Heikki Krogerus
2023-12-14 23:08 ` [PATCH v2 10/12] usb: typec: tcpm: add discover svids and discover modes support for sop' RD Babiera
2023-12-19 14:32   ` Heikki Krogerus
2024-01-08 19:02     ` RD Babiera
2023-12-14 23:08 ` [PATCH v2 11/12] usb: typec: tcpm: add alt mode enter/exit/vdm " RD Babiera
2023-12-14 23:08 ` [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support RD Babiera
2024-01-02 14:02   ` Dan Carpenter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).