From 446a1f030394a973fee976179655805e86645958 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:27 -0700 Subject: [PATCH 1/9] FROMLIST: dt-bindings: soc: qcom: eud: Add per-path child nodes for UTMI routing The Qualcomm Embedded USB Debugger (EUD) can intercept one or two independent High-Speed UTMI paths, depending on the SoC configuration. Each path is distinct, with its own connector/controller connection and role-dependent UTMI routing. Because the EUD sits between the USB connector and the USB controller, it must relay role changes across the UTMI path. In device role, the EUD inserts its internal hub into the path to enable debug functionality. In host role, the path remains directly connected between the PHY and the USB controller, bypassing the EUD hub. These hardware constraints require per-path role awareness, as UTMI path roles may differ. The existing binding models only a single UTMI path and assumes a uniform routing model. While sufficient for simple device-role-only configurations, this representation does not accurately describe EUD hardware when role switching and/or multiple UTMI paths are involved. To address this limitation, per-path child nodes are introduced to describe individual UTMI paths through the EUD. Each path includes its own ports description, allowing controller and connector associations, as well as role-aware routing. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- .../bindings/soc/qcom/qcom,eud.yaml | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml index 84218636c0d8d..21f75038a81c6 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml @@ -45,10 +45,63 @@ properties: $ref: /schemas/graph.yaml#/properties/port description: This port is to be attached to the type C connector. + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + "^eud-path@[0-1]$": + type: object + description: + Represents one High-Speed UTMI path that EUD intercepts. Use eud-path nodes + to associate role-switching behavior with specific port connections, allowing + EUD to manage role transitions independently for each UTMI path. + + properties: + reg: + maxItems: 1 + description: Path number + + usb-role-switch: + type: boolean + description: + Indicates that EUD should act as a role switch for this path. + In device role, debug mode inserts the EUD hub into the UTMI path. In + host role, the EUD hub is bypassed and UTMI traffic flows directly + between the PHY and the USB controller. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: + These ports are to be attached to the endpoint of the USB controller node + and USB connector node. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: This port is to be attached to the USB controller. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: This port is to be attached to the USB connector. + + required: + - reg + - ports + + additionalProperties: false + required: - compatible - reg - - ports + +oneOf: + - required: + - ports + - required: + - eud-path@0 additionalProperties: false From 8711ab46d87d5e632985aa1eaf91abc1cb8f1b3c Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:28 -0700 Subject: [PATCH 2/9] FROMLIST: usb: misc: qcom_eud: add sysfs attribute for port selection EUD can be mapped to either the primary USB port or the secondary USB port depending on the value of the EUD_PORT_SEL register. Add a 'port' sysfs attribute to allow userspace to select which port EUD should operate on and update the ABI documentation. This is needed for systems with dual USB ports where EUD needs to be accessible on either port depending on the system configuration and use case. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- Documentation/ABI/testing/sysfs-driver-eud | 16 +++++++++ drivers/usb/misc/qcom_eud.c | 41 ++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-driver-eud b/Documentation/ABI/testing/sysfs-driver-eud index 2bab0db2d2f0f..a2a4fc5a9915e 100644 --- a/Documentation/ABI/testing/sysfs-driver-eud +++ b/Documentation/ABI/testing/sysfs-driver-eud @@ -7,3 +7,19 @@ Description: EUD based on a 1 or a 0 value. By enabling EUD, the user is able to activate the mini-usb hub of EUD for debug and trace capabilities. + +What: /sys/bus/platform/drivers/qcom_eud/.../port +Date: January 2026 +Contact: Elson Serrao +Description: + Selects which USB port the Embedded USB Debugger (EUD) + is mapped to on platforms providing multiple High-Speed + USB ports. + + Valid values: + primary - Primary USB port + secondary - Secondary USB port + + The attribute is writable only while EUD is disabled. + Reading the attribute returns the currently selected + USB port. diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 926419ca560fc..4aa49f0f58c0c 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -23,14 +23,22 @@ #define EUD_REG_VBUS_INT_CLR 0x0080 #define EUD_REG_CSR_EUD_EN 0x1014 #define EUD_REG_SW_ATTACH_DET 0x1018 +#define EUD_REG_PORT_SEL 0x1028 #define EUD_REG_EUD_EN2 0x0000 +#define EUD_MAX_PORTS 2 + #define EUD_ENABLE BIT(0) #define EUD_INT_PET_EUD BIT(0) #define EUD_INT_VBUS BIT(2) #define EUD_INT_SAFE_MODE BIT(4) #define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE) +static const char * const eud_port_names[] = { + "primary", + "secondary", +}; + struct eud_chip { struct device *dev; struct usb_role_switch *role_sw; @@ -40,6 +48,7 @@ struct eud_chip { int irq; bool enabled; bool usb_attached; + u8 port_idx; }; static int enable_eud(struct eud_chip *priv) @@ -104,8 +113,40 @@ static ssize_t enable_store(struct device *dev, static DEVICE_ATTR_RW(enable); +static ssize_t port_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", eud_port_names[chip->port_idx]); +} + +static ssize_t port_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + int port; + + port = sysfs_match_string(eud_port_names, buf); + if (port < 0) + return port; + + /* Port selection must be done before enabling EUD */ + if (chip->enabled) { + dev_err(chip->dev, "Cannot change port while EUD is enabled\n"); + return -EBUSY; + } + + writel(port, chip->base + EUD_REG_PORT_SEL); + chip->port_idx = port; + + return count; +} + +static DEVICE_ATTR_RW(port); + static struct attribute *eud_attrs[] = { &dev_attr_enable.attr, + &dev_attr_port.attr, NULL, }; ATTRIBUTE_GROUPS(eud); From a8acec838d2aa3e33bea8e5d4ddc4d07839fd921 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:29 -0700 Subject: [PATCH 3/9] FROMLIST: usb: misc: qcom_eud: add per-path High-Speed PHY control EUD hardware can support multiple High-Speed USB paths, each routed through its own PHY. The active path is selected in hardware via the EUD_PORT_SEL register. As a High-Speed hub, EUD requires access to the High-Speed PHY associated with the active path. To support this multi-path capability, the driver must manage PHY resources on a per-path basis, ensuring that the PHY for the currently selected path is properly initialized and powered. This patch restructures the driver to implement per-path PHY management. The driver now powers the appropriate PHY based on the selected and enabled UTMI path, ensuring correct operation when EUD is enabled. Historically, EUD appeared to work on single-path systems because the USB controller kept the PHY initialized. However, EUD is designed to operate independently of the USB controller and therefore requires explicit PHY control for proper operation. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- drivers/usb/misc/qcom_eud.c | 126 +++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 4aa49f0f58c0c..a624c44d5d599 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -39,26 +41,84 @@ static const char * const eud_port_names[] = { "secondary", }; +struct eud_path { + struct eud_chip *chip; + struct phy *phy; + u8 num; +}; + struct eud_chip { struct device *dev; struct usb_role_switch *role_sw; void __iomem *base; + struct eud_path *paths[EUD_MAX_PORTS]; phys_addr_t mode_mgr; unsigned int int_status; int irq; bool enabled; bool usb_attached; + bool phy_enabled; u8 port_idx; }; +static int eud_phy_enable(struct eud_chip *chip) +{ + struct phy *phy; + int ret; + + if (chip->phy_enabled) + return 0; + + phy = chip->paths[chip->port_idx]->phy; + + ret = phy_init(phy); + if (ret) { + dev_err(chip->dev, "Failed to initialize USB2 PHY for port %u: %d\n", + chip->port_idx, ret); + return ret; + } + + ret = phy_power_on(phy); + if (ret) { + dev_err(chip->dev, "Failed to power on USB2 PHY for port %u: %d\n", + chip->port_idx, ret); + phy_exit(phy); + return ret; + } + + chip->phy_enabled = true; + + return 0; +} + +static void eud_phy_disable(struct eud_chip *chip) +{ + struct phy *phy; + + if (!chip->phy_enabled) + return; + + phy = chip->paths[chip->port_idx]->phy; + + phy_power_off(phy); + phy_exit(phy); + chip->phy_enabled = false; +} + static int enable_eud(struct eud_chip *priv) { int ret; - ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1); + ret = eud_phy_enable(priv); if (ret) return ret; + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1); + if (ret) { + eud_phy_disable(priv); + return ret; + } + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, priv->base + EUD_REG_INT1_EN_MASK); @@ -75,6 +135,8 @@ static int disable_eud(struct eud_chip *priv) return ret; writel(0, priv->base + EUD_REG_CSR_EUD_EN); + eud_phy_disable(priv); + return 0; } @@ -130,6 +192,12 @@ static ssize_t port_store(struct device *dev, struct device_attribute *attr, if (port < 0) return port; + /* Check if the corresponding path is available */ + if (!chip->paths[port]) { + dev_err(chip->dev, "EUD not supported on selected port\n"); + return -EOPNOTSUPP; + } + /* Port selection must be done before enabling EUD */ if (chip->enabled) { dev_err(chip->dev, "Cannot change port while EUD is enabled\n"); @@ -229,8 +297,54 @@ static void eud_role_switch_release(void *data) usb_role_switch_put(chip->role_sw); } +static int eud_init_path(struct eud_chip *chip, struct device_node *np) +{ + struct device_node *controller_node; + struct eud_path *path; + u32 path_num; + int ret; + + ret = of_property_read_u32(np, "reg", &path_num); + if (ret) { + /* Legacy DT uses 'ports' node without 'reg' property; treat as path 0 */ + if (of_node_name_eq(np, "ports")) + path_num = 0; + else + return dev_err_probe(chip->dev, ret, "unexpected child node '%s'\n", + np->name); + } + + if (path_num >= EUD_MAX_PORTS) + return dev_err_probe(chip->dev, -EINVAL, "invalid path number: %u (max %d)\n", + path_num, EUD_MAX_PORTS - 1); + + path = devm_kzalloc(chip->dev, sizeof(*path), GFP_KERNEL); + if (!path) + return -ENOMEM; + + path->chip = chip; + path->num = path_num; + + controller_node = of_graph_get_remote_node(np, 0, -1); + if (!controller_node) + return dev_err_probe(chip->dev, -ENODEV, + "failed to get controller node for path %u\n", path_num); + + path->phy = devm_of_phy_get_by_index(chip->dev, controller_node, 0); + of_node_put(controller_node); + + if (IS_ERR(path->phy)) + return dev_err_probe(chip->dev, PTR_ERR(path->phy), + "failed to get PHY for path %d\n", path_num); + + chip->paths[path_num] = path; + + return 0; +} + static int eud_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct eud_chip *chip; struct resource *res; int ret; @@ -241,6 +355,16 @@ static int eud_probe(struct platform_device *pdev) chip->dev = &pdev->dev; + for_each_child_of_node_scoped(np, child) { + ret = eud_init_path(chip, child); + if (ret) + return ret; + } + + /* Primary path is mandatory. Secondary is optional */ + if (!chip->paths[0]) + return dev_err_probe(chip->dev, -ENODEV, "primary path not found\n"); + chip->role_sw = usb_role_switch_get(&pdev->dev); if (IS_ERR(chip->role_sw)) return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), From 5c73f91fc04e9f6501b0ebd8c353c690de14a226 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:30 -0700 Subject: [PATCH 4/9] FROMLIST: usb: misc: qcom_eud: add per-path role switch support The EUD hardware can support multiple High-Speed USB paths, each connected to different USB controllers. The current implementation uses a single chip-level role switch, which cannot properly handle multi-path configurations where each path needs independent role management. Since EUD is physically present between the USB connector and the controller, it should also relay the role change requests from the connector. Restructure the driver to support per-path role switches and remove the chip-level role switch. Additionally, as EUD need not modify the USB role upon enabling, remove the unnecessary role switch call from enable_eud(). Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- drivers/usb/misc/qcom_eud.c | 89 +++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index a624c44d5d599..7fbb74bf1599f 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -44,12 +44,15 @@ static const char * const eud_port_names[] = { struct eud_path { struct eud_chip *chip; struct phy *phy; + struct usb_role_switch *controller_sw; + struct usb_role_switch *eud_sw; + enum usb_role curr_role; + char name[16]; u8 num; }; struct eud_chip { struct device *dev; - struct usb_role_switch *role_sw; void __iomem *base; struct eud_path *paths[EUD_MAX_PORTS]; phys_addr_t mode_mgr; @@ -123,7 +126,7 @@ static int enable_eud(struct eud_chip *priv) writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, priv->base + EUD_REG_INT1_EN_MASK); - return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); + return 0; } static int disable_eud(struct eud_chip *priv) @@ -274,12 +277,15 @@ static irqreturn_t handle_eud_irq(int irq, void *data) static irqreturn_t handle_eud_irq_thread(int irq, void *data) { struct eud_chip *chip = data; + struct eud_path *path; int ret; + path = chip->paths[chip->port_idx]; + if (chip->usb_attached) - ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE); + ret = usb_role_switch_set_role(path->controller_sw, USB_ROLE_DEVICE); else - ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST); + ret = usb_role_switch_set_role(path->controller_sw, USB_ROLE_HOST); if (ret) dev_err(chip->dev, "failed to set role switch\n"); @@ -290,15 +296,36 @@ static irqreturn_t handle_eud_irq_thread(int irq, void *data) return IRQ_HANDLED; } -static void eud_role_switch_release(void *data) +static int eud_role_switch_set(struct usb_role_switch *sw, enum usb_role role) { - struct eud_chip *chip = data; + struct eud_path *path = usb_role_switch_get_drvdata(sw); + int ret; + + /* Forward the role request to the USB controller */ + ret = usb_role_switch_set_role(path->controller_sw, role); + if (ret) { + dev_err(path->chip->dev, "failed to set role %s for port %u: %d\n", + usb_role_string(role), path->num, ret); + return ret; + } - usb_role_switch_put(chip->role_sw); + path->curr_role = role; + + return 0; +} + +static void eud_path_role_switch_release(void *data) +{ + struct eud_path *path = data; + + usb_role_switch_unregister(path->eud_sw); + usb_role_switch_put(path->controller_sw); } static int eud_init_path(struct eud_chip *chip, struct device_node *np) { + struct usb_role_switch_desc role_sw_desc; + struct usb_role_switch *sw; struct device_node *controller_node; struct eud_path *path; u32 path_num; @@ -331,11 +358,44 @@ static int eud_init_path(struct eud_chip *chip, struct device_node *np) "failed to get controller node for path %u\n", path_num); path->phy = devm_of_phy_get_by_index(chip->dev, controller_node, 0); - of_node_put(controller_node); - - if (IS_ERR(path->phy)) + if (IS_ERR(path->phy)) { + of_node_put(controller_node); return dev_err_probe(chip->dev, PTR_ERR(path->phy), "failed to get PHY for path %d\n", path_num); + } + + path->curr_role = USB_ROLE_NONE; + + /* Fetch controller role switch if it is role switch capable */ + if (of_property_read_bool(controller_node, "usb-role-switch")) { + sw = usb_role_switch_find_by_fwnode(of_fwnode_handle(controller_node)); + if (!sw) { + of_node_put(controller_node); + return dev_err_probe(chip->dev, -EPROBE_DEFER, + "Failed to get controller role switch for path %d\n", + path_num); + } + path->controller_sw = sw; + } + + of_node_put(controller_node); + + role_sw_desc.fwnode = of_fwnode_handle(np); + role_sw_desc.set = eud_role_switch_set; + role_sw_desc.driver_data = path; + snprintf(path->name, sizeof(path->name), "eud-path%u", path_num); + role_sw_desc.name = path->name; + + path->eud_sw = usb_role_switch_register(chip->dev, &role_sw_desc); + if (IS_ERR(path->eud_sw)) { + usb_role_switch_put(path->controller_sw); + return dev_err_probe(chip->dev, PTR_ERR(path->eud_sw), + "Failed to register EUD role switch for path %d\n", path_num); + } + + ret = devm_add_action_or_reset(chip->dev, eud_path_role_switch_release, path); + if (ret) + return ret; chip->paths[path_num] = path; @@ -365,15 +425,6 @@ static int eud_probe(struct platform_device *pdev) if (!chip->paths[0]) return dev_err_probe(chip->dev, -ENODEV, "primary path not found\n"); - chip->role_sw = usb_role_switch_get(&pdev->dev); - if (IS_ERR(chip->role_sw)) - return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), - "failed to get role switch\n"); - - ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); - if (ret) - return ret; - chip->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(chip->base)) return PTR_ERR(chip->base); From 5c12d0134a63d2b35bcbf796ae44ff7bd9550f02 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:31 -0700 Subject: [PATCH 5/9] FROMLIST: usb: misc: qcom_eud: improve enable_store API Currently enable_store() allows operations irrespective of the EUD state, which can result in redundant operations. Avoid this by adding duplicate state checks to skip requests when EUD is already in the desired state. Additionally, improve error handling with explicit logging to provide better feedback. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Reviewed-by: Konrad Dybcio Signed-off-by: Akash Kumar --- drivers/usb/misc/qcom_eud.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 7fbb74bf1599f..f656ffc8818a9 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -162,18 +162,27 @@ static ssize_t enable_store(struct device *dev, if (kstrtobool(buf, &enable)) return -EINVAL; + /* Skip operation if already in desired state */ + if (chip->enabled == enable) + return count; + if (enable) { ret = enable_eud(chip); - if (!ret) - chip->enabled = enable; - else - disable_eud(chip); - + if (ret) { + dev_err(chip->dev, "failed to enable eud\n"); + return ret; + } } else { ret = disable_eud(chip); + if (ret) { + dev_err(chip->dev, "failed to disable eud\n"); + return ret; + } } - return ret < 0 ? ret : count; + chip->enabled = enable; + + return count; } static DEVICE_ATTR_RW(enable); From d668abf3c0ad14b0233d9d799b4e68f569a3dd8a Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:32 -0700 Subject: [PATCH 6/9] FROMLIST: usb: misc: qcom_eud: add host mode coordination EUD functions by presenting itself as a USB device to the host PC for debugging, making it incompatible with USB host mode configurations. Enabling EUD while in host mode can also cause the USB controller to misbehave, as the EUD hub supports only a single upstream-facing port. Handle the following scenarios to prevent these conflicts: 1. Prevent the user from enabling EUD via sysfs when the USB port is in host mode. 2. Automatically disable EUD when the USB port switches to host mode, and re-enable it when exiting host mode. This ensures consistent state management without creating conflicts between the EUD debug hub and the USB controller. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- drivers/usb/misc/qcom_eud.c | 65 ++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index f656ffc8818a9..4734b6307a853 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -55,12 +55,15 @@ 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; bool enabled; bool usb_attached; bool phy_enabled; + bool eud_disabled_for_host; u8 port_idx; }; @@ -156,17 +159,43 @@ 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; + /* + * 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. + */ + path = chip->paths[chip->port_idx]; + if (path->curr_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"); @@ -308,9 +337,41 @@ 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); + struct eud_chip *chip = path->chip; int ret; - /* Forward the role request to the USB controller */ + guard(mutex)(&chip->state_lock); + + /* + * EUD must be disabled when USB operates in host mode. EUD functions by + * presenting itself as a USB device to the host PC for debugging, making + * it incompatible with host mode configuration. + * + * chip->enabled preserves user's sysfs configuration and is not modified + * during host mode transitions to maintain user intent. + */ + + /* Only act if EUD is enabled and this is the active path */ + if (chip->enabled && path->num == chip->port_idx) { + 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); + return ret; + } + 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); + return ret; + } + chip->eud_disabled_for_host = false; + } + } + ret = usb_role_switch_set_role(path->controller_sw, role); if (ret) { dev_err(path->chip->dev, "failed to set role %s for port %u: %d\n", @@ -424,6 +485,8 @@ static int eud_probe(struct platform_device *pdev) chip->dev = &pdev->dev; + mutex_init(&chip->state_lock); + for_each_child_of_node_scoped(np, child) { ret = eud_init_path(chip, child); if (ret) From fe01596aa4c96ae765ed328457e779678cb17d56 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:33 -0700 Subject: [PATCH 7/9] FROMLIST: usb: misc: qcom_eud: fix virtual attach/detach event handling EUD provides virtual USB attach/detach events to simulate cable plug/unplug while maintaining the physical debug connection. However, the current implementation incorrectly sets the USB role to HOST on virtual detach, which doesn't represent the disconnected state. Fix the virtual detach handling by setting the USB role to NONE instead of HOST, correctly representing the disconnected state. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Reviewed-by: Konrad Dybcio Signed-off-by: Akash Kumar --- drivers/usb/misc/qcom_eud.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 4734b6307a853..187cbffbab13d 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -320,10 +320,26 @@ static irqreturn_t handle_eud_irq_thread(int irq, void *data) path = chip->paths[chip->port_idx]; + /* + * EUD virtual attach/detach event handling for low power debugging: + * + * When EUD is enabled in debug mode, the device remains physically + * connected to the PC throughout the debug session, keeping the USB + * controller active. This prevents testing of low power scenarios that + * require USB disconnection. + * + * EUD solves this by providing virtual USB attach/detach events while + * maintaining the physical connection. These events are triggered from + * the Host PC via the enumerated EUD control interface and delivered + * to the EUD driver as interrupts. + * + * These notifications are forwarded to the USB controller through role + * switch framework. + */ if (chip->usb_attached) ret = usb_role_switch_set_role(path->controller_sw, USB_ROLE_DEVICE); else - ret = usb_role_switch_set_role(path->controller_sw, USB_ROLE_HOST); + ret = usb_role_switch_set_role(path->controller_sw, USB_ROLE_NONE); if (ret) dev_err(chip->dev, "failed to set role switch\n"); From b0758f540324af1e2f725c6c33ee290e94e2cfee Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:34 -0700 Subject: [PATCH 8/9] FROMLIST: arm64: dts: qcom: kodiak: Describe EUD UTMI path using child node The existing EUD description uses a legacy single-path representation that assumes uniform UTMI routing. This was sufficient for minimal configurations where the USB port operated only in device mode and role switching was not considered. Update the description to explicitly model the topology using eud path child node and add a role-switch declaration to reflect role-dependent UTMI routing. Also on this SoC, the EUD hardware intercepts only the primary UTMI path. So remove the USB endpoint mapping to the secondary controller and associate it with the primary controller. With the UTMI path now accurately described, enable the EUD by default. The connector side of the path is left unspecified, allowing board-level DTS files to describe the physical connector mapping. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/sc7280.dtsi | 57 ++++++++++++++++------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sc7280.dtsi b/arch/arm64/boot/dts/qcom/sc7280.dtsi index 05e05d87b3fe4..b2a785662b815 100644 --- a/arch/arm64/boot/dts/qcom/sc7280.dtsi +++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi @@ -4343,12 +4343,6 @@ phy-names = "usb2-phy"; maximum-speed = "high-speed"; usb-role-switch; - - port { - usb2_role_switch: endpoint { - remote-endpoint = <&eud_ep>; - }; - }; }; qspi: spi@88dc000 { @@ -4668,25 +4662,37 @@ }; eud: eud@88e0000 { - compatible = "qcom,sc7280-eud", "qcom,eud"; - reg = <0 0x88e0000 0 0x2000>, - <0 0x88e2000 0 0x1000>; - interrupts-extended = <&pdc 11 IRQ_TYPE_LEVEL_HIGH>; - - status = "disabled"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - eud_ep: endpoint { - remote-endpoint = <&usb2_role_switch>; - }; - }; - }; - }; + compatible = "qcom,sc7280-eud", "qcom,eud"; + reg = <0 0x88e0000 0 0x2000>, + <0 0x88e2000 0 0x1000>; + interrupts-extended = <&pdc 11 IRQ_TYPE_LEVEL_HIGH>; + + #address-cells = <1>; + #size-cells = <0>; + + eud0: eud-path@0 { + reg = <0>; + usb-role-switch; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + eud_usb: endpoint { + remote-endpoint = <&usb_1_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + eud_con: endpoint { + }; + }; + }; + }; + }; nsp_noc: interconnect@a0c0000 { reg = <0 0x0a0c0000 0 0x10000>; @@ -4916,6 +4922,7 @@ reg = <0>; usb_1_dwc3_hs: endpoint { + remote-endpoint = <&eud_usb>; }; }; From 219607d110437369055d5b562e5321ba06836f6e Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Fri, 1 May 2026 10:06:35 -0700 Subject: [PATCH 9/9] FROMLIST: arm64: dts: qcom: Map USB connector to EUD on Kodiak boards On Kodiak-based boards, the primary USB connector is connected through the EUD on the High-Speed path. Update the board-level descriptions to map the USB connector endpoint to EUD, reflecting the intended hardware topology. Link: https://lore.kernel.org/all/20260501170635.2641748-1-elson.serrao@oss.qualcomm.com/ Signed-off-by: Elson Serrao Signed-off-by: Akash Kumar --- arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 10 +++++----- arch/arm64/boot/dts/qcom/qcm6490-particle-tachyon.dts | 10 +++++----- arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts | 10 +++++----- arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 10 +++++----- arch/arm64/boot/dts/qcom/sm7325-nothing-spacewar.dts | 10 +++++----- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts index 36d5750584831..50d38f902433f 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts @@ -97,7 +97,7 @@ reg = <0>; pmic_glink_hs_in: endpoint { - remote-endpoint = <&usb_1_dwc3_hs>; + remote-endpoint = <&eud_con>; }; }; @@ -654,6 +654,10 @@ }; }; +&eud_con { + remote-endpoint = <&pmic_glink_hs_in>; +}; + &gcc { protected-clocks = , , @@ -1388,10 +1392,6 @@ status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pmic_glink_hs_in>; -}; - &usb_1_hsphy { vdda-pll-supply = <&vreg_l10c>; vdda18-supply = <&vreg_l1c>; diff --git a/arch/arm64/boot/dts/qcom/qcm6490-particle-tachyon.dts b/arch/arm64/boot/dts/qcom/qcm6490-particle-tachyon.dts index 251e72f114287..c8494a9254fcd 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-particle-tachyon.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-particle-tachyon.dts @@ -74,7 +74,7 @@ reg = <0>; pmic_glink_hs_in: endpoint { - remote-endpoint = <&usb_1_dwc3_hs>; + remote-endpoint = <&eud_con>; }; }; @@ -524,6 +524,10 @@ ; }; +&eud_con { + remote-endpoint = <&pmic_glink_hs_in>; +}; + &gpu { status = "okay"; }; @@ -826,10 +830,6 @@ status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pmic_glink_hs_in>; -}; - &usb_1_hsphy { vdda-pll-supply = <&vreg_l10c_0p88>; vdda33-supply = <&vreg_l2b_3p072>; diff --git a/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts b/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts index cc99925798873..9885e1631a87c 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts @@ -84,7 +84,7 @@ reg = <0>; pmic_glink_hs_in: endpoint { - remote-endpoint = <&usb_1_dwc3_hs>; + remote-endpoint = <&eud_con>; }; }; @@ -490,6 +490,10 @@ }; }; +&eud_con { + remote-endpoint = <&pmic_glink_hs_in>; +}; + &gcc { protected-clocks = , , @@ -921,10 +925,6 @@ status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pmic_glink_hs_in>; -}; - &usb_dp_qmpphy_out { remote-endpoint = <&pmic_glink_ss_in>; }; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts index 3926253cc53e8..b18adf79e5430 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts +++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts @@ -200,7 +200,7 @@ reg = <0>; pmic_glink_hs_in: endpoint { - remote-endpoint = <&usb_1_dwc3_hs>; + remote-endpoint = <&eud_con>; }; }; @@ -735,6 +735,10 @@ }; }; +&eud_con { + remote-endpoint = <&pmic_glink_hs_in>; +}; + &gcc { protected-clocks = , , @@ -1599,10 +1603,6 @@ status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pmic_glink_hs_in>; -}; - &usb_1_dwc3_ss { remote-endpoint = <&usb_dp_qmpphy_usb_ss_in>; }; diff --git a/arch/arm64/boot/dts/qcom/sm7325-nothing-spacewar.dts b/arch/arm64/boot/dts/qcom/sm7325-nothing-spacewar.dts index f16b47b6a74c5..4e097ffa18baa 100644 --- a/arch/arm64/boot/dts/qcom/sm7325-nothing-spacewar.dts +++ b/arch/arm64/boot/dts/qcom/sm7325-nothing-spacewar.dts @@ -99,7 +99,7 @@ reg = <0>; pmic_glink_hs_in: endpoint { - remote-endpoint = <&usb_1_dwc3_hs>; + remote-endpoint = <&eud_con>; }; }; @@ -884,6 +884,10 @@ }; }; +&eud_con { + remote-endpoint = <&pmic_glink_hs_in>; +}; + &gcc { protected-clocks = , , @@ -1435,10 +1439,6 @@ status = "okay"; }; -&usb_1_dwc3_hs { - remote-endpoint = <&pmic_glink_hs_in>; -}; - &usb_1_hsphy { vdda-pll-supply = <&vdd_a_usbhs_core>; vdda18-supply = <&vdd_a_usbhs_1p8>;