From 549d5a207316248177f70635f32d2ca93cbdedd5 Mon Sep 17 00:00:00 2001 From: Benoit Goby Date: Thu, 19 Aug 2010 21:28:59 -0700 Subject: [PATCH] [ARM] tegra: usb_phy: Add phy_suspend and phy_clk_disable phy_suspend save the phy registers and power it off. phy_clk_disable disable the phy clock to put the phy in low power mode. Change-Id: If5665ec96beec9441fae3b737923dbe69c86e650 Signed-off-by: Benoit Goby --- arch/arm/mach-tegra/include/mach/usb_phy.h | 41 ++- arch/arm/mach-tegra/usb_phy.c | 304 +++++++++++++++++---- 2 files changed, 288 insertions(+), 57 deletions(-) diff --git a/arch/arm/mach-tegra/include/mach/usb_phy.h b/arch/arm/mach-tegra/include/mach/usb_phy.h index eb25044fa150..8f6bd2a948f3 100644 --- a/arch/arm/mach-tegra/include/mach/usb_phy.h +++ b/arch/arm/mach-tegra/include/mach/usb_phy.h @@ -20,6 +20,8 @@ #include #include +#define USB_PHY_MAX_CONTEXT_REGS 10 + struct tegra_utmip_config { u8 hssync_start_delay; u8 elastic_limit; @@ -30,25 +32,48 @@ struct tegra_utmip_config { u8 xcvr_lsrslew; }; +enum tegra_usb_phy_port_speed { + TEGRA_USB_PHY_PORT_SPEED_FULL = 0, + TEGRA_USB_PHY_PORT_SPEED_LOW, + TEGRA_USB_PHY_PORT_HIGH, +}; + +struct tegra_utmip_context { + bool valid; + u32 regs[USB_PHY_MAX_CONTEXT_REGS]; + int regs_count; + enum tegra_usb_phy_port_speed port_speed; +}; + +enum tegra_usb_phy_mode { + TEGRA_USB_PHY_MODE_DEVICE, + TEGRA_USB_PHY_MODE_HOST, +}; + struct tegra_usb_phy { int instance; void __iomem *regs; void __iomem *pad_regs; struct clk *pll_u; struct clk *pad_clk; + enum tegra_usb_phy_mode mode; struct tegra_utmip_config *config; -}; - -enum tegra_usb_phy_mode { - TEGRA_USB_PHY_MODE_DEVICE, - TEGRA_USB_PHY_MODE_HOST, + struct tegra_utmip_context context; }; struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs, - struct tegra_utmip_config *config); + struct tegra_utmip_config *config, + enum tegra_usb_phy_mode phy_mode); + +int tegra_usb_phy_power_on(struct tegra_usb_phy *phy); + +int tegra_usb_phy_suspend(struct tegra_usb_phy *phy); + +int tegra_usb_phy_resume(struct tegra_usb_phy *phy); + +int tegra_usb_phy_clk_disable(struct tegra_usb_phy *phy); -int tegra_usb_phy_power_on(struct tegra_usb_phy *phy, - enum tegra_usb_phy_mode phy_mode); +int tegra_usb_phy_clk_enable(struct tegra_usb_phy *phy); int tegra_usb_phy_power_off(struct tegra_usb_phy *phy); diff --git a/arch/arm/mach-tegra/usb_phy.c b/arch/arm/mach-tegra/usb_phy.c index 8e9f24cf7ce4..50b099fedd09 100644 --- a/arch/arm/mach-tegra/usb_phy.c +++ b/arch/arm/mach-tegra/usb_phy.c @@ -28,9 +28,28 @@ #include #include +#define USB_USBCMD 0x140 + +#define USB_USBSTS 0x144 +#define USB_USBSTS_PCI (1 << 2) + +#define USB_USBINTR 0x148 +#define USB_PERIODICLISTBASE 0x154 +#define USB_ASYNCLISTADDR 0x158 +#define USB_TXFILLTUNING 0x164 + #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_PTC(x) (((x) & 0xf) << 16) +#define USB_PORTSC1_PP (1 << 12) +#define USB_PORTSC1_SUSP (1 << 7) +#define USB_PORTSC1_PE (1 << 2) +#define USB_PORTSC1_CCS (1 << 0) + +#define USB_OTGSC 0x1a4 +#define USB_USBMODE 0x1a8 #define USB_SUSP_CTRL 0x400 #define USB_WAKE_ON_CNNT_EN_DEV (1 << 3) @@ -215,21 +234,143 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy) return 0; } -static int utmi_phy_wait_stable(struct tegra_usb_phy *phy) +static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result) +{ + unsigned long timeout = 1000000; + do { + if ((readl(reg) & mask) == result) + return 0; + udelay(1); + timeout--; + } while (timeout); + return -1; +} + +static int utmi_phy_restore_context(struct tegra_usb_phy *phy) { void __iomem *base = phy->regs; - unsigned long timeout = jiffies + HZ; + unsigned long val = 0; + int count = 0; - while (time_before(jiffies, timeout)) { - if (readl(base + USB_SUSP_CTRL) & USB_PHY_CLK_VALID) - return 0; - udelay(10); - cpu_relax(); + /* If any saved context is present, restore it */ + if (!phy->context.valid) + return -1; + + /* Restore register context */ + count = phy->context.regs_count; + count--; + writel(phy->context.regs[count--], base + USB_USBMODE); + writel(phy->context.regs[count--], base + USB_OTGSC); + writel(phy->context.regs[count--], base + USB_TXFILLTUNING); + writel(phy->context.regs[count--], base + USB_ASYNCLISTADDR); + writel(phy->context.regs[count--], base + USB_PERIODICLISTBASE); + /* Restore interrupt context later */ + count--; + writel(phy->context.regs[count--], base + USB_USBCMD); + + /* Enable Port Power */ + val = readl(base + USB_PORTSC1); + val |= USB_PORTSC1_PP; + writel(val, base + USB_PORTSC1); + udelay(10); + + /* Program the field PTC in PORTSC based on the saved speed mode */ + if (phy->context.port_speed == TEGRA_USB_PHY_PORT_HIGH) { + val = readl(base + USB_PORTSC1); + val &= ~(USB_PORTSC1_PTC(~0)); + val |= USB_PORTSC1_PTC(5); + writel(val, base + USB_PORTSC1); + } else if (phy->context.port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL) { + val = readl(base + USB_PORTSC1); + val &= ~(USB_PORTSC1_PTC(~0)); + val |= USB_PORTSC1_PTC(6); + writel(val, base + USB_PORTSC1); + } else if (phy->context.port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) { + val = readl(base + USB_PORTSC1); + val &= ~(USB_PORTSC1_PTC(~0)); + val |= USB_PORTSC1_PTC(7); + writel(val, base + USB_PORTSC1); + } else { + pr_err("%s: speed is not configureed properly\n", __func__); + return -1; } - if (readl(base + USB_SUSP_CTRL) & USB_PHY_CLK_VALID) + udelay(10); + + /* Disable test mode by setting PTC field to NORMAL_OP */ + val = readl(base + USB_PORTSC1); + val &= ~(USB_PORTSC1_PTC(~0)); + writel(val, base + USB_PORTSC1); + + /* Poll until CCS is enabled */ + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_CCS, + USB_PORTSC1_CCS)) { + pr_err("%s: timeout waiting for USB_PORTSC1_CCS\n", __func__); + return -1; + } + + /* Poll until PE is enabled */ + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_PE, + USB_PORTSC1_PE)) { + pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__); + return -1; + } + + /* Clear the PCI status, to avoid an interrupt taken upon resume */ + val = readl(base + USB_USBSTS); + val |= USB_USBSTS_PCI; + writel(val, base + USB_USBSTS); + if (utmi_wait_register(base + USB_USBSTS, USB_USBSTS_PCI, 0) < 0) { + pr_err("%s: timeout waiting for USB_USBSTS_PCI\n", __func__); + return -1; + } + + /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */ + val = readl(base + USB_PORTSC1); + if ((val & USB_PORTSC1_PP) && (val & USB_PORTSC1_PE)) { + val |= USB_PORTSC1_SUSP; + writel(val, base + USB_PORTSC1); + + /* Wait until port suspend completes */ + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_SUSP, + USB_PORTSC1_SUSP)) { + pr_err("%s: timeout waiting for USB_PORTSC1_SUSP\n", + __func__); + return -1; + } + } + + /* Restore interrupt register */ + writel(phy->context.regs[1], base + USB_USBINTR); + udelay(10); + + return 0; +} + +static int utmi_phy_save_context(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + int count = 0; + + phy->context.port_speed = (readl(base + USB_PORTSC1) + & USB_PORTSC1_PSPD(3)) >> 26; + + /* If no device connection or invalid speeds just return */ + if (phy->context.port_speed > TEGRA_USB_PHY_PORT_HIGH) { + phy->context.valid = false; return 0; - else - return -ETIMEDOUT; + } + + phy->context.regs[count++] = readl(base + USB_USBCMD); + phy->context.regs[count++] = readl(base + USB_USBINTR); + phy->context.regs[count++] = readl(base + USB_PERIODICLISTBASE); + phy->context.regs[count++] = readl(base + USB_ASYNCLISTADDR); + phy->context.regs[count++] = readl(base + USB_TXFILLTUNING); + phy->context.regs[count++] = readl(base + USB_OTGSC); + phy->context.regs[count++] = readl(base + USB_USBMODE); + phy->context.regs_count = count; + phy->context.valid = true; + + return 0; } static void utmi_phy_init(struct tegra_usb_phy *phy, int freq_sel) @@ -281,14 +422,68 @@ static void utmi_phy_init(struct tegra_usb_phy *phy, int freq_sel) writel(val, base + UTMIP_PLL_CFG1); } -void utmi_phy_power_on(struct tegra_usb_phy *phy, - enum tegra_usb_phy_mode phy_mode) +static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + if (phy->instance == 0) { + val = readl(base + USB_SUSP_CTRL); + val |= USB_SUSP_SET; + writel(val, base + USB_SUSP_CTRL); + + udelay(10); + + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_SET; + writel(val, base + USB_SUSP_CTRL); + } + + if (phy->instance == 2) { + val = readl(base + USB_PORTSC1); + val |= USB_PORTSC1_PHCD; + writel(val, base + USB_PORTSC1); + } + + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0) + pr_err("%s: timeout waiting for phy to stabilize\n", __func__); +} + +static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + if (phy->instance == 0) { + val = readl(base + USB_SUSP_CTRL); + val |= USB_SUSP_CLR; + writel(val, base + USB_SUSP_CTRL); + + udelay(10); + + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_CLR; + writel(val, base + USB_SUSP_CTRL); + } + + if (phy->instance == 2) { + val = readl(base + USB_PORTSC1); + val &= ~USB_PORTSC1_PHCD; + writel(val, base + USB_PORTSC1); + } + + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, + USB_PHY_CLK_VALID)) + pr_err("%s: timeout waiting for phy to stabilize\n", __func__); +} + +static void utmi_phy_power_on(struct tegra_usb_phy *phy) { unsigned long val; void __iomem *base = phy->regs; struct tegra_utmip_config *config = phy->config; - if (phy_mode == TEGRA_USB_PHY_MODE_DEVICE) { + if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) { val = readl(base + USB_SUSP_CTRL); val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV); writel(val, base + USB_SUSP_CTRL); @@ -335,20 +530,9 @@ void utmi_phy_power_on(struct tegra_usb_phy *phy, val = readl(base + USB_SUSP_CTRL); val &= ~USB_SUSP_SET; writel(val, base + USB_SUSP_CTRL); - - val = readl(base + USB_SUSP_CTRL); - val |= USB_SUSP_CLR; - writel(val, base + USB_SUSP_CTRL); - - udelay(10); - - val = readl(base + USB_SUSP_CTRL); - val &= ~USB_SUSP_CLR; - writel(val, base + USB_SUSP_CTRL); } - if (utmi_phy_wait_stable(phy)) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + utmi_phy_clk_enable(phy); if (phy->instance == 2) { val = readl(base + USB_PORTSC1); @@ -357,28 +541,12 @@ void utmi_phy_power_on(struct tegra_usb_phy *phy, } } -void utmi_phy_power_off(struct tegra_usb_phy *phy) +static void utmi_phy_power_off(struct tegra_usb_phy *phy) { unsigned long val; void __iomem *base = phy->regs; - if (phy->instance == 0) { - val = readl(base + USB_SUSP_CTRL); - val |= USB_SUSP_SET; - writel(val, base + USB_SUSP_CTRL); - - udelay(10); - - val = readl(base + USB_SUSP_CTRL); - val &= ~USB_SUSP_SET; - writel(val, base + USB_SUSP_CTRL); - } - - if (phy->instance == 2) { - val = readl(base + USB_PORTSC1); - val |= USB_PORTSC1_PHCD; - writel(val, base + USB_PORTSC1); - } + utmi_phy_clk_disable(phy); val = readl(base + USB_SUSP_CTRL); val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV; @@ -406,7 +574,8 @@ void utmi_phy_power_off(struct tegra_usb_phy *phy) } struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs, - struct tegra_utmip_config *config) + struct tegra_utmip_config *config, + enum tegra_usb_phy_mode phy_mode) { struct tegra_usb_phy *phy; unsigned long parent_rate; @@ -420,6 +589,8 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs, phy->instance = instance; phy->regs = regs; phy->config = config; + phy->context.valid = false; + phy->mode = phy_mode; if (!phy->config) phy->config = &utmip_default[instance]; @@ -430,6 +601,7 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs, err = PTR_ERR(phy->pll_u); goto err0; } + clk_enable(phy->pll_u); parent_rate = clk_get_rate(clk_get_parent(phy->pll_u)); for (freq_sel = 0; freq_sel < ARRAY_SIZE(udc_freq_table); freq_sel++) { @@ -460,13 +632,11 @@ err0: return ERR_PTR(err); } -int tegra_usb_phy_power_on(struct tegra_usb_phy *phy, - enum tegra_usb_phy_mode phy_mode) +int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) { /* TODO usb2 ulpi */ - clk_enable(phy->pll_u); if (phy->instance != 1) - utmi_phy_power_on(phy, phy_mode); + utmi_phy_power_on(phy); return 0; } @@ -476,7 +646,42 @@ int tegra_usb_phy_power_off(struct tegra_usb_phy *phy) /* TODO usb2 ulpi */ if (phy->instance != 1) utmi_phy_power_off(phy); - clk_disable(phy->pll_u); + + return 0; +} + +int tegra_usb_phy_clk_disable(struct tegra_usb_phy *phy) +{ + if (phy->instance != 1) + utmi_phy_clk_disable(phy); + + return 0; +} + +int tegra_usb_phy_clk_enable(struct tegra_usb_phy *phy) +{ + if (phy->instance != 1) + utmi_phy_clk_enable(phy); + + return 0; +} + +int tegra_usb_phy_suspend(struct tegra_usb_phy *phy) +{ + if (phy->instance != 1) { + utmi_phy_save_context(phy); + utmi_phy_power_off(phy); + } + + return 0; +} + +int tegra_usb_phy_resume(struct tegra_usb_phy *phy) +{ + if (phy->instance != 1) { + utmi_phy_power_on(phy); + return utmi_phy_restore_context(phy); + } return 0; } @@ -485,6 +690,7 @@ int tegra_usb_phy_close(struct tegra_usb_phy *phy) { if (phy->instance != 1) utmip_pad_close(phy); + clk_disable(phy->pll_u); clk_put(phy->pll_u); kfree(phy); return 0; -- 2.34.1