diff options
Diffstat (limited to 'drivers/phy/phy-rcar-gen2.c')
-rw-r--r-- | drivers/phy/phy-rcar-gen2.c | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c new file mode 100644 index 0000000000..ee70b81d88 --- /dev/null +++ b/drivers/phy/phy-rcar-gen2.c @@ -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), +}; |