usb3: exynos5: Use external phy clock when needed; shut down when not
authorDoug Anderson <dianders@chromium.org>
Fri, 17 Aug 2012 19:51:39 +0000 (12:51 -0700)
committerGerrit <chrome-bot@google.com>
Fri, 17 Aug 2012 22:45:14 +0000 (15:45 -0700)
The usb3 phy clock is an external PLL that burns a lot of power but is
needed to pass USB 3.0 compliance testing.  When nothing is plugged
into the port we can turn the PLL off and switch to a different clock
to save power.  This other clock is enough to detect that a device
has been inserted.

We also do a little bit of cleanup to use register bit constants
introduced in a previous patch.

BUG=chrome-os-partner:11066
TEST=With other patches in this series can run:
  echo auto > \
    /sys/devices/exynos-dwc3/dwc3.0/xhci-hcd/usb3/power/control
  echo auto > \
    /sys/devices/exynos-dwc3/dwc3.0/xhci-hcd/usb4/power/control
  grep phy_clock_en /sys/kernel/debug/gpio
...and see that the phy clock enable goes low when nothing is
plugged into the USB 3.0 port and goes high when something is
in the port.

Change-Id: I0ca5ab7c8c06187251a8767768e682f43803bd7a
Signed-off-by: Vivek Gautam <gautam.vivek@samsung.com>
Signed-off-by: Doug Anderson <dianders@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/30511
Reviewed-by: Olof Johansson <olofj@chromium.org>
arch/arm/mach-exynos/mach-exynos5-dt.c
arch/arm/mach-exynos/setup-usb-phy.c
arch/arm/plat-samsung/include/plat/usb-phy.h

index 7f8bde9..3cff505 100644 (file)
@@ -546,6 +546,7 @@ static struct dwc3_exynos_data smdk5250_xhci_pdata = {
        .phy_type = S5P_USB_PHY_DRD,
        .phy_init = s5p_usb_phy_init,
        .phy_exit = s5p_usb_phy_exit,
+       .phyclk_switch = exynos5_dwc_phyclk_switch,
 };
 
 struct exynos_gpio_cfg {
index 9404327..66ce418 100644 (file)
@@ -13,6 +13,8 @@
 #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>
@@ -55,6 +57,7 @@ static int exynos5_usb_host_phy20_is_on(void)
        return (readl(EXYNOS5_PHY_HOST_CTRL0) & HOST_CTRL0_PHYSWRSTALL) ? 0 : 1;
 }
 
+/* TODO: Shouldn't there be a disable? */
 static int exynos_usb_phy_clock_enable(struct platform_device *pdev)
 {
        int err;
@@ -157,30 +160,37 @@ static u32 exynos_usb_phy_set_clock(struct platform_device *pdev)
        return refclk_freq;
 }
 
-static u32 exynos_usb_phy30_set_clock(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, refclk;
+       u32 reg;
+       u32 refclk;
 
        refclk = exynos_usb_phy_set_clock(pdev);
-       reg = EXYNOS_USB3_PHYCLKRST_REFCLKSEL(3) |
+
+       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(0x7d) |
+               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(0x68) |
+               reg |= (EXYNOS_USB3_PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
                        EXYNOS_USB3_PHYCLKRST_SSC_REF_CLK_SEL(0x88));
                break;
        }
@@ -188,21 +198,35 @@ static u32 exynos_usb_phy30_set_clock(struct platform_device *pdev)
        return reg;
 }
 
-static int exynos5_usb_phy30_init(struct platform_device *pdev)
+/*
+ * 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);
+
+       reg = 0x24d4e6e4;
+       if (use_ext_clk)
+               reg |= (0x1 << 31);
+       writel(reg, EXYNOS_USB3_PHYPARAM0);
+
        writel(0x03fff81c, EXYNOS_USB3_PHYPARAM1);
        writel(0x00000000, EXYNOS_USB3_PHYRESUME);
 
@@ -219,7 +243,10 @@ static int exynos5_usb_phy30_init(struct platform_device *pdev)
        /* UTMI Power Control */
        writel(EXYNOS_USB3_PHYUTMI_OTGDISABLE, EXYNOS_USB3_PHYUTMI);
 
-       reg = exynos_usb_phy30_set_clock(pdev);
+       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 */
@@ -241,6 +268,23 @@ static int exynos5_usb_phy30_init(struct platform_device *pdev)
        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)
+{
+       int ret;
+
+       ret = exynos_usb_phy_clock_enable(pdev);
+       if (ret)
+               return ret;
+
+       /* We'll start out with the phy clock turned on. */
+       return _exynos5_usb_phy30_init(pdev, true);
+}
+
 static int exynos5_usb_phy30_exit(struct platform_device *pdev)
 {
        u32 reg;
index f784101..6836a5c 100644 (file)
@@ -19,5 +19,7 @@ enum s5p_usb_phy_type {
 
 extern int s5p_usb_phy_init(struct platform_device *pdev, int type);
 extern int s5p_usb_phy_exit(struct platform_device *pdev, int type);
+extern int exynos5_dwc_phyclk_switch(struct platform_device *pdev,
+                                               bool use_ext_clk);
 
 #endif /* __PLAT_SAMSUNG_USB_PHY_H */