#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <mach/regs-pmu.h>
#include <mach/regs-usb-phy.h>
#define PHY_ENABLE 1
#define PHY_DISABLE 0
#define EXYNOS4_USB_CFG (S3C_VA_SYS + 0x21C)
+#define EXYNOS5_USB_CFG S5P_SYSREG(0x0230)
enum usb_phy_type {
USB_PHY = (0x1 << 0),
static atomic_t host_usage;
static struct exynos_usb_phy usb_phy_control;
static DEFINE_SPINLOCK(phy_lock);
-static struct clk *phy_clk;
static int exynos4_usb_host_phy_is_on(void)
{
return (readl(EXYNOS5_PHY_HOST_CTRL0) & HOST_CTRL0_PHYSWRSTALL) ? 0 : 1;
}
-static int exynos_usb_phy_clock_enable(struct platform_device *pdev)
+/* Returning 'clk' here so as to disable clk in phy_init/exit functions */
+static struct clk *exynos_usb_phy_clock_enable(struct platform_device *pdev)
{
int err;
+ struct clk *phy_clk = NULL;
- if (!phy_clk) {
- if (soc_is_exynos5250())
- phy_clk = clk_get(&pdev->dev, "usbhost");
+ if (!soc_is_exynos5250())
+ return NULL;
- if (IS_ERR(phy_clk)) {
- dev_err(&pdev->dev, "Failed to get phy clock\n");
- return PTR_ERR(phy_clk);
- }
+ phy_clk = clk_get(&pdev->dev, "usbhost");
+
+ if (IS_ERR(phy_clk)) {
+ dev_err(&pdev->dev, "Failed to get phy clock\n");
+ return NULL;
}
err = clk_enable(phy_clk);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to enable phy clock\n");
+ clk_put(phy_clk);
+ return NULL;
+ }
+
+ return phy_clk;
+}
- return err;
+static void exynos_usb_phy_clock_disable(struct clk *phy_clk)
+{
+ clk_disable(phy_clk);
+ clk_put(phy_clk);
}
static void exynos_usb_mux_change(struct platform_device *pdev, int val)
{
u32 is_host;
- if (soc_is_exynos5250()) {
- is_host = readl(EXYNOS5_USB_CFG);
- writel(val, EXYNOS5_USB_CFG);
- }
+ if (!soc_is_exynos5250())
+ return;
+
+ is_host = readl(EXYNOS5_USB_CFG);
+ writel(val, EXYNOS5_USB_CFG);
if (is_host != val)
dev_dbg(&pdev->dev, "Change USB MUX from %s to %s",
- is_host ? "Host" : "Device", val ? "Host" : "Device");
+ is_host ? "Host" : "Device", val ? "Host" : "Device");
}
static void exynos_usb_phy_control(enum usb_phy_type phy_type , int on)
return refclk_freq;
}
-static int exynos5_usb_phy30_init(struct platform_device *pdev)
+/*
+ * Sets the phy clk as EXTREFCLK (XXTI) which is internal clock form clock core.
+ */
+static u32 exynos_usb_phy30_set_clock_int(struct platform_device *pdev)
+{
+ u32 reg;
+ u32 refclk;
+
+ refclk = exynos_usb_phy_set_clock(pdev);
+
+ reg = EXYNOS_USB3_PHYCLKRST_REFCLKSEL_EXT_REF_CLK |
+ EXYNOS_USB3_PHYCLKRST_FSEL(refclk);
+
+ switch (refclk) {
+ case EXYNOS5_CLKSEL_50M:
+ /* TODO: multiplier seems wrong; others make ~2.5GHz */
+ reg |= (EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER(0x02) |
+ EXYNOS_USB3_PHYCLKRST_SSC_REF_CLK_SEL(0x00));
+ break;
+ case EXYNOS5_CLKSEL_20M:
+ reg |= (EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF |
+ EXYNOS_USB3_PHYCLKRST_SSC_REF_CLK_SEL(0x00));
+ break;
+ case EXYNOS5_CLKSEL_19200K:
+ /* TODO: multiplier seems wrong; others make ~2.5GHz */
+ reg |= (EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER(0x02) |
+ EXYNOS_USB3_PHYCLKRST_SSC_REF_CLK_SEL(0x88));
+ break;
+ case EXYNOS5_CLKSEL_24M:
+ default:
+ reg |= (EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
+ EXYNOS_USB3_PHYCLKRST_SSC_REF_CLK_SEL(0x88));
+ break;
+ }
+
+ return reg;
+}
+
+/*
+ * Sets the phy clk as ref_pad_clk (XusbXTI) which is clock from external PLL.
+ */
+static u32 exynos_usb_phy30_set_clock_ext(struct platform_device *pdev)
{
- int ret;
u32 reg;
- bool use_ext_clk = true;
- ret = exynos_usb_phy_clock_enable(pdev);
- if (ret)
- return ret;
+ reg = EXYNOS_USB3_PHYCLKRST_REFCLKSEL_PAD_REF_CLK |
+ EXYNOS_USB3_PHYCLKRST_FSEL_PAD_100MHZ |
+ EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF;
+
+ return reg;
+}
+
+static int _exynos5_usb_phy30_init(struct platform_device *pdev,
+ bool use_ext_clk)
+{
+ u32 reg;
exynos_usb_phy_control(USB_PHY0, PHY_ENABLE);
/* Reset USB 3.0 PHY */
writel(0x00000000, EXYNOS_USB3_PHYREG0);
- writel(0x24d4e6e4, EXYNOS_USB3_PHYPARAM0);
- writel(0x03fff820, EXYNOS_USB3_PHYPARAM1);
+
+ reg = 0x24d4e6e4;
+ if (use_ext_clk)
+ reg |= (0x1 << 31);
+ writel(reg, EXYNOS_USB3_PHYPARAM0);
+
+ writel(0x03fff81c, EXYNOS_USB3_PHYPARAM1);
writel(0x00000000, EXYNOS_USB3_PHYRESUME);
- writel(0x08000000, EXYNOS_USB3_LINKSYSTEM);
- writel(0x00000004, EXYNOS_USB3_PHYBATCHG);
- /* REVISIT :use externel clock 100MHz */
- if (use_ext_clk)
- writel(readl(EXYNOS_USB3_PHYPARAM0) | (0x1<<31),
- EXYNOS_USB3_PHYPARAM0);
- else
- writel(readl(EXYNOS_USB3_PHYPARAM0) & ~(0x1<<31),
- EXYNOS_USB3_PHYPARAM0);
+ /* Setting the Frame length Adj value[6:1] to default 0x20 */
+ writel(0x08000040, EXYNOS_USB3_LINKSYSTEM);
+ writel(0x00000004, EXYNOS_USB3_PHYBATCHG);
+
+ /* PHYTEST POWERDOWN Control */
+ reg = readl(EXYNOS_USB3_PHYTEST);
+ reg &= ~(EXYNOS_USB3_PHYTEST_POWERDOWN_SSP |
+ EXYNOS_USB3_PHYTEST_POWERDOWN_HSP);
+ writel(reg, EXYNOS_USB3_PHYTEST);
/* UTMI Power Control */
writel(EXYNOS_USB3_PHYUTMI_OTGDISABLE, EXYNOS_USB3_PHYUTMI);
- /* Set 100MHz external clock */
- reg = EXYNOS_USB3_PHYCLKRST_PORTRESET |
- /* HS PLL uses ref_pad_clk{p,m} or ref_alt_clk_{p,m}
- * as reference */
- EXYNOS_USB3_PHYCLKRST_REFCLKSEL(2) |
+ if (use_ext_clk)
+ reg = exynos_usb_phy30_set_clock_ext(pdev);
+ else
+ reg = exynos_usb_phy30_set_clock_int(pdev);
+
+ reg |= EXYNOS_USB3_PHYCLKRST_PORTRESET |
/* Digital power supply in normal operating mode */
EXYNOS_USB3_PHYCLKRST_RETENABLEN |
- /* 0x27-100MHz, 0x2a-24MHz, 0x31-20MHz, 0x38-19.2MHz */
- EXYNOS_USB3_PHYCLKRST_FSEL(0x27) |
- /* 0x19-100MHz, 0x68-24MHz, 0x7d-20Mhz */
- EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER(0x19) |
/* Enable ref clock for SS function */
EXYNOS_USB3_PHYCLKRST_REF_SSP_EN |
/* Enable spread spectrum */
EXYNOS_USB3_PHYCLKRST_SSC_EN |
+ /* Power down HS Bias and PLL blocks in suspend mode */
EXYNOS_USB3_PHYCLKRST_COMMONONN;
writel(reg, EXYNOS_USB3_PHYCLKRST);
return 0;
}
+int exynos5_dwc_phyclk_switch(struct platform_device *pdev, bool use_ext_clk)
+{
+ return _exynos5_usb_phy30_init(pdev, use_ext_clk);
+}
+
+static int exynos5_usb_phy30_init(struct platform_device *pdev, bool ext_clk)
+{
+ int ret;
+ struct clk *host_clk = NULL;
+
+ host_clk = exynos_usb_phy_clock_enable(pdev);
+ if (!host_clk) {
+ dev_err(&pdev->dev, "Failed to enable USB3.0 host clock\n");
+ return -ENODEV;
+ }
+
+ ret = _exynos5_usb_phy30_init(pdev, ext_clk);
+
+ exynos_usb_phy_clock_disable(host_clk);
+
+ return ret;
+}
+
static int exynos5_usb_phy30_exit(struct platform_device *pdev)
{
u32 reg;
+ struct clk *host_clk = NULL;
+
+ host_clk = exynos_usb_phy_clock_enable(pdev);
+ if (!host_clk) {
+ dev_err(&pdev->dev, "Failed to enable USB3.0 host clock\n");
+ return -ENODEV;
+ }
reg = EXYNOS_USB3_PHYUTMI_OTGDISABLE |
EXYNOS_USB3_PHYUTMI_FORCESUSPEND |
EXYNOS_USB3_PHYUTMI_FORCESLEEP;
writel(reg, EXYNOS_USB3_PHYUTMI);
+ /* Resetting the PHYCLKRST enable bits to reduce leakage current */
+ reg = readl(EXYNOS_USB3_PHYCLKRST);
+ reg &= ~(EXYNOS_USB3_PHYCLKRST_REF_SSP_EN |
+ EXYNOS_USB3_PHYCLKRST_SSC_EN |
+ EXYNOS_USB3_PHYCLKRST_COMMONONN);
+ writel(reg, EXYNOS_USB3_PHYCLKRST);
+
+ /* Control PHYTEST to remove leakage current */
+ reg = readl(EXYNOS_USB3_PHYTEST);
+ reg |= (EXYNOS_USB3_PHYTEST_POWERDOWN_SSP |
+ EXYNOS_USB3_PHYTEST_POWERDOWN_HSP);
+ writel(reg, EXYNOS_USB3_PHYTEST);
+
exynos_usb_phy_control(USB_PHY0, PHY_DISABLE);
+ exynos_usb_phy_clock_disable(host_clk);
+
return 0;
}
static int exynos5_usb_phy20_init(struct platform_device *pdev)
{
- int ret;
u32 refclk_freq;
u32 hostphy_ctrl0, otgphy_sys, hsic_ctrl, ehcictrl;
+ struct clk *host_clk = NULL;
atomic_inc(&host_usage);
- ret = exynos_usb_phy_clock_enable(pdev);
- if (ret)
- return ret;
+
+ host_clk = exynos_usb_phy_clock_enable(pdev);
+ if (!host_clk) {
+ dev_err(&pdev->dev, "Failed to get host_clk\n");
+ return -ENODEV;
+ }
if (exynos5_usb_host_phy20_is_on()) {
dev_err(&pdev->dev, "Already power on PHY\n");
| EHCICTRL_ENAINCR8 | EHCICTRL_ENAINCR16);
writel(ehcictrl, EXYNOS5_PHY_HOST_EHCICTRL);
+ exynos_usb_phy_clock_disable(host_clk);
+
return 0;
}
+
static int exynos5_usb_phy20_exit(struct platform_device *pdev)
{
u32 hostphy_ctrl0, otgphy_sys, hsic_ctrl;
+ struct clk *host_clk = NULL;
if (atomic_dec_return(&host_usage) > 0) {
dev_info(&pdev->dev, "still being used\n");
return -EBUSY;
}
+ host_clk = exynos_usb_phy_clock_enable(pdev);
+ if (!host_clk) {
+ dev_err(&pdev->dev, "Failed to get host_clk\n");
+ return -ENODEV;
+ }
+
hsic_ctrl = (HSIC_CTRL_REFCLKDIV(0x24) | HSIC_CTRL_REFCLKSEL(0x2)
| HSIC_CTRL_SIDDQ | HSIC_CTRL_FORCESLEEP
| HSIC_CTRL_FORCESUSPEND);
exynos_usb_phy_control(USB_PHY1, PHY_DISABLE);
+ exynos_usb_phy_clock_disable(host_clk);
+
return 0;
}
return 0;
}
-int s5p_usb_phy_init(struct platform_device *pdev, int type)
+int s5p_usb_phy_init(struct platform_device *pdev, int type, bool ext_clk)
{
if (type == S5P_USB_PHY_HOST) {
if (soc_is_exynos5250())
return exynos4_usb_phy1_init(pdev);
} else if (type == S5P_USB_PHY_DRD) {
if (soc_is_exynos5250())
- return exynos5_usb_phy30_init(pdev);
+ return exynos5_usb_phy30_init(pdev, ext_clk);
else
dev_err(&pdev->dev, "USB 3.0 DRD not present\n");
}