usb: gadget: add preliminary Tegra support to fsl driver
Erik Gilling [Tue, 9 Feb 2010 23:41:00 +0000 (15:41 -0800)]
Based on work by Gary King.

Further abstraction of the chipidea core support needs to be done.

Signed-off-by: Colin Cross <ccross@android.com>
Cc: Erik Gilling <konkers@android.com>
Cc: Gary King <GKing@nvidia.com>

drivers/usb/gadget/Kconfig
drivers/usb/gadget/Makefile
drivers/usb/gadget/fsl_tegra_udc.c [new file with mode: 0644]
drivers/usb/gadget/fsl_udc_core.c
drivers/usb/gadget/fsl_usb2_udc.h

index 6f50491..49b945f 100644 (file)
@@ -141,7 +141,7 @@ config USB_ATMEL_USBA
 
 config USB_FSL_USB2
        tristate "Freescale Highspeed USB DR Peripheral Controller"
-       depends on FSL_SOC || ARCH_MXC
+       depends on FSL_SOC || ARCH_MXC || ARCH_TEGRA
        select USB_GADGET_DUALSPEED
        select USB_FSL_MPH_DR_OF if OF
        help
index 9ba725a..51f7c0f 100644 (file)
@@ -19,6 +19,7 @@ obj-$(CONFIG_USB_ATMEL_USBA)  += atmel_usba_udc.o
 obj-$(CONFIG_USB_FSL_USB2)     += fsl_usb2_udc.o
 fsl_usb2_udc-y                 := fsl_udc_core.o
 fsl_usb2_udc-$(CONFIG_ARCH_MXC)        += fsl_mxc_udc.o
+fsl_usb2_udc-$(CONFIG_ARCH_TEGRA) += fsl_tegra_udc.o
 obj-$(CONFIG_USB_M66592)       += m66592-udc.o
 obj-$(CONFIG_USB_R8A66597)     += r8a66597-udc.o
 obj-$(CONFIG_USB_FSL_QE)       += fsl_qe_udc.o
diff --git a/drivers/usb/gadget/fsl_tegra_udc.c b/drivers/usb/gadget/fsl_tegra_udc.c
new file mode 100644 (file)
index 0000000..0cb9e37
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Description:
+ * Helper functions to support the tegra USB controller
+ *
+ * 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.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/fsl_devices.h>
+#include <linux/platform_device.h>
+
+int fsl_udc_clk_init(struct platform_device *pdev)
+{
+
+       return 0;
+
+}
+
+void fsl_udc_clk_finalize(struct platform_device *pdev)
+{
+}
+
+void fsl_udc_clk_release(void)
+{
+}
+
+void fsl_udc_clk_suspend(void)
+{
+}
+
+void fsl_udc_clk_resume(void)
+{
+}
index de24a42..c9b5d7a 100644 (file)
 
 #include "fsl_usb2_udc.h"
 
+#ifdef CONFIG_ARCH_TEGRA
+#define        DRIVER_DESC     "NVidia Tegra High-Speed USB SOC Device Controller driver"
+#else
 #define        DRIVER_DESC     "Freescale High-Speed USB SOC Device Controller driver"
+#endif
 #define        DRIVER_AUTHOR   "Li Yang/Jiang Bo"
 #define        DRIVER_VERSION  "Apr 20, 2007"
 
 #define        DMA_ADDR_INVALID        (~(dma_addr_t)0)
 
+#ifdef CONFIG_ARCH_TEGRA
+static const char driver_name[] = "fsl-tegra-udc";
+#else
 static const char driver_name[] = "fsl-usb2-udc";
+#endif
 static const char driver_desc[] = DRIVER_DESC;
 
 static struct usb_dr_device *dr_regs;
