*
  * Description:
  * Freescale high-speed USB SOC DR module device controller driver.
- * This can be found on MPC8349E/MPC8313E cpus.
+ * This can be found on MPC8349E/MPC8313E/MPC5121E cpus.
  * The driver is previously named as mpc_udc.  Based on bare board
  * code from Dave Liu and Shlomi Gridish.
  *
 #include <asm/system.h>
 #include <asm/unaligned.h>
 #include <asm/dma.h>
+#include <asm/cacheflush.h>
 
 #include "fsl_usb2_udc.h"
 
 
        /* Set the controller as device mode */
        tmp = fsl_readl(&dr_regs->usbmode);
+       tmp &= ~USB_MODE_CTRL_MODE_MASK;        /* clear mode bits */
        tmp |= USB_MODE_CTRL_MODE_DEVICE;
        /* Disable Setup Lockout */
        tmp |= USB_MODE_SETUP_LOCK_OFF;
+       if (udc->pdata->es)
+               tmp |= USB_MODE_ES;
        fsl_writel(tmp, &dr_regs->usbmode);
 
        /* Clear the setup status */
 
        /* Config control enable i/o output, cpu endian register */
 #ifndef CONFIG_ARCH_MXC
-       ctrl = __raw_readl(&usb_sys_regs->control);
-       ctrl |= USB_CTRL_IOENB;
-       __raw_writel(ctrl, &usb_sys_regs->control);
+       if (udc->pdata->have_sysif_regs) {
+               ctrl = __raw_readl(&usb_sys_regs->control);
+               ctrl |= USB_CTRL_IOENB;
+               __raw_writel(ctrl, &usb_sys_regs->control);
+       }
 #endif
 
 #if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE)
        /* Turn on cache snooping hardware, since some PowerPC platforms
         * wholly rely on hardware to deal with cache coherent. */
 
-       /* Setup Snooping for all the 4GB space */
-       tmp = SNOOP_SIZE_2GB;   /* starts from 0x0, size 2G */
-       __raw_writel(tmp, &usb_sys_regs->snoop1);
-       tmp |= 0x80000000;      /* starts from 0x8000000, size 2G */
-       __raw_writel(tmp, &usb_sys_regs->snoop2);
+       if (udc->pdata->have_sysif_regs) {
+               /* Setup Snooping for all the 4GB space */
+               tmp = SNOOP_SIZE_2GB;   /* starts from 0x0, size 2G */
+               __raw_writel(tmp, &usb_sys_regs->snoop1);
+               tmp |= 0x80000000;      /* starts from 0x8000000, size 2G */
+               __raw_writel(tmp, &usb_sys_regs->snoop2);
+       }
 #endif
 
        return 0;
        return status;
 }
 
+static int fsl_ep_fifo_status(struct usb_ep *_ep)
+{
+       struct fsl_ep *ep;
+       struct fsl_udc *udc;
+       int size = 0;
+       u32 bitmask;
+       struct ep_queue_head *d_qh;
+
+       ep = container_of(_ep, struct fsl_ep, ep);
+       if (!_ep || (!ep->desc && ep_index(ep) != 0))
+               return -ENODEV;
+
+       udc = (struct fsl_udc *)ep->udc;
+
+       if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN)
+               return -ESHUTDOWN;
+
+       d_qh = &ep->udc->ep_qh[ep_index(ep) * 2 + ep_is_in(ep)];
+
+       bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) :
+           (1 << (ep_index(ep)));
+
+       if (fsl_readl(&dr_regs->endptstatus) & bitmask)
+               size = (d_qh->size_ioc_int_sts & DTD_PACKET_SIZE)
+                   >> DTD_LENGTH_BIT_POS;
+
+       pr_debug("%s %u\n", __func__, size);
+       return size;
+}
+
 static void fsl_ep_fifo_flush(struct usb_ep *_ep)
 {
        struct fsl_ep *ep;
        .dequeue = fsl_ep_dequeue,
 
        .set_halt = fsl_ep_set_halt,
+       .fifo_status = fsl_ep_fifo_status,
        .fifo_flush = fsl_ep_fifo_flush,        /* flush fifo */
 };
 
        req = udc->status_req;
        /* Fill in the reqest structure */
        *((u16 *) req->req.buf) = cpu_to_le16(tmp);
