Commit b6d7dd15 authored by Tommaso Merciai's avatar Tommaso Merciai Committed by Vinod Koul
Browse files

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: default avatarTommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Link: https://patch.msgid.link/6c1aebf60b4d8ff0c51a8243c68b397c1a384867.1766405010.git.tommaso.merciai.xr@bp.renesas.com


Signed-off-by: default avatarVinod Koul <vkoul@kernel.org>
parent 230c817a
Loading
Loading
Loading
Loading
+137 −5
Original line number Diff line number Diff line
@@ -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);