phy: rcar: Add R-Car Gen2 PHY driver
authorMarek Vasut <marek.vasut+renesas@gmail.com>
Sun, 5 Aug 2018 13:22:19 +0000 (15:22 +0200)
committerMarek Vasut <marex@denx.de>
Tue, 14 Aug 2018 09:31:19 +0000 (11:31 +0200)
Add a PHY driver for the R-Car Gen2 which allows configuring the mux
connected to the EHCI controllers and USBHS controller.

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/phy-rcar-gen2.c [new file with mode: 0644]

index 8fc2295..e0822bb 100644 (file)
@@ -110,6 +110,14 @@ config STI_USB_PHY
          used by USB2 and USB3 Host controllers available on
          STiH407 SoC families.
 
+config PHY_RCAR_GEN2
+       tristate "Renesas R-Car Gen2 USB PHY"
+       depends on PHY && RCAR_GEN2
+       help
+         Support for the Renesas R-Car Gen2 USB PHY. This driver operates the
+         PHY connected to USBHS module, PCI EHCI module and USB3.0 module and
+         allows configuring the module multiplexing.
+
 config PHY_STM32_USBPHYC
        tristate "STMicroelectronics STM32 SoC USB HS PHY driver"
        depends on PHY && ARCH_STM32MP
index ba0803c..178fb45 100644 (file)
@@ -12,5 +12,6 @@ obj-$(CONFIG_BCM6368_USBH_PHY) += bcm6368-usbh-phy.o
 obj-$(CONFIG_PHY_SANDBOX) += sandbox-phy.o
 obj-$(CONFIG_$(SPL_)PIPE3_PHY) += ti-pipe3-phy.o
 obj-$(CONFIG_STI_USB_PHY) += sti_usb_phy.o
+obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o
 obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o
 obj-$(CONFIG_MESON_GXL_USB_PHY) += meson-gxl-usb2.o meson-gxl-usb3.o
diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c
new file mode 100644 (file)
index 0000000..ee70b81
--- /dev/null
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RCar Gen2 USB PHY driver
+ *
+ * Copyright (C) 2018 Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include <generic-phy.h>
+#include <reset.h>
+#include <syscon.h>
+#include <usb.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <power/regulator.h>
+
+#define USBHS_LPSTS                    0x02
+#define USBHS_UGCTRL                   0x80
+#define USBHS_UGCTRL2                  0x84
+#define USBHS_UGSTS                    0x88    /* From technical update */
+
+/* Low Power Status register (LPSTS) */
+#define USBHS_LPSTS_SUSPM              0x4000
+
+/* USB General control register (UGCTRL) */
+#define USBHS_UGCTRL_CONNECT           BIT(2)
+#define USBHS_UGCTRL_PLLRESET          BIT(0)
+
+/* USB General control register 2 (UGCTRL2) */
+#define USBHS_UGCTRL2_USB2SEL          0x80000000
+#define USBHS_UGCTRL2_USB2SEL_PCI      0x00000000
+#define USBHS_UGCTRL2_USB2SEL_USB30    0x80000000
+#define USBHS_UGCTRL2_USB0SEL          0x00000030
+#define USBHS_UGCTRL2_USB0SEL_PCI      0x00000010
+#define USBHS_UGCTRL2_USB0SEL_HS_USB   0x00000030
+
+/* USB General status register (UGSTS) */
+#define USBHS_UGSTS_LOCK               0x00000100 /* From technical update */
+
+#define PHYS_PER_CHANNEL       2
+
+struct rcar_gen2_phy {
+       fdt_addr_t      regs;
+       struct clk      clk;
+};
+
+static int rcar_gen2_phy_phy_init(struct phy *phy)
+{
+       struct rcar_gen2_phy *priv = dev_get_priv(phy->dev);
+       u16 chan = phy->id & 0xffff;
+       u16 mode = (phy->id >> 16) & 0xffff;
+       u32 clrmask, setmask;
+
+       if (chan == 0) {
+               clrmask = USBHS_UGCTRL2_USB0SEL;
+               setmask = mode ? USBHS_UGCTRL2_USB0SEL_HS_USB :
+                                USBHS_UGCTRL2_USB0SEL_PCI;
+       } else {
+               clrmask = USBHS_UGCTRL2_USB2SEL;
+               setmask = mode ? USBHS_UGCTRL2_USB2SEL_USB30 :
+                                USBHS_UGCTRL2_USB2SEL_PCI;
+       }
+       clrsetbits_le32(priv->regs + USBHS_UGCTRL2, clrmask, setmask);
+
+       return 0;
+}
+
+static int rcar_gen2_phy_phy_power_on(struct phy *phy)
+{
+       struct rcar_gen2_phy *priv = dev_get_priv(phy->dev);
+       int i;
+       u32 value;
+
+       /* Power on USBHS PHY */
+       clrbits_le32(priv->regs + USBHS_UGCTRL, USBHS_UGCTRL_PLLRESET);
+
+       setbits_le16(priv->regs + USBHS_LPSTS, USBHS_LPSTS_SUSPM);
+
+       for (i = 0; i < 20; i++) {
+               value = readl(priv->regs + USBHS_UGSTS);
+               if ((value & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) {
+                       setbits_le32(priv->regs + USBHS_UGCTRL,
+                                    USBHS_UGCTRL_CONNECT);
+                       return 0;
+               }
+               udelay(1);
+       }
+
+       return -ETIMEDOUT;
+}
+
+static int rcar_gen2_phy_phy_power_off(struct phy *phy)
+{
+       struct rcar_gen2_phy *priv = dev_get_priv(phy->dev);
+
+       /* Power off USBHS PHY */
+       clrbits_le32(priv->regs + USBHS_UGCTRL, USBHS_UGCTRL_CONNECT);
+
+       clrbits_le16(priv->regs + USBHS_LPSTS, USBHS_LPSTS_SUSPM);
+
+       setbits_le32(priv->regs + USBHS_UGCTRL, USBHS_UGCTRL_PLLRESET);
+
+       return 0;
+}
+
+static int rcar_gen2_phy_of_xlate(struct phy *phy,
+                                 struct ofnode_phandle_args *args)
+{
+       if (args->args_count != 2) {
+               dev_err(phy->dev, "Invalid DT PHY argument count: %d\n",
+                       args->args_count);
+               return -EINVAL;
+       }
+
+       if (args->args[0] != 0 && args->args[0] != 2) {
+               dev_err(phy->dev, "Invalid DT PHY channel: %d\n",
+                       args->args[0]);
+               return -EINVAL;
+       }
+
+       if (args->args[1] != 0 && args->args[1] != 1) {
+               dev_err(phy->dev, "Invalid DT PHY mode: %d\n",
+                       args->args[1]);
+               return -EINVAL;
+       }
+
+       if (args->args_count)
+               phy->id = args->args[0] | (args->args[1] << 16);
+       else
+               phy->id = 0;
+
+       return 0;
+}
+
+static const struct phy_ops rcar_gen2_phy_phy_ops = {
+       .init           = rcar_gen2_phy_phy_init,
+       .power_on       = rcar_gen2_phy_phy_power_on,
+       .power_off      = rcar_gen2_phy_phy_power_off,
+       .of_xlate       = rcar_gen2_phy_of_xlate,
+};
+
+static int rcar_gen2_phy_probe(struct udevice *dev)
+{
+       struct rcar_gen2_phy *priv = dev_get_priv(dev);
+       int ret;
+
+       priv->regs = dev_read_addr(dev);
+       if (priv->regs == FDT_ADDR_T_NONE)
+               return -EINVAL;
+
+       /* Enable clock */
+       ret = clk_get_by_index(dev, 0, &priv->clk);
+       if (ret)
+               return ret;
+
+       ret = clk_enable(&priv->clk);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int rcar_gen2_phy_remove(struct udevice *dev)
+{
+       struct rcar_gen2_phy *priv = dev_get_priv(dev);
+
+       clk_disable(&priv->clk);
+       clk_free(&priv->clk);
+
+       return 0;
+}
+
+static const struct udevice_id rcar_gen2_phy_of_match[] = {
+       { .compatible = "renesas,rcar-gen2-usb-phy", },
+       { },
+};
+
+U_BOOT_DRIVER(rcar_gen2_phy) = {
+       .name           = "rcar-gen2-phy",
+       .id             = UCLASS_PHY,
+       .of_match       = rcar_gen2_phy_of_match,
+       .ops            = &rcar_gen2_phy_phy_ops,
+       .probe          = rcar_gen2_phy_probe,
+       .remove         = rcar_gen2_phy_remove,
+       .priv_auto_alloc_size = sizeof(struct rcar_gen2_phy),
+};