[RFC PATCH 3/3] usb: phy: Add platform driver support for ULPI phys

From: Piyush Mehta
Date: Fri Sep 29 2023 - 02:49:52 EST


Added platform driver support to ULPI Phys for Zynq. This modification
enables external five volt supply to drive 5-volts on VBUS. This signal
is or’ed with DrvVbus. Some Phys requires ULPI (OTG Control) register
DrvVbusExternal and DrvVbus bit to operate properly to drive the CPEN
pin/ external VBUS power supply.

The ULPI viewport provides a mechanism for software to read and write
PHY registers with explicit control of the address and data using the
usb.VIEWPORT register. Zynq platform access ULPI PHY via viewport.

Signed-off-by: Piyush Mehta <piyush.mehta@xxxxxxx>
---
On zynq platform chipidea USB controller is capable of fulfilling a wide
range of applications for USB 2.0 implementations as a host, a device, or
On-the-Go. The USB controllers are integrated into the PS IOP to bridge
between the PS interconnect and an external ULPI PHY. The register provides
indirect access to the ULPI PHY register set. The ULPI PHY register I/O
interface uses Viewport to access PHY registers.

In current approach we have extended generic ulpi phy driver and made it a
platform driver. This solves the problem, but would like to know if it is
the right approach? Here, we are modifying the phy-ulpi framework by adapting
the platform driver to fulfill our requirements. ULPI PHY register read/write
should be performed via ULPI framework using read/write API call.

The another approach would be to have access to the ULPI register via
viewport flow by creating a new platform driver at path "driver/usb/phy"
using "phy-ulpi-zynq-usb.c" source file, where the source driver would be
particular to the Xilinx/AMD zynq platform. And binding patch [1/3] would
be specific to Xilinx/AMD-specific.
---
drivers/usb/phy/Kconfig | 2 +-
drivers/usb/phy/Kconfig | 2 +-
drivers/usb/phy/phy-ulpi.c | 90 ++++++++++++++++++++++++++++++++++++++
2 files changed, 91 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index 5f629d7cad64..38ae5458528c 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -160,7 +160,7 @@ config USB_TEGRA_PHY

config USB_ULPI
bool "Generic ULPI Transceiver Driver"
- depends on ARM || ARM64 || COMPILE_TEST
+ depends on ARM || ARM64 || COMPILE_TEST || USB_PHY
select USB_ULPI_VIEWPORT
help
Enable this to support ULPI connected USB OTG transceivers which
diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c
index e683a37e3a7a..61e15a19ea8c 100644
--- a/drivers/usb/phy/phy-ulpi.c
+++ b/drivers/usb/phy/phy-ulpi.c
@@ -13,9 +13,16 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/usb.h>
#include <linux/usb/otg.h>
#include <linux/usb/ulpi.h>
+#include <linux/usb/phy.h>


struct ulpi_info {
@@ -39,6 +46,13 @@ static struct ulpi_info ulpi_ids[] = {
ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"),
};

+struct ulpi_phy {
+ struct usb_phy *usb_phy;
+ void __iomem *regs;
+ unsigned int vp_offset;
+ unsigned int flags;
+};
+
static int ulpi_set_otg_flags(struct usb_phy *phy)
{
unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN |
@@ -240,6 +254,23 @@ static int ulpi_set_vbus(struct usb_otg *otg, bool on)
return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}

+static int usbphy_set_vbus(struct usb_phy *phy, int on)
+{
+ unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL);
+
+ flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT);
+
+ if (on) {
+ if (phy->flags & ULPI_OTG_DRVVBUS)
+ flags |= ULPI_OTG_CTRL_DRVVBUS;
+
+ if (phy->flags & ULPI_OTG_DRVVBUS_EXT)
+ flags |= ULPI_OTG_CTRL_DRVVBUS_EXT;
+ }
+
+ return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
+}
+
static void otg_ulpi_init(struct usb_phy *phy, struct usb_otg *otg,
struct usb_phy_io_ops *ops,
unsigned int flags)
@@ -249,6 +280,7 @@ static void otg_ulpi_init(struct usb_phy *phy, struct usb_otg *otg,
phy->io_ops = ops;
phy->otg = otg;
phy->init = ulpi_init;
+ phy->set_vbus = usbphy_set_vbus;

otg->usb_phy = phy;
otg->set_host = ulpi_set_host;
@@ -301,3 +333,61 @@ devm_otg_ulpi_create(struct device *dev,
return phy;
}
EXPORT_SYMBOL_GPL(devm_otg_ulpi_create);
+
+static int ulpi_phy_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct ulpi_phy *uphy;
+ int ret;
+
+ uphy = devm_kzalloc(&pdev->dev, sizeof(*uphy), GFP_KERNEL);
+ if (!uphy)
+ return -ENOMEM;
+
+ uphy->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(uphy->regs))
+ return PTR_ERR(uphy->regs);
+
+ if (of_property_read_bool(np, "external-drv-vbus"))
+ uphy->flags |= ULPI_OTG_DRVVBUS | ULPI_OTG_DRVVBUS_EXT;
+
+ ret = of_property_read_u32(np, "view-port", &uphy->vp_offset);
+ if (ret)
+ return ret;
+
+ uphy->usb_phy = otg_ulpi_create(&ulpi_viewport_access_ops, uphy->flags);
+ if (!uphy->usb_phy) {
+ dev_err(&pdev->dev, "Failed to create ULPI OTG\n");
+ return -ENOMEM;
+ }
+
+ uphy->usb_phy->dev = &pdev->dev;
+ uphy->usb_phy->io_priv = uphy->regs + uphy->vp_offset;
+ return usb_add_phy_dev(uphy->usb_phy);
+}
+
+static void ulpi_phy_remove(struct platform_device *pdev)
+{
+ struct ulpi_phy *uphy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(uphy->usb_phy);
+}
+
+static const struct of_device_id ulpi_phy_table[] = {
+ { .compatible = "ulpi-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ulpi_phy_table);
+
+static struct platform_driver ulpi_phy_driver = {
+ .probe = ulpi_phy_probe,
+ .remove_new = ulpi_phy_remove,
+ .driver = {
+ .name = "ulpi-phy",
+ .of_match_table = ulpi_phy_table,
+ },
+};
+module_platform_driver(ulpi_phy_driver);
+
+MODULE_DESCRIPTION("ULPI PHY driver");
+MODULE_LICENSE("GPL");
--
2.17.1