[PATCH 3/4] usb: host: Add EHCI driver for NVIDIA Tegra SoCs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Benoit Goby <benoit@xxxxxxxxxxx>
---
 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@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux