summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeshendra Gadagottu <sgadagottu@nvidia.com>2011-01-11 11:41:09 -0800
committerVarun Colbert <vcolbert@nvidia.com>2011-02-10 15:41:11 -0800
commit8df80194dc456a45fc8245de57bd9b46d2cf16a8 (patch)
tree78a310256698305e2ac7dae9df58b39fd5cacec7
parent57a3407736092650cbed228fdf9264e140378762 (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.h24
-rw-r--r--arch/arm/mach-tegra/usb_phy.c341
-rw-r--r--drivers/usb/host/ehci-tegra.c270
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);