From 550462378515a82279e07f12e2c105f617f112f8 Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Mon, 7 Mar 2011 10:28:42 +0530 Subject: serial: msm_serial_hs: Add MSM high speed UART driver This driver supports UART-DM HW on MSM platforms. It uses the on chip DMA to drive data transfers and has optional support for UART power management independent of Linux suspend/resume and wakeup from Rx. The driver was originally developed by Google. It is functionally equivalent to the version available at: http://android.git.kernel.org/?p=kernel/experimental.git the differences being: 1) Remove wakelocks and change unsupported DMA API. 2) Replace clock selection register codes by macros. 3) Fix checkpatch errors and add inline documentation. 4) Add runtime PM hooks for active power state transitions. 5) Handle error path and cleanup resources if required. CC: Nick Pelly Signed-off-by: Sankalp Bose Signed-off-by: Mayank Rana Signed-off-by: Greg Kroah-Hartman --- include/linux/platform_data/msm_serial_hs.h | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 include/linux/platform_data/msm_serial_hs.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/msm_serial_hs.h b/include/linux/platform_data/msm_serial_hs.h new file mode 100644 index 000000000000..98a2046f8b31 --- /dev/null +++ b/include/linux/platform_data/msm_serial_hs.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Author: Nick Pelly + * + * 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 __ASM_ARCH_MSM_SERIAL_HS_H +#define __ASM_ARCH_MSM_SERIAL_HS_H + +#include + +/* API to request the uart clock off or on for low power management + * Clients should call request_clock_off() when no uart data is expected, + * and must call request_clock_on() before any further uart data can be + * received. */ +extern void msm_hs_request_clock_off(struct uart_port *uport); +extern void msm_hs_request_clock_on(struct uart_port *uport); + +/** + * struct msm_serial_hs_platform_data + * @rx_wakeup_irq: Rx activity irq + * @rx_to_inject: extra character to be inserted to Rx tty on wakeup + * @inject_rx: 1 = insert rx_to_inject. 0 = do not insert extra character + * @exit_lpm_cb: function called before every Tx transaction + * + * This is an optional structure required for UART Rx GPIO IRQ based + * wakeup from low power state. UART wakeup can be triggered by RX activity + * (using a wakeup GPIO on the UART RX pin). This should only be used if + * there is not a wakeup GPIO on the UART CTS, and the first RX byte is + * known (eg., with the Bluetooth Texas Instruments HCILL protocol), + * since the first RX byte will always be lost. RTS will be asserted even + * while the UART is clocked off in this mode of operation. + */ +struct msm_serial_hs_platform_data { + int rx_wakeup_irq; + unsigned char inject_rx_on_wakeup; + char rx_to_inject; + void (*exit_lpm_cb)(struct uart_port *); +}; + +#endif -- cgit v1.2.3 From f1a304e7941cc76353363a139cbb6a4b1ca7c737 Mon Sep 17 00:00:00 2001 From: Pratheesh Gangadhar Date: Sat, 5 Mar 2011 04:30:17 +0530 Subject: UIO: add PRUSS UIO driver support This patch implements PRUSS (Programmable Real-time Unit Sub System) UIO driver which exports SOC resources associated with PRUSS like I/O, memories and IRQs to user space. PRUSS is dual 32-bit RISC processors which is efficient in performing embedded tasks that require manipulation of packed memory mapped data structures and handling system events that have tight real time constraints. This driver is currently supported on Texas Instruments DA850, AM18xx and OMAP-L138 devices. For example, PRUSS runs firmware for real-time critical industrial communication data link layer and communicates with application stack running in user space via shared memory and IRQs. Signed-off-by: Pratheesh Gangadhar Reviewed-by: Thomas Gleixner Reviewed-by: Arnd Bergmann Signed-off-by: Hans J. Koch Signed-off-by: Greg Kroah-Hartman --- drivers/uio/Kconfig | 17 +++ drivers/uio/Makefile | 1 + drivers/uio/uio_pruss.c | 247 ++++++++++++++++++++++++++++++++ include/linux/platform_data/uio_pruss.h | 25 ++++ 4 files changed, 290 insertions(+) create mode 100644 drivers/uio/uio_pruss.c create mode 100644 include/linux/platform_data/uio_pruss.h (limited to 'include/linux/platform_data') diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index bb440792a1b7..6f3ea9bbc818 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -94,4 +94,21 @@ config UIO_NETX To compile this driver as a module, choose M here; the module will be called uio_netx. +config UIO_PRUSS + tristate "Texas Instruments PRUSS driver" + depends on ARCH_DAVINCI_DA850 + help + PRUSS driver for OMAPL138/DA850/AM18XX devices + PRUSS driver requires user space components, examples and user space + driver is available from below SVN repo - you may use anonymous login + + https://gforge.ti.com/gf/project/pru_sw/ + + More info on API is available at below wiki + + http://processors.wiki.ti.com/index.php/PRU_Linux_Application_Loader + + To compile this driver as a module, choose M here: the module + will be called uio_pruss. + endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index 18fd818c5b97..d4dd9a5552f8 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_UIO_AEC) += uio_aec.o obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o obj-$(CONFIG_UIO_NETX) += uio_netx.o +obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c new file mode 100644 index 000000000000..daf6e77de2b1 --- /dev/null +++ b/drivers/uio/uio_pruss.c @@ -0,0 +1,247 @@ +/* + * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss) + * + * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM, + * and DDR RAM to user space for applications interacting with PRUSS firmware + * + * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "pruss_uio" +#define DRV_VERSION "1.0" + +static int sram_pool_sz = SZ_16K; +module_param(sram_pool_sz, int, 0); +MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate "); + +static int extram_pool_sz = SZ_256K; +module_param(extram_pool_sz, int, 0); +MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate"); + +/* + * Host event IRQ numbers from PRUSS - PRUSS can generate upto 8 interrupt + * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS + * firmware and user space application, async notification from PRU firmware + * to user space application + * 3 PRU_EVTOUT0 + * 4 PRU_EVTOUT1 + * 5 PRU_EVTOUT2 + * 6 PRU_EVTOUT3 + * 7 PRU_EVTOUT4 + * 8 PRU_EVTOUT5 + * 9 PRU_EVTOUT6 + * 10 PRU_EVTOUT7 +*/ +#define MAX_PRUSS_EVT 8 + +#define PINTC_HIDISR 0x0038 +#define PINTC_HIPIR 0x0900 +#define HIPIR_NOPEND 0x80000000 +#define PINTC_HIER 0x1500 + +struct uio_pruss_dev { + struct uio_info *info; + struct clk *pruss_clk; + dma_addr_t sram_paddr; + dma_addr_t ddr_paddr; + void __iomem *prussio_vaddr; + void *sram_vaddr; + void *ddr_vaddr; + unsigned int hostirq_start; + unsigned int pintc_base; +}; + +static irqreturn_t pruss_handler(int irq, struct uio_info *info) +{ + struct uio_pruss_dev *gdev = info->priv; + int intr_bit = (irq - gdev->hostirq_start + 2); + int val, intr_mask = (1 << intr_bit); + void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base; + void __iomem *intren_reg = base + PINTC_HIER; + void __iomem *intrdis_reg = base + PINTC_HIDISR; + void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2); + + val = ioread32(intren_reg); + /* Is interrupt enabled and active ? */ + if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND)) + return IRQ_NONE; + /* Disable interrupt */ + iowrite32(intr_bit, intrdis_reg); + return IRQ_HANDLED; +} + +static void pruss_cleanup(struct platform_device *dev, + struct uio_pruss_dev *gdev) +{ + int cnt; + struct uio_info *p = gdev->info; + + for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) { + uio_unregister_device(p); + kfree(p->name); + } + iounmap(gdev->prussio_vaddr); + if (gdev->ddr_vaddr) { + dma_free_coherent(&dev->dev, extram_pool_sz, gdev->ddr_vaddr, + gdev->ddr_paddr); + } + if (gdev->sram_vaddr) + sram_free(gdev->sram_vaddr, sram_pool_sz); + kfree(gdev->info); + clk_put(gdev->pruss_clk); + kfree(gdev); +} + +static int __devinit pruss_probe(struct platform_device *dev) +{ + struct uio_info *p; + struct uio_pruss_dev *gdev; + struct resource *regs_prussio; + int ret = -ENODEV, cnt = 0, len; + struct uio_pruss_pdata *pdata = dev->dev.platform_data; + + gdev = kzalloc(sizeof(struct uio_pruss_dev), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + + gdev->info = kzalloc(sizeof(*p) * MAX_PRUSS_EVT, GFP_KERNEL); + if (!gdev->info) { + kfree(gdev); + return -ENOMEM; + } + /* Power on PRU in case its not done as part of boot-loader */ + gdev->pruss_clk = clk_get(&dev->dev, "pruss"); + if (IS_ERR(gdev->pruss_clk)) { + dev_err(&dev->dev, "Failed to get clock\n"); + kfree(gdev->info); + kfree(gdev); + ret = PTR_ERR(gdev->pruss_clk); + return ret; + } else { + clk_enable(gdev->pruss_clk); + } + + regs_prussio = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!regs_prussio) { + dev_err(&dev->dev, "No PRUSS I/O resource specified\n"); + goto out_free; + } + + if (!regs_prussio->start) { + dev_err(&dev->dev, "Invalid memory resource\n"); + goto out_free; + } + + gdev->sram_vaddr = sram_alloc(sram_pool_sz, &(gdev->sram_paddr)); + if (!gdev->sram_vaddr) { + dev_err(&dev->dev, "Could not allocate SRAM pool\n"); + goto out_free; + } + + gdev->ddr_vaddr = dma_alloc_coherent(&dev->dev, extram_pool_sz, + &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); + if (!gdev->ddr_vaddr) { + dev_err(&dev->dev, "Could not allocate external memory\n"); + goto out_free; + } + + len = resource_size(regs_prussio); + gdev->prussio_vaddr = ioremap(regs_prussio->start, len); + if (!gdev->prussio_vaddr) { + dev_err(&dev->dev, "Can't remap PRUSS I/O address range\n"); + goto out_free; + } + + gdev->pintc_base = pdata->pintc_base; + gdev->hostirq_start = platform_get_irq(dev, 0); + + for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) { + p->mem[0].addr = regs_prussio->start; + p->mem[0].size = resource_size(regs_prussio); + p->mem[0].memtype = UIO_MEM_PHYS; + + p->mem[1].addr = gdev->sram_paddr; + p->mem[1].size = sram_pool_sz; + p->mem[1].memtype = UIO_MEM_PHYS; + + p->mem[2].addr = gdev->ddr_paddr; + p->mem[2].size = extram_pool_sz; + p->mem[2].memtype = UIO_MEM_PHYS; + + p->name = kasprintf(GFP_KERNEL, "pruss_evt%d", cnt); + p->version = DRV_VERSION; + + /* Register PRUSS IRQ lines */ + p->irq = gdev->hostirq_start + cnt; + p->handler = pruss_handler; + p->priv = gdev; + + ret = uio_register_device(&dev->dev, p); + if (ret < 0) + goto out_free; + } + + platform_set_drvdata(dev, gdev); + return 0; + +out_free: + pruss_cleanup(dev, gdev); + return ret; +} + +static int __devexit pruss_remove(struct platform_device *dev) +{ + struct uio_pruss_dev *gdev = platform_get_drvdata(dev); + + pruss_cleanup(dev, gdev); + platform_set_drvdata(dev, NULL); + return 0; +} + +static struct platform_driver pruss_driver = { + .probe = pruss_probe, + .remove = __devexit_p(pruss_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pruss_init_module(void) +{ + return platform_driver_register(&pruss_driver); +} + +module_init(pruss_init_module); + +static void __exit pruss_exit_module(void) +{ + platform_driver_unregister(&pruss_driver); +} + +module_exit(pruss_exit_module); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Amit Chatterjee "); +MODULE_AUTHOR("Pratheesh Gangadhar "); diff --git a/include/linux/platform_data/uio_pruss.h b/include/linux/platform_data/uio_pruss.h new file mode 100644 index 000000000000..f39140aabc6f --- /dev/null +++ b/include/linux/platform_data/uio_pruss.h @@ -0,0 +1,25 @@ +/* + * include/linux/platform_data/uio_pruss.h + * + * Platform data for uio_pruss driver + * + * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _UIO_PRUSS_H_ +#define _UIO_PRUSS_H_ + +/* To configure the PRUSS INTC base offset for UIO driver */ +struct uio_pruss_pdata { + u32 pintc_base; +}; +#endif /* _UIO_PRUSS_H_ */ -- cgit v1.2.3 From 79ad3b5add4a683af02d1b51ccb699d1b01f1fbf Mon Sep 17 00:00:00 2001 From: Benoit Goby Date: Wed, 9 Mar 2011 16:28:56 -0800 Subject: usb: host: Add EHCI driver for NVIDIA Tegra SoCs The Tegra 2 SoC has 3 EHCI compatible USB controllers. This patch adds the necessary glue to allow the ehci-hcd driver to work on Tegra 2 SoCs. The platform data is used to configure board-specific phy settings and to configure the operating mode, as one of the ports may be used as a otg port. For additional power saving, the driver supports powering down the phy on bus suspend when it is used, for example, to connect an internal device that use an out-of-band remote wakeup mechanism (e.g. a gpio). Signed-off-by: Benoit Goby Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 8 + drivers/usb/host/ehci-hcd.c | 5 + drivers/usb/host/ehci-tegra.c | 625 ++++++++++++++++++++++++++++++++ include/linux/platform_data/tegra_usb.h | 31 ++ include/linux/usb/ehci_def.h | 4 +- 5 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/host/ehci-tegra.c create mode 100644 include/linux/platform_data/tegra_usb.h (limited to 'include/linux/platform_data') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index b5a908eacb82..9483acdf2e9e 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -165,6 +165,14 @@ config USB_EHCI_MSM This driver is not supported on boards like trout which has an external PHY. +config USB_EHCI_TEGRA + boolean "NVIDIA Tegra HCD support" + depends on USB_EHCI_HCD && ARCH_TEGRA + select USB_EHCI_ROOT_HUB_TT + help + This driver enables support for the internal USB Host Controllers + found in NVIDIA Tegra SoCs. The controllers are EHCI compliant. + config USB_EHCI_HCD_PPC_OF bool "EHCI support for PPC USB controller on OF platform bus" depends on USB_EHCI_HCD && PPC_OF diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index cfeb24b3ee09..d30c4e08c137 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1260,6 +1260,11 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_hcd_msp_driver #endif +#ifdef CONFIG_USB_EHCI_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 000000000000..6202102a6d75 --- /dev/null +++ b/drivers/usb/host/ehci-tegra.c @@ -0,0 +1,625 @@ +/* + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +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, re-enable disconnect detection */ + 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; + } + + temp &= ~PORT_WKCONN_E; + temp |= PORT_WKDISC_E | PORT_WKOC_E; + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + + /* + * If a transaction is in progress, there may be a delay in + * suspending the port. Poll until the port is suspended. + */ + 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; + + /* Disable disconnect detection during port resume */ + 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); + + /* Poll until the controller clears RESUME and SUSPEND */ + if (handshake(ehci, status_reg, PORT_RESUME, 0, 2000)) + pr_err("%s: timeout waiting for RESUME\n", __func__); + 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 void tegra_ehci_restart(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + ehci_reset(ehci); + + /* 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; + } + + /* Force the phy to keep data lines in suspend state */ + tegra_ehci_phy_restore_start(tegra->phy, tegra->port_speed); + + /* Enable host mode */ + tdi_reset(ehci); + + /* 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 &= ~PORT_TEST(~0); + if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_HIGH) + val |= PORT_TEST_FORCE; + else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL) + val |= PORT_TEST(6); + else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) + val |= PORT_TEST(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 &= ~PORT_TEST(~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); + + /* switch to host mode */ + hcd->has_tt = 1; + ehci_reset(ehci); + + retval = ehci_halt(ehci); + if (retval) + return retval; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + ehci->sbrn = 0x20; + + 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 tegra_ehci_hcd *tegra; + struct tegra_ehci_platform_data *pdata; + 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; + } + + tegra->phy = tegra_usb_phy_open(instance, hcd->regs, pdata->phy_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_usb_phy_power_on(tegra->phy); + if (err) { + dev_err(&pdev->dev, "Failed to power on the phy\n"); + goto fail; + } + + tegra->host_resumed = 1; + tegra->power_down_on_bus_suspend = pdata->power_down_on_bus_suspend; + tegra->ehci = hcd_to_ehci(hcd); + + 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); + +#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) { + 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/platform_data/tegra_usb.h b/include/linux/platform_data/tegra_usb.h new file mode 100644 index 000000000000..6bca5b569acb --- /dev/null +++ b/include/linux/platform_data/tegra_usb.h @@ -0,0 +1,31 @@ +/* + * 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 _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_ */ diff --git a/include/linux/usb/ehci_def.h b/include/linux/usb/ehci_def.h index 2e262cb15425..656380245198 100644 --- a/include/linux/usb/ehci_def.h +++ b/include/linux/usb/ehci_def.h @@ -127,7 +127,9 @@ struct ehci_regs { #define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ #define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ /* 19:16 for port testing */ -#define PORT_TEST_PKT (0x4<<16) /* Port Test Control - packet test */ +#define PORT_TEST(x) (((x)&0xf)<<16) /* Port Test Control */ +#define PORT_TEST_PKT PORT_TEST(0x4) /* Port Test Control - packet test */ +#define PORT_TEST_FORCE PORT_TEST(0x5) /* Port Test Control - force enable */ #define PORT_LED_OFF (0<<14) #define PORT_LED_AMBER (1<<14) #define PORT_LED_GREEN (2<<14) -- cgit v1.2.3