]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
phy: renesas: rcar-gen3-usb2: Add regulator for OTG VBUS control
authorTommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Mon, 22 Dec 2025 13:43:46 +0000 (14:43 +0100)
committerVinod Koul <vkoul@kernel.org>
Wed, 21 Jan 2026 08:46:02 +0000 (14:16 +0530)
Enable OTG VBUS control on R-Car Gen3 USB2 PHY by registering a regulator
driver that manages the VBOUT line. This change allows the controller to
handle VBUS output for OTG ports using the regulator framework when the
platform requires hardware-based VBUS control.

Without this, some platforms cannot properly manage VBUS power on OTG-
capable ports, leading to potential USB functionality issues.

Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Link: https://patch.msgid.link/6c1aebf60b4d8ff0c51a8243c68b397c1a384867.1766405010.git.tommaso.merciai.xr@bp.renesas.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/renesas/phy-rcar-gen3-usb2.c

index 94a4521d71874be5dc0bcb9a9864fd02b379c74d..d2c03a846b588c1aedbca12729e03167d4cb905e 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
 #include <linux/reset.h>
 #include <linux/string.h>
 #include <linux/usb/of.h>
@@ -141,6 +142,7 @@ struct rcar_gen3_chan {
        bool extcon_host;
        bool is_otg_channel;
        bool uses_otg_pins;
+       bool otg_internal_reg;
 };
 
 struct rcar_gen3_phy_drv_data {
@@ -225,6 +227,11 @@ static void rcar_gen3_phy_usb2_set_vbus(struct rcar_gen3_chan *ch,
 
 static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus)
 {
+       if (ch->otg_internal_reg) {
+               regulator_hardware_enable(ch->vbus, vbus);
+               return;
+       }
+
        if (ch->phy_data->no_adp_ctrl || ch->phy_data->vblvl_ctrl) {
                if (ch->vbus)
                        regulator_hardware_enable(ch->vbus, vbus);
@@ -593,7 +600,7 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p)
        u32 val;
        int ret = 0;
 
-       if (channel->vbus) {
+       if (channel->vbus && !channel->otg_internal_reg) {
                ret = regulator_enable(channel->vbus);
                if (ret)
                        return ret;
@@ -634,7 +641,7 @@ static int rcar_gen3_phy_usb2_power_off(struct phy *p)
                }
        }
 
-       if (channel->vbus)
+       if (channel->vbus && !channel->otg_internal_reg)
                ret = regulator_disable(channel->vbus);
 
        return ret;
@@ -809,6 +816,128 @@ static int rcar_gen3_phy_usb2_init_bus(struct rcar_gen3_chan *channel)
        return 0;
 }
 
+static int rcar_gen3_phy_usb2_regulator_endisable(struct regulator_dev *rdev,
+                                                 bool enable)
+{
+       struct rcar_gen3_chan *channel = rdev_get_drvdata(rdev);
+       struct device *dev = channel->dev;
+       int ret;
+
+       ret = pm_runtime_resume_and_get(dev);
+       if (ret < 0) {
+               dev_warn(dev, "pm_runtime_get failed: %i\n", ret);
+               return ret;
+       }
+
+       rcar_gen3_phy_usb2_set_vbus(channel, USB2_VBCTRL,
+                                   USB2_VBCTRL_VBOUT, enable);
+       pm_runtime_put_noidle(dev);
+
+       return ret;
+}
+
+static int rcar_gen3_phy_usb2_regulator_enable(struct regulator_dev *rdev)
+{
+       return rcar_gen3_phy_usb2_regulator_endisable(rdev, true);
+}
+
+static int rcar_gen3_phy_usb2_regulator_disable(struct regulator_dev *rdev)
+{
+       return rcar_gen3_phy_usb2_regulator_endisable(rdev, false);
+}
+
+static int rcar_gen3_phy_usb2_regulator_is_enabled(struct regulator_dev *rdev)
+{
+       struct rcar_gen3_chan *channel = rdev_get_drvdata(rdev);
+       void __iomem *usb2_base = channel->base;
+       struct device *dev = channel->dev;
+       u32 vbus_ctrl_reg = USB2_VBCTRL;
+       u32 val;
+       int ret;
+
+       ret = pm_runtime_resume_and_get(dev);
+       if (ret < 0) {
+               dev_warn(dev, "pm_runtime_get failed: %i\n", ret);
+               return ret;
+       }
+
+       val = readl(usb2_base + vbus_ctrl_reg);
+
+       pm_runtime_put_noidle(dev);
+       dev_dbg(channel->dev, "%s: %08x\n", __func__, val);
+
+       return (val & USB2_VBCTRL_VBOUT) ? 1 : 0;
+}
+
+static const struct regulator_ops rcar_gen3_phy_usb2_regulator_ops = {
+       .enable = rcar_gen3_phy_usb2_regulator_enable,
+       .disable = rcar_gen3_phy_usb2_regulator_disable,
+       .is_enabled = rcar_gen3_phy_usb2_regulator_is_enabled,
+};
+
+static const struct regulator_desc rcar_gen3_phy_usb2_regulator = {
+       .name = "otg-vbus-regulator",
+       .of_match = of_match_ptr("vbus-regulator"),
+       .ops = &rcar_gen3_phy_usb2_regulator_ops,
+       .type = REGULATOR_VOLTAGE,
+       .owner = THIS_MODULE,
+       .fixed_uV = 5000000,
+       .n_voltages = 1,
+};
+
+static void rcar_gen3_phy_usb2_vbus_disable_action(void *data)
+{
+       struct regulator *vbus = data;
+
+       regulator_disable(vbus);
+}
+
+static int rcar_gen3_phy_usb2_vbus_regulator_get_exclusive_enable(struct rcar_gen3_chan *channel,
+                                                                 bool enable)
+{
+       struct device *dev = channel->dev;
+       int ret;
+
+       channel->vbus = devm_regulator_get_exclusive(dev, "vbus");
+       if (IS_ERR(channel->vbus))
+               return PTR_ERR(channel->vbus);
+
+       if (!enable)
+               return 0;
+
+       ret = regulator_enable(channel->vbus);
+       if (ret)
+               return ret;
+
+       return devm_add_action_or_reset(dev, rcar_gen3_phy_usb2_vbus_disable_action,
+                                       channel->vbus);
+}
+
+static int rcar_gen3_phy_usb2_vbus_regulator_register(struct rcar_gen3_chan *channel)
+{
+       struct device *dev = channel->dev;
+       struct regulator_config rcfg = { .dev = dev, };
+       struct regulator_dev *rdev;
+       bool enable = false;
+
+       rcfg.of_node = of_get_available_child_by_name(dev->of_node,
+                                                     "vbus-regulator");
+       if (rcfg.of_node) {
+               rcfg.driver_data = channel;
+               rdev = devm_regulator_register(dev, &rcar_gen3_phy_usb2_regulator,
+                                              &rcfg);
+               of_node_put(rcfg.of_node);
+               if (IS_ERR(rdev))
+                       return dev_err_probe(dev, PTR_ERR(rdev),
+                                            "Failed to create vbus-regulator\n");
+
+               channel->otg_internal_reg = true;
+               enable = true;
+       }
+
+       return rcar_gen3_phy_usb2_vbus_regulator_get_exclusive_enable(channel, enable);
+}
+
 static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -890,10 +1019,13 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
                phy_set_drvdata(channel->rphys[i].phy, &channel->rphys[i]);
        }
 
-       if (channel->phy_data->no_adp_ctrl && channel->is_otg_channel)
-               channel->vbus = devm_regulator_get_exclusive(dev, "vbus");
-       else
+       if (channel->phy_data->no_adp_ctrl && channel->is_otg_channel) {
+               ret = rcar_gen3_phy_usb2_vbus_regulator_register(channel);
+               if (ret)
+                       return ret;
+       } else {
                channel->vbus = devm_regulator_get_optional(dev, "vbus");
+       }
        if (IS_ERR(channel->vbus)) {
                if (PTR_ERR(channel->vbus) == -EPROBE_DEFER)
                        return PTR_ERR(channel->vbus);