194 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
| // 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	= sizeof(struct rcar_gen2_phy),
 | |
| };
 |