| // 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 <malloc.h> |
| #include <reset.h> |
| #include <syscon.h> |
| #include <usb.h> |
| #include <asm/io.h> |
| #include <dm/device_compat.h> |
| #include <linux/bitops.h> |
| #include <linux/delay.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), |
| }; |