* [PATCH 1/3] dt-bindings: phy: sophgo,cv1800b-usb2-phy: Add USB role switch properties
2026-11-03 8:58 [PATCH 0/3] phy: sophgo: Add USB role switch for CV18XX/SG200X Chen-Yu Yeh
@ 2026-11-03 8:58 ` Chen-Yu Yeh
2026-11-03 8:58 ` [PATCH 2/3] phy: sophgo: Add USB role switch support for CV18XX/SG200X Chen-Yu Yeh
2026-11-03 8:58 ` [PATCH 3/3] riscv: dts: sophgo: sg2002-milkv-duo256m: Enable USB role switch Chen-Yu Yeh
2 siblings, 0 replies; 4+ messages in thread
From: Chen-Yu Yeh @ 2026-11-03 8:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Inochi Amaoto, Chen Wang
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Hiago De Franco,
linux-phy, devicetree, sophgo, linux-riscv, linux-kernel,
Chen-Yu Yeh
The USB role on CV18XX/SG200X is selected by the ID override in the top
syscon, which belongs to this PHY, so the PHY is what provides the role
switch.
Allow usb-role-switch and role-switch-default-mode on the PHY node. The
default mode only has a meaning when the role switch is present.
Signed-off-by: Chen-Yu Yeh <chenyou910331@gmail.com>
---
.../bindings/phy/sophgo,cv1800b-usb2-phy.yaml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/Documentation/devicetree/bindings/phy/sophgo,cv1800b-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/sophgo,cv1800b-usb2-phy.yaml
index 2ff8f85d0282..a1d164686b07 100644
--- a/Documentation/devicetree/bindings/phy/sophgo,cv1800b-usb2-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/sophgo,cv1800b-usb2-phy.yaml
@@ -34,6 +34,15 @@ properties:
resets:
maxItems: 1
+ usb-role-switch:
+ $ref: /schemas/usb/usb-drd.yaml#/properties/usb-role-switch
+
+ role-switch-default-mode:
+ $ref: /schemas/usb/usb-drd.yaml#/properties/role-switch-default-mode
+
+dependencies:
+ role-switch-default-mode: [ usb-role-switch ]
+
required:
- compatible
- "#phy-cells"
@@ -51,4 +60,6 @@ examples:
clocks = <&clk 93>, <&clk 94>, <&clk 95>;
clock-names = "app", "stb", "lpm";
resets = <&rst 58>;
+ usb-role-switch;
+ role-switch-default-mode = "peripheral";
};
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] phy: sophgo: Add USB role switch support for CV18XX/SG200X
2026-11-03 8:58 [PATCH 0/3] phy: sophgo: Add USB role switch for CV18XX/SG200X Chen-Yu Yeh
2026-11-03 8:58 ` [PATCH 1/3] dt-bindings: phy: sophgo,cv1800b-usb2-phy: Add USB role switch properties Chen-Yu Yeh
@ 2026-11-03 8:58 ` Chen-Yu Yeh
2026-11-03 8:58 ` [PATCH 3/3] riscv: dts: sophgo: sg2002-milkv-duo256m: Enable USB role switch Chen-Yu Yeh
2 siblings, 0 replies; 4+ messages in thread
From: Chen-Yu Yeh @ 2026-11-03 8:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Inochi Amaoto, Chen Wang
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Hiago De Franco,
linux-phy, devicetree, sophgo, linux-riscv, linux-kernel,
Chen-Yu Yeh
The USB role of the dwc2 controller on CV18XX/SG200X is selected by the
ID override in the top syscon (offset 0x48, bits 6 and 7). The vendor
kernel switches roles by writing only this override, and dwc2 follows
through its Connector ID status change handling.
The usb-role-switch support in dwc2 does not work on this SoC. It
switches roles through the GOTGCTL session overrides, and clearing
B-valid while the core is a B-device with VBUS present makes
GOTGINT.SesEndDet fire back to back, which hangs the board in the
interrupt handler. This happens on "echo host" and at probe without
role-switch-default-mode.
Register a role switch in the PHY driver when the PHY node has
usb-role-switch, and write only the ID override when the role changes.
There are two ordering details:
- Set the default role before registering the PHY provider, so dwc2
already sees the final role when it probes. Otherwise it records the
role it sees at probe and keeps the interrupt mask of that role.
- Ignore phy_set_mode() while the role switch is in use, because
usb_add_hcd() asks for host mode on any controller that has a host
side, which would override the default role.
The ID has no "unattached" state, so USB_ROLE_NONE selects the default
role.
Move the register write to a helper shared with the set_mode callback,
and update PHY_VBUS_POWER and the ID override in a single write.
Tested on Milk-V Duo 256M with dr_mode = "otg" and a v7.3-rc5 based tree:
Default peripheral, bare board on a PC, no manual step after boot:
role device, 0x03000048 = 0x3c0, UDC configured, PC sees 0525:a4a2
echo host: role host, 0x03000048 = 0x142
echo device: role device, 0x03000048 = 0x3c0, UDC configured
echo none: role device, 0x03000048 = 0x3c0
Default host, on the USB & Ethernet IO board, no manual step after boot:
role host, 0x03000048 = 0x142, the hub enumerates at 3.4 s, a USB
stick plugged in later shows up as sda
Registers at boot in the host case, before and after this change:
before after
GINTMSK 0xd0bc3cd4 0xf300081e (PrtInt masked -> unmasked)
HPRT0 0x00021403 0x00001005 (ConnDet pending -> port enabled)
Signed-off-by: Chen-Yu Yeh <chenyou910331@gmail.com>
---
drivers/phy/sophgo/Kconfig | 2 +
drivers/phy/sophgo/phy-cv1800-usb2.c | 107 +++++++++++++++++++++++++--
2 files changed, 102 insertions(+), 7 deletions(-)
diff --git a/drivers/phy/sophgo/Kconfig b/drivers/phy/sophgo/Kconfig
index 2c943bbe1f81..055cbcd32891 100644
--- a/drivers/phy/sophgo/Kconfig
+++ b/drivers/phy/sophgo/Kconfig
@@ -10,6 +10,8 @@ config PHY_SOPHGO_CV1800_USB2
depends on MFD_SYSCON
depends on USB_SUPPORT
select GENERIC_PHY
+ select USB_COMMON
+ select USB_ROLE_SWITCH
help
Enable this to support the USB 2.0 PHY used with
the DWC2 USB controller in Sophgo CV18XX/SG200X
diff --git a/drivers/phy/sophgo/phy-cv1800-usb2.c b/drivers/phy/sophgo/phy-cv1800-usb2.c
index 6fe846534e9c..b4c27ab41a6f 100644
--- a/drivers/phy/sophgo/phy-cv1800-usb2.c
+++ b/drivers/phy/sophgo/phy-cv1800-usb2.c
@@ -13,8 +13,11 @@
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/role.h>
#define REG_USB_PHY_CTRL 0x048
@@ -37,8 +40,93 @@ struct cv1800_usb_phy {
struct clk *usb_lpm_clk;
struct clk *usb_stb_clk;
bool support_otg;
+ struct usb_role_switch *role_sw;
+ enum usb_role default_role;
};
+static int cv1800_usb_phy_set_id(struct cv1800_usb_phy *phy, enum usb_role role)
+{
+ unsigned int regval;
+
+ switch (role) {
+ case USB_ROLE_DEVICE:
+ regval = PHY_ID_OVERWRITE_EN | PHY_ID_OVERWRITE_MODE_DEVICE;
+ break;
+ case USB_ROLE_HOST:
+ regval = PHY_ID_OVERWRITE_EN | PHY_ID_OVERWRITE_MODE_HOST |
+ PHY_VBUS_POWER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(phy->syscon, REG_USB_PHY_CTRL,
+ PHY_ID_OVERWRITE_EN | PHY_ID_OVERWRITE_MODE |
+ PHY_VBUS_POWER, regval);
+}
+
+static int cv1800_usb_phy_role_set(struct usb_role_switch *sw, enum usb_role role)
+{
+ struct cv1800_usb_phy *phy = usb_role_switch_get_drvdata(sw);
+
+ if (role == USB_ROLE_NONE)
+ role = phy->default_role;
+
+ return cv1800_usb_phy_set_id(phy, role);
+}
+
+static enum usb_role cv1800_usb_phy_role_get(struct usb_role_switch *sw)
+{
+ struct cv1800_usb_phy *phy = usb_role_switch_get_drvdata(sw);
+ unsigned int val = 0;
+ int ret = regmap_read(phy->syscon, REG_USB_PHY_CTRL, &val);
+
+ if (ret)
+ return USB_ROLE_NONE;
+
+ if (val & PHY_ID_OVERWRITE_MODE)
+ return USB_ROLE_DEVICE;
+
+ return USB_ROLE_HOST;
+}
+
+static void cv1800_usb_phy_role_unregister(void *data)
+{
+ usb_role_switch_unregister(data);
+}
+
+static int cv1800_usb_phy_setup_role_switch(struct device *dev,
+ struct cv1800_usb_phy *phy)
+{
+ struct usb_role_switch_desc desc = {
+ .fwnode = dev_fwnode(dev),
+ .set = cv1800_usb_phy_role_set,
+ .get = cv1800_usb_phy_role_get,
+ .allow_userspace_control = true,
+ .driver_data = phy,
+ };
+ int ret;
+
+ if (!device_property_read_bool(dev, "usb-role-switch"))
+ return 0;
+
+ if (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST)
+ phy->default_role = USB_ROLE_HOST;
+ else
+ phy->default_role = USB_ROLE_DEVICE;
+
+ ret = cv1800_usb_phy_set_id(phy, phy->default_role);
+ if (ret)
+ return ret;
+
+ phy->role_sw = usb_role_switch_register(dev, &desc);
+ if (IS_ERR(phy->role_sw))
+ return PTR_ERR(phy->role_sw);
+
+ return devm_add_action_or_reset(dev, cv1800_usb_phy_role_unregister,
+ phy->role_sw);
+}
+
static int cv1800_usb_phy_set_mode(struct phy *_phy,
enum phy_mode mode, int submode)
{
@@ -46,17 +134,17 @@ static int cv1800_usb_phy_set_mode(struct phy *_phy,
unsigned int regval = 0;
int ret;
+ /* The role switch owns the ID override */
+ if (phy->role_sw)
+ return 0;
+
dev_info(&phy->phy->dev, "set mode %d", (int)mode);
switch (mode) {
case PHY_MODE_USB_DEVICE:
- regval = PHY_ID_OVERWRITE_EN | PHY_ID_OVERWRITE_MODE_DEVICE;
- regmap_clear_bits(phy->syscon, REG_USB_PHY_CTRL, PHY_VBUS_POWER);
- break;
+ return cv1800_usb_phy_set_id(phy, USB_ROLE_DEVICE);
case PHY_MODE_USB_HOST:
- regval = PHY_ID_OVERWRITE_EN | PHY_ID_OVERWRITE_MODE_HOST;
- regmap_set_bits(phy->syscon, REG_USB_PHY_CTRL, PHY_VBUS_POWER);
- break;
+ return cv1800_usb_phy_set_id(phy, USB_ROLE_HOST);
case PHY_MODE_USB_OTG:
if (!phy->support_otg)
return 0;
@@ -107,7 +195,7 @@ static int cv1800_usb_phy_probe(struct platform_device *pdev)
if (!parent)
return -ENODEV;
- phy = devm_kmalloc(dev, sizeof(*phy), GFP_KERNEL);
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
@@ -144,6 +232,11 @@ static int cv1800_usb_phy_probe(struct platform_device *pdev)
return ret;
phy_set_drvdata(phy->phy, phy);
+
+ ret = cv1800_usb_phy_setup_role_switch(dev, phy);
+ if (ret)
+ return ret;
+
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] riscv: dts: sophgo: sg2002-milkv-duo256m: Enable USB role switch
2026-11-03 8:58 [PATCH 0/3] phy: sophgo: Add USB role switch for CV18XX/SG200X Chen-Yu Yeh
2026-11-03 8:58 ` [PATCH 1/3] dt-bindings: phy: sophgo,cv1800b-usb2-phy: Add USB role switch properties Chen-Yu Yeh
2026-11-03 8:58 ` [PATCH 2/3] phy: sophgo: Add USB role switch support for CV18XX/SG200X Chen-Yu Yeh
@ 2026-11-03 8:58 ` Chen-Yu Yeh
2 siblings, 0 replies; 4+ messages in thread
From: Chen-Yu Yeh @ 2026-11-03 8:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Inochi Amaoto, Chen Wang
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Hiago De Franco,
linux-phy, devicetree, sophgo, linux-riscv, linux-kernel,
Chen-Yu Yeh
The USB-C port of the Milk-V Duo 256M is the only USB connector on the
bare board, and the vendor image uses it in device mode by default.
Host mode is mainly needed with the USB & Ethernet IO board, whose USB
hub hangs off the same controller.
Use the role switch in the PHY so that one DTB can do both, and default
to peripheral to match the vendor image.
This changes the default of the port from host to device. To use host
mode, including the hub on the IO board, select it at runtime:
echo host > /sys/class/usb_role/3000000.syscon:phy@48-role-switch/role
Signed-off-by: Chen-Yu Yeh <chenyou910331@gmail.com>
---
arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts b/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts
index d4e86c15664e..0ad1dfd9449c 100644
--- a/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts
+++ b/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts
@@ -105,6 +105,11 @@ &uart0 {
};
&usb {
- dr_mode = "host";
+ dr_mode = "otg";
status = "okay";
};
+
+&usbphy {
+ usb-role-switch;
+ role-switch-default-mode = "peripheral";
+};
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread