[ARM] tegra: usb_phy: Add support for usb2 ulpi external phy
authorBenoit Goby <benoit@android.com>
Fri, 3 Sep 2010 00:47:06 +0000 (17:47 -0700)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:28:23 +0000 (16:28 -0700)
Change-Id: Ie2ed0d22abae1319996fe0a6caf28ec7d7e4313d
Signed-off-by: Benoit Goby <benoit@android.com>
arch/arm/mach-tegra/include/mach/usb_phy.h
arch/arm/mach-tegra/usb_phy.c

index 2fe707070307d91097e3f7d9e57cb920fbf80d3f..71f1b9fa76db462392d40e8d9f962095ef824272 100644 (file)
@@ -32,6 +32,11 @@ struct tegra_utmip_config {
        u8 xcvr_lsrslew;
 };
 
+struct tegra_ulpi_config {
+       int reset_gpio;
+       const char *clk;
+};
+
 enum tegra_usb_phy_port_speed {
        TEGRA_USB_PHY_PORT_SPEED_FULL = 0,
        TEGRA_USB_PHY_PORT_SPEED_LOW,
@@ -55,16 +60,16 @@ struct tegra_usb_phy {
        int freq_sel;
        void __iomem *regs;
        void __iomem *pad_regs;
+       struct clk *clk;
        struct clk *pll_u;
        struct clk *pad_clk;
        enum tegra_usb_phy_mode mode;
-       struct tegra_utmip_config *config;
+       void *config;
        struct tegra_utmip_context context;
 };
 
 struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
-                                        struct tegra_utmip_config *config,
-                                        enum tegra_usb_phy_mode phy_mode);
+                       void *config, enum tegra_usb_phy_mode phy_mode);
 
 int tegra_usb_phy_power_on(struct tegra_usb_phy *phy);
 
index fe3b7f8ecff2a48f36148c386c65c3ce338bcff5..7785b1ee41ec612d89d48f4598e4975a2b6b88a7 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/err.h>
 #include <linux/platform_device.h>
 #include <linux/io.h>
+#include <linux/gpio.h>
 #include <asm/mach-types.h>
 #include <mach/usb_phy.h>
 #include <mach/iomap.h>
 #define USB_USBSTS             0x144
 #define   USB_USBSTS_PCI       (1 << 2)
 
+#define ULPI_VIEWPORT          0x170
+#define   ULPI_WAKEUP          (1 << 31)
+#define   ULPI_RUN             (1 << 30)
+#define   ULPI_RD_RW_WRITE     (1 << 29)
+#define   ULPI_RD_RW_READ      (0 << 29)
+#define   ULPI_PORT(x)         (((x) & 0x7) << 24)
+#define   ULPI_ADDR(x)         (((x) & 0xff) << 16)
+#define   ULPI_DATA_RD(x)      (((x) & 0xff) << 8)
+#define   ULPI_DATA_WR(x)      (((x) & 0xff) << 0)
+
 #define USB_PORTSC1            0x184
 #define   USB_PORTSC1_PTS(x)   (((x) & 0x3) << 30)
 #define   USB_PORTSC1_PSPD(x)  (((x) & 0x3) << 26)
 #define   USB_PORTSC1_PHCD     (1 << 23)
+#define   USB_PORTSC1_WKOC     (1 << 22)
+#define   USB_PORTSC1_WKDS     (1 << 21)
+#define   USB_PORTSC1_WKCN     (1 << 20)
 #define   USB_PORTSC1_PTC(x)   (((x) & 0xf) << 16)
 #define   USB_PORTSC1_PP       (1 << 12)
 #define   USB_PORTSC1_SUSP     (1 << 7)
@@ -47,7 +61,9 @@
 #define   USB_SUSP_CLR         (1 << 5)
 #define   USB_PHY_CLK_VALID    (1 << 7)
 #define   UTMIP_RESET                  (1 << 11)
+#define   UHSIC_RESET                  (1 << 11)
 #define   UTMIP_PHY_ENABLE             (1 << 12)
+#define   ULPI_PHY_ENABLE      (1 << 13)
 #define   USB_SUSP_SET         (1 << 14)
 
 #define USB1_LEGACY_CTRL       0x410
 #define   USB1_VBUS_SENSE_CTL_AB_SESS_VLD      (2 << 1)
 #define   USB1_VBUS_SENSE_CTL_A_SESS_VLD       (3 << 1)
 
+#define ULPI_TIMING_CTRL_0     0x424
+#define   ULPI_OUTPUT_PINMUX_BYP       (1 << 10)
+#define   ULPI_CLKOUT_PINMUX_BYP       (1 << 11)
+
+#define ULPI_TIMING_CTRL_1     0x428
+#define   ULPI_DATA_TRIMMER_LOAD       (1 << 0)
+#define   ULPI_DATA_TRIMMER_SEL(x)     (((x) & 0x7) << 1)
+#define   ULPI_STPDIRNXT_TRIMMER_LOAD  (1 << 16)
+#define   ULPI_STPDIRNXT_TRIMMER_SEL(x)        (((x) & 0x7) << 17)
+#define   ULPI_DIR_TRIMMER_LOAD                (1 << 24)
+#define   ULPI_DIR_TRIMMER_SEL(x)      (((x) & 0x7) << 25)
+
 #define UTMIP_PLL_CFG1         0x804
 #define   UTMIP_XTAL_FREQ_COUNT(x)             (((x) & 0xfff) << 0)
 #define   UTMIP_PLLU_ENABLE_DLY_COUNT(x)       (((x) & 0x1f) << 27)
@@ -446,11 +474,125 @@ static void utmi_phy_power_off(struct tegra_usb_phy *phy)
        utmip_pad_power_off(phy);
 }
 
+static void ulpi_viewport_write(struct tegra_usb_phy *phy, u8 addr, u8 data)
+{
+       unsigned long val;
+       void __iomem *base = phy->regs;
+
+       val = ULPI_RUN | ULPI_RD_RW_WRITE | ULPI_PORT(0);
+       val |= ULPI_ADDR(addr) | ULPI_DATA_WR(data);
+       writel(val, base + ULPI_VIEWPORT);
+
+       if (utmi_wait_register(base + ULPI_VIEWPORT, ULPI_RUN, 0))
+               pr_err("%s: timeout accessing ulpi phy\n", __func__);
+}
+
+static void ulpi_phy_power_on(struct tegra_usb_phy *phy)
+{
+       unsigned long val;
+       void __iomem *base = phy->regs;
+       struct tegra_ulpi_config *config = phy->config;
+
+       gpio_direction_output(config->reset_gpio, 0);
+       msleep(5);
+       gpio_direction_output(config->reset_gpio, 1);
+
+       clk_enable(phy->clk);
+       msleep(1);
+
+       val = readl(base + USB_SUSP_CTRL);
+       val |= UHSIC_RESET;
+       writel(val, base + USB_SUSP_CTRL);
+
+       val = readl(base + ULPI_TIMING_CTRL_0);
+       val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP;
+       writel(val, base + ULPI_TIMING_CTRL_0);
+
+       val = readl(base + USB_SUSP_CTRL);
+       val |= ULPI_PHY_ENABLE;
+       writel(val, base + USB_SUSP_CTRL);
+
+       val = 0;
+       writel(val, base + ULPI_TIMING_CTRL_1);
+
+       val |= ULPI_DATA_TRIMMER_SEL(4);
+       val |= ULPI_STPDIRNXT_TRIMMER_SEL(4);
+       val |= ULPI_DIR_TRIMMER_SEL(4);
+       writel(val, base + ULPI_TIMING_CTRL_1);
+       udelay(10);
+
+       val |= ULPI_DATA_TRIMMER_LOAD;
+       val |= ULPI_STPDIRNXT_TRIMMER_LOAD;
+       val |= ULPI_DIR_TRIMMER_LOAD;
+       writel(val, base + ULPI_TIMING_CTRL_1);
+
+       val = ULPI_WAKEUP | ULPI_RD_RW_WRITE | ULPI_PORT(0);
+       writel(val, base + ULPI_VIEWPORT);
+
+       if (utmi_wait_register(base + ULPI_VIEWPORT, ULPI_WAKEUP, 0)) {
+               pr_err("%s: timeout waiting for ulpi phy wakeup\n", __func__);
+               return;
+       }
+
+       /* Fix VbusInvalid due to floating VBUS */
+       ulpi_viewport_write(phy, 0x08, 0x40);
+       ulpi_viewport_write(phy, 0x0B, 0x80);
+
+       val = readl(base + USB_PORTSC1);
+       val |= USB_PORTSC1_WKOC | USB_PORTSC1_WKDS | USB_PORTSC1_WKCN;
+       writel(val, base + USB_PORTSC1);
+
+       val = readl(base + USB_SUSP_CTRL);
+       val |= USB_SUSP_CLR;
+       writel(val, base + USB_SUSP_CTRL);
+       udelay(100);
+
+       val = readl(base + USB_SUSP_CTRL);
+       val &= ~USB_SUSP_CLR;
+       writel(val, base + USB_SUSP_CTRL);
+}
+
+static void ulpi_phy_power_off(struct tegra_usb_phy *phy)
+{
+       unsigned long val;
+       void __iomem *base = phy->regs;
+
+       /* Programming the ULPI register function control */
+       ulpi_viewport_write(phy, 0x04, 0x4D);
+
+       /* Resetting the ULPI register IndicatorPassThru */
+       ulpi_viewport_write(phy, 0x09, 0x40);
+
+       /* USB Interrupt Rising - making sure vbus comparator and id are off */
+       ulpi_viewport_write(phy, 0x0D, 0x00);
+
+       /* USB Interrupt Falling */
+       ulpi_viewport_write(phy, 0x10, 0x00);
+
+       /* Carkit Control */
+       ulpi_viewport_write(phy, 0x19, 0x00);
+
+       /* Disabling ID float Rise/Fall (Carkit Enable) */
+       ulpi_viewport_write(phy, 0x1D, 0x00);
+
+       /* USB I/O and power */
+       ulpi_viewport_write(phy, 0x39, 0x00);
+
+       /* Clear WKCN/WKDS/WKOC wake-on events that can cause the USB
+        * Controller to immediately bring the ULPI PHY out of low power
+        */
+       val = readl(base + USB_PORTSC1);
+       val &= ~(USB_PORTSC1_WKOC | USB_PORTSC1_WKDS | USB_PORTSC1_WKCN);
+       writel(val, base + USB_PORTSC1);
+
+       clk_disable(phy->clk);
+}
+
 struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
-                                        struct tegra_utmip_config *config,
-                                        enum tegra_usb_phy_mode phy_mode)
+                       void *config, enum tegra_usb_phy_mode phy_mode)
 {
        struct tegra_usb_phy *phy;
+       struct tegra_ulpi_config *ulpi_config;
        unsigned long parent_rate;
        int freq_sel;
        int err;
@@ -465,8 +607,14 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
        phy->context.valid = false;
        phy->mode = phy_mode;
 
-       if (!phy->config)
-               phy->config = &utmip_default[instance];
+       if (!phy->config) {
+               if (instance == 1) {
+                       pr_err("%s: ulpi phy configuration missing", __func__);
+                       goto err0;
+               } else {
+                       phy->config = &utmip_default[instance];
+               }
+       }
 
        phy->pll_u = clk_get_sys(NULL, "pll_u");
        if (IS_ERR(phy->pll_u)) {
@@ -488,8 +636,17 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
        }
        phy->freq_sel = freq_sel;
 
-       /* TODO usb2 ulpi */
-       if (phy->instance != 1) {
+       if (phy->instance == 1) {
+               ulpi_config = config;
+               phy->clk = clk_get_sys(NULL, ulpi_config->clk);
+               if (IS_ERR(phy->clk)) {
+                       pr_err("%s: can't get ulpi clock\n", __func__);
+                       goto err1;
+               }
+               tegra_gpio_enable(ulpi_config->reset_gpio);
+               gpio_request(ulpi_config->reset_gpio, "ulpi_phy_reset_b");
+               gpio_direction_output(ulpi_config->reset_gpio, 0);
+       } else {
                err = utmip_pad_open(phy);
                if (err < 0)
                        goto err1;
@@ -507,8 +664,9 @@ err0:
 
 int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
 {
-       /* TODO usb2 ulpi */
-       if (phy->instance != 1)
+       if (phy->instance == 1)
+               ulpi_phy_power_on(phy);
+       else
                utmi_phy_power_on(phy);
 
        return 0;
@@ -516,8 +674,9 @@ int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
 
 int tegra_usb_phy_power_off(struct tegra_usb_phy *phy)
 {
-       /* TODO usb2 ulpi */
-       if (phy->instance != 1)
+       if (phy->instance == 1)
+               ulpi_phy_power_off(phy);
+       else
                utmi_phy_power_off(phy);
 
        return 0;
@@ -541,7 +700,9 @@ int tegra_usb_phy_clk_enable(struct tegra_usb_phy *phy)
 
 int tegra_usb_phy_close(struct tegra_usb_phy *phy)
 {
-       if (phy->instance != 1)
+       if (phy->instance == 1)
+               clk_put(phy->clk);
+       else
                utmip_pad_close(phy);
        clk_disable(phy->pll_u);
        clk_put(phy->pll_u);