* [PATCH 0/4] Tegra EHCI driver
@ 2011-02-09 5:22 Benoit Goby
[not found] ` <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
0 siblings, 1 reply; 13+ messages in thread
From: Benoit Goby @ 2011-02-09 5:22 UTC (permalink / raw)
To: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell
Cc: Benoit Goby, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA
This patch series adds support for EHCI compliant USB host
controllers found in Tegra SoCs. The EHCI driver depends on
the Tegra PHY configuration interface that is used by both
the ehci driver and the gadget driver. Tested on 2.6.38rc4.
Benoit Goby (2):
[ARM] tegra: Add support for Tegra USB PHYs
usb: host: Add EHCI driver for NVIDIA Tegra SoCs
Gary King (1):
usb: host: ehci-hcd: Add controller_resets_phy quirk
Robert Morell (1):
USB: ehci: tegra: Align DMA transfers to 32 bytes
arch/arm/mach-tegra/Makefile | 1 +
arch/arm/mach-tegra/include/mach/usb_phy.h | 83 +++
arch/arm/mach-tegra/usb_phy.c | 794 ++++++++++++++++++++++++++++
drivers/usb/Kconfig | 1 +
drivers/usb/host/Kconfig | 8 +
drivers/usb/host/ehci-hcd.c | 8 +-
drivers/usb/host/ehci-tegra.c | 763 ++++++++++++++++++++++++++
drivers/usb/host/ehci.h | 1 +
include/linux/tegra_usb.h | 35 ++
9 files changed, 1693 insertions(+), 1 deletions(-)
create mode 100644 arch/arm/mach-tegra/include/mach/usb_phy.h
create mode 100644 arch/arm/mach-tegra/usb_phy.c
create mode 100644 drivers/usb/host/ehci-tegra.c
create mode 100644 include/linux/tegra_usb.h
--
1.7.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs
[not found] ` <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
@ 2011-02-09 5:22 ` Benoit Goby
[not found] ` <1297228927-23497-2-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 5:22 ` [PATCH 2/4] usb: host: ehci-hcd: Add controller_resets_phy quirk Benoit Goby
` (2 subsequent siblings)
3 siblings, 1 reply; 13+ messages in thread
From: Benoit Goby @ 2011-02-09 5:22 UTC (permalink / raw)
To: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell
Cc: Benoit Goby, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA
Interface used by Tegra's gadget driver and ehci driver
to power on and configure the USB PHYs.
Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
---
arch/arm/mach-tegra/Makefile | 1 +
arch/arm/mach-tegra/include/mach/usb_phy.h | 83 +++
arch/arm/mach-tegra/usb_phy.c | 794 ++++++++++++++++++++++++++++
3 files changed, 878 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-tegra/include/mach/usb_phy.h
create mode 100644 arch/arm/mach-tegra/usb_phy.c
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index cdbc68e..38b66a8 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
obj-$(CONFIG_TEGRA_SYSTEM_DMA) += dma.o
obj-$(CONFIG_CPU_FREQ) += cpu-tegra.o
obj-$(CONFIG_TEGRA_PCI) += pcie.o
+obj-$(CONFIG_USB_SUPPORT) += usb_phy.o
obj-${CONFIG_MACH_HARMONY} += board-harmony.o
obj-${CONFIG_MACH_HARMONY} += board-harmony-pinmux.o
diff --git a/arch/arm/mach-tegra/include/mach/usb_phy.h b/arch/arm/mach-tegra/include/mach/usb_phy.h
new file mode 100644
index 0000000..bf97667
--- /dev/null
+++ b/arch/arm/mach-tegra/include/mach/usb_phy.h
@@ -0,0 +1,83 @@
+/*
+ * arch/arm/mach-tegra/include/mach/usb_phy.h
+ *
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MACH_USB_PHY_H
+#define __MACH_USB_PHY_H
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+struct tegra_utmip_config {
+ u8 hssync_start_delay;
+ u8 elastic_limit;
+ u8 idle_wait_delay;
+ u8 term_range_adj;
+ u8 xcvr_setup;
+ u8 xcvr_lsfslew;
+ 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,
+ TEGRA_USB_PHY_PORT_SPEED_HIGH,
+};
+
+enum tegra_usb_phy_mode {
+ TEGRA_USB_PHY_MODE_DEVICE,
+ TEGRA_USB_PHY_MODE_HOST,
+};
+
+struct tegra_usb_phy {
+ int instance;
+ 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;
+ void *config;
+};
+
+struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
+ void *config, enum tegra_usb_phy_mode phy_mode);
+
+int tegra_usb_phy_power_on(struct tegra_usb_phy *phy);
+
+int tegra_usb_phy_clk_disable(struct tegra_usb_phy *phy);
+
+int tegra_usb_phy_clk_enable(struct tegra_usb_phy *phy);
+
+int tegra_usb_phy_power_off(struct tegra_usb_phy *phy);
+
+int tegra_usb_phy_preresume(struct tegra_usb_phy *phy);
+
+int tegra_usb_phy_postresume(struct tegra_usb_phy *phy);
+
+int tegra_ehci_phy_restore_start(struct tegra_usb_phy *phy,
+ enum tegra_usb_phy_port_speed port_speed);
+
+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 */
diff --git a/arch/arm/mach-tegra/usb_phy.c b/arch/arm/mach-tegra/usb_phy.c
new file mode 100644
index 0000000..7242dda
--- /dev/null
+++ b/arch/arm/mach-tegra/usb_phy.c
@@ -0,0 +1,794 @@
+/*
+ * arch/arm/mach-tegra/usb_phy.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * Author:
+ * Erik Gilling <konkers-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
+ * Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/resource.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#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)
+#define USB_PORTSC1_PE (1 << 2)
+#define USB_PORTSC1_CCS (1 << 0)
+
+#define USB_SUSP_CTRL 0x400
+#define USB_WAKE_ON_CNNT_EN_DEV (1 << 3)
+#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 ULPI_PHY_ENABLE (1 << 13)
+#define USB_SUSP_SET (1 << 14)
+#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16)
+
+#define USB1_LEGACY_CTRL 0x410
+#define USB1_NO_LEGACY_MODE (1 << 0)
+#define USB1_VBUS_SENSE_CTL_MASK (3 << 1)
+#define USB1_VBUS_SENSE_CTL_VBUS_WAKEUP (0 << 1)
+#define USB1_VBUS_SENSE_CTL_AB_SESS_VLD_OR_VBUS_WAKEUP \
+ (1 << 1)
+#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)
+
+#define UTMIP_XCVR_CFG0 0x808
+#define UTMIP_XCVR_SETUP(x) (((x) & 0xf) << 0)
+#define UTMIP_XCVR_LSRSLEW(x) (((x) & 0x3) << 8)
+#define UTMIP_XCVR_LSFSLEW(x) (((x) & 0x3) << 10)
+#define UTMIP_FORCE_PD_POWERDOWN (1 << 14)
+#define UTMIP_FORCE_PD2_POWERDOWN (1 << 16)
+#define UTMIP_FORCE_PDZI_POWERDOWN (1 << 18)
+#define UTMIP_XCVR_HSSLEW_MSB(x) (((x) & 0x7f) << 25)
+
+#define UTMIP_BIAS_CFG0 0x80c
+#define UTMIP_OTGPD (1 << 11)
+#define UTMIP_BIASPD (1 << 10)
+
+#define UTMIP_HSRX_CFG0 0x810
+#define UTMIP_ELASTIC_LIMIT(x) (((x) & 0x1f) << 10)
+#define UTMIP_IDLE_WAIT(x) (((x) & 0x1f) << 15)
+
+#define UTMIP_HSRX_CFG1 0x814
+#define UTMIP_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1)
+
+#define UTMIP_TX_CFG0 0x820
+#define UTMIP_FS_PREABMLE_J (1 << 19)
+#define UTMIP_HS_DISCON_DISABLE (1 << 8)
+
+#define UTMIP_MISC_CFG0 0x824
+#define UTMIP_DPDM_OBSERVE (1 << 26)
+#define UTMIP_DPDM_OBSERVE_SEL(x) (((x) & 0xf) << 27)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_J UTMIP_DPDM_OBSERVE_SEL(0xf)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_K UTMIP_DPDM_OBSERVE_SEL(0xe)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_SE1 UTMIP_DPDM_OBSERVE_SEL(0xd)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_SE0 UTMIP_DPDM_OBSERVE_SEL(0xc)
+#define UTMIP_SUSPEND_EXIT_ON_EDGE (1 << 22)
+
+#define UTMIP_MISC_CFG1 0x828
+#define UTMIP_PLL_ACTIVE_DLY_COUNT(x) (((x) & 0x1f) << 18)
+#define UTMIP_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 6)
+
+#define UTMIP_DEBOUNCE_CFG0 0x82c
+#define UTMIP_BIAS_DEBOUNCE_A(x) (((x) & 0xffff) << 0)
+
+#define UTMIP_BAT_CHRG_CFG0 0x830
+#define UTMIP_PD_CHRG (1 << 0)
+
+#define UTMIP_SPARE_CFG0 0x834
+#define FUSE_SETUP_SEL (1 << 3)
+
+#define UTMIP_XCVR_CFG1 0x838
+#define UTMIP_FORCE_PDDISC_POWERDOWN (1 << 0)
+#define UTMIP_FORCE_PDCHRP_POWERDOWN (1 << 2)
+#define UTMIP_FORCE_PDDR_POWERDOWN (1 << 4)
+#define UTMIP_XCVR_TERM_RANGE_ADJ(x) (((x) & 0xf) << 18)
+
+#define UTMIP_BIAS_CFG1 0x83c
+#define UTMIP_BIAS_PDTRK_COUNT(x) (((x) & 0x1f) << 3)
+
+static DEFINE_SPINLOCK(utmip_pad_lock);
+static int utmip_pad_count;
+
+static const int udc_freq_table[] = {
+ 12000000,
+ 13000000,
+ 19200000,
+ 26000000,
+};
+
+static const u8 udc_delay_table[][4] = {
+ /* ENABLE_DLY, STABLE_CNT, ACTIVE_DLY, XTAL_FREQ_CNT */
+ {0x02, 0x2F, 0x04, 0x76}, /* 12 MHz */
+ {0x02, 0x33, 0x05, 0x7F}, /* 13 MHz */
+ {0x03, 0x4B, 0x06, 0xBB}, /* 19.2 MHz */
+ {0x04, 0x66, 0x09, 0xFE}, /* 26 Mhz */
+};
+
+static const u16 udc_debounce_table[] = {
+ 0x7530, /* 12 MHz */
+ 0x7EF4, /* 13 MHz */
+ 0xBB80, /* 19.2 MHz */
+ 0xFDE8, /* 26 MHz */
+};
+
+static struct tegra_utmip_config utmip_default[] = {
+ [0] = {
+ .hssync_start_delay = 9,
+ .idle_wait_delay = 17,
+ .elastic_limit = 16,
+ .term_range_adj = 6,
+ .xcvr_setup = 9,
+ .xcvr_lsfslew = 1,
+ .xcvr_lsrslew = 1,
+ },
+ [2] = {
+ .hssync_start_delay = 9,
+ .idle_wait_delay = 17,
+ .elastic_limit = 16,
+ .term_range_adj = 6,
+ .xcvr_setup = 9,
+ .xcvr_lsfslew = 2,
+ .xcvr_lsrslew = 2,
+ },
+};
+
+static int utmip_pad_open(struct tegra_usb_phy *phy)
+{
+ phy->pad_clk = clk_get_sys("utmip-pad", NULL);
+ if (IS_ERR(phy->pad_clk)) {
+ pr_err("%s: can't get utmip pad clock\n", __func__);
+ return -1;
+ }
+
+ if (phy->instance == 0) {
+ phy->pad_regs = phy->regs;
+ } else {
+ phy->pad_regs = ioremap(TEGRA_USB_BASE, TEGRA_USB_SIZE);
+ if (!phy->pad_regs) {
+ pr_err("%s: can't remap usb registers\n", __func__);
+ clk_put(phy->pad_clk);
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static void utmip_pad_close(struct tegra_usb_phy *phy)
+{
+ if (phy->instance != 0)
+ iounmap(phy->pad_regs);
+ clk_put(phy->pad_clk);
+}
+
+static void utmip_pad_power_on(struct tegra_usb_phy *phy)
+{
+ unsigned long val, flags;
+ void __iomem *base = phy->pad_regs;
+
+ clk_enable(phy->pad_clk);
+
+ spin_lock_irqsave(&utmip_pad_lock, flags);
+
+ if (utmip_pad_count++ == 0) {
+ val = readl(base + UTMIP_BIAS_CFG0);
+ val &= ~(UTMIP_OTGPD | UTMIP_BIASPD);
+ writel(val, base + UTMIP_BIAS_CFG0);
+ }
+
+ spin_unlock_irqrestore(&utmip_pad_lock, flags);
+
+ clk_disable(phy->pad_clk);
+}
+
+static int utmip_pad_power_off(struct tegra_usb_phy *phy)
+{
+ unsigned long val, flags;
+ void __iomem *base = phy->pad_regs;
+
+ if (!utmip_pad_count) {
+ pr_err("%s: utmip pad already powered off\n", __func__);
+ return -1;
+ }
+
+ clk_enable(phy->pad_clk);
+
+ spin_lock_irqsave(&utmip_pad_lock, flags);
+
+ if (--utmip_pad_count == 0) {
+ val = readl(base + UTMIP_BIAS_CFG0);
+ val |= UTMIP_OTGPD | UTMIP_BIASPD;
+ writel(val, base + UTMIP_BIAS_CFG0);
+ }
+
+ spin_unlock_irqrestore(&utmip_pad_lock, flags);
+
+ clk_disable(phy->pad_clk);
+
+ return 0;
+}
+
+static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result)
+{
+ unsigned long timeout = 2000;
+ do {
+ if ((readl(reg) & mask) == result)
+ return 0;
+ udelay(1);
+ timeout--;
+ } while (timeout);
+ return -1;
+}
+
+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;
+
+ val = readl(base + USB_SUSP_CTRL);
+ val |= UTMIP_RESET;
+ writel(val, base + USB_SUSP_CTRL);
+
+ if (phy->instance == 0) {
+ val = readl(base + USB1_LEGACY_CTRL);
+ val |= USB1_NO_LEGACY_MODE;
+ writel(val, base + USB1_LEGACY_CTRL);
+ }
+
+ val = readl(base + UTMIP_TX_CFG0);
+ val &= ~UTMIP_FS_PREABMLE_J;
+ writel(val, base + UTMIP_TX_CFG0);
+
+ val = readl(base + UTMIP_HSRX_CFG0);
+ val &= ~(UTMIP_IDLE_WAIT(~0) | UTMIP_ELASTIC_LIMIT(~0));
+ val |= UTMIP_IDLE_WAIT(config->idle_wait_delay);
+ val |= UTMIP_ELASTIC_LIMIT(config->elastic_limit);
+ writel(val, base + UTMIP_HSRX_CFG0);
+
+ val = readl(base + UTMIP_HSRX_CFG1);
+ val &= ~UTMIP_HS_SYNC_START_DLY(~0);
+ val |= UTMIP_HS_SYNC_START_DLY(config->hssync_start_delay);
+ writel(val, base + UTMIP_HSRX_CFG1);
+
+ val = readl(base + UTMIP_DEBOUNCE_CFG0);
+ val &= ~UTMIP_BIAS_DEBOUNCE_A(~0);
+ val |= UTMIP_BIAS_DEBOUNCE_A(udc_debounce_table[phy->freq_sel]);
+ writel(val, base + UTMIP_DEBOUNCE_CFG0);
+
+ val = readl(base + UTMIP_MISC_CFG0);
+ val &= ~UTMIP_SUSPEND_EXIT_ON_EDGE;
+ writel(val, base + UTMIP_MISC_CFG0);
+
+ val = readl(base + UTMIP_MISC_CFG1);
+ val &= ~(UTMIP_PLL_ACTIVE_DLY_COUNT(~0) | UTMIP_PLLU_STABLE_COUNT(~0));
+ val |= UTMIP_PLL_ACTIVE_DLY_COUNT(udc_delay_table[phy->freq_sel][2]) |
+ UTMIP_PLLU_STABLE_COUNT(udc_delay_table[phy->freq_sel][1]);
+ writel(val, base + UTMIP_MISC_CFG1);
+
+ val = readl(base + UTMIP_PLL_CFG1);
+ val &= ~(UTMIP_XTAL_FREQ_COUNT(~0) | UTMIP_PLLU_ENABLE_DLY_COUNT(~0));
+ val |= UTMIP_XTAL_FREQ_COUNT(udc_delay_table[phy->freq_sel][3]) |
+ UTMIP_PLLU_ENABLE_DLY_COUNT(udc_delay_table[phy->freq_sel][0]);
+ writel(val, base + UTMIP_PLL_CFG1);
+
+ 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);
+ }
+
+ utmip_pad_power_on(phy);
+
+ val = readl(base + UTMIP_XCVR_CFG0);
+ val &= ~(UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
+ UTMIP_FORCE_PDZI_POWERDOWN | UTMIP_XCVR_SETUP(~0) |
+ UTMIP_XCVR_LSFSLEW(~0) | UTMIP_XCVR_LSRSLEW(~0) |
+ UTMIP_XCVR_HSSLEW_MSB(~0));
+ val |= UTMIP_XCVR_SETUP(config->xcvr_setup);
+ val |= UTMIP_XCVR_LSFSLEW(config->xcvr_lsfslew);
+ val |= UTMIP_XCVR_LSRSLEW(config->xcvr_lsrslew);
+ writel(val, base + UTMIP_XCVR_CFG0);
+
+ val = readl(base + UTMIP_XCVR_CFG1);
+ val &= ~(UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN |
+ UTMIP_FORCE_PDDR_POWERDOWN | UTMIP_XCVR_TERM_RANGE_ADJ(~0));
+ val |= UTMIP_XCVR_TERM_RANGE_ADJ(config->term_range_adj);
+ writel(val, base + UTMIP_XCVR_CFG1);
+
+ val = readl(base + UTMIP_BAT_CHRG_CFG0);
+ val &= ~UTMIP_PD_CHRG;
+ writel(val, base + UTMIP_BAT_CHRG_CFG0);
+
+ val = readl(base + UTMIP_BIAS_CFG1);
+ val &= ~UTMIP_BIAS_PDTRK_COUNT(~0);
+ val |= UTMIP_BIAS_PDTRK_COUNT(0x5);
+ writel(val, base + UTMIP_BIAS_CFG1);
+
+ if (phy->instance == 0) {
+ val = readl(base + UTMIP_SPARE_CFG0);
+ if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE)
+ val &= ~FUSE_SETUP_SEL;
+ else
+ val |= FUSE_SETUP_SEL;
+ writel(val, base + UTMIP_SPARE_CFG0);
+ }
+
+ if (phy->instance == 2) {
+ val = readl(base + USB_SUSP_CTRL);
+ val |= UTMIP_PHY_ENABLE;
+ writel(val, base + USB_SUSP_CTRL);
+ }
+
+ val = readl(base + USB_SUSP_CTRL);
+ val &= ~UTMIP_RESET;
+ writel(val, base + USB_SUSP_CTRL);
+
+ if (phy->instance == 0) {
+ val = readl(base + USB1_LEGACY_CTRL);
+ val &= ~USB1_VBUS_SENSE_CTL_MASK;
+ val |= USB1_VBUS_SENSE_CTL_A_SESS_VLD;
+ writel(val, base + USB1_LEGACY_CTRL);
+
+ val = readl(base + USB_SUSP_CTRL);
+ val &= ~USB_SUSP_SET;
+ writel(val, base + USB_SUSP_CTRL);
+ }
+
+ utmi_phy_clk_enable(phy);
+
+ if (phy->instance == 2) {
+ val = readl(base + USB_PORTSC1);
+ val &= ~USB_PORTSC1_PTS(~0);
+ writel(val, base + USB_PORTSC1);
+ }
+}
+
+static void utmi_phy_power_off(struct tegra_usb_phy *phy)
+{
+ unsigned long val;
+ void __iomem *base = phy->regs;
+
+ utmi_phy_clk_disable(phy);
+
+ if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) {
+ val = readl(base + USB_SUSP_CTRL);
+ val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0);
+ val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5);
+ writel(val, base + USB_SUSP_CTRL);
+ }
+
+ val = readl(base + USB_SUSP_CTRL);
+ val |= UTMIP_RESET;
+ writel(val, base + USB_SUSP_CTRL);
+
+ val = readl(base + UTMIP_BAT_CHRG_CFG0);
+ val |= UTMIP_PD_CHRG;
+ writel(val, base + UTMIP_BAT_CHRG_CFG0);
+
+ val = readl(base + UTMIP_XCVR_CFG0);
+ val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
+ UTMIP_FORCE_PDZI_POWERDOWN;
+ writel(val, base + UTMIP_XCVR_CFG0);
+
+ val = readl(base + UTMIP_XCVR_CFG1);
+ val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN |
+ UTMIP_FORCE_PDDR_POWERDOWN;
+ writel(val, base + UTMIP_XCVR_CFG1);
+
+ utmip_pad_power_off(phy);
+}
+
+static void utmi_phy_preresume(struct tegra_usb_phy *phy)
+{
+ unsigned long val;
+ void __iomem *base = phy->regs;
+
+ val = readl(base + UTMIP_TX_CFG0);
+ val |= UTMIP_HS_DISCON_DISABLE;
+ writel(val, base + UTMIP_TX_CFG0);
+}
+
+static void utmi_phy_postresume(struct tegra_usb_phy *phy)
+{
+ unsigned long val;
+ void __iomem *base = phy->regs;
+
+ val = readl(base + UTMIP_TX_CFG0);
+ val &= ~UTMIP_HS_DISCON_DISABLE;
+ writel(val, base + UTMIP_TX_CFG0);
+}
+
+static void utmi_phy_restore_start(struct tegra_usb_phy *phy,
+ enum tegra_usb_phy_port_speed port_speed)
+{
+ unsigned long val;
+ void __iomem *base = phy->regs;
+
+ val = readl(base + UTMIP_MISC_CFG0);
+ val &= ~UTMIP_DPDM_OBSERVE_SEL(~0);
+ if (port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW)
+ val |= UTMIP_DPDM_OBSERVE_SEL_FS_K;
+ else
+ val |= UTMIP_DPDM_OBSERVE_SEL_FS_J;
+ writel(val, base + UTMIP_MISC_CFG0);
+ udelay(1);
+
+ val = readl(base + UTMIP_MISC_CFG0);
+ val |= UTMIP_DPDM_OBSERVE;
+ writel(val, base + UTMIP_MISC_CFG0);
+ udelay(10);
+}
+
+static void utmi_phy_restore_end(struct tegra_usb_phy *phy)
+{
+ unsigned long val;
+ void __iomem *base = phy->regs;
+
+ val = readl(base + UTMIP_MISC_CFG0);
+ val &= ~UTMIP_DPDM_OBSERVE;
+ writel(val, base + UTMIP_MISC_CFG0);
+ udelay(10);
+}
+
+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;
+ struct tegra_ulpi_config *config = phy->config;
+
+ /* 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);
+
+ gpio_direction_output(config->reset_gpio, 0);
+ clk_disable(phy->clk);
+}
+
+struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
+ 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;
+
+ phy = kmalloc(sizeof(struct tegra_usb_phy), GFP_KERNEL);
+ if (!phy)
+ return ERR_PTR(-ENOMEM);
+
+ phy->instance = instance;
+ phy->regs = regs;
+ phy->config = config;
+ phy->mode = phy_mode;
+
+ if (!phy->config) {
+ if (instance == 1) {
+ pr_err("%s: ulpi phy configuration missing", __func__);
+ err = -EINVAL;
+ goto err0;
+ } else {
+ phy->config = &utmip_default[instance];
+ }
+ }
+
+ phy->pll_u = clk_get_sys(NULL, "pll_u");
+ if (IS_ERR(phy->pll_u)) {
+ pr_err("Can't get pll_u clock\n");
+ 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++) {
+ if (udc_freq_table[freq_sel] == parent_rate)
+ break;
+ }
+ if (freq_sel == ARRAY_SIZE(udc_freq_table)) {
+ pr_err("invalid pll_u parent rate %ld\n", parent_rate);
+ err = -EINVAL;
+ goto err1;
+ }
+ phy->freq_sel = freq_sel;
+
+ 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__);
+ err = -ENXIO;
+ 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;
+ }
+
+ return phy;
+
+err1:
+ clk_disable(phy->pll_u);
+ clk_put(phy->pll_u);
+err0:
+ kfree(phy);
+ return ERR_PTR(err);
+}
+
+int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
+{
+ if (phy->instance == 1)
+ ulpi_phy_power_on(phy);
+ else
+ utmi_phy_power_on(phy);
+
+ return 0;
+}
+
+int tegra_usb_phy_power_off(struct tegra_usb_phy *phy)
+{
+ if (phy->instance == 1)
+ ulpi_phy_power_off(phy);
+ else
+ utmi_phy_power_off(phy);
+
+ return 0;
+}
+
+int tegra_usb_phy_preresume(struct tegra_usb_phy *phy)
+{
+ if (phy->instance != 1)
+ utmi_phy_preresume(phy);
+ return 0;
+}
+
+int tegra_usb_phy_postresume(struct tegra_usb_phy *phy)
+{
+ if (phy->instance != 1)
+ utmi_phy_postresume(phy);
+ return 0;
+}
+
+int tegra_ehci_phy_restore_start(struct tegra_usb_phy *phy,
+ enum tegra_usb_phy_port_speed port_speed)
+{
+ if (phy->instance != 1)
+ utmi_phy_restore_start(phy, port_speed);
+ return 0;
+}
+
+int tegra_ehci_phy_restore_end(struct tegra_usb_phy *phy)
+{
+ if (phy->instance != 1)
+ utmi_phy_restore_end(phy);
+ 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_close(struct tegra_usb_phy *phy)
+{
+ if (phy->instance == 1)
+ clk_put(phy->clk);
+ else
+ utmip_pad_close(phy);
+ clk_disable(phy->pll_u);
+ clk_put(phy->pll_u);
+ kfree(phy);
+ return 0;
+}
--
1.7.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 2/4] usb: host: ehci-hcd: Add controller_resets_phy quirk
[not found] ` <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 5:22 ` [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs Benoit Goby
@ 2011-02-09 5:22 ` Benoit Goby
[not found] ` <1297228927-23497-3-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 5:22 ` [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs Benoit Goby
2011-02-09 5:22 ` [PATCH 4/4] USB: ehci: tegra: Align DMA transfers to 32 bytes Benoit Goby
3 siblings, 1 reply; 13+ messages in thread
From: Benoit Goby @ 2011-02-09 5:22 UTC (permalink / raw)
To: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell
Cc: Benoit Goby, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA, Gary King
From: Gary King <gking-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
Tegra quirk: Resetting the controller has the side effect of resetting
the PHY. Only reset the controller when doing so won't also reset the
phy.
Signed-off-by: Gary King <gking-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
---
drivers/usb/host/ehci-hcd.c | 3 ++-
drivers/usb/host/ehci.h | 1 +
2 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 30515d3..7afa345 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -275,7 +275,8 @@ static int ehci_reset (struct ehci_hcd *ehci)
command |= CMD_RESET;
dbg_cmd (ehci, "reset", command);
- ehci_writel(ehci, command, &ehci->regs->command);
+ if (!ehci->controller_resets_phy)
+ ehci_writel(ehci, command, &ehci->regs->command);
ehci_to_hcd(ehci)->state = HC_STATE_HALT;
ehci->next_statechange = jiffies;
retval = handshake (ehci, &ehci->regs->command,
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index f86d3fa..8854491 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
unsigned amd_pll_fix:1;
unsigned fs_i_thresh:1; /* Intel iso scheduling */
unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
+ unsigned controller_resets_phy:1; /* Tegra quirk */
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
--
1.7.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs
[not found] ` <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 5:22 ` [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs Benoit Goby
2011-02-09 5:22 ` [PATCH 2/4] usb: host: ehci-hcd: Add controller_resets_phy quirk Benoit Goby
@ 2011-02-09 5:22 ` Benoit Goby
[not found] ` <1297228927-23497-4-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 5:22 ` [PATCH 4/4] USB: ehci: tegra: Align DMA transfers to 32 bytes Benoit Goby
3 siblings, 1 reply; 13+ messages in thread
From: Benoit Goby @ 2011-02-09 5:22 UTC (permalink / raw)
To: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell
Cc: Benoit Goby, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA
Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
---
drivers/usb/Kconfig | 1 +
drivers/usb/host/Kconfig | 8 +
drivers/usb/host/ehci-hcd.c | 5 +
drivers/usb/host/ehci-tegra.c | 673 +++++++++++++++++++++++++++++++++++++++++
include/linux/tegra_usb.h | 35 +++
5 files changed, 722 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/host/ehci-tegra.c
create mode 100644 include/linux/tegra_usb.h
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index fceea5e..a2fa2a8 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -59,6 +59,7 @@ config USB_ARCH_HAS_EHCI
default y if PPC_MPC512x
default y if SOC_AU1200
default y if ARCH_IXP4XX
+ default y if ARCH_TEGRA
default y if ARCH_W90X900
default y if ARCH_AT91SAM9G45
default y if ARCH_MXC
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 24046c0..a769adc 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -459,6 +459,14 @@ config USB_HWA_HCD
To compile this driver a module, choose M here: the module
will be called "hwa-hc".
+config USB_TEGRA_HCD
+ boolean "NVIDIA Tegra HCD support"
+ depends on USB && ARCH_TEGRA && USB_EHCI_HCD
+ select USB_EHCI_ROOT_HUB_TT
+ help
+ This driver enables support for the internal USB Host Controller
+ found in NVIDIA Tegra SoCs. The Tegra controller is EHCI compliant.
+
config USB_IMX21_HCD
tristate "iMX21 HCD support"
depends on USB && ARM && MACH_MX21
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 7afa345..63e8b29 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -1246,6 +1246,11 @@ MODULE_LICENSE ("GPL");
#define PLATFORM_DRIVER ehci_msm_driver
#endif
+#ifdef CONFIG_ARCH_TEGRA
+#include "ehci-tegra.c"
+#define PLATFORM_DRIVER tegra_ehci_driver
+#endif
+
#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
!defined(XILINX_OF_PLATFORM_DRIVER)
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
new file mode 100644
index 0000000..24783ca
--- /dev/null
+++ b/drivers/usb/host/ehci-tegra.c
@@ -0,0 +1,673 @@
+/*
+ * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2009 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
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/tegra_usb.h>
+#include <linux/irq.h>
+#include <linux/usb/otg.h>
+#include <mach/usb_phy.h>
+
+#define TEGRA_USB_USBCMD_REG_OFFSET 0x140
+#define TEGRA_USB_USBCMD_RESET (1 << 1)
+#define TEGRA_USB_USBMODE_REG_OFFSET 0x1a8
+#define TEGRA_USB_USBMODE_HOST (3 << 0)
+#define TEGRA_USB_PORTSC1_PTC(x) (((x) & 0xf) << 16)
+
+struct tegra_ehci_hcd {
+ struct ehci_hcd *ehci;
+ struct tegra_usb_phy *phy;
+ struct clk *clk;
+ struct clk *emc_clk;
+ struct otg_transceiver *transceiver;
+ int host_resumed;
+ int bus_suspended;
+ int port_resuming;
+ int power_down_on_bus_suspend;
+ enum tegra_usb_phy_port_speed port_speed;
+};
+
+static void tegra_ehci_power_up(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+
+ clk_enable(tegra->emc_clk);
+ clk_enable(tegra->clk);
+ tegra_usb_phy_power_on(tegra->phy);
+ tegra->host_resumed = 1;
+}
+
+static void tegra_ehci_power_down(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+
+ tegra->host_resumed = 0;
+ tegra_usb_phy_power_off(tegra->phy);
+ clk_disable(tegra->clk);
+ clk_disable(tegra->emc_clk);
+}
+
+static int tegra_ehci_hub_control(
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength
+)
+{
+ 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;
+ unsigned long flags;
+ int retval = 0;
+
+ status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1];
+
+ spin_lock_irqsave(&ehci->lock, flags);
+
+ /*
+ * In ehci_hub_control() for USB_PORT_FEAT_ENABLE clears the other bits
+ * that are write on clear, by writing back the register read value, so
+ * USB_PORT_FEAT_ENABLE is handled by masking the set on clear bits
+ */
+ if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_ENABLE) {
+ temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS;
+ ehci_writel(ehci, temp & ~PORT_PE, status_reg);
+ goto done;
+ }
+
+ 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) {
+ temp = ehci_readl(ehci, status_reg);
+ if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
+ retval = -EPIPE;
+ goto done;
+ }
+
+ /* After above check the port must be connected.
+ * 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);
+ if (handshake(ehci, status_reg, PORT_SUSPEND,
+ PORT_SUSPEND, 5000))
+ pr_err("%s: timeout waiting for SUSPEND\n", __func__);
+ set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports);
+ goto done;
+ }
+
+ /*
+ * 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)) {
+ retval = -EPIPE;
+ goto done;
+ }
+
+ if (!(temp & PORT_SUSPEND))
+ goto done;
+
+ tegra_usb_phy_preresume(tegra->phy);
+
+ ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25);
+
+ temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
+ /* start resume signalling */
+ ehci_writel(ehci, temp | PORT_RESUME, status_reg);
+
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ msleep(20);
+ spin_lock_irqsave(&ehci->lock, flags);
+
+ /* polling PORT_RESUME until the controller clear this bit */
+ if (handshake(ehci, status_reg, PORT_RESUME, 0, 2000))
+ pr_err("%s: timeout waiting for RESUME\n", __func__);
+
+ /* polling PORT_SUSPEND until the controller clear this bit */
+ if (handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000))
+ pr_err("%s: timeout waiting for SUSPEND\n", __func__);
+
+ ehci->reset_done[wIndex-1] = 0;
+
+ tegra->port_resuming = 1;
+ goto done;
+ }
+
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ /* Handle the hub control events here */
+ return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+done:
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return retval;
+}
+
+static int tegra_ehci_reset(struct usb_hcd *hcd)
+{
+ unsigned long temp;
+ int usec = 250*1000; /* see ehci_reset */
+
+ temp = readl(hcd->regs + TEGRA_USB_USBCMD_REG_OFFSET);
+ temp |= TEGRA_USB_USBCMD_RESET;
+ writel(temp, hcd->regs + TEGRA_USB_USBCMD_REG_OFFSET);
+
+ do {
+ temp = readl(hcd->regs + TEGRA_USB_USBCMD_REG_OFFSET);
+ if (!(temp & TEGRA_USB_USBCMD_RESET))
+ break;
+ udelay(1);
+ usec--;
+ } while (usec);
+
+ if (!usec)
+ return -ETIMEDOUT;
+
+ /* 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));
+
+ return 0;
+}
+
+static void tegra_ehci_restart(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ tegra_ehci_reset(hcd);
+
+ /* 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);
+ /* setup the command register and set the controller in RUN mode */
+ ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET);
+ ehci->command |= CMD_RUN;
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+
+ down_write(&ehci_cf_port_reset_rwsem);
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ /* flush posted writes */
+ ehci_readl(ehci, &ehci->regs->command);
+ up_write(&ehci_cf_port_reset_rwsem);
+}
+
+static int tegra_usb_suspend(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+ struct ehci_regs __iomem *hw = tegra->ehci->regs;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tegra->ehci->lock, flags);
+
+ tegra->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3;
+ ehci_halt(tegra->ehci);
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ spin_unlock_irqrestore(&tegra->ehci->lock, flags);
+
+ tegra_ehci_power_down(hcd);
+ return 0;
+}
+
+static int tegra_usb_resume(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ struct ehci_regs __iomem *hw = ehci->regs;
+ unsigned long val;
+
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ tegra_ehci_power_up(hcd);
+
+ if (tegra->port_speed > TEGRA_USB_PHY_PORT_SPEED_HIGH) {
+ /* Wait for the phy to detect new devices
+ * before we restart the controller */
+ msleep(10);
+ goto restart;
+ }
+
+ tegra_ehci_phy_restore_start(tegra->phy, tegra->port_speed);
+
+ writel(TEGRA_USB_USBMODE_HOST, &hw->reserved[19]);
+
+ /* Enable Port Power */
+ val = readl(&hw->port_status[0]);
+ val |= PORT_POWER;
+ writel(val, &hw->port_status[0]);
+ udelay(10);
+
+ /* Check if the phy resume from LP0. When the phy resume from LP0
+ * USB register will be reset. */
+ if (!readl(&hw->async_next)) {
+ /* Program the field PTC based on the saved speed mode */
+ val = readl(&hw->port_status[0]);
+ val &= ~(TEGRA_USB_PORTSC1_PTC(~0));
+ if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_HIGH)
+ val |= TEGRA_USB_PORTSC1_PTC(5);
+ else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL)
+ val |= TEGRA_USB_PORTSC1_PTC(6);
+ else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW)
+ val |= TEGRA_USB_PORTSC1_PTC(7);
+ writel(val, &hw->port_status[0]);
+ udelay(10);
+
+ /* Disable test mode by setting PTC field to NORMAL_OP */
+ val = readl(&hw->port_status[0]);
+ val &= ~(TEGRA_USB_PORTSC1_PTC(~0));
+ writel(val, &hw->port_status[0]);
+ udelay(10);
+ }
+
+ /* Poll until CCS is enabled */
+ if (handshake(ehci, &hw->port_status[0], PORT_CONNECT,
+ PORT_CONNECT, 2000)) {
+ pr_err("%s: timeout waiting for PORT_CONNECT\n", __func__);
+ goto restart;
+ }
+
+ /* Poll until PE is enabled */
+ if (handshake(ehci, &hw->port_status[0], PORT_PE,
+ PORT_PE, 2000)) {
+ pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__);
+ goto restart;
+ }
+
+ /* Clear the PCI status, to avoid an interrupt taken upon resume */
+ val = readl(&hw->status);
+ val |= STS_PCD;
+ writel(val, &hw->status);
+
+ /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */
+ val = readl(&hw->port_status[0]);
+ if ((val & PORT_POWER) && (val & PORT_PE)) {
+ val |= PORT_SUSPEND;
+ writel(val, &hw->port_status[0]);
+
+ /* Wait until port suspend completes */
+ if (handshake(ehci, &hw->port_status[0], PORT_SUSPEND,
+ PORT_SUSPEND, 1000)) {
+ pr_err("%s: timeout waiting for PORT_SUSPEND\n",
+ __func__);
+ goto restart;
+ }
+ }
+
+ tegra_ehci_phy_restore_end(tegra->phy);
+ return 0;
+
+restart:
+ if (tegra->port_speed <= TEGRA_USB_PHY_PORT_SPEED_HIGH)
+ tegra_ehci_phy_restore_end(tegra->phy);
+
+ tegra_ehci_restart(hcd);
+ return 0;
+}
+
+static void tegra_ehci_shutdown(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+
+ /* ehci_shutdown touches the USB controller registers, make sure
+ * controller has clocks to it */
+ if (!tegra->host_resumed)
+ tegra_ehci_power_up(hcd);
+
+ ehci_shutdown(hcd);
+}
+
+static int tegra_ehci_setup(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ int retval;
+
+ /* EHCI registers start at offset 0x100 */
+ ehci->caps = hcd->regs + 0x100;
+ ehci->regs = hcd->regs + 0x100 +
+ HC_LENGTH(readl(&ehci->caps->hc_capbase));
+
+ dbg_hcs_params(ehci, "reset");
+ dbg_hcc_params(ehci, "reset");
+
+ /* cache this readonly data; minimize chip reads */
+ ehci->hcs_params = readl(&ehci->caps->hcs_params);
+
+ retval = ehci_halt(ehci);
+ if (retval)
+ return retval;
+
+ /* data structure init */
+ retval = ehci_init(hcd);
+ if (retval)
+ return retval;
+
+ hcd->has_tt = 1;
+ ehci->sbrn = 0x20;
+
+ ehci_reset(ehci);
+
+ /*
+ * Resetting the controller has the side effect of resetting the PHY.
+ * So, never reset the controller after the calling
+ * tegra_ehci_reinit API.
+ */
+ ehci->controller_resets_phy = 1;
+
+ ehci_port_power(ehci, 1);
+ return retval;
+}
+
+#ifdef CONFIG_PM
+static int tegra_ehci_bus_suspend(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+ int error_status = 0;
+
+ error_status = ehci_bus_suspend(hcd);
+ if (!error_status && tegra->power_down_on_bus_suspend) {
+ tegra_usb_suspend(hcd);
+ tegra->bus_suspended = 1;
+ }
+
+ return error_status;
+}
+
+static int tegra_ehci_bus_resume(struct usb_hcd *hcd)
+{
+ struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+
+ if (tegra->bus_suspended && tegra->power_down_on_bus_suspend) {
+ tegra_usb_resume(hcd);
+ tegra->bus_suspended = 0;
+ }
+
+ tegra_usb_phy_preresume(tegra->phy);
+ tegra->port_resuming = 1;
+ return ehci_bus_resume(hcd);
+}
+#endif
+
+static const struct hc_driver tegra_ehci_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "Tegra EHCI Host Controller",
+ .hcd_priv_size = sizeof(struct ehci_hcd),
+
+ .flags = HCD_USB2 | HCD_MEMORY,
+
+ .reset = tegra_ehci_setup,
+ .irq = ehci_irq,
+
+ .start = ehci_run,
+ .stop = ehci_stop,
+ .shutdown = tegra_ehci_shutdown,
+ .urb_enqueue = ehci_urb_enqueue,
+ .urb_dequeue = ehci_urb_dequeue,
+ .endpoint_disable = ehci_endpoint_disable,
+ .endpoint_reset = ehci_endpoint_reset,
+ .get_frame_number = ehci_get_frame,
+ .hub_status_data = ehci_hub_status_data,
+ .hub_control = tegra_ehci_hub_control,
+ .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+#ifdef CONFIG_PM
+ .bus_suspend = tegra_ehci_bus_suspend,
+ .bus_resume = tegra_ehci_bus_resume,
+#endif
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
+};
+
+static int tegra_ehci_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct usb_hcd *hcd;
+ struct ehci_hcd *ehci;
+ struct tegra_ehci_hcd *tegra;
+ struct tegra_ehci_platform_data *pdata;
+ struct tegra_utmip_config *config;
+ int err = 0;
+ int irq;
+ int instance = pdev->id;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "Platform data missing\n");
+ return -EINVAL;
+ }
+
+ tegra = kzalloc(sizeof(struct tegra_ehci_hcd), GFP_KERNEL);
+ if (!tegra)
+ return -ENOMEM;
+
+ hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev,
+ dev_name(&pdev->dev));
+ if (!hcd) {
+ dev_err(&pdev->dev, "Unable to create HCD\n");
+ err = -ENOMEM;
+ goto fail_hcd;
+ }
+
+ platform_set_drvdata(pdev, tegra);
+
+ tegra->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(tegra->clk)) {
+ dev_err(&pdev->dev, "Can't get ehci clock\n");
+ err = PTR_ERR(tegra->clk);
+ goto fail_clk;
+ }
+
+ err = clk_enable(tegra->clk);
+ if (err)
+ goto fail_clken;
+
+ tegra->emc_clk = clk_get(&pdev->dev, "emc");
+ if (IS_ERR(tegra->emc_clk)) {
+ dev_err(&pdev->dev, "Can't get emc clock\n");
+ err = PTR_ERR(tegra->emc_clk);
+ goto fail_emc_clk;
+ }
+
+ clk_enable(tegra->emc_clk);
+ clk_set_rate(tegra->emc_clk, 400000000);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get I/O memory\n");
+ err = -ENXIO;
+ goto fail_io;
+ }
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = ioremap(res->start, resource_size(res));
+ if (!hcd->regs) {
+ dev_err(&pdev->dev, "Failed to remap I/O memory\n");
+ err = -ENOMEM;
+ goto fail_io;
+ }
+
+ config = pdata->phy_config;
+
+ tegra->phy = tegra_usb_phy_open(instance, hcd->regs, config,
+ TEGRA_USB_PHY_MODE_HOST);
+ if (IS_ERR(tegra->phy)) {
+ dev_err(&pdev->dev, "Failed to open USB phy\n");
+ err = -ENXIO;
+ goto fail_phy;
+ }
+
+ err = tegra_ehci_reset(hcd);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to reset controller\n");
+ goto fail;
+ }
+
+ tegra_usb_phy_power_on(tegra->phy);
+ tegra->host_resumed = 1;
+ tegra->power_down_on_bus_suspend = pdata->power_down_on_bus_suspend;
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq) {
+ dev_err(&pdev->dev, "Failed to get IRQ\n");
+ err = -ENODEV;
+ goto fail;
+ }
+
+ set_irq_flags(irq, IRQF_VALID);
+
+ ehci = hcd_to_ehci(hcd);
+ tegra->ehci = ehci;
+
+#ifdef CONFIG_USB_OTG_UTILS
+ if (pdata->operating_mode == TEGRA_USB_OTG) {
+ tegra->transceiver = otg_get_transceiver();
+ if (tegra->transceiver)
+ otg_set_host(tegra->transceiver, &hcd->self);
+ }
+#endif
+
+ err = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);
+ if (err != 0) {
+ dev_err(&pdev->dev, "Failed to add USB HCD\n");
+ goto fail;
+ }
+
+ return err;
+
+fail:
+#ifdef CONFIG_USB_OTG_UTILS
+ if (tegra->transceiver) {
+ otg_set_host(tegra->transceiver, NULL);
+ otg_put_transceiver(tegra->transceiver);
+ }
+#endif
+ tegra_usb_phy_close(tegra->phy);
+fail_phy:
+ iounmap(hcd->regs);
+fail_io:
+ clk_disable(tegra->emc_clk);
+ clk_put(tegra->emc_clk);
+fail_emc_clk:
+ clk_disable(tegra->clk);
+fail_clken:
+ clk_put(tegra->clk);
+fail_clk:
+ usb_put_hcd(hcd);
+fail_hcd:
+ kfree(tegra);
+ return err;
+}
+
+#ifdef CONFIG_PM
+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)
+ return 0;
+
+ return tegra_usb_resume(hcd);
+}
+
+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)
+ return 0;
+
+ if (time_before(jiffies, tegra->ehci->next_statechange))
+ msleep(10);
+
+ return tegra_usb_suspend(hcd);
+}
+#endif
+
+static int tegra_ehci_remove(struct platform_device *pdev)
+{
+ struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci);
+
+ if (tegra == NULL || hcd == NULL)
+ return -EINVAL;
+
+#ifdef CONFIG_USB_OTG_UTILS
+ if (tegra->transceiver) {
+ otg_set_host(tegra->transceiver, NULL);
+ otg_put_transceiver(tegra->transceiver);
+ }
+#endif
+
+ usb_remove_hcd(hcd);
+ usb_put_hcd(hcd);
+
+ tegra_usb_phy_close(tegra->phy);
+ iounmap(hcd->regs);
+
+ clk_disable(tegra->clk);
+ clk_put(tegra->clk);
+
+ clk_disable(tegra->emc_clk);
+ clk_put(tegra->emc_clk);
+
+ kfree(tegra);
+ return 0;
+}
+
+static void tegra_ehci_hcd_shutdown(struct platform_device *pdev)
+{
+ struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci);
+
+ if (hcd->driver->shutdown)
+ hcd->driver->shutdown(hcd);
+}
+
+static struct platform_driver tegra_ehci_driver = {
+ .probe = tegra_ehci_probe,
+ .remove = tegra_ehci_remove,
+#ifdef CONFIG_PM
+ .suspend = tegra_ehci_suspend,
+ .resume = tegra_ehci_resume,
+#endif
+ .shutdown = tegra_ehci_hcd_shutdown,
+ .driver = {
+ .name = "tegra-ehci",
+ }
+};
diff --git a/include/linux/tegra_usb.h b/include/linux/tegra_usb.h
new file mode 100644
index 0000000..2947ed2
--- /dev/null
+++ b/include/linux/tegra_usb.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * 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
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _TEGRA_USB_H_
+#define _TEGRA_USB_H_
+
+enum tegra_usb_operating_modes {
+ TEGRA_USB_DEVICE,
+ TEGRA_USB_HOST,
+ TEGRA_USB_OTG,
+};
+
+struct tegra_ehci_platform_data {
+ enum tegra_usb_operating_modes operating_mode;
+ /* power down the phy on bus suspend */
+ int power_down_on_bus_suspend;
+ void *phy_config;
+};
+
+#endif /* _TEGRA_USB_H_ */
--
1.7.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 4/4] USB: ehci: tegra: Align DMA transfers to 32 bytes
[not found] ` <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
` (2 preceding siblings ...)
2011-02-09 5:22 ` [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs Benoit Goby
@ 2011-02-09 5:22 ` Benoit Goby
3 siblings, 0 replies; 13+ messages in thread
From: Benoit Goby @ 2011-02-09 5:22 UTC (permalink / raw)
To: David Brownell, Greg Kroah-Hartman, linux-usb-u79uwXL29TY76Z2rM5mHXA
Cc: Benoit Goby, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA, Robert Morell
From: Robert Morell <rmorell-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
The Tegra2 USB controller doesn't properly deal with misaligned DMA
buffers, causing corruption. This is especially prevalent with USB
network adapters, where skbuff alignment is often in the middle of a
4-byte dword.
To avoid this, allocate a temporary buffer for the DMA if the provided
buffer isn't sufficiently aligned.
Signed-off-by: Robert Morell <rmorell-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
---
drivers/usb/host/ehci-tegra.c | 90 +++++++++++++++++++++++++++++++++++++++++
1 files changed, 90 insertions(+), 0 deletions(-)
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 24783ca..6d2d247 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -32,6 +32,8 @@
#define TEGRA_USB_USBMODE_HOST (3 << 0)
#define TEGRA_USB_PORTSC1_PTC(x) (((x) & 0xf) << 16)
+#define TEGRA_USB_DMA_ALIGN 32
+
struct tegra_ehci_hcd {
struct ehci_hcd *ehci;
struct tegra_usb_phy *phy;
@@ -423,6 +425,92 @@ static int tegra_ehci_bus_resume(struct usb_hcd *hcd)
}
#endif
+struct temp_buffer {
+ void *kmalloc_ptr;
+ void *old_xfer_buffer;
+ u8 data[0];
+};
+
+static void free_temp_buffer(struct urb *urb)
+{
+ enum dma_data_direction dir;
+ struct temp_buffer *temp;
+
+ if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
+ return;
+
+ dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+ temp = container_of(urb->transfer_buffer, struct temp_buffer,
+ data);
+
+ if (dir == DMA_FROM_DEVICE)
+ memcpy(temp->old_xfer_buffer, temp->data,
+ urb->transfer_buffer_length);
+ urb->transfer_buffer = temp->old_xfer_buffer;
+ kfree(temp->kmalloc_ptr);
+
+ urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
+}
+
+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;
+
+ if (urb->num_sgs || urb->sg ||
+ 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;
+
+ /* Allocate a buffer with enough padding for alignment */
+ kmalloc_size = urb->transfer_buffer_length +
+ sizeof(struct temp_buffer) + TEGRA_USB_DMA_ALIGN - 1;
+
+ kmalloc_ptr = kmalloc(kmalloc_size, mem_flags);
+ if (!kmalloc_ptr)
+ return -ENOMEM;
+
+ /* Position our struct temp_buffer such that data is aligned */
+ temp = PTR_ALIGN(kmalloc_ptr + 1, TEGRA_USB_DMA_ALIGN) - 1;
+
+ temp->kmalloc_ptr = kmalloc_ptr;
+ 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 = temp->data;
+
+ 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)
+{
+ int ret;
+
+ ret = alloc_temp_buffer(urb, mem_flags);
+ if (ret)
+ return ret;
+
+ ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
+ if (ret)
+ free_temp_buffer(urb);
+
+ return ret;
+}
+
+static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
+{
+ usb_hcd_unmap_urb_for_dma(hcd, urb);
+ free_temp_buffer(urb);
+}
+
static const struct hc_driver tegra_ehci_hc_driver = {
.description = hcd_name,
.product_desc = "Tegra EHCI Host Controller",
@@ -438,6 +526,8 @@ static const struct hc_driver tegra_ehci_hc_driver = {
.shutdown = tegra_ehci_shutdown,
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
+ .map_urb_for_dma = tegra_ehci_map_urb_for_dma,
+ .unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma,
.endpoint_disable = ehci_endpoint_disable,
.endpoint_reset = ehci_endpoint_reset,
.get_frame_number = ehci_get_frame,
--
1.7.3.1
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs
[not found] ` <1297228927-23497-2-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
@ 2011-02-09 7:05 ` rmorell-DDmLM1+adcrQT0dZR+AlfA
[not found] ` <20110209070540.GA17627-f3YH7lVHJt/FT5IIyIEb6QC/G2K4zDHf@public.gmane.org>
2011-02-09 8:44 ` Matthieu CASTET
1 sibling, 1 reply; 13+ messages in thread
From: rmorell-DDmLM1+adcrQT0dZR+AlfA @ 2011-02-09 7:05 UTC (permalink / raw)
To: Benoit Goby
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA
On Tue, Feb 08, 2011 at 09:22:04PM -0800, Benoit Goby wrote:
> Interface used by Tegra's gadget driver and ehci driver
> to power on and configure the USB PHYs.
>
> Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
> ---
For some reason this patch (along with patch 3) appears to contain
spaces as indentation (no hard tabs), although patches 2 and 4 in this
series have the correct tabs.
[...]
> +#define USB_USBSTS 0x144
> +#define USB_USBSTS_PCI (1 << 2)
This doesn't appear to be used?
[...]
> +static DEFINE_SPINLOCK(utmip_pad_lock);
> +static int utmip_pad_count;
> +
> +static const int udc_freq_table[] = {
> + 12000000,
> + 13000000,
> + 19200000,
> + 26000000,
> +};
> +
> +static const u8 udc_delay_table[][4] = {
> + /* ENABLE_DLY, STABLE_CNT, ACTIVE_DLY, XTAL_FREQ_CNT */
> + {0x02, 0x2F, 0x04, 0x76}, /* 12 MHz */
> + {0x02, 0x33, 0x05, 0x7F}, /* 13 MHz */
> + {0x03, 0x4B, 0x06, 0xBB}, /* 19.2 MHz */
> + {0x04, 0x66, 0x09, 0xFE}, /* 26 Mhz */
> +};
Use an array of structs here? Writing
"UTMIP_PLL_ACTIVE_DLY_COUNT(udc_delay_table[phy->freq_sel][2])" below is a
lot less clear and more prone to bugs than, say,
"UTMIP_PLL_ACTIVE_DLY_COUNT(udc_delay_table[phy->freq_sel].active_dly)".
It'd be even better to combine all three tables into one; something
like:
static const struct udc_... udc_table[] = {
{ .freq = 12000000,
.delay = {
.enable = 0x02,
.stable = 0x2f,
.active = 0x04
},
.xtal_freq_cnt = 0x76,
.debounce = 0x7530,
},
[... etc]
};
[...]
> +static int utmip_pad_open(struct tegra_usb_phy *phy)
> +{
> + phy->pad_clk = clk_get_sys("utmip-pad", NULL);
> + if (IS_ERR(phy->pad_clk)) {
> + pr_err("%s: can't get utmip pad clock\n", __func__);
> + return -1;
return PTR_ERR(phy->pad_clk);
> + }
> +
> + if (phy->instance == 0) {
> + phy->pad_regs = phy->regs;
> + } else {
> + phy->pad_regs = ioremap(TEGRA_USB_BASE, TEGRA_USB_SIZE);
> + if (!phy->pad_regs) {
> + pr_err("%s: can't remap usb registers\n", __func__);
> + clk_put(phy->pad_clk);
> + return -ENOMEM;
> + }
> + }
> + return 0;
> +}
[...]
> +static int utmip_pad_power_off(struct tegra_usb_phy *phy)
> +{
> + unsigned long val, flags;
> + void __iomem *base = phy->pad_regs;
> +
> + if (!utmip_pad_count) {
> + pr_err("%s: utmip pad already powered off\n", __func__);
> + return -1;
return -EINVAL?
> + }
> +
> + clk_enable(phy->pad_clk);
> +
> + spin_lock_irqsave(&utmip_pad_lock, flags);
> +
> + if (--utmip_pad_count == 0) {
> + val = readl(base + UTMIP_BIAS_CFG0);
> + val |= UTMIP_OTGPD | UTMIP_BIASPD;
> + writel(val, base + UTMIP_BIAS_CFG0);
> + }
> +
> + spin_unlock_irqrestore(&utmip_pad_lock, flags);
> +
> + clk_disable(phy->pad_clk);
> +
> + return 0;
> +}
> +
[...]
> +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);
This msleep seems excessive. Does it take that long for the clock to
settle?
> +
> + 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;
Why does this function return void if it can fail?
tegra_usb_phy_power_on() can propagate the error.
> + }
> +
> + /* 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);
> +}
[...]
> +struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
> + 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;
> +
> + phy = kmalloc(sizeof(struct tegra_usb_phy), GFP_KERNEL);
> + if (!phy)
> + return ERR_PTR(-ENOMEM);
> +
> + phy->instance = instance;
> + phy->regs = regs;
> + phy->config = config;
> + phy->mode = phy_mode;
> +
> + if (!phy->config) {
> + if (instance == 1) {
Instead of all of the (instance == 1) checks here and below (I count
eleven separate places this is checked), it might be more readable to
have a macro or static inline function such as bool phy_is_ulpi(phy).
> + pr_err("%s: ulpi phy configuration missing", __func__);
> + err = -EINVAL;
> + goto err0;
> + } else {
> + phy->config = &utmip_default[instance];
> + }
> + }
> +
> + phy->pll_u = clk_get_sys(NULL, "pll_u");
> + if (IS_ERR(phy->pll_u)) {
> + pr_err("Can't get pll_u clock\n");
> + 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++) {
> + if (udc_freq_table[freq_sel] == parent_rate)
> + break;
> + }
> + if (freq_sel == ARRAY_SIZE(udc_freq_table)) {
> + pr_err("invalid pll_u parent rate %ld\n", parent_rate);
> + err = -EINVAL;
> + goto err1;
> + }
> + phy->freq_sel = freq_sel;
> +
> + 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__);
> + err = -ENXIO;
> + 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;
> + }
> +
> + return phy;
> +
> +err1:
> + clk_disable(phy->pll_u);
> + clk_put(phy->pll_u);
> +err0:
> + kfree(phy);
> + return ERR_PTR(err);
> +}
[...]
The rest seems okay.
> --
> 1.7.3.1
>
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs
[not found] ` <1297228927-23497-2-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 7:05 ` rmorell-DDmLM1+adcrQT0dZR+AlfA
@ 2011-02-09 8:44 ` Matthieu CASTET
1 sibling, 0 replies; 13+ messages in thread
From: Matthieu CASTET @ 2011-02-09 8:44 UTC (permalink / raw)
To: Benoit Goby
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell, Olof Johansson,
Erik Gilling, linux-tegra-u79uwXL29TY76Z2rM5mHXA
Hello,
Benoit Goby a écrit :
> Interface used by Tegra's gadget driver and ehci driver
> to power on and configure the USB PHYs.
>
> + */
> +
> +#include <linux/resource.h>
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#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
It look like your core is a arc/chipidea/mips one that is already used
on other product (msm) for example.
Can't you merge with them ?
for example
> +static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result)
> +{
> + unsigned long timeout = 2000;
> + do {
> + if ((readl(reg) & mask) == result)
> + return 0;
> + udelay(1);
> + timeout--;
> + } while (timeout);
> + return -1;
> +}
> +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__);
> +}
look very similar to what is in drivers/usb/otg/msm72k_otg.c
+static int ulpi_write(struct otg_transceiver *otg, u32 val, u32 reg)
+{
+ struct msm_otg *motg = container_of(otg, struct msm_otg, otg);
+ int cnt = 0;
+
+ /* initiate write operation */
+ writel(ULPI_RUN | ULPI_WRITE |
+ ULPI_ADDR(reg) | ULPI_DATA(val),
+ USB_ULPI_VIEWPORT);
+
+ /* wait for completion */
+ while (cnt < ULPI_IO_TIMEOUT_USEC) {
+ if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN))
+ break;
+ udelay(1);
+ cnt++;
+ }
+
+ if (cnt >= ULPI_IO_TIMEOUT_USEC) {
+ dev_err(otg->dev, "ulpi_write: timeout\n");
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
Matthieu
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs
[not found] ` <1297228927-23497-4-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
@ 2011-02-09 9:01 ` Matthieu CASTET
[not found] ` <4D5257FA.1030605-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org>
2011-02-09 19:43 ` David Daney
1 sibling, 1 reply; 13+ messages in thread
From: Matthieu CASTET @ 2011-02-09 9:01 UTC (permalink / raw)
To: Benoit Goby
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell, Olof Johansson,
Erik Gilling, linux-tegra-u79uwXL29TY76Z2rM5mHXA
Hi,
Benoit Goby a écrit :
> Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
_clk);
> +}
> +
> +static int tegra_ehci_hub_control(
> + struct usb_hcd *hcd,
> + u16 typeReq,
> + u16 wValue,
> + u16 wIndex,
> + char *buf,
> + u16 wLength
> +)
> +{
> + 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;
> + unsigned long flags;
> + int retval = 0;
> +
> + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1];
> +
> + spin_lock_irqsave(&ehci->lock, flags);
> +
> + /*
> + * In ehci_hub_control() for USB_PORT_FEAT_ENABLE clears the other bits
> + * that are write on clear, by writing back the register read value, so
> + * USB_PORT_FEAT_ENABLE is handled by masking the set on clear bits
> + */
> + if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_ENABLE) {
> + temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS;
> + ehci_writel(ehci, temp & ~PORT_PE, status_reg);
> + goto done;
> + }
> +
> + 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) {
> + temp = ehci_readl(ehci, status_reg);
> + if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
> + retval = -EPIPE;
> + goto done;
> + }
> +
> + /* After above check the port must be connected.
> + * 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);
> + if (handshake(ehci, status_reg, PORT_SUSPEND,
> + PORT_SUSPEND, 5000))
> + pr_err("%s: timeout waiting for SUSPEND\n", __func__);
> + set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports);
> + goto done;
> + }
> +
> + /*
> + * 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)) {
> + retval = -EPIPE;
> + goto done;
> + }
> +
> + if (!(temp & PORT_SUSPEND))
> + goto done;
> +
> + tegra_usb_phy_preresume(tegra->phy);
> +
> + ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25);
> +
> + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
> + /* start resume signalling */
> + ehci_writel(ehci, temp | PORT_RESUME, status_reg);
> +
> + spin_unlock_irqrestore(&ehci->lock, flags);
> + msleep(20);
> + spin_lock_irqsave(&ehci->lock, flags);
> +
> + /* polling PORT_RESUME until the controller clear this bit */
> + if (handshake(ehci, status_reg, PORT_RESUME, 0, 2000))
> + pr_err("%s: timeout waiting for RESUME\n", __func__);
> +
> + /* polling PORT_SUSPEND until the controller clear this bit */
> + if (handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000))
> + pr_err("%s: timeout waiting for SUSPEND\n", __func__);
> +
> + ehci->reset_done[wIndex-1] = 0;
> +
> + tegra->port_resuming = 1;
> + goto done;
> + }
> +
> + spin_unlock_irqrestore(&ehci->lock, flags);
> +
> + /* Handle the hub control events here */
> + return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
> +done:
> + spin_unlock_irqrestore(&ehci->lock, flags);
> + return retval;
> +}
> +
I don't know enough usb layer, but can't you done something similar than
in msm otg : let's the otg layer handle the power management ?
http://www.spinics.net/lists/linux-usb/msg41247.html
> +static int tegra_ehci_reset(struct usb_hcd *hcd)
> +{
> + unsigned long temp;
> + int usec = 250*1000; /* see ehci_reset */
> +
> + temp = readl(hcd->regs + TEGRA_USB_USBCMD_REG_OFFSET);
> + temp |= TEGRA_USB_USBCMD_RESET;
> + writel(temp, hcd->regs + TEGRA_USB_USBCMD_REG_OFFSET);
> +
> + do {
> + temp = readl(hcd->regs + TEGRA_USB_USBCMD_REG_OFFSET);
> + if (!(temp & TEGRA_USB_USBCMD_RESET))
> + break;
> + udelay(1);
> + usec--;
> + } while (usec);
> +
> + if (!usec)
> + return -ETIMEDOUT;
> +
> + /* 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));
> +
> + return 0;
> +}
Why can't you use tdi_reset that already this for you ?
Matthieu
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 2/4] usb: host: ehci-hcd: Add controller_resets_phy quirk
[not found] ` <1297228927-23497-3-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
@ 2011-02-09 16:01 ` Matthieu CASTET
[not found] ` <4D52BA75.5010409-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org>
0 siblings, 1 reply; 13+ messages in thread
From: Matthieu CASTET @ 2011-02-09 16:01 UTC (permalink / raw)
To: Benoit Goby
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell, Olof Johansson,
Erik Gilling, linux-tegra-u79uwXL29TY76Z2rM5mHXA, Gary King,
Alan Stern
Benoit Goby a écrit :
> From: Gary King <gking-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
>
> Tegra quirk: Resetting the controller has the side effect of resetting
> the PHY. Only reset the controller when doing so won't also reset the
> phy.
>
> Signed-off-by: Gary King <gking-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
> ---
> drivers/usb/host/ehci-hcd.c | 3 ++-
> drivers/usb/host/ehci.h | 1 +
> 2 files changed, 3 insertions(+), 1 deletions(-)
>
> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
> index 30515d3..7afa345 100644
> --- a/drivers/usb/host/ehci-hcd.c
> +++ b/drivers/usb/host/ehci-hcd.c
> @@ -275,7 +275,8 @@ static int ehci_reset (struct ehci_hcd *ehci)
>
> command |= CMD_RESET;
> dbg_cmd (ehci, "reset", command);
> - ehci_writel(ehci, command, &ehci->regs->command);
> + if (!ehci->controller_resets_phy)
> + ehci_writel(ehci, command, &ehci->regs->command);
> ehci_to_hcd(ehci)->state = HC_STATE_HALT;
> ehci->next_statechange = jiffies;
> retval = handshake (ehci, &ehci->regs->command,
> diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
> index f86d3fa..8854491 100644
> --- a/drivers/usb/host/ehci.h
> +++ b/drivers/usb/host/ehci.h
> @@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
> unsigned amd_pll_fix:1;
> unsigned fs_i_thresh:1; /* Intel iso scheduling */
> unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
> + unsigned controller_resets_phy:1; /* Tegra quirk */
>
> /* required for usb32 quirk */
> #define OHCI_CTRL_HCFS (3 << 6)
Instead of something that prevent reset, shouldn't tdi platform have a
callback to restore phy config ?
And I think we should use ehci_is_TDI(ehci) condition. No need to
introduce another flags. The problem is common to all tdi core.
Matthieu
PS : This may help to remove ehci_msm_run.
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs
[not found] ` <1297228927-23497-4-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 9:01 ` Matthieu CASTET
@ 2011-02-09 19:43 ` David Daney
1 sibling, 0 replies; 13+ messages in thread
From: David Daney @ 2011-02-09 19:43 UTC (permalink / raw)
To: Benoit Goby
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell, Olof Johansson,
Erik Gilling, linux-tegra-u79uwXL29TY76Z2rM5mHXA
On 02/08/2011 09:22 PM, Benoit Goby wrote:
> Signed-off-by: Benoit Goby<benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
> ---
> drivers/usb/Kconfig | 1 +
> drivers/usb/host/Kconfig | 8 +
> drivers/usb/host/ehci-hcd.c | 5 +
> drivers/usb/host/ehci-tegra.c | 673 +++++++++++++++++++++++++++++++++++++++++
> include/linux/tegra_usb.h | 35 +++
> 5 files changed, 722 insertions(+), 0 deletions(-)
> create mode 100644 drivers/usb/host/ehci-tegra.c
> create mode 100644 include/linux/tegra_usb.h
>
> diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
> index fceea5e..a2fa2a8 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -59,6 +59,7 @@ config USB_ARCH_HAS_EHCI
> default y if PPC_MPC512x
> default y if SOC_AU1200
> default y if ARCH_IXP4XX
> + default y if ARCH_TEGRA
> default y if ARCH_W90X900
This doesn't belong here.
Do 'select USB_ARCH_HAS_EHCI' in your arch specific Kconfig instead.
David Daney
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 2/4] usb: host: ehci-hcd: Add controller_resets_phy quirk
[not found] ` <4D52BA75.5010409-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org>
@ 2011-02-10 2:46 ` Pavan Kondeti
0 siblings, 0 replies; 13+ messages in thread
From: Pavan Kondeti @ 2011-02-10 2:46 UTC (permalink / raw)
To: Matthieu CASTET
Cc: Benoit Goby, David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell, Olof Johansson,
Erik Gilling, linux-tegra-u79uwXL29TY76Z2rM5mHXA, Gary King,
Alan Stern
Hi Matthieu,
On 2/9/2011 9:31 PM, Matthieu CASTET wrote:
> Benoit Goby a écrit :
>> From: Gary King <gking-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
>>
>> Tegra quirk: Resetting the controller has the side effect of resetting
>> the PHY. Only reset the controller when doing so won't also reset the
>> phy.
>>
>> Signed-off-by: Gary King <gking-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
>> Signed-off-by: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
>> ---
>> drivers/usb/host/ehci-hcd.c | 3 ++-
>> drivers/usb/host/ehci.h | 1 +
>> 2 files changed, 3 insertions(+), 1 deletions(-)
>>
>> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
>> index 30515d3..7afa345 100644
>> --- a/drivers/usb/host/ehci-hcd.c
>> +++ b/drivers/usb/host/ehci-hcd.c
>> @@ -275,7 +275,8 @@ static int ehci_reset (struct ehci_hcd *ehci)
>>
>> command |= CMD_RESET;
>> dbg_cmd (ehci, "reset", command);
>> - ehci_writel(ehci, command, &ehci->regs->command);
>> + if (!ehci->controller_resets_phy)
>> + ehci_writel(ehci, command, &ehci->regs->command);
>> ehci_to_hcd(ehci)->state = HC_STATE_HALT;
>> ehci->next_statechange = jiffies;
>> retval = handshake (ehci, &ehci->regs->command,
>> diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
>> index f86d3fa..8854491 100644
>> --- a/drivers/usb/host/ehci.h
>> +++ b/drivers/usb/host/ehci.h
>> @@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
>> unsigned amd_pll_fix:1;
>> unsigned fs_i_thresh:1; /* Intel iso scheduling */
>> unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
>> + unsigned controller_resets_phy:1; /* Tegra quirk */
>>
>> /* required for usb32 quirk */
>> #define OHCI_CTRL_HCFS (3 << 6)
> Instead of something that prevent reset, shouldn't tdi platform have a
> callback to restore phy config ?
>
> And I think we should use ehci_is_TDI(ehci) condition. No need to
> introduce another flags. The problem is common to all tdi core.
>
> Matthieu
>
>
> PS : This may help to remove ehci_msm_run.
The reason for having ehci_msm_run is to configure some MSM specific
registers (not just the HC mode bit) after controller reset. In the
current code, there is no way HCD can register it's own tdi_reset
method. If we can have such mechanism, then ehci_msm_run() can be removed.
Thanks,
Pavan
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs
[not found] ` <4D5257FA.1030605-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org>
@ 2011-02-12 0:59 ` Benoit Goby
0 siblings, 0 replies; 13+ messages in thread
From: Benoit Goby @ 2011-02-12 0:59 UTC (permalink / raw)
To: Matthieu CASTET
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Robert Morell, Olof Johansson,
Erik Gilling, linux-tegra-u79uwXL29TY76Z2rM5mHXA
On Wed, Feb 9, 2011 at 1:01 AM, Matthieu CASTET
<matthieu.castet-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org> wrote:
> Benoit Goby a écrit :
>> +static int tegra_ehci_hub_control(
>> + struct usb_hcd *hcd,
>> + u16 typeReq,
>> + u16 wValue,
>> + u16 wIndex,
>> + char *buf,
>> + u16 wLength
>> +)
>> +{
>> +
>
> I don't know enough usb layer, but can't you done something similar than in
> msm otg : let's the otg layer handle the power management ?
>
> http://www.spinics.net/lists/linux-usb/msg41247.html
>
This function overrides ehci_hub_control to fix tegra specific quirks
when we suspend/resume the root hub. I don't see how this could be
handled at the otg level.
Benoit
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs
[not found] ` <20110209070540.GA17627-f3YH7lVHJt/FT5IIyIEb6QC/G2K4zDHf@public.gmane.org>
@ 2011-02-12 3:01 ` Benoit Goby
0 siblings, 0 replies; 13+ messages in thread
From: Benoit Goby @ 2011-02-12 3:01 UTC (permalink / raw)
To: rmorell-DDmLM1+adcrQT0dZR+AlfA
Cc: David Brownell, Greg Kroah-Hartman,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Olof Johansson, Erik Gilling,
linux-tegra-u79uwXL29TY76Z2rM5mHXA
On Tue, Feb 8, 2011 at 11:05 PM, <rmorell-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> wrote:
> On Tue, Feb 08, 2011 at 09:22:04PM -0800, Benoit Goby wrote:
>
>> +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);
>
> This msleep seems excessive. Does it take that long for the clock to
> settle?
>
I tried to use smaller delays but got enumeration issues with delays
<1ms, so I'm keeping the msleep.
Benoit
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2011-02-12 3:01 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-02-09 5:22 [PATCH 0/4] Tegra EHCI driver Benoit Goby
[not found] ` <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 5:22 ` [PATCH 1/4] [ARM] tegra: Add support for Tegra USB PHYs Benoit Goby
[not found] ` <1297228927-23497-2-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 7:05 ` rmorell-DDmLM1+adcrQT0dZR+AlfA
[not found] ` <20110209070540.GA17627-f3YH7lVHJt/FT5IIyIEb6QC/G2K4zDHf@public.gmane.org>
2011-02-12 3:01 ` Benoit Goby
2011-02-09 8:44 ` Matthieu CASTET
2011-02-09 5:22 ` [PATCH 2/4] usb: host: ehci-hcd: Add controller_resets_phy quirk Benoit Goby
[not found] ` <1297228927-23497-3-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 16:01 ` Matthieu CASTET
[not found] ` <4D52BA75.5010409-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org>
2011-02-10 2:46 ` Pavan Kondeti
2011-02-09 5:22 ` [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs Benoit Goby
[not found] ` <1297228927-23497-4-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 9:01 ` Matthieu CASTET
[not found] ` <4D5257FA.1030605-ITF29qwbsa/QT0dZR+AlfA@public.gmane.org>
2011-02-12 0:59 ` Benoit Goby
2011-02-09 19:43 ` David Daney
2011-02-09 5:22 ` [PATCH 4/4] USB: ehci: tegra: Align DMA transfers to 32 bytes Benoit Goby
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.