@@ -245,7 +253,7 @@ static int dr_controller_setup(struct fsl_udc *udc)
 {
        unsigned int tmp, portctrl, ep_num;
        unsigned int max_no_of_ep;
-#ifndef CONFIG_ARCH_MXC
+#if !defined(CONFIG_ARCH_MXC) && !defined(CONFIG_ARCH_TEGRA)
        unsigned int ctrl;
 #endif
        unsigned long timeout;
@@ -301,6 +309,19 @@ static int dr_controller_setup(struct fsl_udc *udc)
                tmp |= USB_MODE_ES;
        fsl_writel(tmp, &dr_regs->usbmode);
 
+#ifdef CONFIG_ARCH_TEGRA
+       /* Wait for controller to switch to device mode */
+       timeout = jiffies + FSL_UDC_RESET_TIMEOUT;
+       while ((fsl_readl(&dr_regs->usbmode) & USB_MODE_CTRL_MODE_DEVICE) !=
+              USB_MODE_CTRL_MODE_DEVICE) {
+               if (time_after(jiffies, timeout)) {
+                       ERR("udc device mode setup timeout!\n");
+                       return -ETIMEDOUT;
+               }
+               cpu_relax();
+       }
+#endif
+
        /* Clear the setup status */
        fsl_writel(0, &dr_regs->usbsts);
 
@@ -321,7 +342,7 @@ static int dr_controller_setup(struct fsl_udc *udc)
                fsl_writel(tmp, &dr_regs->endptctrl[ep_num]);
        }
        /* Config control enable i/o output, cpu endian register */
-#ifndef CONFIG_ARCH_MXC
+#if !defined(CONFIG_ARCH_MXC) && !defined(CONFIG_ARCH_TEGRA)
        if (udc->pdata->have_sysif_regs) {
                ctrl = __raw_readl(&usb_sys_regs->control);
                ctrl |= USB_CTRL_IOENB;
@@ -349,6 +370,20 @@ static int dr_controller_setup(struct fsl_udc *udc)
 static void dr_controller_run(struct fsl_udc *udc)
 {
        u32 temp;
+#ifdef CONFIG_ARCH_TEGRA
+       unsigned long timeout;
+#define FSL_UDC_RUN_TIMEOUT 1000
+#endif
+
+#ifdef CONFIG_ARCH_TEGRA
+       /* Enable cable detection interrupt, without setting the
+        * USB_SYS_VBUS_WAKEUP_INT bit. USB_SYS_VBUS_WAKEUP_INT is
+        * clear on write */
+       temp = fsl_readl(&usb_sys_regs->vbus_wakeup);
+       temp |= (USB_SYS_VBUS_WAKEUP_INT_ENABLE | USB_SYS_VBUS_WAKEUP_ENABLE);
+       temp &= ~USB_SYS_VBUS_WAKEUP_INT_STATUS;
+       fsl_writel(temp, &usb_sys_regs->vbus_wakeup);
+#endif
 
        /* Enable DR irq reg */
        temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN
@@ -369,6 +404,21 @@ static void dr_controller_run(struct fsl_udc *udc)
        temp = fsl_readl(&dr_regs->usbcmd);
        temp |= USB_CMD_RUN_STOP;
        fsl_writel(temp, &dr_regs->usbcmd);
+
+#ifdef CONFIG_ARCH_TEGRA
+       /* Wait for controller to start */
+       timeout = jiffies + FSL_UDC_RUN_TIMEOUT;
+       while ((fsl_readl(&dr_regs->usbcmd) & USB_CMD_RUN_STOP) !=
+              USB_CMD_RUN_STOP) {
+               if (time_after(jiffies, timeout)) {
+                       ERR("udc start timeout!\n");
+                       return;
+               }
+               cpu_relax();
+       }
+#endif
+
+       return;
 }
 
 static void dr_controller_stop(struct fsl_udc *udc)
@@ -641,16 +691,22 @@ static int fsl_ep_disable(struct usb_ep *_ep)
        }
 
        /* disable ep on controller */
-       ep_num = ep_index(ep);
-       epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]);
-       if (ep_is_in(ep)) {
-               epctrl &= ~(EPCTRL_TX_ENABLE | EPCTRL_TX_TYPE);
-               epctrl |= EPCTRL_EP_TYPE_BULK << EPCTRL_TX_EP_TYPE_SHIFT;
-       } else {
-               epctrl &= ~(EPCTRL_RX_ENABLE | EPCTRL_TX_TYPE);
-               epctrl |= EPCTRL_EP_TYPE_BULK << EPCTRL_RX_EP_TYPE_SHIFT;
+#ifdef CONFIG_ARCH_TEGRA
+       /* Touch the registers if cable is connected and phy is on */
+       if (fsl_readl(&usb_sys_regs->vbus_wakeup) & USB_SYS_VBUS_STATUS)
+#endif
+       {
+               ep_num = ep_index(ep);
+               epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]);
+               if (ep_is_in(ep)) {
+                       epctrl &= ~(EPCTRL_TX_ENABLE | EPCTRL_TX_TYPE);
+                       epctrl |= EPCTRL_EP_TYPE_BULK << EPCTRL_TX_EP_TYPE_SHIFT;
+               } else {
+                       epctrl &= ~(EPCTRL_RX_ENABLE | EPCTRL_TX_TYPE);
+                       epctrl |= EPCTRL_EP_TYPE_BULK << EPCTRL_RX_EP_TYPE_SHIFT;
+               }
+               fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]);
        }
-       fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]);
 
        udc = (struct fsl_udc *)ep->udc;
        spin_lock_irqsave(&udc->lock, flags);
@@ -1100,6 +1156,12 @@ static void fsl_ep_fifo_flush(struct usb_ep *_ep)
        unsigned long timeout;
 #define FSL_UDC_FLUSH_TIMEOUT 1000
 
