[PATCH RFC v3 07/10] usb: misc: qcom_eud: add host mode coordination
From: Elson Serrao
Date: Mon Mar 09 2026 - 16:35:10 EST
EUD functions by presenting itself as a USB device to the host PC for
debugging, making it incompatible with USB host mode configurations.
Handle below two scenarios to prevent these conflicts:
1. Prevent user from enabling EUD via sysfs when the USB port is
in host mode.
2. Automatically disable EUD when USB port switches to host mode
and re-enable it when exiting host mode. This is achieved via
the exported qcom_eud_usb_role_notify() API that allows the USB
controller driver to notify EUD of role changes.
This ensures consistent state management without creating conflicts
between the EUD debug hub and the USB controller.
Signed-off-by: Elson Serrao <elson.serrao@xxxxxxxxxxxxxxxx>
---
drivers/usb/misc/qcom_eud.c | 110 ++++++++++++++++++++++++++++++++++-
include/linux/usb/qcom_eud.h | 21 +++++++
2 files changed, 130 insertions(+), 1 deletion(-)
create mode 100644 include/linux/usb/qcom_eud.h
diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
index 3a71a0d27b5e..e01605e1dac8 100644
--- a/drivers/usb/misc/qcom_eud.c
+++ b/drivers/usb/misc/qcom_eud.c
@@ -12,11 +12,13 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
+#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/usb/role.h>
+#include <linux/usb/qcom_eud.h>
#include <linux/firmware/qcom/qcom_scm.h>
#define EUD_REG_INT1_EN_MASK 0x0024
@@ -42,11 +44,14 @@ struct eud_chip {
struct phy *phy[EUD_MAX_PORTS];
void __iomem *base;
phys_addr_t mode_mgr;
+ /* serializes EUD control operations */
+ struct mutex state_lock;
unsigned int int_status;
int irq;
bool enabled;
bool usb_attached;
bool phy_enabled;
+ bool eud_disabled_for_host;
u8 port_idx;
};
@@ -142,17 +147,43 @@ static ssize_t enable_store(struct device *dev,
const char *buf, size_t count)
{
struct eud_chip *chip = dev_get_drvdata(dev);
+ enum usb_role role;
bool enable;
int ret;
if (kstrtobool(buf, &enable))
return -EINVAL;
+ guard(mutex)(&chip->state_lock);
+
/* Skip operation if already in desired state */
if (chip->enabled == enable)
return count;
+ /*
+ * Handle double-disable scenario: User is disabling EUD that was already
+ * disabled due to host mode. Since the hardware is already disabled, we
+ * only need to clear the host-disabled flag to prevent unwanted re-enabling
+ * when exiting host mode. This respects the user's explicit disable request.
+ */
+ if (!enable && chip->eud_disabled_for_host) {
+ chip->eud_disabled_for_host = false;
+ chip->enabled = false;
+ return count;
+ }
+
if (enable) {
+ /*
+ * EUD functions by presenting itself as a USB device to the host PC for
+ * debugging, making it incompatible with USB host mode configuration.
+ * Prevent enabling EUD in this configuration to avoid hardware conflicts.
+ */
+ role = usb_role_switch_get_role(chip->role_sw[chip->port_idx]);
+ if (role == USB_ROLE_HOST) {
+ dev_err(chip->dev, "Cannot enable EUD: USB port is in host mode\n");
+ return -EBUSY;
+ }
+
ret = enable_eud(chip);
if (ret) {
dev_err(chip->dev, "failed to enable eud\n");
@@ -353,6 +384,75 @@ static int eud_parse_dt_port(struct eud_chip *chip, u8 port_id)
return 0;
}
+/**
+ * qcom_eud_usb_role_notify - Notify EUD of USB role change
+ * @eud_node: Device node of the EUD device
+ * @phy: HSUSB PHY of the port changing role
+ * @role: New role being set
+ *
+ * Notifies EUD that a USB port is changing roles. EUD will disable itself
+ * if the port is switching to HOST mode, as EUD is incompatible with host
+ * mode operation. This API should be called by the USB controller driver
+ * when it switches the USB role.
+ *
+ * The PHY parameter is used to identify which physical USB port is changing
+ * roles. This is important in multi-port systems where EUD may be active on
+ * one port while another port changes roles.
+ *
+ * This is a best-effort notification - failures are logged but do not affect
+ * the role change operation.
+ */
+void qcom_eud_usb_role_notify(struct device_node *eud_node, struct phy *phy,
+ enum usb_role role)
+{
+ struct platform_device *pdev;
+ struct eud_chip *chip;
+ int ret;
+
+ if (!of_device_is_compatible(eud_node, "qcom,eud"))
+ return;
+
+ pdev = of_find_device_by_node(eud_node);
+ if (!pdev)
+ return;
+
+ chip = platform_get_drvdata(pdev);
+ if (!chip)
+ goto put_dev;
+
+ mutex_lock(&chip->state_lock);
+
+ /* Only act if this notification is for the currently active EUD port */
+ if (!chip->enabled || chip->phy[chip->port_idx] != phy) {
+ mutex_unlock(&chip->state_lock);
+ goto put_dev;
+ }
+
+ /*
+ * chip->enabled preserves user's sysfs configuration and is not modified
+ * during host mode transitions to preserve user intent.
+ */
+ if (role == USB_ROLE_HOST && !chip->eud_disabled_for_host) {
+ ret = disable_eud(chip);
+ if (ret)
+ dev_err(chip->dev, "Failed to disable EUD for host mode: %d\n", ret);
+ else
+ chip->eud_disabled_for_host = true;
+ } else if (role != USB_ROLE_HOST && chip->eud_disabled_for_host) {
+ ret = enable_eud(chip);
+ if (ret)
+ dev_err(chip->dev, "Failed to re-enable EUD after host mode: %d\n", ret);
+ else
+ chip->eud_disabled_for_host = false;
+ }
+
+ mutex_unlock(&chip->state_lock);
+
+put_dev:
+ platform_device_put(pdev);
+}
+EXPORT_SYMBOL_GPL(qcom_eud_usb_role_notify);
+
static void eud_role_switch_release(void *data)
{
struct eud_chip *chip = data;
@@ -374,6 +474,8 @@ static int eud_probe(struct platform_device *pdev)
chip->dev = &pdev->dev;
+ mutex_init(&chip->state_lock);
+
/*
* Parse the DT resources for primary port.
* This is the default EUD port and is mandatory.
@@ -418,8 +520,14 @@ static void eud_remove(struct platform_device *pdev)
{
struct eud_chip *chip = platform_get_drvdata(pdev);
- if (chip->enabled)
+ platform_set_drvdata(pdev, NULL);
+
+ mutex_lock(&chip->state_lock);
+ if (chip->enabled) {
disable_eud(chip);
+ chip->enabled = false;
+ }
+ mutex_unlock(&chip->state_lock);
device_init_wakeup(&pdev->dev, false);
disable_irq_wake(chip->irq);
diff --git a/include/linux/usb/qcom_eud.h b/include/linux/usb/qcom_eud.h
new file mode 100644
index 000000000000..57e86056303c
--- /dev/null
+++ b/include/linux/usb/qcom_eud.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __LINUX_USB_QCOM_EUD_H
+#define __LINUX_USB_QCOM_EUD_H
+
+#include <linux/usb/role.h>
+
+#if IS_ENABLED(CONFIG_USB_QCOM_EUD)
+void qcom_eud_usb_role_notify(struct device_node *eud_node, struct phy *phy,
+ enum usb_role role);
+#else
+static inline void qcom_eud_usb_role_notify(struct device_node *eud_node, struct phy *phy,
+ enum usb_role role)
+{
+}
+#endif
+
+#endif /* __LINUX_USB_QCOM_EUD_H */
--
2.34.1