From: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
To: David Brownell
<dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org>,
Greg Kroah-Hartman <gregkh-l3A5Bk7waGM@public.gmane.org>,
linux-usb-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Robert Morell <rmorell-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
Cc: Benoit Goby <benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>,
Olof Johansson <olof-nZhT3qVonbNeoWH0uzbU5w@public.gmane.org>,
Erik Gilling <konkers-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>,
linux-tegra-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Subject: [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs
Date: Tue, 8 Feb 2011 21:22:06 -0800 [thread overview]
Message-ID: <1297228927-23497-4-git-send-email-benoit@android.com> (raw)
In-Reply-To: <1297228927-23497-1-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
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
next prev parent reply other threads:[~2011-02-09 5:22 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Benoit Goby [this message]
[not found] ` <1297228927-23497-4-git-send-email-benoit-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org>
2011-02-09 9:01 ` [PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1297228927-23497-4-git-send-email-benoit@android.com \
--to=benoit-z5hga2qsfarbdgjk7y7tuq@public.gmane.org \
--cc=dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org \
--cc=gregkh-l3A5Bk7waGM@public.gmane.org \
--cc=konkers-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org \
--cc=linux-tegra-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-usb-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=olof-nZhT3qVonbNeoWH0uzbU5w@public.gmane.org \
--cc=rmorell-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.