+#ifdef CONFIG_ARCH_TEGRA
+       /* Touch the registers if cable is connected and phy is on */
+       if (!(fsl_readl(&usb_sys_regs->vbus_wakeup) & USB_SYS_VBUS_STATUS))
+               return;
+#endif
+
        if (!_ep) {
                return;
        } else {
@@ -1677,7 +1739,12 @@ static void dtd_complete_irq(struct fsl_udc *udc)
        if (!bit_pos)
                return;
 
+#ifdef CONFIG_ARCH_TEGRA
+       /* XXX what's going on here */
+       for (i = 0; i < udc->max_ep; i++) {
+#else
        for (i = 0; i < udc->max_ep * 2; i++) {
+#endif
                ep_num = i >> 1;
                direction = i % 2;
 
@@ -1828,6 +1895,15 @@ static void reset_irq(struct fsl_udc *udc)
        /* Write 1s to the flush register */
        fsl_writel(0xffffffff, &dr_regs->endptflush);
 
+#if defined(CONFIG_ARCH_TEGRA)
+       /* When the bus reset is seen on Tegra, the PORTSCX_PORT_RESET bit
+        * is not set */
+       VDBG("Bus reset");
+       /* Reset all the queues, include XD, dTD, EP queue
+        * head and TR Queue */
+       reset_queues(udc);
+       udc->usb_state = USB_STATE_DEFAULT;
+#else
        if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) {
                VDBG("Bus reset");
                /* Bus is reseting */
@@ -1851,6 +1927,7 @@ static void reset_irq(struct fsl_udc *udc)
                dr_controller_run(udc);
                udc->usb_state = USB_STATE_ATTACHED;
        }
+#endif
 }
 
 /*
@@ -1862,6 +1939,9 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
        u32 irq_src;
        irqreturn_t status = IRQ_NONE;
        unsigned long flags;
+#if defined(CONFIG_ARCH_TEGRA)
+       u32 temp;
+#endif
 
        /* Disable ISR for OTG host mode */
        if (udc->stopped)
@@ -1872,6 +1952,34 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
        fsl_writel(irq_src, &dr_regs->usbsts);
 
        /* VDBG("irq_src [0x%8x]", irq_src); */
+#if defined(CONFIG_ARCH_TEGRA)
+       /* VBUS A session detection interrupts. When the interrupt is received,
+        * the mark the vbus active shadow.
+        */
+       temp = fsl_readl(&usb_sys_regs->vbus_sensors);
+       if (temp & USB_SYS_VBUS_ASESSION_CHANGED) {
+               if (temp & USB_SYS_VBUS_ASESSION) {
+                       udc->vbus_active = 1;
+               } else {
+                       reset_queues(udc);
+                       udc->vbus_active = 0;
+                       udc->usb_state = USB_STATE_DEFAULT;
+               }
+               /* write back the register to clear the interrupt */
+               fsl_writel(temp, &usb_sys_regs->vbus_wakeup);
+
+#if 0
+               /* XXX */
+               if (udc->vbus_active) {
+                       fsl_udc_clk_resume();
+               } else {
+                       fsl_udc_clk_suspend();
+               }
+#endif
+
+               status = IRQ_HANDLED;
+       }
+#endif
 
        /* Need to resume? */
        if (udc->usb_state == USB_STATE_SUSPENDED)
@@ -2053,7 +2161,11 @@ static int fsl_stop(struct usb_gadget_driver *driver)
 
 #include <linux/seq_file.h>
 
+#ifdef CONFIG_ARCH_TEGRA
+static const char proc_filename[] = "driver/fsl_tegra_udc";
+#else
 static const char proc_filename[] = "driver/fsl_usb2_udc";
+#endif
 
 static int fsl_proc_read(char *page, char **start, off_t off, int count,
                int *eof, void *_dev)
@@ -2235,7 +2347,7 @@ static int fsl_proc_read(char *page, char **start, off_t off, int count,
        size -= t;
        next += t;
 
-#ifndef CONFIG_ARCH_MXC
+#if !defined(CONFIG_ARCH_MXC) && !defined(CONFIG_ARCH_TEGRA)
        if (udc->pdata->have_sysif_regs) {
                tmp_reg = usb_sys_regs->snoop1;
                t = scnprintf(next, size, "Snoop1 Reg : = [0x%x]\n\n", tmp_reg);
@@ -2352,6 +2464,13 @@ static int __init struct_udc_setup(struct fsl_udc *udc,
                return -1;
        }
 
+#ifdef CONFIG_ARCH_TEGRA
+       /* Tegra uses hardware queue heads */
+       size = udc->max_ep * sizeof(struct ep_queue_head);
+       udc->ep_qh = (struct ep_queue_head *)((u8 *)dr_regs + QH_OFFSET);
+       udc->ep_qh_dma = platform_get_resource(pdev, IORESOURCE_MEM, 0)->start +
+               QH_OFFSET;
+#else
        /* initialized QHs, take care of alignment */
        size = udc->max_ep * sizeof(struct ep_queue_head);
        if (size < QH_ALIGNMENT)
@@ -2367,6 +2486,7 @@ static int __init struct_udc_setup(struct fsl_udc *udc,
                kfree(udc->eps);
                return -1;
        }
+#endif
 
        udc->ep_qh_size = size;
 
@@ -2431,6 +2551,9 @@ static int __init fsl_udc_probe(struct platform_device *pdev)
        int ret = -ENODEV;
        unsigned int i;
        u32 dccparams;
+#if defined(CONFIG_ARCH_TEGRA)
+       struct resource *res_sys = NULL;
+#endif
 
        if (strcmp(pdev->name, driver_name)) {
                VDBG("Wrong device");
@@ -2493,7 +2616,21 @@ static int __init fsl_udc_probe(struct platform_device *pdev)
        /* Set accessors only after pdata->init() ! */
        fsl_set_accessors(pdata);
 
-#ifndef CONFIG_ARCH_MXC
+#if defined(CONFIG_ARCH_TEGRA)
+       /* If the PHY registers are NOT provided as a seperate aperture, then
+        * we should be using the registers inside the controller aperture. */
+       res_sys = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       if (res_sys)  {
+               usb_sys_regs = ioremap(res_sys->start, resource_size(res_sys));
+               if (!usb_sys_regs)
+                       goto err_release_mem_region;
+       } else {
+               usb_sys_regs = (struct usb_sys_interface *)
+                       ((u32)dr_regs + USB_DR_SYS_OFFSET);
+       }
+#endif
+
+#if !defined(CONFIG_ARCH_MXC) && !defined(CONFIG_ARCH_TEGRA)
        if (pdata->have_sysif_regs)
                usb_sys_regs = (struct usb_sys_interface *)
                                ((u32)dr_regs + USB_DR_SYS_OFFSET);
@@ -2599,6 +2736,16 @@ static int __init fsl_udc_probe(struct platform_device *pdev)
                goto err_del_udc;
 
        create_proc_file();
+
+#if 0
+/* XXX */
+#ifdef CONFIG_ARCH_TEGRA
+       /* Power down the phy if cable is not connected */
+       if (!(fsl_readl(&usb_sys_regs->vbus_wakeup) & USB_SYS_VBUS_STATUS))
+               platform_udc_clk_suspend();
+#endif
+#endif
+
        return 0;
 
 err_del_udc:
index 1d51be8..bb10c5f 100644 (file)
@@ -84,6 +84,15 @@ struct usb_dr_host {
 };
 
  /* non-EHCI USB system interface registers (Big Endian) */
+#ifdef CONFIG_ARCH_TEGRA
+struct usb_sys_interface {
+       u32 suspend_ctrl;
+       u32 vbus_sensors;
+       u32 vbus_wakeup;
+       u32 vbus_alt_status;
+       u32 legacy_ctrl;
+};
+#else
 struct usb_sys_interface {
        u32 snoop1;
        u32 snoop2;
@@ -93,6 +102,7 @@ struct usb_sys_interface {
        u8 res[236];
        u32 control;            /* General Purpose Control Register */
 };
+#endif
 
 /* ep0 transfer state */
 #define WAIT_FOR_SETUP          0
@@ -422,10 +432,19 @@ struct ep_td_struct {
 /* Alignment requirements; must be a power of two */
 #define DTD_ALIGNMENT                          0x20
 #define QH_ALIGNMENT                           2048
+#define QH_OFFSET                              0x1000
 
 /* Controller dma boundary */
 #define UDC_DMA_BOUNDARY                       0x1000
 
+#define USB_SYS_VBUS_ASESSION_INT_EN           0x10000
+#define USB_SYS_VBUS_ASESSION_CHANGED          0x20000
+#define USB_SYS_VBUS_ASESSION                  0x40000
+#define USB_SYS_VBUS_WAKEUP_ENABLE             0x40000000
+#define USB_SYS_VBUS_WAKEUP_INT_ENABLE         0x100
+#define USB_SYS_VBUS_WAKEUP_INT_STATUS         0x200
+#define USB_SYS_VBUS_STATUS                    0x400
+
 /*-------------------------------------------------------------------------*/
 
 /* ### driver private data
@@ -570,10 +589,12 @@ static void dump_msg(const char *label, const u8 * buf, unsigned int length)
 #define get_pipe_by_ep(EP)     (ep_index(EP) * 2 + ep_is_in(EP))
 
 struct platform_device;
-#ifdef CONFIG_ARCH_MXC
+#if defined(CONFIG_ARCH_MXC) || defined(CONFIG_ARCH_TEGRA)
 int fsl_udc_clk_init(struct platform_device *pdev);
 void fsl_udc_clk_finalize(struct platform_device *pdev);
 void fsl_udc_clk_release(void);
+void fsl_udc_clk_suspend(void);
+void fsl_udc_clk_resume(void);
 #else
 static inline int fsl_udc_clk_init(struct platform_device *pdev)
 {
@@ -585,6 +606,12 @@ static inline void fsl_udc_clk_finalize(struct platform_device *pdev)
 static inline void fsl_udc_clk_release(void)
 {
 }
+static inline void fsl_udc_clk_suspend(void)
+{
+}
+static inline void fsl_udc_clk_resume(void)
+{
+}
 #endif
 
 #endif