phy: renesas: rcar-gen3-usb2: Add regulator for OTG VBUS control

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>
This commit is contained in:
Tommaso Merciai 2025-12-22 14:43:46 +01:00 committed by Vinod Koul
parent 230c817a16
commit b6d7dd1577

View 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);