+
+       /* flush cache for the req buffer */
+       flush_dcache_range((u32)req->req.buf, (u32)req->req.buf + 8);
+
        req->ep = ep;
        req->req.length = 2;
        req->req.status = -EINPROGRESS;
                /* Status phase from udc */
        {
                int rc = -EOPNOTSUPP;
+               u16 ptc = 0;
 
                if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK))
                                == (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) {
                                | USB_TYPE_STANDARD)) {
                        /* Note: The driver has not include OTG support yet.
                         * This will be set when OTG support is added */
-                       if (!gadget_is_otg(&udc->gadget))
-                               break;
-                       else if (setup->bRequest == USB_DEVICE_B_HNP_ENABLE)
-                               udc->gadget.b_hnp_enable = 1;
-                       else if (setup->bRequest == USB_DEVICE_A_HNP_SUPPORT)
-                               udc->gadget.a_hnp_support = 1;
-                       else if (setup->bRequest ==
-                                       USB_DEVICE_A_ALT_HNP_SUPPORT)
-                               udc->gadget.a_alt_hnp_support = 1;
-                       else
-                               break;
+                       if (wValue == USB_DEVICE_TEST_MODE)
+                               ptc = wIndex >> 8;
+                       else if (gadget_is_otg(&udc->gadget)) {
+                               if (setup->bRequest ==
+                                   USB_DEVICE_B_HNP_ENABLE)
+                                       udc->gadget.b_hnp_enable = 1;
+                               else if (setup->bRequest ==
+                                        USB_DEVICE_A_HNP_SUPPORT)
+                                       udc->gadget.a_hnp_support = 1;
+                               else if (setup->bRequest ==
+                                        USB_DEVICE_A_ALT_HNP_SUPPORT)
+                                       udc->gadget.a_alt_hnp_support = 1;
+                       }
                        rc = 0;
                } else
                        break;
                        if (ep0_prime_status(udc, EP_DIR_IN))
                                ep0stall(udc);
                }
+               if (ptc) {
+                       u32 tmp;
+
+                       mdelay(10);
+                       tmp = fsl_readl(&dr_regs->portsc1) | (ptc << 16);
+                       fsl_writel(tmp, &dr_regs->portsc1);
+                       printk(KERN_INFO "udc: switch to test mode %d.\n", ptc);
+               }
+
                return;
        }
 
        next += t;
 
 #ifndef CONFIG_ARCH_MXC
-       tmp_reg = usb_sys_regs->snoop1;
-       t = scnprintf(next, size, "Snoop1 Reg : = [0x%x]\n\n", tmp_reg);
-       size -= t;
-       next += t;
+       if (udc->pdata->have_sysif_regs) {
+               tmp_reg = usb_sys_regs->snoop1;
+               t = scnprintf(next, size, "Snoop1 Reg : = [0x%x]\n\n", tmp_reg);
+               size -= t;
+               next += t;
 
-       tmp_reg = usb_sys_regs->control;
-       t = scnprintf(next, size, "General Control Reg : = [0x%x]\n\n",
-                       tmp_reg);
-       size -= t;
-       next += t;
+               tmp_reg = usb_sys_regs->control;
+               t = scnprintf(next, size, "General Control Reg : = [0x%x]\n\n",
+                               tmp_reg);
+               size -= t;
+               next += t;
+       }
 #endif
 
        /* ------fsl_udc, fsl_ep, fsl_request structure information ----- */
                goto err_release_mem_region;
        }
 
+       pdata->regs = (void *)dr_regs;
+
+       /*
+        * do platform specific init: check the clock, grab/config pins, etc.
+        */
+       if (pdata->init && pdata->init(pdev)) {
+               ret = -ENODEV;
+               goto err_iounmap_noclk;
+       }
+
+       /* Set accessors only after pdata->init() ! */
        if (pdata->big_endian_mmio) {
                _fsl_readl = _fsl_readl_be;
                _fsl_writel = _fsl_writel_be;
        }
 
 #ifndef CONFIG_ARCH_MXC
-       usb_sys_regs = (struct usb_sys_interface *)
-                       ((u32)dr_regs + USB_DR_SYS_OFFSET);
+       if (pdata->have_sysif_regs)
+               usb_sys_regs = (struct usb_sys_interface *)
+                               ((u32)dr_regs + USB_DR_SYS_OFFSET);
 #endif
 
        /* Initialize USB clocks */
 err_free_irq:
        free_irq(udc_controller->irq, udc_controller);
 err_iounmap:
+       if (pdata->exit)
+               pdata->exit(pdev);
        fsl_udc_clk_release();
 err_iounmap_noclk:
        iounmap(dr_regs);
 static int __exit fsl_udc_remove(struct platform_device *pdev)
 {
        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data;
 
        DECLARE_COMPLETION(done);
 
        /* free udc --wait for the release() finished */
        wait_for_completion(&done);
 
+       /*
+        * do platform specific un-initialization:
+        * release iomux pins, etc.
+        */
+       if (pdata->exit)
+               pdata->exit(pdev);
+
        return 0;
 }