[PATCH 5/5] usb: typec: fusb302: Enable SOP' communication

From: Alexey Charkov

Date: Tue Nov 03 2026 - 07:02:04 EST


Let TCPM discover e-marked cables through the FUSB302 and learn their
current and speed ratings.

Receive SOP' only while sourcing VCONN with PD reception on. Only the
VCONN Source talks to the cable plug, and acking the traffic between the
partner and the plug would collide with their own GoodCRC.

Switch SOP' reception off before VCONN or PD reception goes off and on
only once both are on. If that fails, log it instead of failing the
switch TCPM asked for: SOP' reception left off only costs cable
discovery, and left on with VCONN off makes the port ack the partner's
traffic with the cable plug until PD reception goes off, which beats
keeping VCONN or PD reception in a state TCPM did not request.

Tested on a FUSB302B as source with an e-marked 5A EPR cable. The
datasheet documents the auto-GoodCRC header bits for SOP only, but the
cable plug took the chip's acks to its SOP' replies without retrying.

Signed-off-by: Alexey Charkov <alchark@xxxxxxxxxxx>
---
drivers/usb/typec/tcpm/fusb302.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)

diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
index 8b3a2e095ef8..10f2dabf3318 100644
--- a/drivers/usb/typec/tcpm/fusb302.c
+++ b/drivers/usb/typec/tcpm/fusb302.c
@@ -725,6 +725,27 @@ static int tcpm_set_polarity(struct tcpc_dev *dev,
return 0;
}

+/*
+ * Without VCONN from us, SOP' traffic is between the partner and the cable
+ * plug, and our GoodCRC would collide with theirs. So stop receiving SOP'
+ * before VCONN or PD reception goes off, and start only once both are on.
+ * Neither is worth failing the VCONN or PD reception switch over.
+ */
+static void fusb302_pd_set_sop_prime(struct fusb302_chip *chip, bool on)
+{
+ int ret;
+
+ if (on)
+ ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1,
+ FUSB_REG_CONTROL1_ENSOP1);
+ else
+ ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL1,
+ FUSB_REG_CONTROL1_ENSOP1);
+ if (ret < 0)
+ fusb302_log(chip, "cannot turn SOP' rx %s, ret=%d",
+ str_on_off(on), ret);
+}
+
static int tcpm_set_vconn(struct tcpc_dev *dev, bool on)
{
struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
@@ -743,6 +764,8 @@ static int tcpm_set_vconn(struct tcpc_dev *dev, bool on)
switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
FUSB_REG_SWITCHES0_VCONN_CC2 :
FUSB_REG_SWITCHES0_VCONN_CC1;
+ } else {
+ fusb302_pd_set_sop_prime(chip, false);
}
ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
switches0_mask, switches0_data);
@@ -750,6 +773,9 @@ static int tcpm_set_vconn(struct tcpc_dev *dev, bool on)
goto done;
chip->vconn_on = on;
fusb302_log(chip, "vconn := %s", str_on_off(on));
+
+ if (on && chip->pd_rx_on)
+ fusb302_pd_set_sop_prime(chip, true);
done:
mutex_unlock(&chip->lock);

@@ -848,6 +874,8 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on)
goto done;
}

+ if (!on)
+ fusb302_pd_set_sop_prime(chip, false);
ret = fusb302_pd_rx_flush(chip);
if (ret < 0) {
fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret);
@@ -873,6 +901,9 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on)

chip->pd_rx_on = on;
fusb302_log(chip, "pd := %s", str_on_off(on));
+
+ if (on && chip->vconn_on)
+ fusb302_pd_set_sop_prime(chip, true);
done:
mutex_unlock(&chip->lock);

@@ -1136,6 +1167,11 @@ static void fusb302_bc_lvl_handler_work(struct work_struct *work)
mutex_unlock(&chip->lock);
}

+static bool tcpm_cable_comm_capable(struct tcpc_dev *dev)
+{
+ return true;
+}
+
static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev)
{
fusb302_tcpc_dev->init = tcpm_init;
@@ -1150,6 +1186,7 @@ static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev)
fusb302_tcpc_dev->set_roles = tcpm_set_roles;
fusb302_tcpc_dev->start_toggling = tcpm_start_toggling;
fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit;
+ fusb302_tcpc_dev->cable_comm_capable = tcpm_cable_comm_capable;
}

static const char * const cc_polarity_name[] = {

--
2.56.0