/* * Renesas R-Car Gen2 USB phy driver * * Copyright (C) 2013 Renesas Solutions Corp. * Copyright (C) 2013 Cogent Embedded, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include struct rcar_gen2_usb_phy_priv { struct usb_phy phy; void __iomem *base; struct clk *clk; spinlock_t lock; int usecount; u32 ugctrl2; }; #define usb_phy_to_priv(p) container_of(p, struct rcar_gen2_usb_phy_priv, phy) /* Low Power Status register */ #define USBHS_LPSTS_REG 0x02 #define USBHS_LPSTS_SUSPM (1 << 14) /* USB General control register */ #define USBHS_UGCTRL_REG 0x80 #define USBHS_UGCTRL_CONNECT (1 << 2) #define USBHS_UGCTRL_PLLRESET (1 << 0) /* USB General control register 2 */ #define USBHS_UGCTRL2_REG 0x84 #define USBHS_UGCTRL2_USB0_PCI (1 << 4) #define USBHS_UGCTRL2_USB0_HS (3 << 4) #define USBHS_UGCTRL2_USB2_PCI (0 << 31) #define USBHS_UGCTRL2_USB2_SS (1 << 31) /* USB General status register */ #define USBHS_UGSTS_REG 0x88 #define USBHS_UGSTS_LOCK (3 << 8) /* Enable USBHS internal phy */ static int __rcar_gen2_usbhs_phy_enable(void __iomem *base) { u32 val; int i; /* USBHS PHY power on */ val = ioread32(base + USBHS_UGCTRL_REG); val &= ~USBHS_UGCTRL_PLLRESET; iowrite32(val, base + USBHS_UGCTRL_REG); val = ioread16(base + USBHS_LPSTS_REG); val |= USBHS_LPSTS_SUSPM; iowrite16(val, base + USBHS_LPSTS_REG); for (i = 0; i < 20; i++) { val = ioread32(base + USBHS_UGSTS_REG); if ((val & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) { val = ioread32(base + USBHS_UGCTRL_REG); val |= USBHS_UGCTRL_CONNECT; iowrite32(val, base + USBHS_UGCTRL_REG); return 0; } udelay(1); } /* Timed out waiting for the PLL lock */ return -ETIMEDOUT; } /* Disable USBHS internal phy */ static int __rcar_gen2_usbhs_phy_disable(void __iomem *base) { u32 val; /* USBHS PHY power off */ val = ioread32(base + USBHS_UGCTRL_REG); val &= ~USBHS_UGCTRL_CONNECT; iowrite32(val, base + USBHS_UGCTRL_REG); val = ioread16(base + USBHS_LPSTS_REG); val &= ~USBHS_LPSTS_SUSPM; iowrite16(val, base + USBHS_LPSTS_REG); val = ioread32(base + USBHS_UGCTRL_REG); val |= USBHS_UGCTRL_PLLRESET; iowrite32(val, base + USBHS_UGCTRL_REG); return 0; } /* Setup USB channels */ static void __rcar_gen2_usb_phy_init(struct rcar_gen2_usb_phy_priv *priv) { u32 val; clk_prepare_enable(priv->clk); /* Set USB channels in the USBHS UGCTRL2 register */ val = ioread32(priv->base + USBHS_UGCTRL2_REG); val &= ~(USBHS_UGCTRL2_USB0_HS | USBHS_UGCTRL2_USB2_SS); val |= priv->ugctrl2; iowrite32(val, priv->base + USBHS_UGCTRL2_REG); } /* Shutdown USB channels */ static void __rcar_gen2_usb_phy_shutdown(struct rcar_gen2_usb_phy_priv *priv) { __rcar_gen2_usbhs_phy_disable(priv->base); clk_disable_unprepare(priv->clk); } static int rcar_gen2_usb_phy_set_suspend(struct usb_phy *phy, int suspend) { struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy); unsigned long flags; int retval; spin_lock_irqsave(&priv->lock, flags); retval = suspend ? __rcar_gen2_usbhs_phy_disable(priv->base) : __rcar_gen2_usbhs_phy_enable(priv->base); spin_unlock_irqrestore(&priv->lock, flags); return retval; } static int rcar_gen2_usb_phy_init(struct usb_phy *phy) { struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy); unsigned long flags; spin_lock_irqsave(&priv->lock, flags); /* * Enable the clock and setup USB channels * if it's the first user */ if (!priv->usecount++) __rcar_gen2_usb_phy_init(priv); spin_unlock_irqrestore(&priv->lock, flags); return 0; } static void rcar_gen2_usb_phy_shutdown(struct usb_phy *phy) { struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy); unsigned long flags; spin_lock_irqsave(&priv->lock, flags); if (!priv->usecount) { dev_warn(phy->dev, "Trying to disable phy with 0 usecount\n"); goto out; } /* Disable everything if it's the last user */ if (!--priv->usecount) __rcar_gen2_usb_phy_shutdown(priv); out: spin_unlock_irqrestore(&priv->lock, flags); } static int rcar_gen2_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rcar_gen2_phy_platform_data *pdata; struct rcar_gen2_usb_phy_priv *priv; struct resource *res; void __iomem *base; struct clk *clk; int retval; pdata = dev_get_platdata(dev); if (!pdata) { dev_err(dev, "No platform data\n"); return -EINVAL; } clk = devm_clk_get(dev, "usbhs"); if (IS_ERR(clk)) { dev_err(dev, "Can't get the clock\n"); return PTR_ERR(clk); } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) return PTR_ERR(base); priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; spin_lock_init(&priv->lock); priv->clk = clk; priv->base = base; priv->ugctrl2 = pdata->chan0_pci ? USBHS_UGCTRL2_USB0_PCI : USBHS_UGCTRL2_USB0_HS; priv->ugctrl2 |= pdata->chan2_pci ? USBHS_UGCTRL2_USB2_PCI : USBHS_UGCTRL2_USB2_SS; priv->phy.dev = dev; priv->phy.label = dev_name(dev); priv->phy.init = rcar_gen2_usb_phy_init; priv->phy.shutdown = rcar_gen2_usb_phy_shutdown; priv->phy.set_suspend = rcar_gen2_usb_phy_set_suspend; retval = usb_add_phy_dev(&priv->phy); if (retval < 0) { dev_err(dev, "Failed to add USB phy\n"); return retval; } platform_set_drvdata(pdev, priv); return retval; } static int rcar_gen2_usb_phy_remove(struct platform_device *pdev) { struct rcar_gen2_usb_phy_priv *priv = platform_get_drvdata(pdev); usb_remove_phy(&priv->phy); return 0; } static struct platform_driver rcar_gen2_usb_phy_driver = { .driver = { .name = "usb_phy_rcar_gen2", }, .probe = rcar_gen2_usb_phy_probe, .remove = rcar_gen2_usb_phy_remove, }; module_platform_driver(rcar_gen2_usb_phy_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Renesas R-Car Gen2 USB phy"); MODULE_AUTHOR("Valentine Barshak ");