diff options
author | Seshendra Gadagottu <sgadagottu@nvidia.com> | 2011-01-11 11:41:09 -0800 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-02-10 15:41:11 -0800 |
commit | 8df80194dc456a45fc8245de57bd9b46d2cf16a8 (patch) | |
tree | 78a310256698305e2ac7dae9df58b39fd5cacec7 | |
parent | 57a3407736092650cbed228fdf9264e140378762 (diff) |
tegra hsic: Adding USB hsic driver support to K36
Adding hsic funtionality to USB2 instance.
Add the changes required for hsic functionality and
power management.
BUG 756184
Change-Id: Ife8a1fc6ea95b15f66d840b1565d858ee25d5ded
Reviewed-on: http://git-master/r/15192
Tested-by: Seshendra Gadagottu <sgadagottu@nvidia.com>
Reviewed-by: Michael Hsu <mhsu@nvidia.com>
Tested-by: Michael Hsu <mhsu@nvidia.com>
Reviewed-by: Shail Dave <sdave@nvidia.com>
-rw-r--r-- | arch/arm/mach-tegra/include/mach/usb_phy.h | 24 | ||||
-rw-r--r-- | arch/arm/mach-tegra/usb_phy.c | 341 | ||||
-rw-r--r-- | drivers/usb/host/ehci-tegra.c | 270 |
3 files changed, 573 insertions, 62 deletions
diff --git a/arch/arm/mach-tegra/include/mach/usb_phy.h b/arch/arm/mach-tegra/include/mach/usb_phy.h index 8cb7eb457ff3..30c0f57fee70 100644 --- a/arch/arm/mach-tegra/include/mach/usb_phy.h +++ b/arch/arm/mach-tegra/include/mach/usb_phy.h @@ -2,6 +2,7 @@ * arch/arm/mach-tegra/include/mach/usb_phy.h * * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2011 NVIDIA Corporation. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -33,8 +34,9 @@ struct tegra_utmip_config { }; enum tegra_ulpi_inf_type { - TEGRA_USB_LINK_ULPI = 0, - TEGRA_USB_NULL_ULPI, + TEGRA_USB_LINK_ULPI = 0, + TEGRA_USB_NULL_ULPI, + TEGRA_USB_UHSIC, }; struct tegra_ulpi_trimmer { @@ -53,6 +55,14 @@ struct tegra_ulpi_config { int (*postinit)(void); }; +struct tegra_uhsic_config { + u8 sync_start_delay; + u8 idle_wait_delay; + u8 term_range_adj; + u8 elastic_underrun_limit; + u8 elastic_overrun_limit; +}; + enum tegra_usb_phy_port_speed { TEGRA_USB_PHY_PORT_SPEED_FULL = 0, TEGRA_USB_PHY_PORT_SPEED_LOW, @@ -98,4 +108,12 @@ int tegra_ehci_phy_restore_end(struct tegra_usb_phy *phy); int tegra_usb_phy_close(struct tegra_usb_phy *phy); -#endif //__MACH_USB_PHY_H +int tegra_usb_phy_bus_connect(struct tegra_usb_phy *phy); + +int tegra_usb_phy_bus_reset(struct tegra_usb_phy *phy); + +int tegra_usb_phy_bus_idle(struct tegra_usb_phy *phy); + +bool tegra_usb_phy_is_device_connected(struct tegra_usb_phy *phy); + +#endif /*__MACH_USB_PHY_H */ diff --git a/arch/arm/mach-tegra/usb_phy.c b/arch/arm/mach-tegra/usb_phy.c index 9e809253170f..bcc5cc464eb6 100644 --- a/arch/arm/mach-tegra/usb_phy.c +++ b/arch/arm/mach-tegra/usb_phy.c @@ -2,6 +2,7 @@ * arch/arm/mach-tegra/usb_phy.c * * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 - 2011 NVIDIA Corporation * * Author: * Erik Gilling <konkers@google.com> @@ -31,8 +32,12 @@ #include <mach/pinmux.h> #include "gpio-names.h" +#define USB_USBCMD 0x140 +#define USB_USBCMD_RS (1 << 0) + #define USB_USBSTS 0x144 #define USB_USBSTS_PCI (1 << 2) +#define USB_USBSTS_HCH (1 << 12) #define ULPI_VIEWPORT 0x170 #define ULPI_WAKEUP (1 << 31) @@ -53,6 +58,7 @@ #define USB_PORTSC1_WKCN (1 << 20) #define USB_PORTSC1_PTC(x) (((x) & 0xf) << 16) #define USB_PORTSC1_PP (1 << 12) +#define USB_PORTSC1_LS(x) (((x) & 0x3) << 10) #define USB_PORTSC1_SUSP (1 << 7) #define USB_PORTSC1_PE (1 << 2) #define USB_PORTSC1_CCS (1 << 0) @@ -62,9 +68,10 @@ #define USB_WAKE_ON_DISCON_EN_DEV (1 << 4) #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 UTMIP_RESET (1 << 11) +#define UHSIC_RESET (1 << 11) +#define UTMIP_PHY_ENABLE (1 << 12) +#define UHSIC_PHY_ENABLE (1 << 12) #define ULPI_PHY_ENABLE (1 << 13) #define USB_SUSP_SET (1 << 14) #define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) @@ -168,6 +175,55 @@ #define UTMIP_BIAS_CFG1 0x83c #define UTMIP_BIAS_PDTRK_COUNT(x) (((x) & 0x1f) << 3) +#define UHSIC_PLL_CFG0 0x800 + +#define UHSIC_PLL_CFG1 0x804 +#define UHSIC_XTAL_FREQ_COUNT(x) (((x) & 0xfff) << 0) +#define UHSIC_PLLU_ENABLE_DLY_COUNT(x) (((x) & 0x1f) << 14) + +#define UHSIC_HSRX_CFG0 0x808 +#define UHSIC_ELASTIC_UNDERRUN_LIMIT(x) (((x) & 0x1f) << 2) +#define UHSIC_ELASTIC_OVERRUN_LIMIT(x) (((x) & 0x1f) << 8) +#define UHSIC_IDLE_WAIT(x) (((x) & 0x1f) << 13) + +#define UHSIC_HSRX_CFG1 0x80c +#define UHSIC_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1) + +#define UHSIC_TX_CFG0 0x810 +#define UHSIC_HS_POSTAMBLE_OUTPUT_ENABLE (1 << 6) + +#define UHSIC_MISC_CFG0 0x814 +#define UHSIC_SUSPEND_EXIT_ON_EDGE (1 << 7) +#define UHSIC_DETECT_SHORT_CONNECT (1 << 8) +#define UHSIC_FORCE_XCVR_MODE (1 << 15) + +#define UHSIC_MISC_CFG1 0X818 +#define UHSIC_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 2) + +#define UHSIC_PADS_CFG0 0x81c +#define UHSIC_TX_RTUNEN 0xf000 +#define UHSIC_TX_RTUNE(x) (((x) & 0xf) << 12) + +#define UHSIC_PADS_CFG1 0x820 +#define UHSIC_PD_BG (1 << 2) +#define UHSIC_PD_TX (1 << 3) +#define UHSIC_PD_TRK (1 << 4) +#define UHSIC_PD_RX (1 << 5) +#define UHSIC_PD_ZI (1 << 6) +#define UHSIC_RX_SEL (1 << 7) +#define UHSIC_RPD_DATA (1 << 9) +#define UHSIC_RPD_STROBE (1 << 10) +#define UHSIC_RPU_DATA (1 << 11) +#define UHSIC_RPU_STROBE (1 << 12) + +#define UHSIC_CMD_CFG0 0x824 +#define UHSIC_PRETEND_CONNECT_DETECT (1 << 5) + +#define UHSIC_STAT_CFG0 0x828 +#define UHSIC_CONNECT_DETECT (1 << 0) + +#define UHSIC_SPARE_CFG0 0x82c + static DEFINE_SPINLOCK(utmip_pad_lock); static int utmip_pad_count; @@ -186,6 +242,14 @@ static const u8 udc_delay_table[][4] = { {0x04, 0x66, 0x09, 0xFE}, /* 26 Mhz */ }; +static const u16 uhsic_delay_table[][4] = { + /* ENABLE_DLY, STABLE_CNT, ACTIVE_DLY, XTAL_FREQ_CNT */ + {0x02, 0x2F, 0x0, 0x1CA}, /* 12 MHz */ + {0x02, 0x33, 0x0, 0x1F0}, /* 13 MHz */ + {0x03, 0x4B, 0x0, 0x2DD}, /* 19.2 MHz */ + {0x04, 0x66, 0x0, 0x3E0}, /* 26 Mhz */ +}; + static const u16 udc_debounce_table[] = { 0x7530, /* 12 MHz */ 0x7EF4, /* 13 MHz */ @@ -214,6 +278,14 @@ static struct tegra_utmip_config utmip_default[] = { }, }; +static struct tegra_uhsic_config uhsic_default = { + .sync_start_delay = 9, + .idle_wait_delay = 17, + .term_range_adj = 0, + .elastic_underrun_limit = 16, + .elastic_overrun_limit = 16, +}; + static int utmip_pad_open(struct tegra_usb_phy *phy) { phy->pad_clk = clk_get_sys("utmip-pad", NULL); @@ -471,15 +543,15 @@ static void utmi_phy_power_on(struct tegra_usb_phy *phy) val &= ~USB_SUSP_SET; writel(val, base + USB_SUSP_CTRL); if (phy->mode == TEGRA_USB_PHY_MODE_HOST) { - gpio_status = gpio_request(TEGRA_GPIO_PD0,"VBUS_BUS"); + gpio_status = gpio_request(TEGRA_GPIO_PD0, "VBUS_BUS"); if (gpio_status < 0) { - printk("VBUS_USB1 request GPIO FAILED\n"); + printk(KERN_ERR "VBUS_USB1 request GPIO FAILED\n"); WARN_ON(1); } tegra_gpio_enable(TEGRA_GPIO_PD0); gpio_status = gpio_direction_output(TEGRA_GPIO_PD0, 1); if (gpio_status < 0) { - printk("VBUS_USB1 request GPIO DIRECTION FAILED \n"); + printk(KERN_ERR "VBUS_USB1 request GPIO DIRECTION FAILED\n"); WARN_ON(1); } gpio_set_value(TEGRA_GPIO_PD0, 1); @@ -727,7 +799,7 @@ static void null_phy_power_on(struct tegra_usb_phy *phy) /* enable null phy mode */ val = ULPIS2S_ENA; val |= ULPIS2S_PLLU_MASTER_BLASTER60; - val |= ULPIS2S_SPARE((phy->mode == TEGRA_USB_PHY_MODE_HOST)? 3:1); + val |= ULPIS2S_SPARE((phy->mode == TEGRA_USB_PHY_MODE_HOST) ? 3 : 1); writel(val, base + ULPIS2S_CTRL); /* select ULPI_CORE_CLK_SEL to SHADOW_CLK */ @@ -785,6 +857,86 @@ static void null_phy_power_off(struct tegra_usb_phy *phy) writel(val, base + ULPI_TIMING_CTRL_0); } +static void uhsic_phy_power_on(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + struct tegra_uhsic_config *config = &uhsic_default; + struct tegra_ulpi_config *ulpi_config = phy->config; + + if (ulpi_config->preinit) + ulpi_config->preinit(); + + val = readl(base + UHSIC_PADS_CFG1); + val &= ~(UHSIC_PD_BG | UHSIC_PD_TX | UHSIC_PD_TRK | UHSIC_PD_RX | + UHSIC_PD_ZI | UHSIC_RPD_DATA | UHSIC_RPD_STROBE); + val |= UHSIC_RX_SEL; + writel(val, base + UHSIC_PADS_CFG1); + udelay(2); + + val = readl(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel(val, base + USB_SUSP_CTRL); + udelay(30); + + val = readl(base + USB_SUSP_CTRL); + val |= UHSIC_PHY_ENABLE; + writel(val, base + USB_SUSP_CTRL); + + val = readl(base + UHSIC_HSRX_CFG0); + val |= UHSIC_IDLE_WAIT(config->idle_wait_delay); + val |= UHSIC_ELASTIC_UNDERRUN_LIMIT(config->elastic_underrun_limit); + val |= UHSIC_ELASTIC_OVERRUN_LIMIT(config->elastic_overrun_limit); + writel(val, base + UHSIC_HSRX_CFG0); + + val = readl(base + UHSIC_HSRX_CFG1); + val |= UHSIC_HS_SYNC_START_DLY(config->sync_start_delay); + writel(val, base + UHSIC_HSRX_CFG1); + + val = readl(base + UHSIC_MISC_CFG0); + val |= UHSIC_SUSPEND_EXIT_ON_EDGE; + writel(val, base + UHSIC_MISC_CFG0); + + val = readl(base + UHSIC_MISC_CFG1); + val |= UHSIC_PLLU_STABLE_COUNT(uhsic_delay_table[phy->freq_sel][1]); + writel(val, base + UHSIC_MISC_CFG1); + + val = readl(base + UHSIC_PLL_CFG1); + val |= UHSIC_PLLU_ENABLE_DLY_COUNT(uhsic_delay_table[phy->freq_sel][0]); + val |= UHSIC_XTAL_FREQ_COUNT(uhsic_delay_table[phy->freq_sel][3]); + writel(val, base + UHSIC_PLL_CFG1); + + val = readl(base + USB_SUSP_CTRL); + val &= ~(UHSIC_RESET); + writel(val, base + USB_SUSP_CTRL); + udelay(2); + + val = readl(base + USB_PORTSC1); + val &= ~USB_PORTSC1_PTS(~0); + writel(val, base + USB_PORTSC1); + + val = readl(base + USB_PORTSC1); + val &= ~(USB_PORTSC1_WKOC | USB_PORTSC1_WKDS | USB_PORTSC1_WKCN); + writel(val, base + USB_PORTSC1); + + val = readl(base + UHSIC_PADS_CFG0); + val &= ~(UHSIC_TX_RTUNEN); + /* set Rtune impedance to 40 ohm */ + val |= UHSIC_TX_RTUNE(0); + writel(val, base + UHSIC_PADS_CFG0); + + 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 uhsic_phy_power_off(struct tegra_usb_phy *phy) +{ + + /* Do not do any thing here */ +} + struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode) { @@ -805,7 +957,7 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs, if (!phy->config) { if (instance == 1) { - pr_err("%s: ulpi phy configuration missing", __func__); + pr_err("%s: ulpi/uhsic phy configuration missing", __func__); err = -EINVAL; goto err0; } else { @@ -867,14 +1019,15 @@ int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) { if (phy->instance == 1) { struct tegra_ulpi_config *ulpi_config = phy->config; - if (ulpi_config->inf_type == TEGRA_USB_LINK_ULPI) ulpi_phy_power_on(phy); - else + else if (ulpi_config->inf_type == TEGRA_USB_NULL_ULPI) null_phy_power_on(phy); - } else + else if (ulpi_config->inf_type == TEGRA_USB_UHSIC) + uhsic_phy_power_on(phy); + } else { utmi_phy_power_on(phy); - + } return 0; } @@ -882,14 +1035,15 @@ int tegra_usb_phy_power_off(struct tegra_usb_phy *phy) { if (phy->instance == 1) { struct tegra_ulpi_config *ulpi_config = phy->config; - if (ulpi_config->inf_type == TEGRA_USB_LINK_ULPI) ulpi_phy_power_off(phy); - else + else if (ulpi_config->inf_type == TEGRA_USB_NULL_ULPI) null_phy_power_off(phy); - } else + else if (ulpi_config->inf_type == TEGRA_USB_UHSIC) + uhsic_phy_power_off(phy); + } else { utmi_phy_power_off(phy); - + } return 0; } @@ -952,3 +1106,158 @@ int tegra_usb_phy_close(struct tegra_usb_phy *phy) kfree(phy); return 0; } + +int tegra_usb_phy_bus_connect(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + struct tegra_ulpi_config *config = phy->config; + + if ((phy->instance == 1) && + (config->inf_type == TEGRA_USB_UHSIC)) { + + val = readl(base + UHSIC_MISC_CFG0); + val |= UHSIC_DETECT_SHORT_CONNECT; + writel(val, base + UHSIC_MISC_CFG0); + udelay(1); + + val = readl(base + UHSIC_MISC_CFG0); + val |= UHSIC_FORCE_XCVR_MODE; + writel(val, base + UHSIC_MISC_CFG0); + + val = readl(base + UHSIC_PADS_CFG1); + val &= ~UHSIC_RPD_STROBE; + val |= UHSIC_RPU_STROBE; + writel(val, base + UHSIC_PADS_CFG1); + + if (utmi_wait_register(base + UHSIC_STAT_CFG0, UHSIC_CONNECT_DETECT, UHSIC_CONNECT_DETECT) < 0) { + pr_err("%s: timeout waiting for hsic connect detect\n", __func__); + return -ETIMEDOUT; + } + + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_LS(2), USB_PORTSC1_LS(2)) < 0) { + pr_err("%s: timeout waiting for dplus state\n", __func__); + return -ETIMEDOUT; + } + } + + return 0; +} + +int tegra_usb_phy_bus_reset(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + struct tegra_ulpi_config *config = phy->config; + + if ((phy->instance == 1) && + (config->inf_type == TEGRA_USB_UHSIC)) { + + val = readl(base + USB_PORTSC1); + val |= USB_PORTSC1_PTC(5); + writel(val, base + USB_PORTSC1); + udelay(2); + + val = readl(base + USB_PORTSC1); + val &= ~USB_PORTSC1_PTC(~0); + writel(val, base + USB_PORTSC1); + udelay(2); + + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_LS(0), 0) < 0) { + pr_err("%s: timeout waiting for SE0\n", __func__); + return -ETIMEDOUT; + } + + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_CCS, USB_PORTSC1_CCS) < 0) { + pr_err("%s: timeout waiting for connection status\n", __func__); + return -ETIMEDOUT; + } + + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_PSPD(2), USB_PORTSC1_PSPD(2)) < 0) { + pr_err("%s: timeout waiting hsic high speed configuration\n", __func__); + return -ETIMEDOUT; + } + + val = readl(base + USB_USBCMD); + val &= ~USB_USBCMD_RS; + writel(val, base + USB_USBCMD); + + if (utmi_wait_register(base + USB_USBSTS, USB_USBSTS_HCH, USB_USBSTS_HCH) < 0) { + pr_err("%s: timeout waiting for stopping the controller\n", __func__); + return -ETIMEDOUT; + } + + val = readl(base + UHSIC_PADS_CFG1); + val &= ~UHSIC_RPU_STROBE; + val |= UHSIC_RPD_STROBE; + writel(val, base + UHSIC_PADS_CFG1); + + mdelay(50); + + val = readl(base + UHSIC_PADS_CFG1); + val &= ~UHSIC_RPD_STROBE; + val |= UHSIC_RPU_STROBE; + writel(val, base + UHSIC_PADS_CFG1); + + val = readl(base + USB_USBCMD); + val |= USB_USBCMD_RS; + writel(val, base + USB_USBCMD); + + val = readl(base + UHSIC_PADS_CFG1); + val &= ~UHSIC_RPU_STROBE; + writel(val, base + UHSIC_PADS_CFG1); + + if (utmi_wait_register(base + USB_USBCMD, USB_USBCMD_RS, USB_USBCMD_RS) < 0) { + pr_err("%s: timeout waiting for starting the controller\n", __func__); + return -ETIMEDOUT; + } + } + + return 0; +} + +int tegra_usb_phy_bus_idle(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + struct tegra_ulpi_config *config = phy->config; + + if ((phy->instance == 1) && + (config->inf_type == TEGRA_USB_UHSIC)) { + + val = readl(base + UHSIC_MISC_CFG0); + val |= UHSIC_DETECT_SHORT_CONNECT; + writel(val, base + UHSIC_MISC_CFG0); + udelay(1); + + val = readl(base + UHSIC_MISC_CFG0); + val |= UHSIC_FORCE_XCVR_MODE; + writel(val, base + UHSIC_MISC_CFG0); + + val = readl(base + UHSIC_PADS_CFG1); + val &= ~UHSIC_RPD_STROBE; + val |= UHSIC_RPU_STROBE; + writel(val, base + UHSIC_PADS_CFG1); + } + return 0; +} + +bool tegra_usb_phy_is_device_connected(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + struct tegra_ulpi_config *config = phy->config; + + if ((phy->instance == 1) && + (config->inf_type == TEGRA_USB_UHSIC)) { + if (!((readl(base + UHSIC_STAT_CFG0) & UHSIC_CONNECT_DETECT) == UHSIC_CONNECT_DETECT)) { + pr_err("%s: hsic no device connection\n", __func__); + return false; + } + if (utmi_wait_register(base + USB_PORTSC1, USB_PORTSC1_LS(2), USB_PORTSC1_LS(2)) < 0) { + pr_err("%s: timeout waiting for dplus state\n", __func__); + return false; + } + } + return true; +} + diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 5765496218a1..a6dd7ac7dc42 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -2,7 +2,7 @@ * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs * * Copyright (C) 2010 Google, Inc. - * Copyright (C) 2009 NVIDIA Corporation + * Copyright (C) 2009 - 2011 NVIDIA Corporation * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -57,6 +57,7 @@ struct tegra_ehci_hcd { int port_resuming; struct tegra_ehci_context context; int power_down_on_bus_suspend; + struct delayed_work work; }; static void tegra_ehci_power_up(struct usb_hcd *hcd) @@ -89,11 +90,20 @@ static int tegra_ehci_hub_control( ) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - u32 __iomem *status_reg; - u32 temp; + int ports = HCS_N_PORTS(ehci->hcs_params); + u32 __iomem *status_reg = &ehci->regs->port_status[ + (wIndex & 0xff) - 1]; + u32 temp, status; unsigned long flags; int retval = 0; + unsigned selector; + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + bool hsic = false; + + if (tegra->phy->instance == 1) { + struct tegra_ulpi_config *config = tegra->phy->config; + hsic = (config->inf_type == TEGRA_USB_UHSIC); + } status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; @@ -108,18 +118,14 @@ static int tegra_ehci_hub_control( temp = ehci_readl(ehci, status_reg); ehci_writel(ehci, (temp & ~PORT_RWC_BITS) & ~PORT_PE, status_reg); goto done; - } - - else if (typeReq == GetPortStatus) { + } else if (typeReq == GetPortStatus) { temp = ehci_readl(ehci, status_reg); if (tegra->port_resuming && !(temp & PORT_SUSPEND)) { /* resume completed */ tegra->port_resuming = 0; tegra_usb_phy_postresume(tegra->phy); } - } - - else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { + } else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { temp = ehci_readl(ehci, status_reg); if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { retval = -EPIPE; @@ -127,9 +133,9 @@ static int tegra_ehci_hub_control( } /* After above check the port must be connected. - * Set appropriate bit thus could put phy into low power - * mode if we have hostpc feature - */ + * Set appropriate bit thus could put phy into low power + * mode if we have hostpc feature + */ temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); @@ -141,12 +147,13 @@ static int tegra_ehci_hub_control( } /* - * Tegra host controller will time the resume operation to clear the bit - * when the port control state switches to HS or FS Idle. This behavior - * is different from EHCI where the host controller driver is required - * to set this bit to a zero after the resume duration is timed in the - * driver. - */ + * Tegra host controller will time the resume operation to clear the bit + * when the port control state switches to HS or FS Idle. This behavior + * is different from EHCI where the host controller driver is required + * to set this bit to a zero after the resume duration is timed in the + * driver. + */ + else if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { temp = ehci_readl(ehci, status_reg); if ((temp & PORT_RESET) || !(temp & PORT_PE)) { @@ -183,6 +190,65 @@ static int tegra_ehci_hub_control( goto done; } + /* Handle port reset here */ + if ((hsic) && (typeReq == SetPortFeature) && + ((wValue == USB_PORT_FEAT_RESET) || (wValue == USB_PORT_FEAT_POWER))) { + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) { + retval = -EPIPE; + goto done; + } + wIndex--; + status = 0; + temp = ehci_readl(ehci, status_reg); + if (temp & PORT_OWNER) + goto done; + temp &= ~PORT_RWC_BITS; + + switch (wValue) { + case USB_PORT_FEAT_RESET: + { + if (temp & PORT_RESUME) { + retval = -EPIPE; + goto done; + } + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && !ehci_is_TDI(ehci) && PORT_USB11 (temp)) { + ehci_dbg (ehci, "port %d low speed --> companion\n", wIndex + 1); + temp |= PORT_OWNER; + ehci_writel(ehci, temp, status_reg); + } else { + ehci_vdbg(ehci, "port %d reset\n", wIndex + 1); + temp &= ~PORT_PE; + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + ehci->reset_done[wIndex] = jiffies + msecs_to_jiffies(50); + ehci_writel(ehci, temp, status_reg); + if (hsic && (wIndex == 0)) + tegra_usb_phy_bus_reset(tegra->phy); + } + + break; + } + case USB_PORT_FEAT_POWER: + { + if (HCS_PPC(ehci->hcs_params)) + ehci_writel(ehci, temp | PORT_POWER, status_reg); + if (hsic && (wIndex == 0)) + tegra_usb_phy_bus_connect(tegra->phy); + break; + } + } + goto done; + } + spin_unlock_irqrestore(&ehci->lock, flags); /* Handle the hub control events here */ @@ -197,16 +263,16 @@ static void tegra_ehci_restart(struct usb_hcd *hcd) unsigned int temp; struct ehci_hcd *ehci = hcd_to_ehci(hcd); - /* reset the ehci controller */ - ehci->controller_resets_phy = 0; - ehci_reset(ehci); - ehci->controller_resets_phy = 1; - /* Set to Host mode by setting bit 0-1 of USB device mode register */ temp = readl(hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET); writel((temp | TEGRA_USB_USBMODE_HOST), (hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET)); + /* reset the ehci controller */ + ehci->controller_resets_phy = 0; + ehci_reset(ehci); + ehci->controller_resets_phy = 1; + /* setup the frame list and Async q heads */ ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); @@ -215,7 +281,14 @@ static void tegra_ehci_restart(struct usb_hcd *hcd) ehci->command |= CMD_RUN; ehci_writel(ehci, ehci->command, &ehci->regs->command); + /* Enable the root Port Power */ + if (HCS_PPC(ehci->hcs_params)) { + temp = ehci_readl(ehci, &ehci->regs->port_status[0]); + ehci_writel(ehci, temp | PORT_POWER, &ehci->regs->port_status[0]); + } + down_write(&ehci_cf_port_reset_rwsem); + hcd->state = HC_STATE_RUNNING; ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); /* flush posted writes */ ehci_readl(ehci, &ehci->regs->command); @@ -228,12 +301,19 @@ static int tegra_usb_suspend(struct usb_hcd *hcd) struct ehci_regs __iomem *hw = tegra->ehci->regs; struct tegra_ehci_context *context = &tegra->context; unsigned long flags; + int hsic = 0; + struct tegra_ulpi_config *config; + + if (tegra->phy->instance == 1) { + config = tegra->phy->config; + hsic = (config->inf_type == TEGRA_USB_UHSIC); + } spin_lock_irqsave(&tegra->ehci->lock, flags); context->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3; - if (context->port_speed > TEGRA_USB_PHY_PORT_HIGH) { + if ((context->port_speed > TEGRA_USB_PHY_PORT_HIGH) || hsic) { /* If no device connection or invalid speeds, * don't save the context */ context->valid = false; @@ -259,12 +339,19 @@ static int tegra_usb_resume(struct usb_hcd *hcd) { struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); struct tegra_ehci_context *context = &tegra->context; + struct usb_device *udev = hcd->self.root_hub; struct ehci_regs __iomem *hw = tegra->ehci->regs; unsigned long val; int lp0_resume = 0; + int hsic = 0; + struct tegra_ulpi_config *config; - set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + if (tegra->phy->instance == 1) { + config = tegra->phy->config; + hsic = (config->inf_type == TEGRA_USB_UHSIC); + } tegra_ehci_power_up(ehci_to_hcd(tegra->ehci)); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); if (!context->valid) { /* Wait for the phy to detect new devices @@ -355,8 +442,19 @@ static int tegra_usb_resume(struct usb_hcd *hcd) restart: if (context->valid) tegra_ehci_phy_restore_end(tegra->phy); + if (hsic) { + val = readl(&hw->port_status[0]); + if (!((val & PORT_POWER) && (val & PORT_PE))) { + tegra_ehci_restart(hcd); + usb_set_device_state(udev, USB_STATE_CONFIGURED); + } + tegra_usb_phy_bus_idle(tegra->phy); + if (!tegra_usb_phy_is_device_connected(tegra->phy)) + schedule_delayed_work(&tegra->work, 50); + } else { + tegra_ehci_restart(hcd); + } - tegra_ehci_restart(hcd); return 0; } @@ -456,7 +554,6 @@ static int tegra_ehci_bus_suspend(struct usb_hcd *hcd) tegra_usb_suspend(hcd); tegra->bus_suspended = 1; } - return error_status; } @@ -476,7 +573,7 @@ static int tegra_ehci_bus_resume(struct usb_hcd *hcd) #endif struct temp_buffer { - void *kmalloc_ptr; + void *kmalloc_ptr; void *old_xfer_buffer; u8 data[0]; }; @@ -492,14 +589,13 @@ static void free_temp_buffer(struct urb *urb) dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; temp = container_of(urb->transfer_buffer, struct temp_buffer, - data); + data); if (dir == DMA_FROM_DEVICE) memcpy(temp->old_xfer_buffer, temp->data, - urb->transfer_buffer_length); + urb->transfer_buffer_length); urb->transfer_buffer = temp->old_xfer_buffer; kfree(temp->kmalloc_ptr); - urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; } @@ -507,13 +603,12 @@ static int alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) { enum dma_data_direction dir; struct temp_buffer *temp, *kmalloc_ptr; - size_t kmalloc_size; - void *data; + size_t kmalloc_size; if (urb->num_sgs || urb->sg || - urb->transfer_buffer_length == 0 || - !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) - return 0; + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) + return 0; dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; @@ -532,17 +627,17 @@ static int alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) temp->old_xfer_buffer = urb->transfer_buffer; if (dir == DMA_TO_DEVICE) memcpy(temp->data, urb->transfer_buffer, - urb->transfer_buffer_length); + urb->transfer_buffer_length); urb->transfer_buffer = temp->data; - BUILD_BUG_ON(!(URB_ALIGNED_TEMP_BUFFER & URB_DRIVER_PRIVATE)); + BUILD_BUG_ON(!(URB_ALIGNED_TEMP_BUFFER & URB_DRIVER_PRIVATE)); urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; return 0; } static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) + gfp_t mem_flags) { int ret; @@ -553,7 +648,6 @@ static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); if (ret) free_temp_buffer(urb); - return ret; } @@ -563,6 +657,76 @@ static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) free_temp_buffer(urb); } +static void tegra_hsic_connection_work(struct work_struct *work) +{ + struct tegra_ehci_hcd *tegra = + container_of(work, struct tegra_ehci_hcd, work.work); + if (tegra_usb_phy_is_device_connected(tegra->phy)) { + cancel_delayed_work(&tegra->work); + return; + } + schedule_delayed_work(&tegra->work, jiffies + msecs_to_jiffies(50)); + return; +} + + + +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE +struct usb_hcd *ehci_handle; /* For ehci on/off sysfs */ +int ehci_tegra_irq; + +static ssize_t show_ehci_power(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "EHCI Power %s\n", (ehci_handle) ? "on" : "off"); +} + +static ssize_t store_ehci_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int power_on; + int retval; + struct tegra_ehci_hcd *tegra = dev_get_drvdata(dev); + struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); + + if (sscanf(buf, "%d", &power_on) != 1) + return -EINVAL; + + if (power_on == 0 && ehci_handle != NULL) { + usb_remove_hcd(hcd); + tegra_ehci_power_down(hcd); + ehci_handle = NULL; + } else if (power_on == 1) { + if (ehci_handle) + usb_remove_hcd(hcd); + tegra_ehci_power_up(hcd); + retval = usb_add_hcd(hcd, ehci_tegra_irq, + IRQF_DISABLED | IRQF_SHARED); + if (retval < 0) + printk(KERN_ERR "power_on error\n"); + ehci_handle = hcd; + } + + return count; +} + +static DEVICE_ATTR(ehci_power, 0666, show_ehci_power, store_ehci_power); + +static inline int create_ehci_sys_file(struct ehci_hcd *ehci) +{ + return device_create_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_ehci_power); +} + +static inline void remove_ehci_sys_file(struct ehci_hcd *ehci) +{ + device_remove_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_ehci_power); +} +#endif + static const struct hc_driver tegra_ehci_hc_driver = { .description = hcd_name, .product_desc = "Tegra EHCI Host Controller", @@ -664,6 +828,8 @@ static int tegra_ehci_probe(struct platform_device *pdev) config = pdata->phy_config; + INIT_DELAYED_WORK(&tegra->work, tegra_hsic_connection_work); + tegra->phy = tegra_usb_phy_open(instance, hcd->regs, config, TEGRA_USB_PHY_MODE_HOST); if (IS_ERR(tegra->phy)) { @@ -694,6 +860,12 @@ static int tegra_ehci_probe(struct platform_device *pdev) ehci = hcd_to_ehci(hcd); tegra->ehci = ehci; +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE + if (instance == 1) { + ehci_tegra_irq = irq; + create_ehci_sys_file(ehci); + } +#endif #ifdef CONFIG_USB_OTG_UTILS if (pdata->operating_mode == TEGRA_USB_OTG) { tegra->transceiver = otg_get_transceiver(); @@ -708,6 +880,10 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto fail; } +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE + if (instance == 1) + ehci_handle = hcd; +#endif return err; fail: @@ -740,7 +916,7 @@ static int tegra_ehci_resume(struct platform_device *pdev) struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - if (tegra->bus_suspended) + if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) return 0; return tegra_usb_resume(hcd); @@ -751,7 +927,7 @@ static int tegra_ehci_suspend(struct platform_device *pdev, pm_message_t state) struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - if (tegra->bus_suspended) + if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) return 0; if (time_before(jiffies, tegra->ehci->next_statechange)) @@ -776,11 +952,19 @@ static int tegra_ehci_remove(struct platform_device *pdev) } #endif +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE + if (tegra->phy->instance == 1) { + remove_ehci_sys_file(hcd_to_ehci(hcd)); + ehci_handle = NULL; + } +#endif + /* Turn Off Interrupts */ ehci_writel(tegra->ehci, 0, &tegra->ehci->regs->intr_enable); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); usb_remove_hcd(hcd); usb_put_hcd(hcd); + cancel_delayed_work(&tegra->work); tegra_usb_phy_power_off(tegra->phy); tegra_usb_phy_close(tegra->phy); iounmap(hcd->regs); |