[PATCH v8 6/8] usb: misc: qcom_eud: add role-based EUD control
From: Elson Serrao <hidden>
Date: 2026-09-12 22:53:13
Also in:
linux-arm-msm, linux-usb, lkml
Subsystem:
arm/qualcomm mailing list, qcom embedded usb debugger (eud), the rest, usb subsystem · Maintainers:
Souradeep Chowdhury, Linus Torvalds, Greg Kroah-Hartman
EUD functions by presenting itself as a USB device to the host PC and is therefore only applicable when the port operates in device role. Keeping EUD enabled in other roles provides no benefit and unnecessarily keeps the associated PHY resources enabled. Furthermore, enabling EUD in host role can cause the USB controller to misbehave because the EUD hub supports only a single upstream-facing port. Address this by treating EUD as usable only in device role. The role-switch callback enables or disables EUD as the port enters or leaves device role. A sysfs enable request made outside device role is recorded and honored when the port subsequently switches to device role. Additionally, synchronize EUD state management across different paths to avoid races and maintain consistent hardware state. Reviewed-by: Peter Chen <peter.chen@kernel.org> Reviewed-by: Konrad Dybcio <redacted> Signed-off-by: Elson Serrao <redacted> --- drivers/usb/misc/qcom_eud.c | 60 +++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
index d280628370e4..353c45dfd328 100644
--- a/drivers/usb/misc/qcom_eud.c
+++ b/drivers/usb/misc/qcom_eud.c@@ -15,6 +15,7 @@ #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/sysfs.h> #include <linux/usb/role.h> #include <linux/firmware/qcom/qcom_scm.h>
@@ -55,6 +56,8 @@ struct eud_chip { struct device *dev; void __iomem *base; struct eud_path *paths[EUD_MAX_PORTS]; + /* serializes EUD control operations */ + struct mutex state_lock; phys_addr_t mode_mgr; unsigned int int_status; int irq;
@@ -156,17 +159,33 @@ static ssize_t enable_store(struct device *dev, const char *buf, size_t count) { struct eud_chip *chip = dev_get_drvdata(dev); + struct eud_path *path; 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; if (enable) { + path = chip->paths[chip->port_idx]; + + /* + * If not yet in device role, honor the userspace request and defer + * EUD enablement until the port transitions to device role in the + * set_role callback. + */ + if (path->curr_role != USB_ROLE_DEVICE) { + dev_info(chip->dev, "Deferring EUD enable until port enters device mode\n"); + chip->enabled = enable; + return count; + } + ret = enable_eud(chip); if (ret) { dev_err(chip->dev, "failed to enable eud\n");
@@ -204,6 +223,8 @@ static ssize_t port_store(struct device *dev, struct device_attribute *attr, if (port < 0) return port; + guard(mutex)(&chip->state_lock); + /* Check if the corresponding path is available */ if (!chip->paths[port]) return -EOPNOTSUPP;
@@ -285,8 +306,13 @@ static irqreturn_t handle_eud_irq_thread(int irq, void *data) struct eud_path *path; int ret; + guard(mutex)(&chip->state_lock); + path = chip->paths[chip->port_idx]; + if (!chip->enabled || path->curr_role != USB_ROLE_DEVICE) + goto out; + if (chip->usb_attached) ret = usb_role_switch_set_role(path->controller_sw, USB_ROLE_DEVICE); else
@@ -294,6 +320,7 @@ static irqreturn_t handle_eud_irq_thread(int irq, void *data) if (ret) dev_err(chip->dev, "failed to set role switch\n"); +out: /* set and clear vbus_int_clr[0] to clear interrupt */ writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR); writel(0, chip->base + EUD_REG_VBUS_INT_CLR);
@@ -304,7 +331,30 @@ static irqreturn_t handle_eud_irq_thread(int irq, void *data) static int eud_role_switch_set(struct usb_role_switch *sw, enum usb_role role) { struct eud_path *path = usb_role_switch_get_drvdata(sw); - int ret; + struct eud_chip *chip = path->chip; + int ret = 0; + + guard(mutex)(&chip->state_lock); + + /* + * EUD is usable only in device role. Power it down for every other + * role to avoid keeping an unusable module 'ON'. chip->enabled + * preserves user's sysfs configuration and is not modified across + * role transitions. + */ + if (chip->enabled && path->num == chip->port_idx && role != path->curr_role) { + if (role == USB_ROLE_DEVICE) + ret = enable_eud(chip); + else if (path->curr_role == USB_ROLE_DEVICE) + ret = disable_eud(chip); + + if (ret) { + dev_err(chip->dev, "failed to %s EUD for role %s: %d\n", + str_enable_disable(role == USB_ROLE_DEVICE), + usb_role_string(role), ret); + return ret; + } + } /* curr_role tracks the role from EUD's point of view */ path->curr_role = role;
@@ -434,6 +484,8 @@ static int eud_probe(struct platform_device *pdev) chip->dev = &pdev->dev; + mutex_init(&chip->state_lock); + chip->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(chip->base)) return PTR_ERR(chip->base);
@@ -473,8 +525,12 @@ static void eud_remove(struct platform_device *pdev) { struct eud_chip *chip = platform_get_drvdata(pdev); - if (chip->enabled) + 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);
--
2.34.1