From: Zhang Yan Date: Mon, 7 Mar 2011 02:00:58 +0000 (+0800) Subject: ENGR00141217-4 USB: Add USB Host/device/otg drivers support X-Git-Tag: v3.0.35-fsl~2476 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=70da8d08a130e99289a14f559ef3acb4dadd4c2e;p=karo-tx-linux.git ENGR00141217-4 USB: Add USB Host/device/otg drivers support Add usb support for 2.6.38 Signed-off-by: Zhang Yan --- diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig old mode 100644 new mode 100755 index 8c8ea4343238..fb005e6af47a --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -522,6 +522,31 @@ config USB_GOKU default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_ARC + boolean "Freescale USB Device Controller" + depends on ARCH_MXC || ARCH_STMP3XXX || ARCH_MXS + select USB_GADGET_DUALSPEED + select USB_OTG_UTILS + help + Some Freescale processors have a USBOTG controller, + which supports device mode. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "arc_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_STATIC_IRAM_PPH + bool "Apply static IRAM patch" + depends on USB_GADGET_ARC && (ARCH_MX37 || ARCH_MX3 || ARCH_MX25 || ARCH_MX51) + help + Apply static IRAM patch to peripheral driver. + +config USB_ARC + tristate + depends on USB_GADGET_ARC + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_LANGWELL boolean "Intel Langwell USB Device Controller" depends on PCI diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile old mode 100644 new mode 100755 index 4fe92b18a055..18648b01ea54 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_USB_PXA_U2O) += mv_udc.o mv_udc-y := mv_udc_core.o mv_udc_phy.o obj-$(CONFIG_USB_CI13XXX_MSM) += ci13xxx_msm.o obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o +obj-$(CONFIG_USB_ARC) += arcotg_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c new file mode 100755 index 000000000000..9d4e29e2da36 --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.c @@ -0,0 +1,3302 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#undef VERBOSE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arcotg_udc.h" +#include +#include + +#define DRIVER_DESC "ARC USBOTG Device Controller driver" +#define DRIVER_AUTHOR "Freescale Semiconductor" +#define DRIVER_VERSION "1 August 2005" + +#ifdef CONFIG_PPC_MPC512x +#define BIG_ENDIAN_DESC +#endif + +#ifdef BIG_ENDIAN_DESC +#define cpu_to_hc32(x) (x) +#define hc32_to_cpu(x) (x) +#else +#define cpu_to_hc32(x) cpu_to_le32((x)) +#define hc32_to_cpu(x) le32_to_cpu((x)) +#endif + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) +DEFINE_MUTEX(udc_resume_mutex); +extern void usb_debounce_id_vbus(void); +static const char driver_name[] = "fsl-usb2-udc"; +static const char driver_desc[] = DRIVER_DESC; + +volatile static struct usb_dr_device *dr_regs; +volatile static struct usb_sys_interface *usb_sys_regs; + +/* it is initialized in probe() */ +static struct fsl_udc *udc_controller; + +#ifdef POSTPONE_FREE_LAST_DTD +static struct ep_td_struct *last_free_td; +#endif +static const struct usb_endpoint_descriptor +fsl_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; +static const size_t g_iram_size = IRAM_TD_PPH_SIZE; +static unsigned long g_iram_base; +static __iomem void *g_iram_addr; + +typedef int (*dev_sus)(struct device *dev, pm_message_t state); +typedef int (*dev_res) (struct device *dev); +static int udc_suspend(struct fsl_udc *udc); +static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state); +static int fsl_udc_resume(struct platform_device *pdev); +static void fsl_ep_fifo_flush(struct usb_ep *_ep); + +#ifdef CONFIG_USB_OTG +/* Get platform resource from OTG driver */ +extern struct resource *otg_get_resources(void); +#endif + +extern void fsl_platform_set_test_mode(struct fsl_usb2_platform_data *pdata, enum usb_test_mode mode); + +#ifdef CONFIG_WORKAROUND_ARCUSB_REG_RW +static void safe_writel(u32 val32, volatile u32 *addr) +{ + __asm__ ("swp %0, %0, [%1]" : : "r"(val32), "r"(addr)); +} +#endif + +#ifdef CONFIG_PPC32 +#define fsl_readl(addr) in_le32((addr)) +#define fsl_writel(addr, val32) out_le32((val32), (addr)) +#elif defined (CONFIG_WORKAROUND_ARCUSB_REG_RW) +#define fsl_readl(addr) readl((addr)) +#define fsl_writel(val32, addr) safe_writel(val32, addr) +#else +#define fsl_readl(addr) readl((addr)) +#define fsl_writel(addr, val32) writel((addr), (val32)) +#endif + +/******************************************************************** + * Internal Used Function +********************************************************************/ + +#ifdef DUMP_QUEUES +static void dump_ep_queue(struct fsl_ep *ep) +{ + int ep_index; + struct fsl_req *req; + struct ep_td_struct *dtd; + + if (list_empty(&ep->queue)) { + pr_debug("udc: empty\n"); + return; + } + + ep_index = ep_index(ep) * 2 + ep_is_in(ep); + pr_debug("udc: ep=0x%p index=%d\n", ep, ep_index); + + list_for_each_entry(req, &ep->queue, queue) { + pr_debug("udc: req=0x%p dTD count=%d\n", req, req->dtd_count); + pr_debug("udc: dTD head=0x%p tail=0x%p\n", req->head, + req->tail); + + dtd = req->head; + + while (dtd) { + if (le32_to_cpu(dtd->next_td_ptr) & DTD_NEXT_TERMINATE) + break; /* end of dTD list */ + + dtd = dtd->next_td_virt; + } + } +} +#else +static inline void dump_ep_queue(struct fsl_ep *ep) +{ +} +#endif + +#if (defined CONFIG_ARCH_MX35 || defined CONFIG_ARCH_MX25) +/* + * The Phy at MX35 and MX25 have bugs, it must disable, and re-eable phy + * if the phy clock is disabled before + */ +static void reset_phy(void) +{ + u32 phyctrl; + phyctrl = fsl_readl(&dr_regs->phyctrl1); + phyctrl &= ~PHY_CTRL0_USBEN; + fsl_writel(phyctrl, &dr_regs->phyctrl1); + + phyctrl = fsl_readl(&dr_regs->phyctrl1); + phyctrl |= PHY_CTRL0_USBEN; + fsl_writel(phyctrl, &dr_regs->phyctrl1); +} +#else +static void reset_phy(void){; } +#endif +/*----------------------------------------------------------------- + * done() - retire a request; caller blocked irqs + * @status : request status to be set, only works when + * request is still in progress. + *--------------------------------------------------------------*/ +static void done(struct fsl_ep *ep, struct fsl_req *req, int status) +{ + struct fsl_udc *udc = NULL; + unsigned char stopped = ep->stopped; + struct ep_td_struct *curr_td, *next_td; + int j; + + udc = (struct fsl_udc *)ep->udc; + /* Removed the req from fsl_ep->queue */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + /* Free dtd for the request */ + next_td = req->head; + for (j = 0; j < req->dtd_count; j++) { + curr_td = next_td; + if (j != req->dtd_count - 1) { + next_td = curr_td->next_td_virt; +#ifdef POSTPONE_FREE_LAST_DTD + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); + } else { + if (last_free_td != NULL) + dma_pool_free(udc->td_pool, last_free_td, + last_free_td->td_dma); + last_free_td = curr_td; + } +#else + } + + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); +#endif + } + + if (USE_MSC_WR(req->req.length)) { + req->req.dma -= 1; + memmove(req->req.buf, req->req.buf + 1, MSC_BULK_CB_WRAP_LEN); + } + + if (req->mapped) { + dma_unmap_single(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } else + dma_sync_single_for_cpu(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + if (status && (status != -ESHUTDOWN)) + VDBG("complete %s req %p stat %d len %u/%u", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + /* complete() is from gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*----------------------------------------------------------------- + * nuke(): delete all requests related to this ep + * called with spinlock held + *--------------------------------------------------------------*/ +static void nuke(struct fsl_ep *ep, int status) +{ + ep->stopped = 1; + /* + * At udc stop mode, the clock is already off + * So flush fifo, should be done at clock on mode. + */ + if (!ep->udc->stopped) + fsl_ep_fifo_flush(&ep->ep); + + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct fsl_req *req = NULL; + + req = list_entry(ep->queue.next, struct fsl_req, queue); + done(ep, req, status); + } + dump_ep_queue(ep); +} + +/*------------------------------------------------------------------ + Internal Hardware related function + ------------------------------------------------------------------*/ +static inline void +dr_wake_up_enable(struct fsl_udc *udc, bool enable) +{ + struct fsl_usb2_platform_data *pdata; + pdata = udc->pdata; + + if (pdata && pdata->wake_up_enable) + pdata->wake_up_enable(pdata, enable); +} + +static inline void dr_clk_gate(bool on) +{ + struct fsl_usb2_platform_data *pdata = udc_controller->pdata; + + if (!pdata || !pdata->usb_clock_for_pm) + return; + pdata->usb_clock_for_pm(on); + if (on) + reset_phy(); +} + +static void dr_phy_low_power_mode(struct fsl_udc *udc, bool enable) +{ + struct fsl_usb2_platform_data *pdata = udc->pdata; + u32 portsc; + + if (pdata && pdata->phy_lowpower_suspend) { + pdata->phy_lowpower_suspend(pdata, enable); + } else { + if (enable) { + portsc = fsl_readl(&dr_regs->portsc1); + portsc |= PORTSCX_PHY_LOW_POWER_SPD; + fsl_writel(portsc, &dr_regs->portsc1); + } else { + portsc = fsl_readl(&dr_regs->portsc1); + portsc &= ~PORTSCX_PHY_LOW_POWER_SPD; + fsl_writel(portsc, &dr_regs->portsc1); + } + } + pdata->lowpower = enable; +} + + +/* workaroud for some boards, maybe there is a large capacitor between the ground and the Vbus + * that will cause the vbus dropping very slowly when device is detached, + * may cost 2-3 seconds to below 0.8V */ +static void udc_wait_b_session_low(void) +{ + u32 temp; + u32 wait = 5000/jiffies_to_msecs(1); /* max wait time is 5000 ms */ + /* if we are in host mode, don't need to care the B session */ + if ((fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID) == 0) + return; + /* if the udc is dettached , there will be a suspend irq */ + if (udc_controller->usb_state != USB_STATE_SUSPENDED) + return; + temp = fsl_readl(&dr_regs->otgsc); + temp &= ~OTGSC_B_SESSION_VALID_IRQ_EN; + fsl_writel(temp, &dr_regs->otgsc); + + do { + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_B_SESSION_VALID)) + break; + msleep(jiffies_to_msecs(1)); + wait -= 1; + } while (wait); + if (!wait) + printk(KERN_ERR "ERROR!!!!!: the vbus can not be lower \ + then 0.8V for 5 seconds, Pls Check your HW design\n"); + temp = fsl_readl(&dr_regs->otgsc); + temp |= OTGSC_B_SESSION_VALID_IRQ_EN; + fsl_writel(temp, &dr_regs->otgsc); +} + +static int dr_controller_setup(struct fsl_udc *udc) +{ + unsigned int tmp = 0, portctrl = 0; + unsigned int __attribute((unused)) ctrl = 0; + unsigned long timeout; + struct fsl_usb2_platform_data *pdata; + +#define FSL_UDC_RESET_TIMEOUT 1000 + + /* before here, make sure dr_regs has been initialized */ + if (!udc) + return -EINVAL; + pdata = udc->pdata; + + /* Stop and reset the usb controller */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + tmp = fsl_readl(&dr_regs->usbcmd); + tmp |= USB_CMD_CTRL_RESET; + fsl_writel(tmp, &dr_regs->usbcmd); + + /* Wait for reset to complete */ + timeout = jiffies + FSL_UDC_RESET_TIMEOUT; + while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { + if (time_after(jiffies, timeout)) { + ERR("udc reset timeout! \n"); + return -ETIMEDOUT; + } + cpu_relax(); + } + + /* 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 (pdata->es) + tmp |= USB_MODE_ES; + fsl_writel(tmp, &dr_regs->usbmode); + + fsl_platform_set_device_mode(pdata); + + /* Clear the setup status */ + fsl_writel(0xffffffff, &dr_regs->usbsts); + + tmp = udc->ep_qh_dma; + tmp &= USB_EP_LIST_ADDRESS_MASK; + fsl_writel(tmp, &dr_regs->endpointlistaddr); + + VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", + (int)udc->ep_qh, (int)tmp, + fsl_readl(&dr_regs->endpointlistaddr)); + + /* Config PHY interface */ + portctrl = fsl_readl(&dr_regs->portsc1); + portctrl &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); + switch (udc->phy_mode) { + case FSL_USB2_PHY_ULPI: + portctrl |= PORTSCX_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portctrl |= PORTSCX_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + portctrl |= PORTSCX_PTS_UTMI; + break; + case FSL_USB2_PHY_SERIAL: + portctrl |= PORTSCX_PTS_FSLS; + break; + default: + return -EINVAL; + } + fsl_writel(portctrl, &dr_regs->portsc1); + + if (pdata->change_ahb_burst) { + /* if usb should not work in default INCRx mode */ + tmp = fsl_readl(&dr_regs->sbuscfg); + tmp = (tmp & ~0x07) | pdata->ahb_burst_mode; + fsl_writel(tmp, &dr_regs->sbuscfg); + } + + if (pdata->have_sysif_regs) { + /* Config control enable i/o output, cpu endian register */ + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl |= USB_CTRL_IOENB; + __raw_writel(ctrl, &usb_sys_regs->control); + } + +#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. */ + + if (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; +} + +/* Enable DR irq and set controller to run state */ +static void dr_controller_run(struct fsl_udc *udc) +{ + u32 temp; + + fsl_platform_pullup_enable(udc->pdata); + + /* Enable DR irq reg */ + temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN + | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN + | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + fsl_writel(temp, &dr_regs->usbintr); + + /* enable BSV irq */ + temp = fsl_readl(&dr_regs->otgsc); + temp |= OTGSC_B_SESSION_VALID_IRQ_EN; + fsl_writel(temp, &dr_regs->otgsc); + + /* If vbus not on and used low power mode */ + if (!(temp & OTGSC_B_SESSION_VALID)) { + /* Set stopped before low power mode */ + udc->stopped = 1; + /* enable wake up */ + dr_wake_up_enable(udc, true); + /* enter lower power mode */ + dr_phy_low_power_mode(udc, true); + printk(KERN_DEBUG "%s: udc enter low power mode \n", __func__); + } else { +#ifdef CONFIG_ARCH_MX37 + /* + add some delay for USB timing issue. USB may be + recognize as FS device + during USB gadget remote wake up function + */ + mdelay(100); +#endif + /* Clear stopped bit */ + udc->stopped = 0; + + /* The usb line has already been connected to pc */ + temp = fsl_readl(&dr_regs->usbcmd); + temp |= USB_CMD_RUN_STOP; + fsl_writel(temp, &dr_regs->usbcmd); + printk(KERN_DEBUG "%s: udc out low power mode\n", __func__); + } + + return; +} + +static void dr_controller_stop(struct fsl_udc *udc) +{ + unsigned int tmp; + + pr_debug("%s\n", __func__); + + /* if we're in OTG mode, and the Host is currently using the port, + * stop now and don't rip the controller out from under the + * ehci driver + */ + if (udc->gadget.is_otg) { + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) { + pr_debug("udc: Leaving early\n"); + return; + } + } + + /* disable all INTR */ + fsl_writel(0, &dr_regs->usbintr); + + /* disable wake up */ + dr_wake_up_enable(udc, false); + /* disable BSV irq */ + tmp = fsl_readl(&dr_regs->otgsc); + tmp &= ~OTGSC_B_SESSION_VALID_IRQ_EN; + fsl_writel(tmp, &dr_regs->otgsc); + + /* Set stopped bit for isr */ + udc->stopped = 1; + + /* disable IO output */ +/* usb_sys_regs->control = 0; */ + + fsl_platform_pullup_disable(udc->pdata); + + /* set controller to Stop */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + return; +} + +void dr_ep_setup(unsigned char ep_num, unsigned char dir, unsigned char ep_type) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) { + if (ep_num) + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_TX_ENABLE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + if (ep_num) + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_RX_ENABLE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_RX_EP_TYPE_SHIFT); + } + + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +static void +dr_ep_change_stall(unsigned char ep_num, unsigned char dir, int value) +{ + u32 tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + + if (value) { + /* set the stall bit */ + if (dir) + tmp_epctrl |= EPCTRL_TX_EP_STALL; + else + tmp_epctrl |= EPCTRL_RX_EP_STALL; + } else { + /* clear the stall bit and reset data toggle */ + if (dir) { + tmp_epctrl &= ~EPCTRL_TX_EP_STALL; + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + tmp_epctrl &= ~EPCTRL_RX_EP_STALL; + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + } + } + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +/* Get stall status of a specific ep + Return: 0: not stalled; 1:stalled */ +static int dr_ep_get_stall(unsigned char ep_num, unsigned char dir) +{ + u32 epctrl; + + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) + return (epctrl & EPCTRL_TX_EP_STALL) ? 1 : 0; + else + return (epctrl & EPCTRL_RX_EP_STALL) ? 1 : 0; +} + +/******************************************************************** + Internal Structure Build up functions +********************************************************************/ + +/*------------------------------------------------------------------ +* struct_ep_qh_setup(): set the Endpoint Capabilites field of QH + * @zlt: Zero Length Termination Select (1: disable; 0: enable) + * @mult: Mult field + ------------------------------------------------------------------*/ +static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, + unsigned char dir, unsigned char ep_type, + unsigned int max_pkt_len, + unsigned int zlt, unsigned char mult) +{ + struct ep_queue_head *p_QH = &udc->ep_qh[2 * ep_num + dir]; + unsigned int tmp = 0; + + /* set the Endpoint Capabilites in QH */ + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + /* Interrupt On Setup (IOS). for control ep */ + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | EP_QUEUE_HEAD_IOS; + break; + case USB_ENDPOINT_XFER_ISOC: + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | (mult << EP_QUEUE_HEAD_MULT_POS); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + break; + default: + VDBG("error ep type is %d", ep_type); + return; + } + if (zlt) + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + p_QH->max_pkt_length = cpu_to_hc32(tmp); + + return; +} + +/* Setup qh structure and ep register for ep0. */ +static void ep0_setup(struct fsl_udc *udc) +{ + /* the intialization of an ep includes: fields in QH, Regs, + * fsl_ep struct */ + struct_ep_qh_setup(udc, 0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + struct_ep_qh_setup(udc, 0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); + dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); + + return; + +} + +/*********************************************************************** + Endpoint Management Functions +***********************************************************************/ + +/*------------------------------------------------------------------------- + * when configurations are set, or when interface settings change + * for example the do_set_interface() in gadget layer, + * the driver will enable or disable the relevant endpoints + * ep0 doesn't use this routine. It is always enabled. +-------------------------------------------------------------------------*/ +static int fsl_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned short max = 0; + unsigned char mult = 0, zlt; + int retval = -EINVAL; + unsigned long flags = 0; + + ep = container_of(_ep, struct fsl_ep, ep); + + pr_debug("udc: %s ep.name=%s\n", __func__, ep->ep.name); + /* catch various bogus parameters */ + if (!_ep || !desc || ep->desc + || (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + udc = ep->udc; + + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + max = le16_to_cpu(desc->wMaxPacketSize); + + /* Disable automatic zlp generation. Driver is reponsible to indicate + * explicitly through req->req.zero. This is needed to enable multi-td + * request. */ + zlt = 1; + + /* Assume the max packet size from gadget is always correct */ + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + /* mult = 0. Execute N Transactions as demonstrated by + * the USB variable length packet protocol where N is + * computed using the Maximum Packet Length (dQH) and + * the Total Bytes field (dTD) */ + mult = 0; + break; + case USB_ENDPOINT_XFER_ISOC: + /* Calculate transactions needed for high bandwidth iso */ + mult = (unsigned char)(1 + ((max >> 11) & 0x03)); + max = max & 0x7ff; /* bit 0~10 */ + /* 3 transactions at most */ + if (mult > 3) + goto en_done; + break; + default: + goto en_done; + } + + spin_lock_irqsave(&udc->lock, flags); + ep->ep.maxpacket = max; + ep->desc = desc; + ep->stopped = 0; + + /* Controller related setup */ + /* Init EPx Queue Head (Ep Capabilites field in QH + * according to max, zlt, mult) */ + struct_ep_qh_setup(udc, (unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK), + max, zlt, mult); + + /* Init endpoint ctrl register */ + dr_ep_setup((unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK)); + + spin_unlock_irqrestore(&udc->lock, flags); + retval = 0; + + VDBG("enabled %s (ep%d%s) maxpacket %d", ep->ep.name, + ep->desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) + ? "in" : "out", max); +en_done: + return retval; +} + +/*--------------------------------------------------------------------- + * @ep : the ep being unconfigured. May not be ep0 + * Any pending and uncomplete req will complete with status (-ESHUTDOWN) +*---------------------------------------------------------------------*/ +static int fsl_ep_disable(struct usb_ep *_ep) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + u32 epctrl; + int ep_num; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || !ep->desc) { + VDBG("%s not enabled", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + /* 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; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + udc = (struct fsl_udc *)ep->udc; + spin_lock_irqsave(&udc->lock, flags); + /* nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + VDBG("disabled %s OK", _ep->name); + return 0; +} + +/*--------------------------------------------------------------------- + * allocate a request object used by this endpoint + * the main operation is to insert the req->queue to the eq->queue + * Returns the request, or null if one could not be allocated +*---------------------------------------------------------------------*/ +static struct usb_request * +fsl_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct fsl_req *req = NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fsl_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_req *req = NULL; + + req = container_of(_req, struct fsl_req, req); + + if (_req) + kfree(req); +} + +static void update_qh(struct fsl_req *req) +{ + struct fsl_ep *ep = req->ep; + int i = ep_index(ep) * 2 + ep_is_in(ep); + u32 temp; + struct ep_queue_head *dQH = &ep->udc->ep_qh[i]; + + /* Write dQH next pointer and terminate bit to 0 */ + temp = req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + if (NEED_IRAM(req->ep)) { + /* set next dtd stop bit,ensure only one dtd in this list */ + req->cur->next_td_ptr |= cpu_to_hc32(DTD_NEXT_TERMINATE); + temp = req->cur->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + } + dQH->next_dtd_ptr = cpu_to_hc32(temp); + /* Clear active and halt bit */ + temp = cpu_to_hc32(~(EP_QUEUE_HEAD_STATUS_ACTIVE + | EP_QUEUE_HEAD_STATUS_HALT)); + dQH->size_ioc_int_sts &= temp; + + /* Prime endpoint by writing 1 to ENDPTPRIME */ + temp = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + fsl_writel(temp, &dr_regs->endpointprime); +} + +/*-------------------------------------------------------------------------*/ +static int fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) +{ + u32 temp, bitmask, tmp_stat; + + /* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr); + VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */ + + bitmask = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + /* Add td to the end */ + struct fsl_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct fsl_req, queue); + if (NEED_IRAM(ep)) { + /* only one dtd in dqh */ + lastreq->tail->next_td_ptr = + cpu_to_hc32(req->head->td_dma | DTD_NEXT_TERMINATE); + goto out; + } else { + lastreq->tail->next_td_ptr = + cpu_to_hc32(req->head->td_dma & DTD_ADDR_MASK); + } + /* Read prime bit, if 1 goto done */ + if (fsl_readl(&dr_regs->endpointprime) & bitmask) + goto out; + do { + /* Set ATDTW bit in USBCMD */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_ATDTW, &dr_regs->usbcmd); + + /* Read correct status bit */ + tmp_stat = fsl_readl(&dr_regs->endptstatus) & bitmask; + + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_ATDTW)); + + /* Write ATDTW bit to 0 */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_ATDTW, &dr_regs->usbcmd); + + if (tmp_stat) + goto out; + } + update_qh(req); +out: + return 0; +} + +/* Fill in the dTD structure + * @req: request that the transfer belongs to + * @length: return actually data length of the dTD + * @dma: return dma address of the dTD + * @is_last: return flag if it is the last dTD of the request + * return: pointer to the built dTD */ +static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, + dma_addr_t *dma, int *is_last) +{ + u32 swap_temp; + struct ep_td_struct *dtd; + + /* how big will this transfer be? */ + *length = min(req->req.length - req->req.actual, + (unsigned)EP_MAX_LENGTH_TRANSFER); + if (NEED_IRAM(req->ep)) + *length = min(*length, g_iram_size); + dtd = dma_pool_alloc(udc_controller->td_pool, GFP_ATOMIC, dma); + if (dtd == NULL) + return dtd; + + dtd->td_dma = *dma; + /* Clear reserved field */ + swap_temp = hc32_to_cpu(dtd->size_ioc_sts); + swap_temp &= ~DTD_RESERVED_FIELDS; + dtd->size_ioc_sts = cpu_to_hc32(swap_temp); + + /* Init all of buffer page pointers */ + swap_temp = (u32) (req->req.dma + req->req.actual); + if (NEED_IRAM(req->ep)) + swap_temp = (u32) (req->req.dma); + dtd->buff_ptr0 = cpu_to_hc32(swap_temp); + dtd->buff_ptr1 = cpu_to_hc32(swap_temp + 0x1000); + dtd->buff_ptr2 = cpu_to_hc32(swap_temp + 0x2000); + dtd->buff_ptr3 = cpu_to_hc32(swap_temp + 0x3000); + dtd->buff_ptr4 = cpu_to_hc32(swap_temp + 0x4000); + + req->req.actual += *length; + + /* zlp is needed if req->req.zero is set */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) + *is_last = 1; + else + *is_last = 0; + + if ((*is_last) == 0) + VDBG("multi-dtd request!\n"); + /* Fill in the transfer size; set active bit */ + swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); + + /* Enable interrupt for the last dtd of a request */ + if (*is_last && !req->req.no_interrupt) + swap_temp |= DTD_IOC; + if (NEED_IRAM(req->ep)) + swap_temp |= DTD_IOC; + + dtd->size_ioc_sts = cpu_to_hc32(swap_temp); + + mb(); + + VDBG("length = %d address= 0x%x", *length, (int)*dma); + + return dtd; +} + +/* Generate dtd chain for a request */ +static int fsl_req_to_dtd(struct fsl_req *req) +{ + unsigned count; + int is_last; + int is_first = 1; + struct ep_td_struct *last_dtd = NULL, *dtd; + dma_addr_t dma; + + if (NEED_IRAM(req->ep)) { + req->oridma = req->req.dma; + /* here, replace user buffer to iram buffer */ + if (ep_is_in(req->ep)) { + req->req.dma = req->ep->udc->iram_buffer[1]; + if ((list_empty(&req->ep->queue))) { + /* copy data only when no bulk in transfer is + running */ + memcpy((char *)req->ep->udc->iram_buffer_v[1], + req->req.buf, min(req->req.length, + g_iram_size)); + } + } else { + req->req.dma = req->ep->udc->iram_buffer[0]; + } + } + + if (USE_MSC_WR(req->req.length)) + req->req.dma += 1; + + do { + dtd = fsl_build_dtd(req, &count, &dma, &is_last); + if (dtd == NULL) + return -ENOMEM; + + if (is_first) { + is_first = 0; + req->head = dtd; + } else { + last_dtd->next_td_ptr = cpu_to_hc32(dma); + last_dtd->next_td_virt = dtd; + } + last_dtd = dtd; + + req->dtd_count++; + } while (!is_last); + + dtd->next_td_ptr = cpu_to_hc32(DTD_NEXT_TERMINATE); + req->cur = req->head; + req->tail = dtd; + + return 0; +} + +/* queues (submits) an I/O request to an endpoint */ +static int +fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req = container_of(_req, struct fsl_req, req); + struct fsl_udc *udc; + unsigned long flags; + int is_iso = 0; + + + if (!_ep || !ep->desc) { + VDBG("%s, bad ep\n", __func__); + return -EINVAL; + } + + udc = ep->udc; + spin_lock_irqsave(&udc->lock, flags); + + /* catch various bogus parameters */ + if (!_req || !req->req.buf || (ep_index(ep) + && !list_empty(&req->queue))) { + VDBG("%s, bad params\n", __func__); + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EMSGSIZE; + } + is_iso = 1; + } + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&udc->lock, flags); + return -ESHUTDOWN; + } + req->ep = ep; + + /* map virtual address to hardware */ + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, + req->req.length, ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->mapped = 1; + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->mapped = 0; + } + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + if (NEED_IRAM(ep)) { + req->last_one = 0; + req->buffer_offset = 0; + } + + /* build dtds and push them to device queue */ + if (!fsl_req_to_dtd(req)) { + fsl_queue_td(ep, req); + } else { + spin_unlock_irqrestore(&udc->lock, flags); + return -ENOMEM; + } + + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int fsl_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req; + unsigned long flags; + int ep_num, stopped, ret = 0; + struct fsl_udc *udc = NULL; + u32 epctrl; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + stopped = ep->stopped; + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&ep->udc->lock, flags); + return -ESHUTDOWN; + } + + /* Stop the ep before we deal with the queue */ + ep->stopped = 1; + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl &= ~EPCTRL_TX_ENABLE; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + ret = -EINVAL; + goto out; + } + + /* The request is in progress, or completed but not dequeued */ + if (ep->queue.next == &req->queue) { + _req->status = -ECONNRESET; + fsl_ep_fifo_flush(_ep); /* flush current transfer */ + + /* The request isn't the last request in this ep queue */ + if (req->queue.next != &ep->queue) { + struct ep_queue_head *qh; + struct fsl_req *next_req; + + qh = ep->qh; + next_req = list_entry(req->queue.next, struct fsl_req, + queue); + + /* Point the QH to the first TD of next request */ + fsl_writel((u32) next_req->head, &qh->curr_dtd_ptr); + } + + /* The request hasn't been processed, patch up the TD chain */ + } else { + struct fsl_req *prev_req; + + prev_req = list_entry(req->queue.prev, struct fsl_req, queue); + fsl_writel(fsl_readl(&req->tail->next_td_ptr), + &prev_req->tail->next_td_ptr); + + } + + done(ep, req, -ECONNRESET); + + /* Enable EP */ +out: epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl |= EPCTRL_TX_ENABLE; + else + epctrl |= EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + ep->stopped = stopped; + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------- + * modify the endpoint halt feature + * @ep: the non-isochronous endpoint being stalled + * @value: 1--set halt 0--clear halt + * Returns zero, or a negative error code. +*----------------------------------------------------------------*/ +static int fsl_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + int status = -EOPNOTSUPP; /* operation not supported */ + unsigned char ep_dir = 0, ep_num = 0; + struct fsl_udc *udc = NULL; + + ep = container_of(_ep, struct fsl_ep, ep); + udc = ep->udc; + if (!_ep || !ep->desc) { + status = -EINVAL; + goto out; + } + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) { + status = -ESHUTDOWN; + goto out; + } + + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + ep_num = (unsigned char)(ep_index(ep)); + spin_lock_irqsave(&ep->udc->lock, flags); + dr_ep_change_stall(ep_num, ep_dir, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep_index(ep) == 0) { + udc->ep0_dir = 0; + } +out: + VDBG(" %s %s halt stat %d", ep->ep.name, + value ? "set" : "clear", status); + + return status; +} + +static int arcotg_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; + int ep_num, ep_dir; + u32 bits; + unsigned long timeout; +#define FSL_UDC_FLUSH_TIMEOUT 1000 + + if (!_ep) { + return; + } else { + ep = container_of(_ep, struct fsl_ep, ep); + if (!ep->desc) + return; + } + ep_num = ep_index(ep); + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + + if (ep_num == 0) + bits = (1 << 16) | 1; + else if (ep_dir == USB_SEND) + bits = 1 << (16 + ep_num); + else + bits = 1 << ep_num; + + timeout = jiffies + FSL_UDC_FLUSH_TIMEOUT; + do { + fsl_writel(bits, &dr_regs->endptflush); + + /* Wait until flush complete */ + while (fsl_readl(&dr_regs->endptflush)) { + if (time_after(jiffies, timeout)) { + ERR("ep flush timeout\n"); + return; + } + cpu_relax(); + } + /* See if we need to flush again */ + } while (fsl_readl(&dr_regs->endptstatus) & bits); +} + +static struct usb_ep_ops fsl_ep_ops = { + .enable = fsl_ep_enable, + .disable = fsl_ep_disable, + + .alloc_request = fsl_alloc_request, + .free_request = fsl_free_request, + + .queue = fsl_ep_queue, + .dequeue = fsl_ep_dequeue, + + .set_halt = fsl_ep_set_halt, + .fifo_status = arcotg_fifo_status, + .fifo_flush = fsl_ep_fifo_flush, /* flush fifo */ +}; + +/*------------------------------------------------------------------------- + Gadget Driver Layer Operations +-------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------- + * Get the current frame number (from DR frame_index Reg ) + *----------------------------------------------------------------------*/ +static int fsl_get_frame(struct usb_gadget *gadget) +{ + return (int)(fsl_readl(&dr_regs->frindex) & USB_FRINDEX_MASKS); +} + +/*----------------------------------------------------------------------- + * Tries to wake up the host connected to this gadget + -----------------------------------------------------------------------*/ +static int fsl_wakeup(struct usb_gadget *gadget) +{ + struct fsl_udc *udc = container_of(gadget, struct fsl_udc, gadget); + u32 portsc; + + /* Remote wakeup feature not enabled by host */ + if (!udc->remote_wakeup) + return -ENOTSUPP; + + portsc = fsl_readl(&dr_regs->portsc1); + /* not suspended? */ + if (!(portsc & PORTSCX_PORT_SUSPEND)) + return 0; + /* trigger force resume */ + portsc |= PORTSCX_PORT_FORCE_RESUME; + fsl_writel(portsc, &dr_regs->portsc1); + return 0; +} + +static int can_pullup(struct fsl_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +/* Notify controller that VBUS is powered, Called by whatever + detects VBUS sessions */ +static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct fsl_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct fsl_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + VDBG("VBUS %s\n", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * + * Returns zero on success, else negative errno. + */ +static int fsl_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct fsl_udc *udc; + struct fsl_usb2_platform_data *pdata; + + udc = container_of(gadget, struct fsl_udc, gadget); + if (udc->transceiver) + return otg_set_power(udc->transceiver, mA); + pdata = udc->pdata; + if (pdata->xcvr_ops && pdata->xcvr_ops->set_vbus_draw) { + pdata->xcvr_ops->set_vbus_draw(pdata->xcvr_ops, pdata, mA); + return 0; + } + return -ENOTSUPP; +} + +/* Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + */ +static int fsl_pullup(struct usb_gadget *gadget, int is_on) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + + return 0; +} + +/* defined in gadget.h */ +static struct usb_gadget_ops fsl_gadget_ops = { + .get_frame = fsl_get_frame, + .wakeup = fsl_wakeup, +/* .set_selfpowered = fsl_set_selfpowered, */ /* Always selfpowered */ + .vbus_session = fsl_vbus_session, + .vbus_draw = fsl_vbus_draw, + .pullup = fsl_pullup, +}; + +/* Set protocol stall on ep0, protocol stall will automatically be cleared + on new transaction */ +static void ep0stall(struct fsl_udc *udc) +{ + u32 tmp; + + /* must set tx and rx to stall at the same time */ + tmp = fsl_readl(&dr_regs->endptctrl[0]); + tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + fsl_writel(tmp, &dr_regs->endptctrl[0]); + udc->ep0_dir = 0; +} + +/* Prime a status phase for ep0 */ +static int ep0_prime_status(struct fsl_udc *udc, int direction) +{ + struct fsl_req *req = udc->status_req; + struct fsl_ep *ep; + int status = 0; + + if (direction == EP_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + ep = &udc->eps[0]; + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + + status = fsl_ep_queue(&ep->ep, &req->req, GFP_ATOMIC); + return status; +} + +static inline int udc_reset_ep_queue(struct fsl_udc *udc, u8 pipe) +{ + struct fsl_ep *ep = get_ep_by_pipe(udc, pipe); + + if (!ep->name) + return 0; + + nuke(ep, -ESHUTDOWN); + + return 0; +} + +/* + * ch9 Set address + */ +static void ch9setaddress(struct fsl_udc *udc, u16 value, u16 index, u16 length) +{ + /* Save the new address to device struct */ + udc->device_address = (u8) value; + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + /* Status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); +} + +/* + * ch9 Get status + */ +static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) +{ + u16 tmp = 0; /* Status, cpu endian */ + + struct fsl_req *req; + struct fsl_ep *ep; + int status = 0; + + ep = &udc->eps[0]; + + if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Get device status */ + tmp = 1 << USB_DEVICE_SELF_POWERED; + tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { + /* Get interface status */ + /* We don't have interface information in udc driver */ + tmp = 0; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + /* Get endpoint status */ + struct fsl_ep *target_ep; + + target_ep = get_ep_by_pipe(udc, get_pipe_by_windex(index)); + + /* stall if endpoint doesn't exist */ + if (!target_ep->desc) + goto stall; + tmp = dr_ep_get_stall(ep_index(target_ep), ep_is_in(target_ep)) + << USB_ENDPOINT_HALT; + } + + udc->ep0_dir = USB_DIR_IN; + /* Borrow the per device data_req */ + /* status_req had been used to prime status */ + req = udc->data_req; + /* Fill in the reqest structure */ + *((u16 *) req->req.buf) = cpu_to_le16(tmp); + req->ep = ep; + req->req.length = 2; + + status = fsl_ep_queue(&ep->ep, &req->req, GFP_ATOMIC); + if (status) { + udc_reset_ep_queue(udc, 0); + ERR("Can't respond to getstatus request \n"); + goto stall; + } + return; +stall: + ep0stall(udc); + +} + +static void setup_received_irq(struct fsl_udc *udc, + struct usb_ctrlrequest *setup) +{ + u16 wValue = le16_to_cpu(setup->wValue); + u16 wIndex = le16_to_cpu(setup->wIndex); + u16 wLength = le16_to_cpu(setup->wLength); + struct usb_gadget *gadget = &(udc->gadget); + unsigned mA = 500; + udc_reset_ep_queue(udc, 0); + + if (wLength) { + int dir; + dir = EP_DIR_IN; + if (setup->bRequestType & USB_DIR_IN) { + dir = EP_DIR_OUT; + } + spin_unlock(&udc->lock); + if (ep0_prime_status(udc, dir)) + ep0stall(udc); + spin_lock(&udc->lock); + } + /* We process some stardard setup requests here */ + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase from udc */ + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + spin_unlock(&udc->lock); + ch9getstatus(udc, setup->bRequestType, wValue, wIndex, wLength); + spin_lock(&udc->lock); + return; + + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD + | USB_RECIP_DEVICE)) + break; + spin_unlock(&udc->lock); + ch9setaddress(udc, wValue, wIndex, wLength); + spin_lock(&udc->lock); + return; + case USB_REQ_SET_CONFIGURATION: + spin_unlock(&udc->lock); + fsl_vbus_draw(gadget, mA); + spin_lock(&udc->lock); + break; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* 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)) { + int pipe = get_pipe_by_windex(wIndex); + struct fsl_ep *ep; + + if (wValue != 0 || wLength != 0 || pipe > udc->max_ep) + break; + ep = get_ep_by_pipe(udc, pipe); + + spin_unlock(&udc->lock); + rc = fsl_ep_set_halt(&ep->ep, + (setup->bRequest == USB_REQ_SET_FEATURE) + ? 1 : 0); + spin_lock(&udc->lock); + + } else if ((setup->bRequestType & (USB_RECIP_MASK + | USB_TYPE_MASK)) == (USB_RECIP_DEVICE + | USB_TYPE_STANDARD)) { + /* Note: The driver has not include OTG support yet. + * This will be set when OTG support is added */ + if (setup->wValue == USB_DEVICE_TEST_MODE) + ptc = setup->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 (rc == 0) { + spin_unlock(&udc->lock); + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); + spin_lock(&udc->lock); + } + if (ptc) { + u32 tmp; + + mdelay(10); + fsl_platform_set_test_mode(udc->pdata, ptc); + tmp = fsl_readl(&dr_regs->portsc1) | (ptc << 16); + fsl_writel(tmp, &dr_regs->portsc1); + printk(KERN_INFO "udc: switch to test mode 0x%x.\n", ptc); + } + + return; + } + + default: + break; + } + + /* Requests handled by gadget */ + if (wLength) { + /* Data phase from gadget, status phase from udc */ + udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? USB_DIR_IN : USB_DIR_OUT; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) { + /* cancel status phase */ + udc_reset_ep_queue(udc, 0); + ep0stall(udc); + } + } else { + /* No data phase, IN status from gadget */ + udc->ep0_dir = USB_DIR_IN; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0stall(udc); + } + spin_lock(&udc->lock); +} + +/* Process request for Data or Status phase of ep0 + * prime status phase if needed */ +static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, + struct fsl_req *req) +{ + if (udc->usb_state == USB_STATE_ADDRESS) { + /* Set the new address */ + u32 new_address = (u32) udc->device_address; + fsl_writel(new_address << USB_DEVICE_ADDRESS_BIT_POS, + &dr_regs->deviceaddr); + } + + done(ep0, req, 0); +} + +/* Tripwire mechanism to ensure a setup packet payload is extracted without + * being corrupted by another incoming setup packet */ +static void tripwire_handler(struct fsl_udc *udc, u8 ep_num, u8 *buffer_ptr) +{ + u32 temp; + struct ep_queue_head *qh; + struct fsl_usb2_platform_data *pdata = udc->pdata; + + qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp | (1 << ep_num), &dr_regs->endptsetupstat); + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_SUTW, &dr_regs->usbcmd); + + /* Copy the setup packet to local buffer */ + if (pdata->le_setup_buf) { + u32 *p = (u32 *)buffer_ptr; + u32 *s = (u32 *)qh->setup_buffer; + + /* Convert little endian setup buffer to CPU endian */ + *p++ = le32_to_cpu(*s++); + *p = le32_to_cpu(*s); + } else { + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + } + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_SUTW)); + + /* Clear Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_SUTW, &dr_regs->usbcmd); +} + +static void iram_process_ep_complete(struct fsl_req *curr_req, + int cur_transfer) +{ + char *buf; + u32 len; + int in = ep_is_in(curr_req->ep); + + if (in) + buf = (char *)udc_controller->iram_buffer_v[1]; + else + buf = (char *)udc_controller->iram_buffer_v[0]; + + if (curr_req->cur->next_td_ptr == cpu_to_hc32(DTD_NEXT_TERMINATE) + || (cur_transfer < g_iram_size) + || (curr_req->req.length == curr_req->req.actual)) + curr_req->last_one = 1; + + if (curr_req->last_one) { + /* the last transfer */ + if (!in) { + memcpy(curr_req->req.buf + curr_req->buffer_offset, buf, + cur_transfer); + } + if (curr_req->tail->next_td_ptr != + cpu_to_hc32(DTD_NEXT_TERMINATE)) { + /* have next request,queue it */ + struct fsl_req *next_req; + next_req = + list_entry(curr_req->queue.next, + struct fsl_req, queue); + if (in) + memcpy(buf, next_req->req.buf, + min(g_iram_size, next_req->req.length)); + update_qh(next_req); + } + curr_req->req.dma = curr_req->oridma; + } else { + /* queue next dtd */ + /* because had next dtd, so should finish */ + /* tranferring g_iram_size data */ + curr_req->buffer_offset += g_iram_size; + /* pervious set stop bit,now clear it */ + curr_req->cur->next_td_ptr &= ~cpu_to_hc32(DTD_NEXT_TERMINATE); + curr_req->cur = curr_req->cur->next_td_virt; + if (in) { + len = + min(curr_req->req.length - curr_req->buffer_offset, + g_iram_size); + memcpy(buf, curr_req->req.buf + curr_req->buffer_offset, + len); + } else { + memcpy(curr_req->req.buf + curr_req->buffer_offset - + g_iram_size, buf, g_iram_size); + } + update_qh(curr_req); + } +} + +/* process-ep_req(): free the completed Tds for this req */ +static int process_ep_req(struct fsl_udc *udc, int pipe, + struct fsl_req *curr_req) +{ + struct ep_td_struct *curr_td; + int td_complete, actual, remaining_length, j, tmp; + int status = 0; + int errors = 0; + struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; + int direction = pipe % 2; + int total = 0, real_len; + + curr_td = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + real_len = curr_req->req.length; + + for (j = 0; j < curr_req->dtd_count; j++) { + remaining_length = (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + if (NEED_IRAM(curr_req->ep)) { + if (real_len >= g_iram_size) { + actual = g_iram_size; + real_len -= g_iram_size; + } else { /* the last packet */ + actual = real_len; + curr_req->last_one = 1; + } + } + actual -= remaining_length; + total += actual; + + errors = hc32_to_cpu(curr_td->size_ioc_sts) & DTD_ERROR_MASK; + if (errors) { + if (errors & DTD_STATUS_HALTED) { + ERR("dTD error %08x QH=%d\n", errors, pipe); + /* Clear the errors and Halt condition */ + tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts); + tmp &= ~errors; + curr_qh->size_ioc_int_sts = cpu_to_hc32(tmp); + status = -EPIPE; + /* FIXME: continue with next queued TD? */ + + break; + } + if (errors & DTD_STATUS_DATA_BUFF_ERR) { + VDBG("Transfer overflow"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + VDBG("ISO error"); + status = -EILSEQ; + break; + } else + ERR("Unknown error has occured (0x%x)!\r\n", + errors); + + } else if (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_STATUS_ACTIVE) { + VDBG("Request not complete"); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) { + if (direction) { + VDBG("Transmit dTD remaining length not zero"); + status = -EPROTO; + break; + } else { + td_complete++; + break; + } + } else { + td_complete++; + VDBG("dTD transmitted successful "); + } + if (NEED_IRAM(curr_req->ep)) + if (curr_td-> + next_td_ptr & cpu_to_hc32(DTD_NEXT_TERMINATE)) + break; + if (j != curr_req->dtd_count - 1) + curr_td = (struct ep_td_struct *)curr_td->next_td_virt; + } + + if (status) + return status; + curr_req->req.actual = total; + if (NEED_IRAM(curr_req->ep)) + iram_process_ep_complete(curr_req, actual); + return 0; +} + +/* Process a DTD completion interrupt */ +static void dtd_complete_irq(struct fsl_udc *udc) +{ + u32 bit_pos; + int i, ep_num, direction, bit_mask, status; + struct fsl_ep *curr_ep; + struct fsl_req *curr_req, *temp_req; + + /* Clear the bits in the register */ + bit_pos = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(bit_pos, &dr_regs->endptcomplete); + + if (!bit_pos) + return; + + for (i = 0; i < udc->max_ep * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_mask = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & bit_mask)) + continue; + + curr_ep = get_ep_by_pipe(udc, i); + + /* If the ep is configured */ + if (curr_ep->name == NULL) { + INFO("Invalid EP?"); + continue; + } + + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, + queue) { + status = process_ep_req(udc, i, curr_req); + + VDBG("status of process_ep_req= %d, ep = %d", + status, ep_num); + if (status == REQ_UNCOMPLETE) + break; + /* write back status to req */ + curr_req->req.status = status; + + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else { + if (NEED_IRAM(curr_ep)) { + if (curr_req->last_one) + done(curr_ep, curr_req, status); + /* only check the 1th req */ + break; + } else + done(curr_ep, curr_req, status); + } + } + dump_ep_queue(curr_ep); + } +} + +static void fsl_udc_speed_update(struct fsl_udc *udc) +{ + u32 speed = 0; + u32 loop = 0; + + /* Wait for port reset finished */ + while ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) + && (loop++ < 1000)) + ; + + speed = (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SPEED_MASK); + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSCX_PORT_SPEED_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSCX_PORT_SPEED_LOW: + udc->gadget.speed = USB_SPEED_LOW; + break; + default: + udc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } +} + +/* Process a port change interrupt */ +static void port_change_irq(struct fsl_udc *udc) +{ + if (udc->bus_reset) + udc->bus_reset = 0; + + /* Update port speed */ + fsl_udc_speed_update(udc); + + /* Update USB state */ + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +/* Process suspend interrupt */ +static void suspend_irq(struct fsl_udc *udc) +{ + u32 otgsc = 0; + + pr_debug("%s\n", __func__); + + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* Set discharge vbus */ + otgsc = fsl_readl(&dr_regs->otgsc); + otgsc &= ~(OTGSC_INTSTS_MASK); + otgsc |= OTGSC_CTRL_VBUS_DISCHARGE; + fsl_writel(otgsc, &dr_regs->otgsc); + + /* discharge in work queue */ + cancel_delayed_work(&udc->gadget_delay_work); + schedule_delayed_work(&udc->gadget_delay_work, msecs_to_jiffies(20)); + + /* report suspend to the driver, serial.c does not support this */ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void bus_resume(struct fsl_udc *udc) +{ + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver, serial.c does not support this */ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* Clear up all ep queues */ +static int reset_queues(struct fsl_udc *udc) +{ + u8 pipe; + + for (pipe = 0; pipe < udc->max_pipes; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + udc->driver->disconnect(&udc->gadget); + + return 0; +} + +/* Process reset interrupt */ +static void reset_irq(struct fsl_udc *udc) +{ + u32 temp; + + /* Clear the device address */ + temp = fsl_readl(&dr_regs->deviceaddr); + fsl_writel(temp & ~USB_DEVICE_ADDRESS_MASK, &dr_regs->deviceaddr); + + udc->device_address = 0; + + /* Clear usb state */ + udc->resume_state = 0; + udc->ep0_dir = 0; + udc->remote_wakeup = 0; /* default to 0 on reset */ + udc->gadget.b_hnp_enable = 0; + udc->gadget.a_hnp_support = 0; + udc->gadget.a_alt_hnp_support = 0; + + /* Clear all the setup token semaphores */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp, &dr_regs->endptsetupstat); + + /* Clear all the endpoint complete status bits */ + temp = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(temp, &dr_regs->endptcomplete); + + /* Write 1s to the flush register */ + fsl_writel(0xffffffff, &dr_regs->endptflush); + + /* Bus is reseting */ + udc->bus_reset = 1; + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc); + udc->usb_state = USB_STATE_DEFAULT; +} + +static void fsl_gadget_event(struct work_struct *work) +{ + struct fsl_udc *udc = udc_controller; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* update port status */ + fsl_udc_speed_update(udc); + spin_unlock_irqrestore(&udc->lock, flags); + + /* close dr controller clock */ + dr_clk_gate(false); +} + +static void fsl_gadget_delay_event(struct work_struct *work) +{ + u32 otgsc = 0; + + dr_clk_gate(true); + otgsc = fsl_readl(&dr_regs->otgsc); + /* clear vbus discharge */ + if (otgsc & OTGSC_CTRL_VBUS_DISCHARGE) { + otgsc &= ~(OTGSC_INTSTS_MASK | OTGSC_CTRL_VBUS_DISCHARGE); + fsl_writel(otgsc, &dr_regs->otgsc); + } + dr_clk_gate(false); +} + +/* if wakup udc, return true; else return false*/ +bool try_wake_up_udc(struct fsl_udc *udc) +{ + struct fsl_usb2_platform_data *pdata; + u32 irq_src; + + pdata = udc->pdata; + + /* check if Vbus change irq */ + irq_src = fsl_readl(&dr_regs->otgsc) & (~OTGSC_ID_CHANGE_IRQ_STS); + if (irq_src & OTGSC_B_SESSION_VALID_IRQ_STS) { + u32 tmp; + fsl_writel(irq_src, &dr_regs->otgsc); + /* only handle device interrupt event */ + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) + return false; + + tmp = fsl_readl(&dr_regs->usbcmd); + /* check BSV bit to see if fall or rise */ + if (irq_src & OTGSC_B_SESSION_VALID) { + if (udc->suspended) /*let the system pm resume the udc */ + return true; + udc->stopped = 0; + fsl_writel(tmp | USB_CMD_RUN_STOP, &dr_regs->usbcmd); + printk(KERN_DEBUG "%s: udc out low power mode\n", __func__); + } else { + if (udc->driver) + udc->driver->disconnect(&udc->gadget); + fsl_writel(tmp & ~USB_CMD_RUN_STOP, &dr_regs->usbcmd); + udc->stopped = 1; + /* enable wake up */ + dr_wake_up_enable(udc, true); + /* close USB PHY clock */ + dr_phy_low_power_mode(udc, true); + schedule_work(&udc->gadget_work); + printk(KERN_DEBUG "%s: udc enter low power mode\n", __func__); + return false; + } + } + + return true; +} +/* + * USB device controller interrupt handler + */ +static irqreturn_t fsl_udc_irq(int irq, void *_udc) +{ + struct fsl_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + struct fsl_usb2_platform_data *pdata = udc->pdata; + + if (pdata->irq_delay) + return status; + + spin_lock_irqsave(&udc->lock, flags); + + if (try_wake_up_udc(udc) == false) { + goto irq_end; + } +#ifdef CONFIG_USB_OTG + /* if no gadget register in this driver, we need do noting */ + if (udc->transceiver->gadget == NULL) { + goto irq_end; + } + /* only handle device interrupt event */ + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) { + goto irq_end; + } +#endif + irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr); + /* Clear notification bits */ + fsl_writel(irq_src, &dr_regs->usbsts); + + VDBG("0x%x\n", irq_src); + /* Need to resume? */ + if (udc->usb_state == USB_STATE_SUSPENDED) + if ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SUSPEND) == 0) + bus_resume(udc); + + /* USB Interrupt */ + if (irq_src & USB_STS_INT) { + VDBG("Packet int"); + /* Setup package, we only support ep0 as control ep */ + if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { + tripwire_handler(udc, 0, + (u8 *) (&udc->local_setup_buff)); + setup_received_irq(udc, &udc->local_setup_buff); + status = IRQ_HANDLED; + } + + /* completion of dtd */ + if (fsl_readl(&dr_regs->endptcomplete)) { + dtd_complete_irq(udc); + status = IRQ_HANDLED; + } + } + + /* SOF (for ISO transfer) */ + if (irq_src & USB_STS_SOF) { + status = IRQ_HANDLED; + } + + /* Port Change */ + if (irq_src & USB_STS_PORT_CHANGE) { + port_change_irq(udc); + status = IRQ_HANDLED; + } + + /* Reset Received */ + if (irq_src & USB_STS_RESET) { + VDBG("reset int"); + reset_irq(udc); + status = IRQ_HANDLED; + } + + /* Sleep Enable (Suspend) */ + if (irq_src & USB_STS_SUSPEND) { + VDBG("suspend int"); + suspend_irq(udc); + status = IRQ_HANDLED; + } + + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + VDBG("Error IRQ %x ", irq_src); + } + +irq_end: + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/*----------------------------------------------------------------* + * Hook to gadget drivers + * Called by initialization code of gadget drivers +*----------------------------------------------------------------*/ +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + int retval = -ENODEV; + unsigned long flags = 0; + + if (!udc_controller) + return -ENODEV; + + if (!bind || !driver || (driver->speed != USB_SPEED_FULL + && driver->speed != USB_SPEED_HIGH) + || !driver->disconnect + || !driver->setup) + return -EINVAL; + + if (udc_controller->driver) + return -EBUSY; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc_controller->lock, flags); + + driver->driver.bus = 0; + udc_controller->pdata->port_enables = 1; + /* hook up the driver */ + udc_controller->driver = driver; + udc_controller->gadget.dev.driver = &driver->driver; + spin_unlock_irqrestore(&udc_controller->lock, flags); + dr_clk_gate(true); + /* It doesn't need to switch usb from low power mode to normal mode + * at otg mode + */ + if (!udc_controller->transceiver) + dr_phy_low_power_mode(udc_controller, false); + + /* bind udc driver to gadget driver */ + retval = bind(&udc_controller->gadget); + if (retval) { + VDBG("bind to %s --> %d", driver->driver.name, retval); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + dr_clk_gate(false); + goto out; + } + + if (udc_controller->transceiver) { + /* Suspend the controller until OTG enable it */ + udc_controller->suspended = 1;/* let the otg resume it */ + printk(KERN_DEBUG "Suspend udc for OTG auto detect\n"); + dr_wake_up_enable(udc_controller, true); + dr_clk_gate(false); + /* export udc suspend/resume call to OTG */ + udc_controller->gadget.dev.driver->suspend = (dev_sus)fsl_udc_suspend; + udc_controller->gadget.dev.driver->resume = (dev_res)fsl_udc_resume; + + /* connect to bus through transceiver */ + retval = otg_set_peripheral(udc_controller->transceiver, + &udc_controller->gadget); + if (retval < 0) { + ERR("can't bind to transceiver\n"); + driver->unbind(&udc_controller->gadget); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + return retval; + } + } else { + /* Enable DR IRQ reg and Set usbcmd reg Run bit */ + dr_controller_run(udc_controller); + if (udc_controller->stopped) + dr_clk_gate(false); + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_dir = 0; + } + printk(KERN_INFO "%s: bind to driver %s \n", + udc_controller->gadget.name, driver->driver.name); + +out: + if (retval) { + printk(KERN_DEBUG "retval %d \n", retval); + udc_controller->pdata->port_enables = 0; + } + return retval; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +/* Disconnect from gadget driver */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct fsl_ep *loop_ep; + unsigned long flags; + + if (!udc_controller) + return -ENODEV; + + if (!driver || driver != udc_controller->driver || !driver->unbind) + return -EINVAL; + + if (udc_controller->stopped) + dr_clk_gate(true); + + if (udc_controller->transceiver) + (void)otg_set_peripheral(udc_controller->transceiver, 0); + + /* stop DR, disable intr */ + dr_controller_stop(udc_controller); + + udc_controller->pdata->port_enables = 0; + /* in fact, no needed */ + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc_controller->lock, flags); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc_controller->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc_controller->gadget.ep_list, + ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&udc_controller->lock, flags); + + /* disconnect gadget before unbinding */ + driver->disconnect(&udc_controller->gadget); + + /* unbind gadget and unhook driver. */ + driver->unbind(&udc_controller->gadget); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + + if (udc_controller->gadget.is_otg) { + dr_wake_up_enable(udc_controller, true); + } + + dr_phy_low_power_mode(udc_controller, true); + + printk(KERN_INFO "unregistered gadget driver '%s'\r\n", + driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include + +static const char proc_filename[] = "driver/fsl_usb2_udc"; + +static int fsl_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t, i; + u32 tmp_reg; + struct fsl_ep *ep = NULL; + struct fsl_req *req; + struct fsl_usb2_platform_data *pdata; + + struct fsl_udc *udc = udc_controller; + pdata = udc->pdata; + if (off != 0) + return 0; + + dr_clk_gate(true); + spin_lock_irqsave(&udc->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n\n", + driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* ------ DR Registers ----- */ + tmp_reg = fsl_readl(&dr_regs->usbcmd); + t = scnprintf(next, size, + "USBCMD reg:\n" + "SetupTW: %d\n" + "Run/Stop: %s\n\n", + (tmp_reg & USB_CMD_SUTW) ? 1 : 0, + (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbsts); + t = scnprintf(next, size, + "USB Status Reg:\n" + "Dr Suspend: %d" "Reset Received: %d" "System Error: %s" + "USB Error Interrupt: %s\n\n", + (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, + (tmp_reg & USB_STS_RESET) ? 1 : 0, + (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", + (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbintr); + t = scnprintf(next, size, + "USB Intrrupt Enable Reg:\n" + "Sleep Enable: %d" "SOF Received Enable: %d" + "Reset Enable: %d\n" + "System Error Enable: %d" + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d" "USB Intr Enable: %d\n\n", + (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, + (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, + (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, + (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, + (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->frindex); + t = scnprintf(next, size, + "USB Frame Index Reg:" "Frame Number is 0x%x\n\n", + (tmp_reg & USB_FRINDEX_MASKS)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->deviceaddr); + t = scnprintf(next, size, + "USB Device Address Reg:" "Device Addr is 0x%x\n\n", + (tmp_reg & USB_DEVICE_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->endpointlistaddr); + t = scnprintf(next, size, + "USB Endpoint List Address Reg:" + "Device Addr is 0x%x\n\n", + (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->portsc1); + t = scnprintf(next, size, + "USB Port Status&Control Reg:\n" + "Port Transceiver Type : %s" "Port Speed: %s \n" + "PHY Low Power Suspend: %s" "Port Reset: %s" + "Port Suspend Mode: %s \n" "Over-current Change: %s" + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s" + "Current Connect Status: %s\n\n", ({ + char *s; + switch (tmp_reg & PORTSCX_PTS_FSLS) { + case PORTSCX_PTS_UTMI: + s = "UTMI"; break; + case PORTSCX_PTS_ULPI: + s = "ULPI "; break; + case PORTSCX_PTS_FSLS: + s = "FS/LS Serial"; break; + default: + s = "None"; break; + } + s; }), ({ + char *s; + switch (tmp_reg & PORTSCX_PORT_SPEED_UNDEF) { + case PORTSCX_PORT_SPEED_FULL: + s = "Full Speed"; break; + case PORTSCX_PORT_SPEED_LOW: + s = "Low Speed"; break; + case PORTSCX_PORT_SPEED_HIGH: + s = "High Speed"; break; + default: + s = "Undefined"; break; + } + s; + }), + (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? + "Normal PHY mode" : "Low power mode", + (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : + "Not in Reset", + (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", + (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : + "No", + (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : + "Not change", + (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : + "Not correct", + (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? + "Attached" : "Not-Att"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbmode); + t = scnprintf(next, size, + "USB Mode Reg:" "Controller Mode is : %s\n\n", ({ + char *s; + switch (tmp_reg & USB_MODE_CTRL_MODE_HOST) { + case USB_MODE_CTRL_MODE_IDLE: + s = "Idle"; break; + case USB_MODE_CTRL_MODE_DEVICE: + s = "Device Controller"; break; + case USB_MODE_CTRL_MODE_HOST: + s = "Host Controller"; break; + default: + s = "None"; break; + } + s; + })); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->endptsetupstat); + t = scnprintf(next, size, + "Endpoint Setup Status Reg:" "SETUP on ep 0x%x\n\n", + (tmp_reg & EP_SETUP_STATUS_MASK)); + size -= t; + next += t; + + for (i = 0; i < udc->max_ep / 2; i++) { + tmp_reg = fsl_readl(&dr_regs->endptctrl[i]); + t = scnprintf(next, size, "EP Ctrl Reg [0x%x]: = [0x%x]\n", + i, tmp_reg); + size -= t; + next += t; + } + tmp_reg = fsl_readl(&dr_regs->endpointprime); + t = scnprintf(next, size, "EP Prime Reg = [0x%x]\n", tmp_reg); + size -= t; + next += t; + + if (pdata->have_sysif_regs) { + tmp_reg = usb_sys_regs->snoop1; + t = scnprintf(next, size, "\nSnoop1 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; + } + + /* ------fsl_udc, fsl_ep, fsl_request structure information ----- */ + ep = &udc->eps[0]; + t = scnprintf(next, size, "For %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + t = scnprintf(next, size, + "\nFor %s Maxpkt is 0x%x " + "index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), + ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length" + "0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } /* end for each_entry of ep req */ + } /* end for else */ + } /* end for if(ep->queue) */ + } /* end (ep->desc) */ + + spin_unlock_irqrestore(&udc->lock, flags); + dr_clk_gate(false); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, fsl_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/* Release udc structures */ +static void fsl_udc_release(struct device *dev) +{ + complete(udc_controller->done); + dma_free_coherent(dev, udc_controller->ep_qh_size, + udc_controller->ep_qh, udc_controller->ep_qh_dma); + kfree(udc_controller); +} + +/****************************************************************** + Internal structure setup functions +*******************************************************************/ +/*------------------------------------------------------------------ + * init resource for globle controller + * Return the udc handle on success or NULL on failure + ------------------------------------------------------------------*/ +static int __init struct_udc_setup(struct fsl_udc *udc, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + size_t size; + + pdata = pdev->dev.platform_data; + udc->phy_mode = pdata->phy_mode; + + udc->eps = kzalloc(sizeof(struct fsl_ep) * udc->max_ep, GFP_KERNEL); + if (!udc->eps) { + ERR("malloc fsl_ep failed\n"); + return -1; + } + + /* initialized QHs, take care of alignment */ + size = udc->max_ep * sizeof(struct ep_queue_head); + if (size < QH_ALIGNMENT) + size = QH_ALIGNMENT; + else if ((size % QH_ALIGNMENT) != 0) { + size += QH_ALIGNMENT + 1; + size &= ~(QH_ALIGNMENT - 1); + } + udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, + &udc->ep_qh_dma, GFP_KERNEL); + if (!udc->ep_qh) { + ERR("malloc QHs for udc failed\n"); + kfree(udc->eps); + return -1; + } + + udc->ep_qh_size = size; + + /* Initialize ep0 status request structure */ + /* FIXME: fsl_alloc_request() ignores ep argument */ + udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), + struct fsl_req, req); + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->status_req->req.dma = virt_to_phys(udc->status_req->req.buf); + /* Initialize ep0 data request structure */ + udc->data_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), + struct fsl_req, req); + udc->data_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->data_req->req.dma = virt_to_phys(udc->data_req->req.buf); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + udc->remote_wakeup = 0; /* default to 0 on reset */ + spin_lock_init(&udc->lock); + + return 0; +} + +/*---------------------------------------------------------------- + * Setup the fsl_ep struct for eps + * Link fsl_ep->ep to gadget->ep_list + * ep0out is not used so do nothing here + * ep0in should be taken care + *--------------------------------------------------------------*/ +static int __init struct_ep_setup(struct fsl_udc *udc, unsigned char index, + char *name, int link) +{ + struct fsl_ep *ep = &udc->eps[index]; + + ep->udc = udc; + strcpy(ep->name, name); + ep->ep.name = ep->name; + + ep->ep.ops = &fsl_ep_ops; + ep->stopped = 0; + + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + ep->ep.maxpacket = (unsigned short) ~0; + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* gagdet.ep_list used for ep_autoconfig so no ep0 */ + if (link) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->gadget = &udc->gadget; + ep->qh = &udc->ep_qh[index]; + + return 0; +} + +/* Driver probe function + * all intialization operations implemented here except enabling usb_intr reg + * board setup should have been done in the platform code + */ +static int __init fsl_udc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + int ret = -ENODEV; + unsigned int i; + u32 dccparams; + + udc_controller = kzalloc(sizeof(struct fsl_udc), GFP_KERNEL); + if (udc_controller == NULL) { + ERR("malloc udc failed\n"); + return -ENOMEM; + } + udc_controller->pdata = pdata; + +#ifdef CONFIG_USB_OTG + /* Memory and interrupt resources will be passed from OTG */ + udc_controller->transceiver = otg_get_transceiver(); + if (!udc_controller->transceiver) { + printk(KERN_ERR "Can't find OTG driver!\n"); + ret = -ENODEV; + goto err1a; + } + udc_controller->gadget.is_otg = 1; +#endif + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENXIO; + goto err1a; + } + +#ifndef CONFIG_USB_OTG + if (!request_mem_region(res->start, resource_size(res), + driver_name)) { + ERR("request mem region for %s failed \n", pdev->name); + ret = -EBUSY; + goto err1a; + } +#endif + dr_regs = ioremap(res->start, resource_size(res)); + if (!dr_regs) { + ret = -ENOMEM; + goto err1; + } + 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 err2a; + } + + /* Due to mx35/mx25's phy's bug */ + reset_phy(); + + if (pdata->have_sysif_regs) + usb_sys_regs = (struct usb_sys_interface *) + ((u32)dr_regs + USB_DR_SYS_OFFSET); + + /* Read Device Controller Capability Parameters register */ + dccparams = fsl_readl(&dr_regs->dccparams); + if (!(dccparams & DCCPARAMS_DC)) { + ERR("This SOC doesn't support device role\n"); + ret = -ENODEV; + goto err2; + } + /* Get max device endpoints */ + /* DEN is bidirectional ep number, max_ep doubles the number */ + udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; + + udc_controller->irq = platform_get_irq(pdev, 0); + if (!udc_controller->irq) { + ret = -ENODEV; + goto err2; + } + + ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, + driver_name, udc_controller); + if (ret != 0) { + ERR("cannot request irq %d err %d \n", + udc_controller->irq, ret); + goto err2; + } + + /* Initialize the udc structure including QH member and other member */ + if (struct_udc_setup(udc_controller, pdev)) { + ERR("Can't initialize udc data structure\n"); + ret = -ENOMEM; + goto err3; + } + + if (!udc_controller->transceiver) { + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched */ + dr_controller_setup(udc_controller); + } + + /* Setup gadget structure */ + udc_controller->gadget.ops = &fsl_gadget_ops; + udc_controller->gadget.is_dualspeed = 1; + udc_controller->gadget.ep0 = &udc_controller->eps[0].ep; + INIT_LIST_HEAD(&udc_controller->gadget.ep_list); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + udc_controller->gadget.name = driver_name; + + /* Setup gadget.dev and register with kernel */ + dev_set_name(&udc_controller->gadget.dev, "gadget"); + udc_controller->gadget.dev.release = fsl_udc_release; + udc_controller->gadget.dev.parent = &pdev->dev; + ret = device_register(&udc_controller->gadget.dev); + if (ret < 0) + goto err3; + + /* setup QH and epctrl for ep0 */ + ep0_setup(udc_controller); + + /* setup udc->eps[] for ep0 */ + struct_ep_setup(udc_controller, 0, "ep0", 0); + /* for ep0: the desc defined here; + * for other eps, gadget layer called ep_enable with defined desc + */ + udc_controller->eps[0].desc = &fsl_ep0_desc; + udc_controller->eps[0].ep.maxpacket = USB_MAX_CTRL_PAYLOAD; + + /* setup the udc->eps[] for non-control endpoints and link + * to gadget.ep_list */ + for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) { + char name[14]; + + sprintf(name, "ep%dout", i); + struct_ep_setup(udc_controller, i * 2, name, 1); + sprintf(name, "ep%din", i); + struct_ep_setup(udc_controller, i * 2 + 1, name, 1); + } + + /* use dma_pool for TD management */ + udc_controller->td_pool = dma_pool_create("udc_td", &pdev->dev, + sizeof(struct ep_td_struct), + DTD_ALIGNMENT, UDC_DMA_BOUNDARY); + if (udc_controller->td_pool == NULL) { + ret = -ENOMEM; + goto err4; + } + if (g_iram_size) { + g_iram_addr = iram_alloc(USB_IRAM_SIZE, &g_iram_base); + for (i = 0; i < IRAM_PPH_NTD; i++) { + udc_controller->iram_buffer[i] = + g_iram_base + i * g_iram_size; + udc_controller->iram_buffer_v[i] = + g_iram_addr + i * g_iram_size; + } + } + + INIT_WORK(&udc_controller->gadget_work, fsl_gadget_event); + INIT_DELAYED_WORK(&udc_controller->gadget_delay_work, + fsl_gadget_delay_event); +#ifdef POSTPONE_FREE_LAST_DTD + last_free_td = NULL; +#endif + + /* disable all INTR */ +#ifndef CONFIG_USB_OTG + fsl_writel(0, &dr_regs->usbintr); + dr_wake_up_enable(udc_controller, false); +#else + dr_wake_up_enable(udc_controller, true); +#endif + +/* + * As mx25/mx35 does not implement clk_gate, should not let phy to low + * power mode due to IC bug + */ +#if !(defined CONFIG_ARCH_MX35 || defined CONFIG_ARCH_MX25) +{ + dr_phy_low_power_mode(udc_controller, true); +} +#endif + udc_controller->stopped = 1; + + /* let the gadget register function open the clk */ + dr_clk_gate(false); + + create_proc_file(); + return 0; + +err4: + device_unregister(&udc_controller->gadget.dev); +err3: + free_irq(udc_controller->irq, udc_controller); +err2: + if (pdata->exit) + pdata->exit(pdata->pdev); +err2a: + iounmap((u8 __iomem *)dr_regs); +err1: + if (!udc_controller->transceiver) + release_mem_region(res->start, resource_size(res)); +err1a: + kfree(udc_controller); + udc_controller = NULL; + return ret; +} + +/* Driver removal function + * Free resources and finish pending transactions + */ +static int __exit fsl_udc_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + DECLARE_COMPLETION(done); + + if (!udc_controller) + return -ENODEV; + udc_controller->done = &done; + /* open USB PHY clock */ + if (udc_controller->stopped) + dr_clk_gate(true); + + /* DR has been stopped in usb_gadget_unregister_driver() */ + remove_proc_file(); + + /* Free allocated memory */ + if (g_iram_size) + iram_free(g_iram_base, IRAM_PPH_NTD * g_iram_size); + kfree(udc_controller->status_req->req.buf); + kfree(udc_controller->status_req); + kfree(udc_controller->data_req->req.buf); + kfree(udc_controller->data_req); + kfree(udc_controller->eps); +#ifdef POSTPONE_FREE_LAST_DTD + if (last_free_td != NULL) + dma_pool_free(udc_controller->td_pool, last_free_td, + last_free_td->td_dma); +#endif + dma_pool_destroy(udc_controller->td_pool); + free_irq(udc_controller->irq, udc_controller); + iounmap((u8 __iomem *)dr_regs); + +#ifndef CONFIG_USB_OTG +{ + struct resource *res; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); +} +#endif + + device_unregister(&udc_controller->gadget.dev); + /* 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(pdata->pdev); + + if (udc_controller->stopped) + dr_clk_gate(false); + return 0; +} + +static bool udc_can_wakeup_system(void) +{ + struct fsl_usb2_platform_data *pdata = udc_controller->pdata; + + if (pdata->operating_mode == FSL_USB2_DR_OTG) + if (device_may_wakeup(udc_controller->transceiver->dev)) + return true; + else + return false; + else + if (device_may_wakeup(udc_controller->gadget.dev.parent)) + return true; + else + return false; +} + +static int udc_suspend(struct fsl_udc *udc) +{ + struct fsl_usb2_platform_data *pdata = udc_controller->pdata; + u32 mode, usbcmd; + + /* + * When it is the PM suspend routine and the device has no + * abilities to wakeup system, it should not set wakeup enable. + * Otherwise, the system will wakeup even the user only wants to + * charge using usb + */ + printk(KERN_DEBUG "udc suspend begins\n"); + if (pdata->pmflags == 0) { + if (!udc_can_wakeup_system()) + dr_wake_up_enable(udc, false); + else + dr_wake_up_enable(udc, true); + } + + mode = fsl_readl(&dr_regs->usbmode) & USB_MODE_CTRL_MODE_MASK; + usbcmd = fsl_readl(&dr_regs->usbcmd); + + /* + * If the controller is already stopped, then this must be a + * PM suspend. Remember this fact, so that we will leave the + * controller stopped at PM resume time. + */ + if (udc->suspended) { + printk(KERN_DEBUG "gadget already suspended, leaving early\n"); + goto out; + } + + if (mode != USB_MODE_CTRL_MODE_DEVICE) { + printk(KERN_DEBUG "gadget not in device mode, leaving early\n"); + goto out; + } + + /* Comment udc_wait_b_session_low, uncomment it at below two + * situations: + * 1. the user wants to debug some problems about vbus + * 2. the vbus discharges very slow at user's board + */ + + /* For some buggy hardware designs, see comment of this function for detail */ + /* udc_wait_b_session_low(); */ + + udc->stopped = 1; + + /* stop the controller */ + usbcmd = fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP; + fsl_writel(usbcmd, &dr_regs->usbcmd); + + dr_phy_low_power_mode(udc, true); + printk(KERN_DEBUG "USB Gadget suspend ends\n"); +out: + udc->suspended++; + if (udc->suspended > 2) + printk(KERN_ERR "ERROR: suspended times > 2\n"); + + return 0; +} + +/*----------------------------------------------------------------- + * Modify Power management attributes + * Used by OTG statemachine to disable gadget temporarily + -----------------------------------------------------------------*/ +static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret; +#ifdef CONFIG_USB_OTG + if (udc_controller->transceiver->gadget == NULL) + return 0; +#endif + if (udc_controller->stopped) + dr_clk_gate(true); + if (((!(udc_controller->gadget.is_otg)) || + (fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) && + (udc_controller->usb_state > USB_STATE_POWERED) && + (udc_controller->usb_state < USB_STATE_SUSPENDED)) { + return -EBUSY;/* keep the clk on */ + } else + ret = udc_suspend(udc_controller); + dr_clk_gate(false); + + return ret; +} + +/*----------------------------------------------------------------- + * Invoked on USB resume. May be called in_interrupt. + * Here we start the DR controller and enable the irq + *-----------------------------------------------------------------*/ +static int fsl_udc_resume(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = udc_controller->pdata; + struct fsl_usb2_wakeup_platform_data *wake_up_pdata = pdata->wakeup_pdata; + printk(KERN_DEBUG "USB Gadget resume begins\n"); + + if (pdata->pmflags == 0) { + printk(KERN_DEBUG "%s, Wait for wakeup thread finishes\n", __func__); + wait_event_interruptible(wake_up_pdata->wq, !wake_up_pdata->usb_wakeup_is_pending); + } + +#ifdef CONFIG_USB_OTG + if (udc_controller->transceiver->gadget == NULL) { + return 0; + } +#endif + mutex_lock(&udc_resume_mutex); + + pr_debug("%s(): stopped %d suspended %d\n", __func__, + udc_controller->stopped, udc_controller->suspended); + /* Do noop if the udc is already at resume state */ + if (udc_controller->suspended == 0) { + mutex_unlock(&udc_resume_mutex); + return 0; + } + + /* + * If the controller was stopped at suspend time, then + * don't resume it now. + */ + + if (udc_controller->suspended > 1) { + printk(KERN_DEBUG "gadget was already stopped, leaving early\n"); + if (udc_controller->stopped) { + dr_clk_gate(true); + } + goto end; + } + + /* Enable DR irq reg and set controller Run */ + if (udc_controller->stopped) { + /* the clock is already on at usb wakeup routine */ + if (pdata->lowpower) + dr_clk_gate(true); + dr_wake_up_enable(udc_controller, false); + dr_phy_low_power_mode(udc_controller, false); + usb_debounce_id_vbus(); + /* if in host mode, we need to do nothing */ + if ((fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID) == 0) { + dr_phy_low_power_mode(udc_controller, true); + dr_wake_up_enable(udc_controller, true); + goto end; + } + dr_controller_setup(udc_controller); + dr_controller_run(udc_controller); + } + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_dir = 0; + +end: + /* if udc is resume by otg id change and no device + * connecting to the otg, otg will enter low power mode*/ + if (udc_controller->stopped) { + /* + * If it is PM resume routine, the udc is at low power mode, + * and the udc has no abilities to wakeup system, it should + * set the abilities to wakeup itself. Otherwise, the usb + * subsystem will not leave from low power mode. + */ + if (!udc_can_wakeup_system() && + (pdata->pmflags == 0)) { + dr_wake_up_enable(udc_controller, true); + } + + dr_clk_gate(false); + } + --udc_controller->suspended; + mutex_unlock(&udc_resume_mutex); + printk(KERN_DEBUG "USB Gadget resume ends\n"); + return 0; +} + +/*------------------------------------------------------------------------- + Register entry point for the peripheral controller driver +--------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .remove = __exit_p(fsl_udc_remove), + /* these suspend and resume are not usb suspend and resume */ + .suspend = fsl_udc_suspend, + .resume = fsl_udc_resume, + .probe = fsl_udc_probe, + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, +}; + +static int __init udc_init(void) +{ + printk(KERN_INFO "%s (%s)\n", driver_desc, DRIVER_VERSION); + return platform_driver_register(&udc_driver); +} +#ifdef CONFIG_MXS_VBUS_CURRENT_DRAW + fs_initcall(udc_init); +#else + module_init(udc_init); +#endif +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); + printk(KERN_INFO "%s unregistered \n", driver_desc); +} + +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/arcotg_udc.h b/drivers/usb/gadget/arcotg_udc.h new file mode 100755 index 000000000000..bc88afb360c3 --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.h @@ -0,0 +1,712 @@ +/* + * Copyright (C) 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + */ + +/*! + * @file arcotg_udc.h + * @brief Freescale USB device/endpoint management registers + * @ingroup USB + */ + +#ifndef __ARCOTG_UDC_H +#define __ARCOTG_UDC_H + +#define TRUE 1 +#define FALSE 0 + +#define MSC_BULK_CB_WRAP_LEN 31 +#define USE_MSC_WR(len) false + +/* Iram patch */ +#ifdef CONFIG_USB_STATIC_IRAM_PPH +/* size of 1 qTD's buffer,one is for BULK IN and other is BULK OUT */ +#define USB_IRAM_SIZE SZ_8K +#define IRAM_TD_PPH_SIZE (USB_IRAM_SIZE / 2) +#define IRAM_PPH_NTD 2 /* number of TDs in IRAM */ +#else +#define USB_IRAM_SIZE 0 +#define IRAM_TD_PPH_SIZE 0 +#define IRAM_PPH_NTD 0 +#endif + +#define NEED_IRAM(ep) ((g_iram_size) && \ + ((ep)->desc->bmAttributes == USB_ENDPOINT_XFER_BULK)) + +#ifdef CONFIG_ARCH_MX5 +#define POSTPONE_FREE_LAST_DTD +#else +#undef POSTPONE_FREE_LAST_DTD +#endif + +/* ### define USB registers here + */ +#define USB_MAX_ENDPOINTS 8 +#define USB_MAX_PIPES (USB_MAX_ENDPOINTS*2) +#define USB_MAX_CTRL_PAYLOAD 64 +#define USB_DR_SYS_OFFSET 0x400 + +#define USB_DR_OFFSET 0x3100 + +struct usb_dr_device { + /* Capability register */ + u32 id; + u32 res1[35]; + u32 sbuscfg; /* sbuscfg ahb burst */ + u32 res11[27]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u32 res2[5]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u32 res3[6]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u32 res4; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u32 res5; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u32 res6[6]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u32 res7[7]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[8 * 2]; /* Endpoint Control Registers */ + u32 res8[256]; +#ifdef CONFIG_ARCH_MX5 + u32 res9[128]; /* i.MX51 start from 0x800 */ +#endif + u32 usbctrl; + u32 otgmirror; + u32 phyctrl0; + u32 phyctrl1; + u32 ctrl1; + u32 uh2ctrl; +}; + + /* non-EHCI USB system interface registers (Big Endian) */ +struct usb_sys_interface { + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res[236]; + u32 control; /* General Purpose Control Register */ +}; + +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +/* Device Controller Capability Parameter register */ +#define DCCPARAMS_DC 0x00000080 +#define DCCPARAMS_DEN_MASK 0x0000001f + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS (0x3fff) +/* USB CMD Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x00000001) +#define USB_CMD_CTRL_RESET (0x00000002) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x00000010) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x00000020) +#define USB_CMD_INT_AA_DOORBELL (0x00000040) +#define USB_CMD_ASP (0x00000300) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x00000800) +#define USB_CMD_SUTW (0x00002000) +#define USB_CMD_ATDTW (0x00004000) +#define USB_CMD_ITC (0x00FF0000) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x00000000) +#define USB_CMD_FRAME_SIZE_512 (0x00000004) +#define USB_CMD_FRAME_SIZE_256 (0x00000008) +#define USB_CMD_FRAME_SIZE_128 (0x0000000C) +#define USB_CMD_FRAME_SIZE_64 (0x00008000) +#define USB_CMD_FRAME_SIZE_32 (0x00008004) +#define USB_CMD_FRAME_SIZE_16 (0x00008008) +#define USB_CMD_FRAME_SIZE_8 (0x0000800C) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x00000000) +#define USB_CMD_ASP_01 (0x00000100) +#define USB_CMD_ASP_10 (0x00000200) +#define USB_CMD_ASP_11 (0x00000300) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00000000) +#define USB_CMD_ITC_1_MICRO_FRM (0x00010000) +#define USB_CMD_ITC_2_MICRO_FRM (0x00020000) +#define USB_CMD_ITC_4_MICRO_FRM (0x00040000) +#define USB_CMD_ITC_8_MICRO_FRM (0x00080000) +#define USB_CMD_ITC_16_MICRO_FRM (0x00100000) +#define USB_CMD_ITC_32_MICRO_FRM (0x00200000) +#define USB_CMD_ITC_64_MICRO_FRM (0x00400000) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB STS Register Bit Masks */ +#define USB_STS_INT (0x00000001) +#define USB_STS_ERR (0x00000002) +#define USB_STS_PORT_CHANGE (0x00000004) +#define USB_STS_FRM_LST_ROLL (0x00000008) +#define USB_STS_SYS_ERR (0x00000010) +#define USB_STS_IAA (0x00000020) +#define USB_STS_RESET (0x00000040) +#define USB_STS_SOF (0x00000080) +#define USB_STS_SUSPEND (0x00000100) +#define USB_STS_HC_HALTED (0x00001000) +#define USB_STS_RCL (0x00002000) +#define USB_STS_PERIODIC_SCHEDULE (0x00004000) +#define USB_STS_ASYNC_SCHEDULE (0x00008000) + +/* USB INTR Register Bit Masks */ +#define USB_INTR_INT_EN (0x00000001) +#define USB_INTR_ERR_INT_EN (0x00000002) +#define USB_INTR_PTC_DETECT_EN (0x00000004) +#define USB_INTR_FRM_LST_ROLL_EN (0x00000008) +#define USB_INTR_SYS_ERR_EN (0x00000010) +#define USB_INTR_ASYN_ADV_EN (0x00000020) +#define USB_INTR_RESET_EN (0x00000040) +#define USB_INTR_SOF_EN (0x00000080) +#define USB_INTR_DEVICE_SUSPEND (0x00000100) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0xFE000000) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK (0xfffff800) + +/* PORTSCX Register Bit Masks */ +#define PORTSCX_CURRENT_CONNECT_STATUS (0x00000001) +#define PORTSCX_CONNECT_STATUS_CHANGE (0x00000002) +#define PORTSCX_PORT_ENABLE (0x00000004) +#define PORTSCX_PORT_EN_DIS_CHANGE (0x00000008) +#define PORTSCX_OVER_CURRENT_ACT (0x00000010) +#define PORTSCX_OVER_CURRENT_CHG (0x00000020) +#define PORTSCX_PORT_FORCE_RESUME (0x00000040) +#define PORTSCX_PORT_SUSPEND (0x00000080) +#define PORTSCX_PORT_RESET (0x00000100) +#define PORTSCX_LINE_STATUS_BITS (0x00000C00) +#define PORTSCX_PORT_POWER (0x00001000) +#define PORTSCX_PORT_INDICTOR_CTRL (0x0000C000) +#define PORTSCX_PORT_TEST_CTRL (0x000F0000) +#define PORTSCX_WAKE_ON_CONNECT_EN (0x00100000) +#define PORTSCX_WAKE_ON_CONNECT_DIS (0x00200000) +#define PORTSCX_WAKE_ON_OVER_CURRENT (0x00400000) +#define PORTSCX_PHY_LOW_POWER_SPD (0x00800000) +#define PORTSCX_PORT_FORCE_FULL_SPEED (0x01000000) +#define PORTSCX_PORT_SPEED_MASK (0x0C000000) +#define PORTSCX_PORT_WIDTH (0x10000000) +#define PORTSCX_PHY_TYPE_SEL (0xC0000000) + +/* bit 11-10 are line status */ +#define PORTSCX_LINE_STATUS_SE0 (0x00000000) +#define PORTSCX_LINE_STATUS_JSTATE (0x00000400) +#define PORTSCX_LINE_STATUS_KSTATE (0x00000800) +#define PORTSCX_LINE_STATUS_UNDEF (0x00000C00) +#define PORTSCX_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSCX_PIC_OFF (0x00000000) +#define PORTSCX_PIC_AMBER (0x00004000) +#define PORTSCX_PIC_GREEN (0x00008000) +#define PORTSCX_PIC_UNDEF (0x0000C000) +#define PORTSCX_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSCX_PTC_DISABLE (0x00000000) +#define PORTSCX_PTC_JSTATE (0x00010000) +#define PORTSCX_PTC_KSTATE (0x00020000) +#define PORTSCX_PTC_SEQNAK (0x00030000) +#define PORTSCX_PTC_PACKET (0x00040000) +#define PORTSCX_PTC_FORCE_EN (0x00050000) +#define PORTSCX_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSCX_PORT_SPEED_FULL (0x00000000) +#define PORTSCX_PORT_SPEED_LOW (0x04000000) +#define PORTSCX_PORT_SPEED_HIGH (0x08000000) +#define PORTSCX_PORT_SPEED_UNDEF (0x0C000000) +#define PORTSCX_SPEED_BIT_POS (26) + +/* OTGSC Register Bit Masks */ +#define OTGSC_ID_CHANGE_IRQ_STS (1 << 16) +#define OTGSC_B_SESSION_VALID_IRQ_EN (1 << 27) +#define OTGSC_B_SESSION_VALID_IRQ_STS (1 << 19) +#define OTGSC_B_SESSION_VALID (1 << 11) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSCX_PTW (0x10000000) +#define PORTSCX_PTW_8BIT (0x00000000) +#define PORTSCX_PTW_16BIT (0x10000000) + +/* bit 31-30 are port transceiver select */ +#define PORTSCX_PTS_UTMI (0x00000000) +#define PORTSCX_PTS_ULPI (0x80000000) +#define PORTSCX_PTS_FSLS (0xC0000000) +#define PORTSCX_PTS_BIT_POS (30) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x00000000) +#define USB_MODE_CTRL_MODE_DEVICE (0x00000002) +#define USB_MODE_CTRL_MODE_HOST (0x00000003) +#define USB_MODE_CTRL_MODE_MASK 0x00000003 +#define USB_MODE_CTRL_MODE_RSV (0x00000001) +#define USB_MODE_ES 0x00000004 /* (big) Endian Sel */ +#define USB_MODE_SETUP_LOCK_OFF (0x00000008) +#define USB_MODE_STREAM_DISABLE (0x00000010) +/* Endpoint Flush Register */ +#define EPFLUSH_TX_OFFSET (0x00010000) +#define EPFLUSH_RX_OFFSET (0x00000000) + +/* Endpoint Setup Status bit masks */ +#define EP_SETUP_STATUS_MASK (0x0000003F) +#define EP_SETUP_STATUS_EP0 (0x00000001) + +/* ENDPOINTCTRLx Register Bit Masks */ +#define EPCTRL_TX_ENABLE (0x00800000) +#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) /* Not EP0 */ +#define EPCTRL_TX_DATA_TOGGLE_INH (0x00200000) /* Not EP0 */ +#define EPCTRL_TX_TYPE (0x000C0000) +#define EPCTRL_TX_DATA_SOURCE (0x00020000) /* Not EP0 */ +#define EPCTRL_TX_EP_STALL (0x00010000) +#define EPCTRL_RX_ENABLE (0x00000080) +#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) /* Not EP0 */ +#define EPCTRL_RX_DATA_TOGGLE_INH (0x00000020) /* Not EP0 */ +#define EPCTRL_RX_TYPE (0x0000000C) +#define EPCTRL_RX_DATA_SINK (0x00000002) /* Not EP0 */ +#define EPCTRL_RX_EP_STALL (0x00000001) + +/* bit 19-18 and 3-2 are endpoint type */ +#define EPCTRL_EP_TYPE_CONTROL (0) +#define EPCTRL_EP_TYPE_ISO (1) +#define EPCTRL_EP_TYPE_BULK (2) +#define EPCTRL_EP_TYPE_INTERRUPT (3) +#define EPCTRL_TX_EP_TYPE_SHIFT (18) +#define EPCTRL_RX_EP_TYPE_SHIFT (2) + +/* SNOOPn Register Bit Masks */ +#define SNOOP_ADDRESS_MASK (0xFFFFF000) +#define SNOOP_SIZE_ZERO (0x00) /* snooping disable */ +#define SNOOP_SIZE_4KB (0x0B) /* 4KB snoop size */ +#define SNOOP_SIZE_8KB (0x0C) +#define SNOOP_SIZE_16KB (0x0D) +#define SNOOP_SIZE_32KB (0x0E) +#define SNOOP_SIZE_64KB (0x0F) +#define SNOOP_SIZE_128KB (0x10) +#define SNOOP_SIZE_256KB (0x11) +#define SNOOP_SIZE_512KB (0x12) +#define SNOOP_SIZE_1MB (0x13) +#define SNOOP_SIZE_2MB (0x14) +#define SNOOP_SIZE_4MB (0x15) +#define SNOOP_SIZE_8MB (0x16) +#define SNOOP_SIZE_16MB (0x17) +#define SNOOP_SIZE_32MB (0x18) +#define SNOOP_SIZE_64MB (0x19) +#define SNOOP_SIZE_128MB (0x1A) +#define SNOOP_SIZE_256MB (0x1B) +#define SNOOP_SIZE_512MB (0x1C) +#define SNOOP_SIZE_1GB (0x1D) +#define SNOOP_SIZE_2GB (0x1E) /* 2GB snoop size */ + +/* pri_ctrl Register Bit Masks */ +#define PRI_CTRL_PRI_LVL1 (0x0000000C) +#define PRI_CTRL_PRI_LVL0 (0x00000003) + +/* si_ctrl Register Bit Masks */ +#define SI_CTRL_ERR_DISABLE (0x00000010) +#define SI_CTRL_IDRC_DISABLE (0x00000008) +#define SI_CTRL_RD_SAFE_EN (0x00000004) +#define SI_CTRL_RD_PREFETCH_DISABLE (0x00000002) +#define SI_CTRL_RD_PREFEFETCH_VAL (0x00000001) + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x00000004) +#define USB_CTRL_ULPI_INT0EN (0x00000001) +#define USB_CTRL_OTG_WUIR (0x80000000) +#define USB_CTRL_OTG_WUIE (0x08000000) +#define USB_CTRL_OTG_VWUE (0x00001000) +#define USB_CTRL_OTG_IWUE (0x00100000) + +/* PHY control0 Register Bit Masks */ +#define PHY_CTRL0_CONF2 (1 << 26) +#define PHY_CTRL0_USBEN (1 << 24) /* USB UTMI PHY Enable */ + +/* USB UH2 CTRL Register Bits */ +#define USB_UH2_OVBWK_EN (1 << 6) /* OTG VBUS Wakeup Enable */ +#define USB_UH2_OIDWK_EN (1 << 5) /* OTG ID Wakeup Enable */ +/*! + * Endpoint Queue Head data struct + * Rem: all the variables of qh are LittleEndian Mode + * and NEXT_POINTER_MASK should operate on a LittleEndian, Phy Addr + */ +struct ep_queue_head { + /*! + * Mult(31-30) , Zlt(29) , Max Pkt len and IOS(15) + */ + u32 max_pkt_length; + + /*! + * Current dTD Pointer(31-5) + */ + u32 curr_dtd_ptr; + + /*! + * Next dTD Pointer(31-5), T(0) + */ + u32 next_dtd_ptr; + + /*! + * Total bytes (30-16), IOC (15), MultO(11-10), STS (7-0) + */ + u32 size_ioc_int_sts; + + /*! + * Buffer pointer Page 0 (31-12) + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 (31-12) + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 (31-12) + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 (31-12) + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 (31-12) + */ + u32 buff_ptr4; + + /*! + * reserved field 1 + */ + u32 res1; + /*! + * Setup data 8 bytes + */ + u8 setup_buffer[8]; /* Setup data 8 bytes */ + + /*! + * reserved field 2,pad out to 64 bytes + */ + u32 res2[4]; +}; + +/* Endpoint Queue Head Bit Masks */ +#define EP_QUEUE_HEAD_MULT_POS (30) +#define EP_QUEUE_HEAD_ZLT_SEL (0x20000000) +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS (16) +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS (0x00008000) +#define EP_QUEUE_HEAD_NEXT_TERMINATE (0x00000001) +#define EP_QUEUE_HEAD_IOC (0x00008000) +#define EP_QUEUE_HEAD_MULTO (0x00000C00) +#define EP_QUEUE_HEAD_STATUS_HALT (0x00000040) +#define EP_QUEUE_HEAD_STATUS_ACTIVE (0x00000080) +#define EP_QUEUE_CURRENT_OFFSET_MASK (0x00000FFF) +#define EP_QUEUE_HEAD_NEXT_POINTER_MASK 0xFFFFFFE0 +#define EP_QUEUE_FRINDEX_MASK (0x000007FF) +#define EP_MAX_LENGTH_TRANSFER (0x4000) + +/*! + * Endpoint Transfer Descriptor data struct + * Rem: all the variables of td are LittleEndian Mode + * must be 32-byte aligned + */ +struct ep_td_struct { + /*! + * Next TD pointer(31-5), T(0) set indicate invalid + */ + u32 next_td_ptr; + + /*! + * Total bytes (30-16), IOC (15),MultO(11-10), STS (7-0) + */ + u32 size_ioc_sts; + + /*! + * Buffer pointer Page 0 + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 + */ + u32 buff_ptr4; + + /*! + * dma address of this td + * */ + dma_addr_t td_dma; + + /*! + * virtual address of next td + * */ + struct ep_td_struct *next_td_virt; + + /*! + * make it an even 16 words + * */ + u32 res[7]; +}; + +/*! + * Endpoint Transfer Descriptor bit Masks + */ +#define DTD_NEXT_TERMINATE (0x00000001) +#define DTD_IOC (0x00008000) +#define DTD_STATUS_ACTIVE (0x00000080) +#define DTD_STATUS_HALTED (0x00000040) +#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) +#define DTD_STATUS_TRANSACTION_ERR (0x00000008) +#define DTD_RESERVED_FIELDS (0x80007300) +#define DTD_ADDR_MASK 0xFFFFFFE0 +#define DTD_PACKET_SIZE (0x7FFF0000) +#define DTD_LENGTH_BIT_POS (16) +#define DTD_ERROR_MASK (DTD_STATUS_HALTED | \ + DTD_STATUS_DATA_BUFF_ERR | \ + DTD_STATUS_TRANSACTION_ERR) +/* Alignment requirements; must be a power of two */ +#define DTD_ALIGNMENT 0x20 +#define QH_ALIGNMENT 2048 + +/* Controller dma boundary */ +#define UDC_DMA_BOUNDARY 0x1000 + +/* -----------------------------------------------------------------------*/ +/* ##### enum data +*/ +typedef enum { + e_ULPI, + e_UTMI_8BIT, + e_UTMI_16BIT, + e_SERIAL +} e_PhyInterface; + +/*-------------------------------------------------------------------------*/ + +struct fsl_req { + struct usb_request req; + struct list_head queue; + /* ep_queue() func will add + a request->queue into a udc_ep->queue 'd tail */ + struct fsl_ep *ep; + unsigned mapped; + + struct ep_td_struct *head, *tail; /* For dTD List + this is a BigEndian Virtual addr */ + unsigned int dtd_count; + /* just for IRAM patch */ + dma_addr_t oridma; /* original dma */ + size_t buffer_offset; /* offset of user buffer */ + int last_one; /* mark if reach to last packet */ + struct ep_td_struct *cur; /* current tranfer dtd */ +}; + +#define REQ_UNCOMPLETE (1) + +struct fsl_ep { + struct usb_ep ep; + struct list_head queue; + struct fsl_udc *udc; + struct ep_queue_head *qh; + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + + char name[14]; + unsigned stopped:1; +}; + +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +struct fsl_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct fsl_usb2_platform_data *pdata; + struct fsl_ep *eps; + unsigned int max_ep; + unsigned int irq; + + struct usb_ctrlrequest local_setup_buff; + spinlock_t lock; + u32 xcvr_type; + struct otg_transceiver *transceiver; + unsigned softconnect:1; + unsigned vbus_active:1; + unsigned remote_wakeup:1; + /* we must distinguish the stopped and suspended state, + * stopped means the udc enter lowpower mode, suspended + * means the udc is suspended by system pm or by otg + * switching to host mode.if the udc in suspended state + * it also in the stopped state, while if the udc in + * stopped state,it may not be in the suspended state*/ + unsigned stopped:1; + int suspended; + + struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ + struct fsl_req *status_req; /* ep0 status request */ + struct fsl_req *data_req; /* ep0 data request */ + struct dma_pool *td_pool; /* dma pool for DTD */ + enum fsl_usb2_phy_modes phy_mode; + + size_t ep_qh_size; /* size after alignment adjustment*/ + dma_addr_t ep_qh_dma; /* dma address of QH */ + + u32 max_pipes; /* Device max pipes */ + u32 max_use_endpts; /* Max endpointes to be used */ + u32 bus_reset; /* Device is bus reseting */ + u32 resume_state; /* USB state to resume */ + u32 usb_state; /* USB current state */ + u32 usb_next_state; /* USB next state */ + u32 ep0_dir; /* Endpoint zero direction: can be + USB_DIR_IN or USB_DIR_OUT */ + u32 usb_sof_count; /* SOF count */ + u32 errors; /* USB ERRORs count */ + u8 device_address; /* Device USB address */ + + struct completion *done; /* to make sure release() is done */ + u32 iram_buffer[IRAM_PPH_NTD]; + void *iram_buffer_v[IRAM_PPH_NTD]; + struct work_struct gadget_work; + struct delayed_work gadget_delay_work; +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt "\n", \ + __func__, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#if 0 +static void dump_msg(const char *label, const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + pr_debug("udc: %s, length %u:\n", label, length); + start = 0; + while (length > 0) { + num = min(length, 16u); + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + sprintf(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + printk(KERN_DEBUG "%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do {} while (0) +#endif + +#define ERR(stuff...) printk(KERN_ERR "udc: " stuff) +#define INFO(stuff...) printk(KERN_INFO "udc: " stuff) + +/*-------------------------------------------------------------------------*/ + +/* ### Add board specific defines here + */ + +/* + * ### pipe direction macro from device view + */ +#define USB_RECV (0) /* OUT EP */ +#define USB_SEND (1) /* IN EP */ + +/* + * ### internal used help routines. + */ +#define ep_index(EP) ((EP)->desc->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#define ep_is_in(EP) ((ep_index(EP) == 0) ? (EP->udc->ep0_dir == \ + USB_DIR_IN) : ((EP)->desc->bEndpointAddress \ + & USB_DIR_IN) == USB_DIR_IN) + +#define get_ep_by_pipe(udc, pipe) ((pipe == 1) ? &udc->eps[0] : \ + &udc->eps[pipe]) +#define get_pipe_by_windex(windex) ((windex & USB_ENDPOINT_NUMBER_MASK) \ + * 2 + ((windex & USB_DIR_IN) ? 1 : 0)) + +/* Bulk only class request */ +#define USB_BULK_RESET_REQUEST 0xff + +#if defined(CONFIG_ARCH_MXC) || defined(CONFIG_ARCH_STMP3XXX) || \ + defined(CONFIG_ARCH_MXS) +#include +#elif defined (CONFIG_PPC32) +#include +#endif + +#endif /* __ARCOTG_UDC_H */ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig old mode 100644 new mode 100755 index ab085f12d570..11ee0f5cda5b --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -59,6 +59,36 @@ config USB_EHCI_HCD To compile this driver as a module, choose M here: the module will be called ehci-hcd. +config USB_EHCI_ARC + bool "Support for Freescale controller" + depends on USB_EHCI_HCD && (ARCH_MXC || ARCH_STMP3XXX || ARCH_MXS) + select USB_OTG_UTILS + ---help--- + Some Freescale processors have an integrated High Speed + USBOTG controller, which supports EHCI host mode. + + Say "y" here to add support for this controller + to the EHCI HCD driver. + +config USB_EHCI_ARC_OTG + bool "Support for DR host port on Freescale controller" + depends on USB_EHCI_ARC + default y + ---help--- + Enable support for the USB OTG port in HS/FS Host mode. + +config USB_STATIC_IRAM + bool "Use IRAM for USB" + depends on USB_EHCI_ARC + ---help--- + Enable this option to use IRAM instead of DRAM for USB + structures and buffers. This option will reduce bus + contention on systems with large (VGA+) framebuffer + devices and heavy USB activity. There are performance + penalties and usage restrictions when using this option. + + If in doubt, say N. + config USB_EHCI_ROOT_HUB_TT bool "Root Hub Transaction Translators" depends on USB_EHCI_HCD diff --git a/drivers/usb/host/ehci-arc.c b/drivers/usb/host/ehci-arc.c new file mode 100755 index 000000000000..ee28bcc0b434 --- /dev/null +++ b/drivers/usb/host/ehci-arc.c @@ -0,0 +1,736 @@ +/* + * Copyright (c) 2005 MontaVista Software + * Copyright (C) 2011 Freescale Semiconductor, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Ported to 834x by Randy Vinson using code provided + * by Hunter Wu. + */ + +#include +#include +#include +#include + +#include "../core/usb.h" +#include "ehci-fsl.h" +#include + +extern int usb_host_wakeup_irq(struct device *wkup_dev); +extern void usb_host_set_wakeup(struct device *wkup_dev, bool para); +static void fsl_usb_lowpower_mode(struct fsl_usb2_platform_data *pdata, bool enable) +{ + if (enable) { + if (pdata->phy_lowpower_suspend) + pdata->phy_lowpower_suspend(pdata, true); + } else { + if (pdata->phy_lowpower_suspend) + pdata->phy_lowpower_suspend(pdata, false); + } + pdata->lowpower = enable; +} + +static void fsl_usb_clk_gate(struct fsl_usb2_platform_data *pdata, bool enable) +{ + if (pdata->usb_clock_for_pm) + pdata->usb_clock_for_pm(enable); +} +#undef EHCI_PROC_PTC +#ifdef EHCI_PROC_PTC /* /proc PORTSC:PTC support */ +/* + * write a PORTSC:PTC value to /proc/driver/ehci-ptc + * to put the controller into test mode. + */ +#include +#include +#define EFPSL 3 /* ehci fsl proc string length */ + +static int ehci_fsl_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + return 0; +} + +static int ehci_fsl_proc_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + int ptc; + u32 portsc; + struct ehci_hcd *ehci = (struct ehci_hcd *) data; + char str[EFPSL] = {0}; + + if (count > EFPSL-1) + return -EINVAL; + + if (copy_from_user(str, buffer, count)) + return -EFAULT; + + str[count] = '\0'; + + ptc = simple_strtoul(str, NULL, 0); + + portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + portsc &= ~(0xf << 16); + portsc |= (ptc << 16); + printk(KERN_INFO "PTC %x portsc %08x\n", ptc, portsc); + + ehci_writel(ehci, portsc, &ehci->regs->port_status[0]); + + return count; +} + +static int ehci_testmode_init(struct ehci_hcd *ehci) +{ + struct proc_dir_entry *entry; + + entry = create_proc_read_entry("driver/ehci-ptc", 0644, NULL, + ehci_fsl_proc_read, ehci); + if (!entry) + return -ENODEV; + + entry->write_proc = ehci_fsl_proc_write; + return 0; +} +#else +static int ehci_testmode_init(struct ehci_hcd *ehci) +{ + return 0; +} +#endif /* /proc PORTSC:PTC support */ + +/** + * The hcd operation need to be done during the wakeup irq + */ +void fsl_usb_recover_hcd(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 cmd = 0; + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + /* After receive remote wakeup signaling. Must restore + * CMDRUN bit in 20ms to keep port status. + */ + cmd = ehci_readl(ehci, &ehci->regs->command); + if (!(cmd & CMD_RUN)) { + ehci_writel(ehci, ehci->command, &ehci->regs->command); + /* Resume root hub here? */ + usb_hcd_resume_root_hub(hcd); + } + + /* disable all interrupt, will re-enable in resume */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); +} + +/** + * usb_hcd_fsl_probe - initialize FSL-based HCDs + * @drvier: Driver to be used for this HCD + * @pdev: USB Host Controller being probed + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller. + * + */ +int usb_hcd_fsl_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval; + + pr_debug("initializing FSL-SOC USB Controller\n"); + + /* Need platform data for setup */ + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, + "No platform data for %s.\n", dev_name(&pdev->dev)); + return -ENODEV; + } + + /* + * This is a host mode driver, verify that we're supposed to be + * in host mode. + */ + if (!((pdata->operating_mode == FSL_USB2_DR_HOST) || + (pdata->operating_mode == FSL_USB2_MPH_HOST) || + (pdata->operating_mode == FSL_USB2_DR_OTG))) { + dev_err(&pdev->dev, + "Non Host Mode configured for %s. Wrong driver linked.\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (pdata->operating_mode != FSL_USB2_DR_OTG) { + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + retval = -EBUSY; + goto err2; + } + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto err3; + } + pdata->regs = hcd->regs; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->init && pdata->init(pdev)) { + retval = -ENODEV; + goto err3; + } + + fsl_platform_set_host_mode(hcd); + hcd->power_budget = pdata->power_budget; + + retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); + if (retval != 0) + goto err4; + + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + dbg("pdev=0x%p hcd=0x%p ehci=0x%p\n", pdev, hcd, ehci); + + ehci->transceiver = otg_get_transceiver(); + dbg("ehci->transceiver=0x%p\n", ehci->transceiver); + + if (!ehci->transceiver) { + printk(KERN_ERR "can't find transceiver\n"); + retval = -ENODEV; + goto err5; + } + + retval = otg_set_host(ehci->transceiver, &ehci_to_hcd(ehci)->self); + if (retval) + otg_put_transceiver(ehci->transceiver); + } else if ((pdata->operating_mode == FSL_USB2_MPH_HOST) || \ + (pdata->operating_mode == FSL_USB2_DR_HOST)) + fsl_platform_set_vbus_power(pdata, 1); + + fsl_platform_set_ahb_burst(hcd); + ehci_testmode_init(hcd_to_ehci(hcd)); + return retval; +err5: + usb_remove_hcd(hcd); +err4: + iounmap(hcd->regs); +err3: + if (pdata->operating_mode != FSL_USB2_DR_OTG) + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err2: + usb_put_hcd(hcd); +err1: + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval); + if (pdata->exit) + pdata->exit(pdata->pdev); + return retval; +} + +/** + * usb_hcd_fsl_remove - shutdown processing for FSL-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_fsl_probe(). + * + */ +static void usb_hcd_fsl_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + u32 tmp; + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + /* Need open clock for register access */ + if (pdata->usb_clock_for_pm) + pdata->usb_clock_for_pm(true); + + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + if (tmp & PORT_PTS_PHCD) { + tmp &= ~PORT_PTS_PHCD; + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + msleep(100); + + if (pdata->usb_clock_for_pm) + pdata->usb_clock_for_pm(false); + } + } + + /* DDD shouldn't we turn off the power here? */ + fsl_platform_set_vbus_power(pdata, 0); + + if (ehci->transceiver) { + (void)otg_set_host(ehci->transceiver, 0); + otg_put_transceiver(ehci->transceiver); + } else { + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + } + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->exit) + pdata->exit(pdata->pdev); + + iounmap(hcd->regs); +} + +static void fsl_setup_phy(struct ehci_hcd *ehci, + enum fsl_usb2_phy_modes phy_mode, int port_offset) +{ + u32 portsc; + + portsc = ehci_readl(ehci, &ehci->regs->port_status[port_offset]); + portsc &= ~(PORT_PTS_MSK | PORT_PTS_PTW); + + switch (phy_mode) { + case FSL_USB2_PHY_ULPI: + portsc |= PORT_PTS_ULPI; + break; + case FSL_USB2_PHY_SERIAL: + portsc |= PORT_PTS_SERIAL; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portsc |= PORT_PTS_PTW; + /* fall through */ + case FSL_USB2_PHY_UTMI: + portsc |= PORT_PTS_UTMI; + break; + case FSL_USB2_PHY_NONE: + break; + } + ehci_writel(ehci, portsc, &ehci->regs->port_status[port_offset]); +} + +/* called after powerup, by probe or system-pm "wakeup" */ +static int ehci_fsl_reinit(struct ehci_hcd *ehci) +{ + fsl_platform_usb_setup(ehci); + ehci_port_power(ehci, 0); + + return 0; +} + +static int ehci_fsl_bus_suspend(struct usb_hcd *hcd) +{ + int ret = 0; + struct fsl_usb2_platform_data *pdata; + + pdata = hcd->self.controller->platform_data; + printk(KERN_DEBUG "%s, %s\n", __func__, pdata->name); + + /* the host is already at low power mode */ + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + return 0; + } + + ret = ehci_bus_suspend(hcd); + if (ret != 0) + return ret; + + if (pdata->platform_suspend) + pdata->platform_suspend(pdata); + + usb_host_set_wakeup(hcd->self.controller, true); + fsl_usb_lowpower_mode(pdata, true); + fsl_usb_clk_gate(hcd->self.controller->platform_data, false); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + return ret; +} + +static int ehci_fsl_bus_resume(struct usb_hcd *hcd) +{ + int ret = 0; + struct fsl_usb2_platform_data *pdata; + + pdata = hcd->self.controller->platform_data; + printk(KERN_DEBUG "%s, %s\n", __func__, pdata->name); + + /* + * At otg mode, it should not call host resume for usb gadget device + * Otherwise, this usb device can't be recognized as a gadget + */ + if (hcd->self.is_b_host) { + return -ESHUTDOWN; + } + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + fsl_usb_clk_gate(hcd->self.controller->platform_data, true); + usb_host_set_wakeup(hcd->self.controller, false); + fsl_usb_lowpower_mode(pdata, false); + } + + if (pdata->platform_resume) + pdata->platform_resume(pdata); + + ret = ehci_bus_resume(hcd); + if (ret) + return ret; + + return ret; +} + +static void ehci_fsl_shutdown(struct usb_hcd *hcd) +{ + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + fsl_usb_clk_gate(hcd->self.controller->platform_data, true); + } + /* Disable wakeup event first */ + usb_host_set_wakeup(hcd->self.controller, false); + + ehci_shutdown(hcd); + if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + fsl_usb_clk_gate(hcd->self.controller->platform_data, false); + } +} + +/* called during probe() after chip reset completes */ +static int ehci_fsl_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(ehci_readl(ehci, &ehci->caps->hc_capbase)); + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = ehci_readl(ehci, &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); + + retval = ehci_fsl_reinit(ehci); + return retval; +} + +static const struct hc_driver ehci_fsl_hc_driver = { + .description = hcd_name, + .product_desc = "Freescale On-Chip EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_fsl_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_fsl_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_fsl_bus_suspend, + .bus_resume = ehci_fsl_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_fsl_drv_probe(struct platform_device *pdev) +{ + if (usb_disabled()) + return -ENODEV; + + /* FIXME we only want one one probe() not two */ + return usb_hcd_fsl_probe(&ehci_fsl_hc_driver, pdev); +} + +static int ehci_fsl_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + /* FIXME we only want one one remove() not two */ + usb_hcd_fsl_remove(hcd, pdev); + return 0; +} + +#ifdef CONFIG_PM + +static bool host_can_wakeup_system(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + if (pdata->operating_mode == FSL_USB2_DR_OTG) + if (device_may_wakeup(ehci->transceiver->dev)) + return true; + else + return false; + else + if (device_may_wakeup(&(pdev->dev))) + return true; + else + return false; +} + +/* suspend/resume, section 4.3 */ + +/* These routines rely on the bus (pci, platform, etc) + * to handle powerdown and wakeup, and currently also on + * transceivers that don't need any software attention to set up + * the right sort of wakeup. + * + * They're also used for turning on/off the port when doing OTG. + */ +static int ehci_fsl_drv_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct usb_device *roothub = hcd->self.root_hub; + u32 port_status; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + printk(KERN_DEBUG "USB Host suspend begins\n"); + /* Only handles OTG mode switch event, system suspend event will be done in bus suspend */ + if (pdata->pmflags == 0) { + printk(KERN_DEBUG "%s, pm event \n", __func__); + if (!host_can_wakeup_system(pdev)) { + int mask; + /* Need open clock for register access */ + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) + fsl_usb_clk_gate(hcd->self.controller->platform_data, true); + + mask = ehci_readl(ehci, &ehci->regs->intr_enable); + mask &= ~STS_PCD; + ehci_writel(ehci, mask, &ehci->regs->intr_enable); + + usb_host_set_wakeup(hcd->self.controller, false); + fsl_usb_clk_gate(hcd->self.controller->platform_data, false); + } + return 0; + } + + /* only the otg host can go here */ + /* wait for all usb device on the hcd dettached */ + usb_lock_device(roothub); + if (roothub->children[0] != NULL) { + int old = hcd->self.is_b_host; + printk(KERN_DEBUG "will resume roothub and its children\n"); + hcd->self.is_b_host = 0; + /* resume the roothub, so that it can test the children is disconnected */ + if (roothub->state == USB_STATE_SUSPENDED) + usb_resume(&roothub->dev, PMSG_USER_SUSPEND); + /* we must do unlock here, the hubd thread will hold the same lock + * here release the lock, so that the hubd thread can process the usb + * disconnect event and set the children[0] be NULL, or there will be + * a deadlock */ + usb_unlock_device(roothub); + while (roothub->children[0] != NULL) + msleep(1); + usb_lock_device(roothub); + hcd->self.is_b_host = old; + } + usb_unlock_device(roothub); + + if (!(hcd->state & HC_STATE_SUSPENDED)) { + printk(KERN_DEBUG "will suspend roothub and its children\n"); + usb_lock_device(roothub); + usb_suspend(&roothub->dev, PMSG_USER_SUSPEND); + usb_unlock_device(roothub); + } + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + fsl_usb_clk_gate(hcd->self.controller->platform_data, true); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + } + + port_status = ehci_readl(ehci, &ehci->regs->port_status[0]); + /* save EHCI registers */ + pdata->pm_command = ehci_readl(ehci, &ehci->regs->command); + pdata->pm_command &= ~CMD_RUN; + pdata->pm_status = ehci_readl(ehci, &ehci->regs->status); + pdata->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable); + pdata->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index); + pdata->pm_segment = ehci_readl(ehci, &ehci->regs->segment); + pdata->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list); + pdata->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next); + pdata->pm_configured_flag = + ehci_readl(ehci, &ehci->regs->configured_flag); + pdata->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + + /* clear the W1C bits */ + pdata->pm_portsc &= cpu_to_hc32(ehci, ~PORT_RWC_BITS); + + /* clear PHCD bit */ + pdata->pm_portsc &= ~PORT_PTS_PHCD; + + usb_host_set_wakeup(hcd->self.controller, true); + fsl_usb_lowpower_mode(pdata, true); + + if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + fsl_usb_clk_gate(hcd->self.controller->platform_data, false); + } + pdata->pmflags = 0; + printk(KERN_DEBUG "host suspend ends\n"); + return 0; +} + +static int ehci_fsl_drv_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct usb_device *roothub = hcd->self.root_hub; + u32 tmp; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct fsl_usb2_wakeup_platform_data *wake_up_pdata = pdata->wakeup_pdata; + /* Only handles OTG mode switch event */ + printk(KERN_DEBUG "ehci fsl drv resume begins: %s\n", pdata->name); + if (pdata->pmflags == 0) { + printk(KERN_DEBUG "%s,pm event, wait for wakeup irq if needed\n", __func__); + wait_event_interruptible(wake_up_pdata->wq, !wake_up_pdata->usb_wakeup_is_pending); + if (!host_can_wakeup_system(pdev)) { + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + fsl_usb_clk_gate(hcd->self.controller->platform_data, true); + } + usb_host_set_wakeup(hcd->self.controller, true); + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + fsl_usb_clk_gate(hcd->self.controller->platform_data, false); + } + } + return 0; + } + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + fsl_usb_clk_gate(hcd->self.controller->platform_data, true); + usb_host_set_wakeup(hcd->self.controller, false); + fsl_usb_lowpower_mode(pdata, false); + } + + /* set host mode */ + fsl_platform_set_host_mode(hcd); + + /* restore EHCI registers */ + ehci_writel(ehci, pdata->pm_portsc, &ehci->regs->port_status[0]); + ehci_writel(ehci, pdata->pm_command, &ehci->regs->command); + ehci_writel(ehci, pdata->pm_intr_enable, &ehci->regs->intr_enable); + ehci_writel(ehci, pdata->pm_frame_index, &ehci->regs->frame_index); + ehci_writel(ehci, pdata->pm_segment, &ehci->regs->segment); + ehci_writel(ehci, pdata->pm_frame_list, &ehci->regs->frame_list); + ehci_writel(ehci, pdata->pm_async_next, &ehci->regs->async_next); + ehci_writel(ehci, pdata->pm_configured_flag, + &ehci->regs->configured_flag); + + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + + if ((hcd->state & HC_STATE_SUSPENDED)) { + printk(KERN_DEBUG "will resume roothub and its children\n"); + usb_lock_device(roothub); + usb_resume(&roothub->dev, PMSG_USER_RESUME); + usb_unlock_device(roothub); + } + pdata->pmflags = 0; + printk(KERN_DEBUG "ehci fsl drv resume ends: %s\n", pdata->name); + + return 0; +} +#endif +MODULE_ALIAS("platform:fsl-ehci"); + +static struct platform_driver ehci_fsl_driver = { + .probe = ehci_fsl_drv_probe, + .remove = ehci_fsl_drv_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ehci_fsl_drv_suspend, + .resume = ehci_fsl_drv_resume, +#endif + .driver = { + .name = "fsl-ehci", + }, +}; diff --git a/drivers/usb/host/ehci-fsl.h b/drivers/usb/host/ehci-fsl.h old mode 100644 new mode 100755 index bea5013ab7f5..f4282a3d6aa2 --- a/drivers/usb/host/ehci-fsl.h +++ b/drivers/usb/host/ehci-fsl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2010 Freescale Semiconductor, Inc. +/* Copyright (C) 2005-2011 Freescale Semiconductor, Inc. * Copyright (c) 2005 MontaVista Software * * This program is free software; you can redistribute it and/or modify it @@ -19,6 +19,9 @@ #define _EHCI_FSL_H /* offsets for the non-ehci registers in the FSL SOC USB controller */ +#define FSL_SOC_USB_SBUSCFG 0x90 +#define FSL_SOC_USB_BURSTSIZE 0x160 +#define FSL_SOC_USB_TXFILLTUNING 0x164 #define FSL_SOC_USB_ULPIVP 0x170 #define FSL_SOC_USB_PORTSC1 0x184 #define PORT_PTS_MSK (3<<30) @@ -26,6 +29,7 @@ #define PORT_PTS_ULPI (2<<30) #define PORT_PTS_SERIAL (3<<30) #define PORT_PTS_PTW (1<<28) +#define PORT_PTS_PHCD (1<<23) #define FSL_SOC_USB_PORTSC2 0x188 #define FSL_SOC_USB_USBMODE 0x1a8 #define USBMODE_CM_MASK (3 << 0) /* controller mode mask */ diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig old mode 100644 new mode 100755 index c66481ad98d7..b597c373bbaf --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -130,4 +130,12 @@ config FSL_USB2_OTG help Enable this to support Freescale USB OTG transceiver. +config MXC_OTG + tristate "USB OTG pin detect support" + select USB_OTG_UTILS + select USB_OTG + depends on USB_GADGET_ARC && USB_EHCI_HCD + help + Support for USB OTG PIN detect on MXC platforms. + endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile old mode 100644 new mode 100755 index 566655c53331..a5c5679e2313 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -21,3 +21,5 @@ obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o obj-$(CONFIG_AB8500_USB) += ab8500-usb.o fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o +fsl_otg_arc-objs := fsl_otg.o otg_fsm.o +obj-$(CONFIG_MXC_OTG) += fsl_otg_arc.o diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c old mode 100644 new mode 100755 index 0f420b25e9a9..e529cdba3bac --- a/drivers/usb/otg/fsl_otg.c +++ b/drivers/usb/otg/fsl_otg.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007,2008 Freescale semiconductor, Inc. + * Copyright (C) 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. * * Author: Li Yang * Jerry Huang @@ -20,35 +20,53 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. */ +#define VERBOSE #include +#include #include #include +#include +#include #include +#include #include #include #include -#include -#include +#include #include +#include +#include #include #include #include #include #include #include +#include #include #include -#include - +#include +#include +#include +#include +#include +#include +#include +#include #include - #include "fsl_otg.h" -#define DRIVER_VERSION "Rev. 1.55" +#define CONFIG_USB_OTG_DEBUG_FILES +#define DRIVER_VERSION "$Revision: 1.55 $" #define DRIVER_AUTHOR "Jerry Huang/Li Yang" -#define DRIVER_DESC "Freescale USB OTG Transceiver Driver" -#define DRIVER_INFO DRIVER_DESC " " DRIVER_VERSION +#define DRIVER_DESC "Freescale USB OTG Driver" +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +#define TIMER_FREQ 1000 /* 1000 ms */ +#define IDLE_TIME 5000 /* 5000 ms */ + +MODULE_DESCRIPTION("Freescale USB OTG Driver"); static const char driver_name[] = "fsl-usb2-otg"; @@ -56,12 +74,12 @@ const pm_message_t otg_suspend_state = { .event = 1, }; -#define HA_DATA_PULSE +#define HA_DATA_PULSE 1 -static struct usb_dr_mmap *usb_dr_regs; +volatile static struct usb_dr_mmap *usb_dr_regs; static struct fsl_otg *fsl_otg_dev; static int srp_wait_done; - +static int gpio_id; /* FSM timers */ struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr; @@ -76,112 +94,78 @@ static struct fsl_otg_config fsl_otg_initdata = { .otg_port = 1, }; -#ifdef CONFIG_PPC32 -static u32 _fsl_readl_be(const unsigned __iomem *p) -{ - return in_be32(p); -} - -static u32 _fsl_readl_le(const unsigned __iomem *p) -{ - return in_le32(p); -} - -static void _fsl_writel_be(u32 v, unsigned __iomem *p) -{ - out_be32(p, v); -} - -static void _fsl_writel_le(u32 v, unsigned __iomem *p) -{ - out_le32(p, v); -} - -static u32 (*_fsl_readl)(const unsigned __iomem *p); -static void (*_fsl_writel)(u32 v, unsigned __iomem *p); - -#define fsl_readl(p) (*_fsl_readl)((p)) -#define fsl_writel(v, p) (*_fsl_writel)((v), (p)) - -#else -#define fsl_readl(addr) readl(addr) -#define fsl_writel(val, addr) writel(val, addr) -#endif /* CONFIG_PPC32 */ +/* the timer is used to monitor the otg loading, if idle for some times + * we will close the otg clk + */ +static struct workqueue_struct *otg_queue; -/* Routines to access transceiver ULPI registers */ -u8 view_ulpi(u8 addr) +static void schedule_otg_work(struct delayed_work *dwork, unsigned long delay) { - u32 temp; - - temp = 0x40000000 | (addr << 16); - fsl_writel(temp, &usb_dr_regs->ulpiview); - udelay(1000); - while (temp & 0x40) - temp = fsl_readl(&usb_dr_regs->ulpiview); - return (le32_to_cpu(temp) & 0x0000ff00) >> 8; + queue_delayed_work(otg_queue, dwork, delay); } int write_ulpi(u8 addr, u8 data) { u32 temp; - temp = 0x60000000 | (addr << 16) | data; - fsl_writel(temp, &usb_dr_regs->ulpiview); + temp = cpu_to_le32(temp); + usb_dr_regs->ulpiview = temp; return 0; } +/* prototype declaration */ +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); +static void fsl_otg_clk_gate(bool on); + /* -------------------------------------------------------------*/ /* Operations that will be called from OTG Finite State Machine */ /* Charge vbus for vbus pulsing in SRP */ void fsl_otg_chrg_vbus(int on) { - u32 tmp; - - tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; - if (on) - /* stop discharging, start charging */ - tmp = (tmp & ~OTGSC_CTRL_VBUS_DISCHARGE) | - OTGSC_CTRL_VBUS_CHARGE; + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE) | + OTGSC_CTRL_VBUS_CHARGE); else - /* stop charging */ - tmp &= ~OTGSC_CTRL_VBUS_CHARGE; - - fsl_writel(tmp, &usb_dr_regs->otgsc); + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_VBUS_CHARGE)); } /* Discharge vbus through a resistor to ground */ void fsl_otg_dischrg_vbus(int on) { - u32 tmp; - - tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; - if (on) - /* stop charging, start discharging */ - tmp = (tmp & ~OTGSC_CTRL_VBUS_CHARGE) | - OTGSC_CTRL_VBUS_DISCHARGE; + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) + | OTGSC_CTRL_VBUS_DISCHARGE); else - /* stop discharging */ - tmp &= ~OTGSC_CTRL_VBUS_DISCHARGE; + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE)); +} - fsl_writel(tmp, &usb_dr_regs->otgsc); +/* Wait for VBUS discharge after set VBUS lower */ +static void fsl_otg_wait_dischrg_vbus(void) +{ + fsl_otg_clk_gate(true); + fsl_otg_dischrg_vbus(1); + msleep(20); + fsl_otg_dischrg_vbus(0); + fsl_otg_clk_gate(false); } /* A-device driver vbus, controlled through PP bit in PORTSC */ -void fsl_otg_drv_vbus(int on) +void fsl_otg_drv_vbus(struct fsl_usb2_platform_data *pdata, int on) { - u32 tmp; - - if (on) { - tmp = fsl_readl(&usb_dr_regs->portsc) & ~PORTSC_W1C_BITS; - fsl_writel(tmp | PORTSC_PORT_POWER, &usb_dr_regs->portsc); - } else { - tmp = fsl_readl(&usb_dr_regs->portsc) & - ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER; - fsl_writel(tmp, &usb_dr_regs->portsc); - } + if (pdata->xcvr_ops && pdata->xcvr_ops->set_vbus_power) + pdata->xcvr_ops->set_vbus_power(pdata->xcvr_ops, pdata, on); } /* @@ -190,47 +174,41 @@ void fsl_otg_drv_vbus(int on) */ void fsl_otg_loc_conn(int on) { - u32 tmp; - - tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; - if (on) - tmp |= OTGSC_CTRL_DATA_PULSING; + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) | OTGSC_CTRL_DATA_PULSING); else - tmp &= ~OTGSC_CTRL_DATA_PULSING; - - fsl_writel(tmp, &usb_dr_regs->otgsc); + usb_dr_regs->otgsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_DATA_PULSING); } -/* - * Generate SOF by host. This is controlled through suspend/resume the +/* Generate SOF by host. This is controlled through suspend/resume the * port. In host mode, controller will automatically send SOF. * Suspend will block the data on the port. */ void fsl_otg_loc_sof(int on) { - u32 tmp; + u32 tmpval; - tmp = fsl_readl(&fsl_otg_dev->dr_mem_map->portsc) & ~PORTSC_W1C_BITS; + tmpval = readl(&fsl_otg_dev->dr_mem_map->portsc) & ~PORTSC_W1C_BITS; if (on) - tmp |= PORTSC_PORT_FORCE_RESUME; + tmpval |= PORTSC_PORT_FORCE_RESUME; else - tmp |= PORTSC_PORT_SUSPEND; - - fsl_writel(tmp, &fsl_otg_dev->dr_mem_map->portsc); + tmpval |= PORTSC_PORT_SUSPEND; + writel(tmpval, &fsl_otg_dev->dr_mem_map->portsc); } /* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ void fsl_otg_start_pulse(void) { - u32 tmp; - srp_wait_done = 0; #ifdef HA_DATA_PULSE - tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; - tmp |= OTGSC_HA_DATA_PULSE; - fsl_writel(tmp, &usb_dr_regs->otgsc); + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK) + | OTGSC_HA_DATA_PULSE); #else fsl_otg_loc_conn(1); #endif @@ -238,6 +216,8 @@ void fsl_otg_start_pulse(void) fsl_otg_add_timer(b_data_pulse_tmr); } +void fsl_otg_pulse_vbus(void); + void b_data_pulse_end(unsigned long foo) { #ifdef HA_DATA_PULSE @@ -261,11 +241,9 @@ void b_vbus_pulse_end(unsigned long foo) { fsl_otg_chrg_vbus(0); - /* - * As USB3300 using the same a_sess_vld and b_sess_vld voltage + /* As USB3300 using the same a_sess_vld and b_sess_vld voltage * we need to discharge the bus for a while to distinguish - * residual voltage of vbus pulsing and A device pull up - */ + * residual voltage of vbus pulsing and A device pull up */ fsl_otg_dischrg_vbus(1); fsl_otg_add_timer(b_srp_wait_tmr); } @@ -280,8 +258,7 @@ void b_srp_end(unsigned long foo) fsl_otg_dev->fsm.b_srp_done = 1; } -/* - * Workaround for a_host suspending too fast. When a_bus_req=0, +/* Workaround for a_host suspending too fast. When a_bus_req=0, * a_host will start by SRP. It needs to set b_hnp_enable before * actually suspending to start HNP */ @@ -294,6 +271,8 @@ void a_wait_enum(unsigned long foo) otg_statemachine(&fsl_otg_dev->fsm); } +/* ------------------------------------------------------*/ + /* The timeout callback function to set time out bit */ void set_tmout(unsigned long indicator) { @@ -306,52 +285,52 @@ int fsl_otg_init_timers(struct otg_fsm *fsm) /* FSM used timers */ a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, (unsigned long)&fsm->a_wait_vrise_tmout); - if (!a_wait_vrise_tmr) + if (a_wait_vrise_tmr == NULL) return -ENOMEM; a_wait_bcon_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_BCON, (unsigned long)&fsm->a_wait_bcon_tmout); - if (!a_wait_bcon_tmr) + if (a_wait_bcon_tmr == NULL) return -ENOMEM; a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, (unsigned long)&fsm->a_aidl_bdis_tmout); - if (!a_aidl_bdis_tmr) + if (a_aidl_bdis_tmr == NULL) return -ENOMEM; b_ase0_brst_tmr = otg_timer_initializer(&set_tmout, TB_ASE0_BRST, (unsigned long)&fsm->b_ase0_brst_tmout); - if (!b_ase0_brst_tmr) + if (b_ase0_brst_tmr == NULL) return -ENOMEM; b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP, (unsigned long)&fsm->b_se0_srp); - if (!b_se0_srp_tmr) + if (b_se0_srp_tmr == NULL) return -ENOMEM; b_srp_fail_tmr = otg_timer_initializer(&set_tmout, TB_SRP_FAIL, (unsigned long)&fsm->b_srp_done); - if (!b_srp_fail_tmr) + if (b_srp_fail_tmr == NULL) return -ENOMEM; a_wait_enum_tmr = otg_timer_initializer(&a_wait_enum, 10, (unsigned long)&fsm); - if (!a_wait_enum_tmr) + if (a_wait_enum_tmr == NULL) return -ENOMEM; /* device driver used timers */ b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); - if (!b_srp_wait_tmr) + if (b_srp_wait_tmr == NULL) return -ENOMEM; b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, TB_DATA_PLS, 0); - if (!b_data_pulse_tmr) + if (b_data_pulse_tmr == NULL) return -ENOMEM; b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, TB_VBUS_PLS, 0); - if (!b_vbus_pulse_tmr) + if (b_vbus_pulse_tmr == NULL) return -ENOMEM; return 0; @@ -388,11 +367,10 @@ void fsl_otg_uninit_timers(void) /* Add timer to timer list */ void fsl_otg_add_timer(void *gtimer) { - struct fsl_otg_timer *timer = gtimer; + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; struct fsl_otg_timer *tmp_timer; - /* - * Check if the timer is already in the active list, + /* Check if the timer is already in the active list, * if so update timer count */ list_for_each_entry(tmp_timer, &active_timers, list) @@ -407,7 +385,7 @@ void fsl_otg_add_timer(void *gtimer) /* Remove timer from the timer list; clear timeout status */ void fsl_otg_del_timer(void *gtimer) { - struct fsl_otg_timer *timer = gtimer; + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; struct fsl_otg_timer *tmp_timer, *del_tmp; list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) @@ -415,8 +393,7 @@ void fsl_otg_del_timer(void *gtimer) list_del(&timer->list); } -/* - * Reduce timer count by 1, and find timeout conditions. +/* Reduce timer count by 1, and find timeout conditions. * Called by fsl_otg 1ms timer interrupt */ int fsl_otg_tick_timer(void) @@ -437,15 +414,56 @@ int fsl_otg_tick_timer(void) return expired; } +static void fsl_otg_clk_gate(bool on) +{ + struct device *dev = fsl_otg_dev->otg.dev; + struct fsl_usb2_platform_data *pdata; + + if (dev) { + pdata = dev->platform_data; + if (pdata && pdata->usb_clock_for_pm) + pdata->usb_clock_for_pm(on); + } +} + +/** + * Enable vbus interrupt + * The otg cares USB_ID interrupt + * The device cares B Session Valid + */ +static void b_session_irq_enable(bool enable) +{ + u32 osc; + fsl_otg_clk_gate(true); + if (le32_to_cpu(usb_dr_regs->portsc) & PORTSC_PHY_LOW_POWER_SPD) { + pr_debug("%s: the usb is in low power mode, vbus should not changed \n", __func__); + fsl_otg_clk_gate(false); + return; + } + + osc = le32_to_cpu(usb_dr_regs->otgsc); + pr_debug("%s:otgsc=0x%x", __func__, osc); + /* The other interrupts' status should not be cleared */ + osc &= ~(OTGSC_INTSTS_USB_ID | OTGSC_INTSTS_A_VBUS_VALID + | OTGSC_INTSTS_A_SESSION_VALID | OTGSC_INTSTS_B_SESSION_VALID); + osc |= OTGSC_INTSTS_B_SESSION_VALID; + if (enable) + osc |= OTGSC_INTR_B_SESSION_VALID_EN; + else + osc &= ~OTGSC_INTR_B_SESSION_VALID_EN; + usb_dr_regs->otgsc = cpu_to_le32(osc); + fsl_otg_clk_gate(false); +} + /* Reset controller, not reset the bus */ void otg_reset_controller(void) { u32 command; - command = fsl_readl(&usb_dr_regs->usbcmd); + command = readl(&usb_dr_regs->usbcmd); command |= (1 << 1); - fsl_writel(command, &usb_dr_regs->usbcmd); - while (fsl_readl(&usb_dr_regs->usbcmd) & (1 << 1)) + writel(command, &usb_dr_regs->usbcmd); + while (readl(&usb_dr_regs->usbcmd) & (1 << 1)) ; } @@ -455,18 +473,29 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on) struct otg_transceiver *xceiv = fsm->transceiver; struct device *dev; struct fsl_otg *otg_dev = container_of(xceiv, struct fsl_otg, otg); + struct platform_driver *host_pdrv; + struct platform_device *host_pdev; + struct fsl_usb2_platform_data *pdata; u32 retval = 0; if (!xceiv->host) return -ENODEV; dev = xceiv->host->controller; + host_pdrv = container_of((dev->driver), struct platform_driver, driver); + host_pdev = to_platform_device(dev); + pdata = host_pdev->dev.platform_data; - /* - * Update a_vbus_vld state as a_vbus_vld int is disabled + /* Mask PM operation from otg event */ + pdata->pmflags = 1; + + /* Update a_vbus_vld state as a_vbus_vld int is disabled * in device mode */ + fsl_otg_clk_gate(true); fsm->a_vbus_vld = - !!(fsl_readl(&usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID); + (le32_to_cpu(usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID) ? 1 : 0; + /*fsl_otg_clk_gate(false);*/ + if (on) { /* start fsl usb host controller */ if (otg_dev->host_working) @@ -474,19 +503,19 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on) else { otg_reset_controller(); VDBG("host on......\n"); - if (dev->driver->pm && dev->driver->pm->resume) { - retval = dev->driver->pm->resume(dev); + if (host_pdrv->resume) { + retval = host_pdrv->resume(host_pdev); if (fsm->id) { /* default-b */ - fsl_otg_drv_vbus(1); - /* - * Workaround: b_host can't driver + fsl_otg_drv_vbus(dev->platform_data, 1); + /* Workaround: b_host can't driver * vbus, but PP in PORTSC needs to * be 1 for host to work. * So we set drv_vbus bit in - * transceiver to 0 thru ULPI. - */ + * transceiver to 0 thru ULPI. */ +#if defined(CONFIG_ISP1504_MXC) write_ulpi(0x0c, 0x20); +#endif } } @@ -498,48 +527,57 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on) goto end; else { VDBG("host off......\n"); - if (dev && dev->driver) { - if (dev->driver->pm && dev->driver->pm->suspend) - retval = dev->driver->pm->suspend(dev); + if (host_pdrv->suspend) { + retval = host_pdrv->suspend(host_pdev, + otg_suspend_state); if (fsm->id) /* default-b */ - fsl_otg_drv_vbus(0); + fsl_otg_drv_vbus(dev->platform_data, 0); } otg_dev->host_working = 0; } } end: + pdata->pmflags = 0; + fsl_otg_clk_gate(false); return retval; } -/* - * Call suspend and resume function in udc driver +/* Call suspend and resume function in udc driver * to stop and start udc driver. */ int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) { struct otg_transceiver *xceiv = fsm->transceiver; struct device *dev; + struct platform_driver *gadget_pdrv; + struct platform_device *gadget_pdev; + struct fsl_usb2_platform_data *pdata; if (!xceiv->gadget || !xceiv->gadget->dev.parent) return -ENODEV; - VDBG("gadget %s\n", on ? "on" : "off"); + VDBG("gadget %s \n", on ? "on" : "off"); dev = xceiv->gadget->dev.parent; - if (on) { - if (dev->driver->resume) - dev->driver->resume(dev); - } else { - if (dev->driver->suspend) - dev->driver->suspend(dev, otg_suspend_state); - } + gadget_pdrv = container_of((dev->driver), + struct platform_driver, driver); + gadget_pdev = to_platform_device(dev); + pdata = gadget_pdev->dev.platform_data; + + /* Mask PM operation from otg event */ + pdata->pmflags = 1; + if (on) + gadget_pdrv->resume(gadget_pdev); + else + gadget_pdrv->suspend(gadget_pdev, otg_suspend_state); + + pdata->pmflags = 0; /* Restore*/ return 0; } -/* - * Called by initialization code of host driver. Register host controller +/* Called by initialization code of host driver. Register host controller * to the OTG. Suspend host for OTG role detection. */ static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) @@ -556,20 +594,18 @@ static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) if (host) { VDBG("host off......\n"); - otg_p->host->otg_port = fsl_otg_initdata.otg_port; otg_p->host->is_b_host = otg_dev->fsm.id; - /* - * must leave time for khubd to finish its thing + /* must leave time for khubd to finish its thing * before yanking the host driver out from under it, * so suspend the host after a short delay. */ otg_dev->host_working = 1; - schedule_delayed_work(&otg_dev->otg_event, 100); + schedule_otg_work(&otg_dev->otg_event, 100); return 0; - } else { - /* host driver going away */ - if (!(fsl_readl(&otg_dev->dr_mem_map->otgsc) & + } else { /* host driver going away */ + + if (!(le32_to_cpu(otg_dev->dr_mem_map->otgsc) & OTGSC_STS_USB_ID)) { /* Mini-A cable connected */ struct otg_fsm *fsm = &otg_dev->fsm; @@ -577,6 +613,13 @@ static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) otg_p->state = OTG_STATE_UNDEFINED; fsm->protocol = PROTO_UNDEF; } + if (gpio_id) { + if (gpio_get_value(gpio_id)) { + struct otg_fsm *fsm = &otg_dev->fsm; + otg_p->state = OTG_STATE_UNDEFINED; + fsm->protocol = PROTO_UNDEF; + } + } } otg_dev->host_working = 0; @@ -586,7 +629,7 @@ static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) return 0; } -/* Called by initialization code of udc. Register udc to OTG. */ +/* Called by initialization code of udc. Register udc to OTG.*/ static int fsl_otg_set_peripheral(struct otg_transceiver *otg_p, struct usb_gadget *gadget) { @@ -618,6 +661,7 @@ static int fsl_otg_set_peripheral(struct otg_transceiver *otg_p, if (otg_dev->fsm.id == 1) { fsl_otg_start_host(&otg_dev->fsm, 0); otg_drv_vbus(&otg_dev->fsm, 0); + fsl_otg_wait_dischrg_vbus(); fsl_otg_start_gadget(&otg_dev->fsm, 1); } @@ -630,13 +674,12 @@ static int fsl_otg_set_power(struct otg_transceiver *otg_p, unsigned mA) if (!fsl_otg_dev) return -ENODEV; if (otg_p->state == OTG_STATE_B_PERIPHERAL) - pr_info("FSL OTG: Draw %d mA\n", mA); + printk(KERN_INFO "FSL OTG:Draw %d mA\n", mA); return 0; } -/* - * Delayed pin detect interrupt processing. +/* Delayed pin detect interrupt processing. * * When the Mini-A cable is disconnected from the board, * the pin-detect interrupt happens before the disconnnect @@ -649,12 +692,30 @@ static void fsl_otg_event(struct work_struct *work) { struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work); struct otg_fsm *fsm = &og->fsm; + struct otg_transceiver *otg = &og->otg; + + mutex_lock(&pm_mutex); + b_session_irq_enable(false); + otg->default_a = (fsm->id == 0); + /* clear conn information */ + if (fsm->id) + fsm->b_conn = 0; + else + fsm->a_conn = 0; if (fsm->id) { /* switch to gadget */ fsl_otg_start_host(fsm, 0); otg_drv_vbus(fsm, 0); + fsl_otg_wait_dischrg_vbus(); + b_session_irq_enable(false); fsl_otg_start_gadget(fsm, 1); + } else { /* switch to host */ + fsl_otg_start_gadget(fsm, 0); + otg_drv_vbus(fsm, 1); + b_session_irq_enable(false); + fsl_otg_start_host(fsm, 1); } + mutex_unlock(&pm_mutex); } /* B-device start SRP */ @@ -680,75 +741,121 @@ static int fsl_otg_start_hnp(struct otg_transceiver *otg_p) if (!otg_p || otg_dev != fsl_otg_dev) return -ENODEV; - DBG("start_hnp...n"); - + /* printk("start_hnp.............\n"); */ /* clear a_bus_req to enter a_suspend state */ otg_dev->fsm.a_bus_req = 0; otg_statemachine(&otg_dev->fsm); return 0; } +/* Interrupt handler for gpio id pin */ +irqreturn_t fsl_otg_isr_gpio(int irq, void *dev_id) +{ + struct otg_fsm *fsm; + struct fsl_usb2_platform_data *pdata = + (struct fsl_usb2_platform_data *)dev_id; + struct fsl_otg *f_otg; + struct otg_transceiver *otg_trans = otg_get_transceiver(); + int value; + f_otg = container_of(otg_trans, struct fsl_otg, otg); + fsm = &f_otg->fsm; -/* - * Interrupt handler. OTG/host/peripheral share the same int line. + if (pdata->id_gpio == 0) + return IRQ_NONE; + + value = gpio_get_value(pdata->id_gpio) ? 1 : 0; + + if (value) + set_irq_type(gpio_to_irq(pdata->id_gpio), IRQ_TYPE_LEVEL_LOW); + else + set_irq_type(gpio_to_irq(pdata->id_gpio), IRQ_TYPE_LEVEL_HIGH); + + + if (value == f_otg->fsm.id) + return IRQ_HANDLED; + + f_otg->fsm.id = value; + + cancel_delayed_work(&f_otg->otg_event); + schedule_otg_work(&f_otg->otg_event, msecs_to_jiffies(10)); + + return IRQ_HANDLED; +} + +/* Interrupt handler. OTG/host/peripheral share the same int line. * OTG driver clears OTGSC interrupts and leaves USB interrupts * intact. It needs to have knowledge of some USB interrupts * such as port change. */ irqreturn_t fsl_otg_isr(int irq, void *dev_id) { - struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; - struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg; + struct fsl_otg *fotg = (struct fsl_otg *)dev_id; + struct otg_transceiver *otg = &fotg->otg; u32 otg_int_src, otg_sc; - - otg_sc = fsl_readl(&usb_dr_regs->otgsc); + irqreturn_t ret = IRQ_NONE; + struct fsl_usb2_platform_data *pdata; + if (fotg && fotg->otg.dev) { + pdata = fotg->otg.dev->platform_data; + if (pdata->irq_delay) + return ret; + } + otg_sc = le32_to_cpu(usb_dr_regs->otgsc); otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); - /* Only clear otg interrupts */ - fsl_writel(otg_sc, &usb_dr_regs->otgsc); + /* Only clear otg interrupts, expect B_SESSION_VALID, + * Leave it to be handled by arcotg_udc */ + usb_dr_regs->otgsc = ((usb_dr_regs->otgsc | cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK))& + (~OTGSC_INTSTS_B_SESSION_VALID)); /*FIXME: ID change not generate when init to 0 */ - fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; - otg->default_a = (fsm->id == 0); + fotg->fsm.id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fotg->fsm.id == 0); + + /* is_b_host, is_a_peripheral may be used at host/gadget driver + * so, assign them at irq, the otg event is scheduled too late + */ + if (otg->host) + otg->host->is_b_host = fotg->fsm.id; + if (otg->gadget) + otg->gadget->is_a_peripheral = !(fotg->fsm.id); /* process OTG interrupts */ if (otg_int_src) { if (otg_int_src & OTGSC_INTSTS_USB_ID) { - fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; - otg->default_a = (fsm->id == 0); - /* clear conn information */ - if (fsm->id) - fsm->b_conn = 0; - else - fsm->a_conn = 0; - - if (otg->host) - otg->host->is_b_host = fsm->id; - if (otg->gadget) - otg->gadget->is_a_peripheral = !fsm->id; - VDBG("ID int (ID is %d)\n", fsm->id); - - if (fsm->id) { /* switch to gadget */ - schedule_delayed_work( - &((struct fsl_otg *)dev_id)->otg_event, - 100); - } else { /* switch to host */ - cancel_delayed_work(& - ((struct fsl_otg *)dev_id)-> - otg_event); - fsl_otg_start_gadget(fsm, 0); - otg_drv_vbus(fsm, 1); - fsl_otg_start_host(fsm, 1); - } - return IRQ_HANDLED; + fotg->fsm.id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + + printk(KERN_DEBUG "ID int (ID is %d)\n", fotg->fsm.id); + + cancel_delayed_work(&fotg->otg_event); + schedule_otg_work(&fotg->otg_event, msecs_to_jiffies(10)); + ret = IRQ_HANDLED; } } - return IRQ_NONE; + + return ret; +} + +static void fsl_otg_fsm_drv_vbus(int on) +{ + struct otg_fsm *fsm = &(fsl_otg_dev->fsm); + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + /* + * The host is assigned at otg_set_host + */ + if (!xceiv->host) + return; + /* + * The dev is assigned at usb_create_hcd which is called earlier + * than otg_set_host at host driver's probe + */ + dev = xceiv->host->controller; + fsl_otg_drv_vbus(dev->platform_data, on); } static struct otg_fsm_ops fsl_otg_ops = { .chrg_vbus = fsl_otg_chrg_vbus, - .drv_vbus = fsl_otg_drv_vbus, + .drv_vbus = fsl_otg_fsm_drv_vbus, .loc_conn = fsl_otg_loc_conn, .loc_sof = fsl_otg_loc_sof, .start_pulse = fsl_otg_start_pulse, @@ -763,8 +870,13 @@ static struct otg_fsm_ops fsl_otg_ops = { /* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ static int fsl_otg_conf(struct platform_device *pdev) { - struct fsl_otg *fsl_otg_tc; int status; + struct fsl_otg *fsl_otg_tc; + struct fsl_usb2_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + DBG(); if (fsl_otg_dev) return 0; @@ -772,15 +884,21 @@ static int fsl_otg_conf(struct platform_device *pdev) /* allocate space to fsl otg device */ fsl_otg_tc = kzalloc(sizeof(struct fsl_otg), GFP_KERNEL); if (!fsl_otg_tc) + return -ENODEV; + otg_queue = create_workqueue("otg_switch"); + if (otg_queue == NULL) { + printk(KERN_ERR "Coulndn't create work queue\n"); return -ENOMEM; - + } INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); INIT_LIST_HEAD(&active_timers); status = fsl_otg_init_timers(&fsl_otg_tc->fsm); if (status) { - pr_info("Couldn't init OTG timers\n"); - goto err; + printk(KERN_INFO "Couldn't init OTG timers\n"); + fsl_otg_uninit_timers(); + kfree(fsl_otg_tc); + return status; } spin_lock_init(&fsl_otg_tc->fsm.lock); @@ -794,29 +912,27 @@ static int fsl_otg_conf(struct platform_device *pdev) fsl_otg_tc->otg.set_power = fsl_otg_set_power; fsl_otg_tc->otg.start_hnp = fsl_otg_start_hnp; fsl_otg_tc->otg.start_srp = fsl_otg_start_srp; + fsl_otg_tc->otg.dev = &pdev->dev; fsl_otg_dev = fsl_otg_tc; /* Store the otg transceiver */ status = otg_set_transceiver(&fsl_otg_tc->otg); if (status) { - pr_warn(FSL_OTG_NAME ": unable to register OTG transceiver.\n"); - goto err; + printk(KERN_WARNING ": unable to register OTG transceiver.\n"); + return status; } return 0; -err: - fsl_otg_uninit_timers(); - kfree(fsl_otg_tc); - return status; } -/* OTG Initialization */ +/* OTG Initialization*/ int usb_otg_start(struct platform_device *pdev) { struct fsl_otg *p_otg; struct otg_transceiver *otg_trans = otg_get_transceiver(); struct otg_fsm *fsm; + volatile unsigned long *p; int status; struct resource *res; u32 temp; @@ -844,18 +960,17 @@ int usb_otg_start(struct platform_device *pdev) if (pdata->init && pdata->init(pdev) != 0) return -EINVAL; - if (pdata->big_endian_mmio) { - _fsl_readl = _fsl_readl_be; - _fsl_writel = _fsl_writel_be; - } else { - _fsl_readl = _fsl_readl_le; - _fsl_writel = _fsl_writel_le; - } - + gpio_id = pdata->id_gpio; /* request irq */ - p_otg->irq = platform_get_irq(pdev, 0); - status = request_irq(p_otg->irq, fsl_otg_isr, + if (pdata->id_gpio == 0) { + p_otg->irq = platform_get_irq(pdev, 0); + status = request_irq(p_otg->irq, fsl_otg_isr, IRQF_SHARED, driver_name, p_otg); + } else { + status = request_irq(gpio_to_irq(pdata->id_gpio), + fsl_otg_isr_gpio, + IRQF_SHARED, driver_name, pdata); + } if (status) { dev_dbg(p_otg->otg.dev, "can't get IRQ %d, error %d\n", p_otg->irq, status); @@ -865,25 +980,25 @@ int usb_otg_start(struct platform_device *pdev) } /* stop the controller */ - temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); + temp = readl(&p_otg->dr_mem_map->usbcmd); temp &= ~USB_CMD_RUN_STOP; - fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + writel(temp, &p_otg->dr_mem_map->usbcmd); /* reset the controller */ - temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); + temp = readl(&p_otg->dr_mem_map->usbcmd); temp |= USB_CMD_CTRL_RESET; - fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + writel(temp, &p_otg->dr_mem_map->usbcmd); /* wait reset completed */ - while (fsl_readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) + while (readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) ; /* configure the VBUSHS as IDLE(both host and device) */ temp = USB_MODE_STREAM_DISABLE | (pdata->es ? USB_MODE_ES : 0); - fsl_writel(temp, &p_otg->dr_mem_map->usbmode); + writel(temp, &p_otg->dr_mem_map->usbmode); /* configure PHY interface */ - temp = fsl_readl(&p_otg->dr_mem_map->portsc); + temp = readl(&p_otg->dr_mem_map->portsc); temp &= ~(PORTSC_PHY_TYPE_SEL | PORTSC_PTW); switch (pdata->phy_mode) { case FSL_USB2_PHY_ULPI: @@ -898,141 +1013,214 @@ int usb_otg_start(struct platform_device *pdev) default: break; } - fsl_writel(temp, &p_otg->dr_mem_map->portsc); + writel(temp, &p_otg->dr_mem_map->portsc); if (pdata->have_sysif_regs) { /* configure control enable IO output, big endian register */ - temp = __raw_readl(&p_otg->dr_mem_map->control); + p = (volatile unsigned long *)(&p_otg->dr_mem_map->control); + temp = *p; temp |= USB_CTRL_IOENB; - __raw_writel(temp, &p_otg->dr_mem_map->control); + *p = temp; } /* disable all interrupt and clear all OTGSC status */ - temp = fsl_readl(&p_otg->dr_mem_map->otgsc); + temp = readl(&p_otg->dr_mem_map->otgsc); temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; temp |= OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE; - fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + writel(temp, &p_otg->dr_mem_map->otgsc); + /* * The identification (id) input is FALSE when a Mini-A plug is inserted * in the devices Mini-AB receptacle. Otherwise, this input is TRUE. * Also: record initial state of ID pin */ - if (fsl_readl(&p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) { - p_otg->otg.state = OTG_STATE_UNDEFINED; + if (le32_to_cpu(p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) { p_otg->fsm.id = 1; } else { - p_otg->otg.state = OTG_STATE_A_IDLE; p_otg->fsm.id = 0; } + if (pdata->id_gpio != 0) { + p_otg->fsm.id = gpio_get_value(pdata->id_gpio) ? 1 : 0; + if (p_otg->fsm.id) + set_irq_type(gpio_to_irq(pdata->id_gpio), + IRQ_TYPE_LEVEL_LOW); + else + set_irq_type(gpio_to_irq(pdata->id_gpio), + IRQ_TYPE_LEVEL_HIGH); + } + p_otg->otg.state = p_otg->fsm.id ? OTG_STATE_UNDEFINED : + OTG_STATE_A_IDLE; + DBG("initial ID pin=%d\n", p_otg->fsm.id); /* enable OTG ID pin interrupt */ - temp = fsl_readl(&p_otg->dr_mem_map->otgsc); - temp |= OTGSC_INTR_USB_ID_EN; + temp = readl(&p_otg->dr_mem_map->otgsc); + if (!pdata->id_gpio) + temp |= OTGSC_INTR_USB_ID_EN; temp &= ~(OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN); - fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + + writel(temp, &p_otg->dr_mem_map->otgsc); return 0; } -/* - * state file in sysfs - */ -static int show_fsl_usb2_otg_state(struct device *dev, - struct device_attribute *attr, char *buf) +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_OTG_DEBUG_FILES + +#include + +static const char proc_filename[] = "driver/isp1504_otg"; + +static int otg_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) { struct otg_fsm *fsm = &fsl_otg_dev->fsm; + char *buf = page; char *next = buf; - unsigned size = PAGE_SIZE; + unsigned size = count; unsigned long flags; int t; + u32 tmp_reg; + + if (off != 0) + return 0; + fsl_otg_clk_gate(true); spin_lock_irqsave(&fsm->lock, flags); - /* basic driver infomation */ + /* ------basic driver infomation ---- */ t = scnprintf(next, size, - DRIVER_DESC "\n" "fsl_usb2_otg version: %s\n\n", - DRIVER_VERSION); + DRIVER_DESC "\n" "fsl_usb2_otg version: %s\n\n", + DRIVER_VERSION); size -= t; next += t; - /* Registers */ - t = scnprintf(next, size, - "OTGSC: 0x%08x\n" - "PORTSC: 0x%08x\n" - "USBMODE: 0x%08x\n" - "USBCMD: 0x%08x\n" - "USBSTS: 0x%08x\n" - "USBINTR: 0x%08x\n", - fsl_readl(&usb_dr_regs->otgsc), - fsl_readl(&usb_dr_regs->portsc), - fsl_readl(&usb_dr_regs->usbmode), - fsl_readl(&usb_dr_regs->usbcmd), - fsl_readl(&usb_dr_regs->usbsts), - fsl_readl(&usb_dr_regs->usbintr)); + /* ------ Registers ----- */ + tmp_reg = le32_to_cpu(usb_dr_regs->otgsc); + t = scnprintf(next, size, "OTGSC reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->portsc); + t = scnprintf(next, size, "PORTSC reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbmode); + t = scnprintf(next, size, "USBMODE reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbcmd); + t = scnprintf(next, size, "USBCMD reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbsts); + t = scnprintf(next, size, "USBSTS reg: %08x\n", tmp_reg); size -= t; next += t; - /* State */ + /* ------ State ----- */ t = scnprintf(next, size, "OTG state: %s\n\n", - otg_state_string(fsl_otg_dev->otg.state)); + state_string(fsl_otg_dev->otg.state)); size -= t; next += t; - /* State Machine Variables */ - t = scnprintf(next, size, - "a_bus_req: %d\n" - "b_bus_req: %d\n" - "a_bus_resume: %d\n" - "a_bus_suspend: %d\n" - "a_conn: %d\n" - "a_sess_vld: %d\n" - "a_srp_det: %d\n" - "a_vbus_vld: %d\n" - "b_bus_resume: %d\n" - "b_bus_suspend: %d\n" - "b_conn: %d\n" - "b_se0_srp: %d\n" - "b_sess_end: %d\n" - "b_sess_vld: %d\n" - "id: %d\n", - fsm->a_bus_req, - fsm->b_bus_req, - fsm->a_bus_resume, - fsm->a_bus_suspend, - fsm->a_conn, - fsm->a_sess_vld, - fsm->a_srp_det, - fsm->a_vbus_vld, - fsm->b_bus_resume, - fsm->b_bus_suspend, - fsm->b_conn, - fsm->b_se0_srp, - fsm->b_sess_end, - fsm->b_sess_vld, - fsm->id); +#if 1 || defined DEBUG + /* ------ State Machine Variables ----- */ + t = scnprintf(next, size, "a_bus_req: %d\n", fsm->a_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_req: %d\n", fsm->b_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_resume: %d\n", fsm->a_bus_resume); size -= t; next += t; + t = scnprintf(next, size, "a_bus_suspend: %d\n", fsm->a_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "a_conn: %d\n", fsm->a_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "a_sess_vld: %d\n", fsm->a_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "a_srp_det: %d\n", fsm->a_srp_det); + size -= t; + next += t; + + t = scnprintf(next, size, "a_vbus_vld: %d\n", fsm->a_vbus_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_resume: %d\n", fsm->b_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_suspend: %d\n", fsm->b_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "b_conn: %d\n", fsm->b_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "b_se0_srp: %d\n", fsm->b_se0_srp); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_end: %d\n", fsm->b_sess_end); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_vld: %d\n", fsm->b_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "id: %d\n", fsm->id); + size -= t; + next += t; +#endif spin_unlock_irqrestore(&fsm->lock, flags); + fsl_otg_clk_gate(false); - return PAGE_SIZE - size; + *eof = 1; + return count - size; } -static DEVICE_ATTR(fsl_usb2_otg_state, S_IRUGO, show_fsl_usb2_otg_state, NULL); +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, otg_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_OTG_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) +#endif /*CONFIG_USB_OTG_DEBUG_FILES */ +/*----------------------------------------------------------*/ /* Char driver interface to control some OTG input */ -/* - * Handle some ioctl command, such as get otg +/* This function handle some ioctl command,such as get otg * status and set host suspend */ -static long fsl_otg_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static int fsl_otg_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { u32 retval = 0; @@ -1068,59 +1256,63 @@ static long fsl_otg_ioctl(struct file *file, unsigned int cmd, static int fsl_otg_open(struct inode *inode, struct file *file) { + return 0; } static int fsl_otg_release(struct inode *inode, struct file *file) { + return 0; } -static const struct file_operations otg_fops = { - .owner = THIS_MODULE, - .llseek = NULL, - .read = NULL, - .write = NULL, - .unlocked_ioctl = fsl_otg_ioctl, - .open = fsl_otg_open, - .release = fsl_otg_release, +static struct file_operations otg_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .read = NULL, + .write = NULL, + .unlocked_ioctl = fsl_otg_ioctl, + .open = fsl_otg_open, + .release = fsl_otg_release, }; -static int __devinit fsl_otg_probe(struct platform_device *pdev) +static int __init fsl_otg_probe(struct platform_device *pdev) { - int ret; + int status; + struct fsl_usb2_platform_data *pdata; - if (!pdev->dev.platform_data) + DBG("pdev=0x%p\n", pdev); + + if (!pdev) return -ENODEV; + if (!pdev->dev.platform_data) + return -ENOMEM; + + pdata = pdev->dev.platform_data; + /* configure the OTG */ - ret = fsl_otg_conf(pdev); - if (ret) { - dev_err(&pdev->dev, "Couldn't configure OTG module\n"); - return ret; + status = fsl_otg_conf(pdev); + if (status) { + printk(KERN_INFO "Couldn't init OTG module\n"); + return -status; } /* start OTG */ - ret = usb_otg_start(pdev); - if (ret) { - dev_err(&pdev->dev, "Can't init FSL OTG device\n"); - return ret; - } + status = usb_otg_start(pdev); - ret = register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops); - if (ret) { - dev_err(&pdev->dev, "unable to register FSL OTG device\n"); - return ret; + if (register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops)) { + printk(KERN_WARNING FSL_OTG_NAME + ": unable to register FSL OTG device\n"); + return -EIO; } - ret = device_create_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); - if (ret) - dev_warn(&pdev->dev, "Can't register sysfs attribute\n"); - - return ret; + create_proc_file(); + fsl_otg_clk_gate(false); + return status; } -static int __devexit fsl_otg_remove(struct platform_device *pdev) +static int fsl_otg_remove(struct platform_device *pdev) { struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; @@ -1129,39 +1321,42 @@ static int __devexit fsl_otg_remove(struct platform_device *pdev) iounmap((void *)usb_dr_regs); - fsl_otg_uninit_timers(); kfree(fsl_otg_dev); - device_remove_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); + remove_proc_file(); unregister_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME); if (pdata->exit) - pdata->exit(pdev); + pdata->exit(pdata->pdev); return 0; } struct platform_driver fsl_otg_driver = { .probe = fsl_otg_probe, - .remove = __devexit_p(fsl_otg_remove), + .remove = fsl_otg_remove, .driver = { .name = driver_name, .owner = THIS_MODULE, }, }; +/*-------------------------------------------------------------------------*/ + static int __init fsl_usb_otg_init(void) { - pr_info(DRIVER_INFO "\n"); + printk(KERN_INFO DRIVER_DESC " loaded, %s\n", DRIVER_VERSION); return platform_driver_register(&fsl_otg_driver); } -module_init(fsl_usb_otg_init); static void __exit fsl_usb_otg_exit(void) { platform_driver_unregister(&fsl_otg_driver); + printk(KERN_INFO DRIVER_DESC " unloaded\n"); } + +subsys_initcall(fsl_usb_otg_init); module_exit(fsl_usb_otg_exit); MODULE_DESCRIPTION(DRIVER_INFO); diff --git a/drivers/usb/otg/fsl_otg.h b/drivers/usb/otg/fsl_otg.h old mode 100644 new mode 100755 index 3f8ef731aac9..a8536815d0bf --- a/drivers/usb/otg/fsl_otg.h +++ b/drivers/usb/otg/fsl_otg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. +/* Copyright (C) 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. * * 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 @@ -19,7 +19,7 @@ #include #include -/* USB Command Register Bit Masks */ + /* USB Command Register Bit Masks */ #define USB_CMD_RUN_STOP (0x1<<0) #define USB_CMD_CTRL_RESET (0x1<<1) #define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4) @@ -64,7 +64,7 @@ #define USB_STS_ERR (0x1<<1) #define USB_STS_PORT_CHANGE (0x1<<2) #define USB_STS_FRM_LST_ROLL (0x1<<3) -#define USB_STS_SYS_ERR (0x1<<4) +#define USB_STS_SYS_ERR (0x1<<4) #define USB_STS_IAA (0x1<<5) #define USB_STS_RESET_RECEIVED (0x1<<6) #define USB_STS_SOF (0x1<<7) @@ -151,10 +151,10 @@ #define PORTSC_PTS_FSLS_SERIAL (0x3<<30) #define PORTSC_PTS_BIT_POS (30) -#define PORTSC_W1C_BITS \ - (PORTSC_CONNECT_STATUS_CHANGE | \ - PORTSC_PORT_EN_DIS_CHANGE | \ - PORTSC_OVER_CUURENT_CHG) +#define PORTSC_W1C_BITS \ + (PORTSC_CONNECT_STATUS_CHANGE | \ + PORTSC_PORT_EN_DIS_CHANGE | \ + PORTSC_OVER_CUURENT_CHG) /* OTG Status Control Register Bit Masks */ #define OTGSC_CTRL_VBUS_DISCHARGE (0x1<<0) @@ -194,7 +194,11 @@ #define USB_MODE_CTRL_MODE_RSV (0x1<<0) #define USB_MODE_SETUP_LOCK_OFF (0x1<<3) #define USB_MODE_STREAM_DISABLE (0x1<<4) -#define USB_MODE_ES (0x1<<2) /* Endian Select */ +#define USB_MODE_ES (0x1<<2) /* (big) Endian Select */ + +#define MPC8349_OTG_IRQ (38) +#define CFG_IMMR_BASE (0xfe000000) +#define MPC83xx_USB_DR_BASE (CFG_IMMR_BASE + 0x23000) /* control Register Bit Masks */ #define USB_CTRL_IOENB (0x1<<2) @@ -223,7 +227,7 @@ /* OTG interrupt enable bit masks */ #define OTGSC_INTERRUPT_ENABLE_BITS_MASK \ (OTGSC_INTR_USB_ID_EN | \ - OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTR_1MS_TIMER_EN | \ OTGSC_INTR_A_VBUS_VALID_EN | \ OTGSC_INTR_A_SESSION_VALID_EN | \ OTGSC_INTR_B_SESSION_VALID_EN | \ @@ -298,6 +302,7 @@ /* SE0 Time Before SRP */ #define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */ + #define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state = newstate) struct usb_dr_mmap { @@ -343,9 +348,13 @@ struct usb_dr_mmap { u32 pri_ctrl; /* Priority Control Register */ u32 si_ctrl; /* System Interface Control Register */ u8 res10[236]; +#ifdef CONFIG_ARCH_MX5 + u32 res11[128]; +#endif u32 control; /* General Purpose Control Register */ }; + struct fsl_otg_timer { unsigned long expires; /* Number of count increase to timeout */ unsigned long count; /* Tick counter */ @@ -358,9 +367,8 @@ inline struct fsl_otg_timer *otg_timer_initializer (void (*function)(unsigned long), unsigned long expires, unsigned long data) { struct fsl_otg_timer *timer; - timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); - if (!timer) + if (timer == NULL) return NULL; timer->function = function; timer->expires = expires; @@ -374,7 +382,7 @@ struct fsl_otg { struct usb_dr_mmap *dr_mem_map; struct delayed_work otg_event; - /* used for usb host */ + /*used for usb host */ struct work_struct work_wq; u8 host_working; @@ -385,12 +393,12 @@ struct fsl_otg_config { u8 otg_port; }; -/* For SRP and HNP handle */ -#define FSL_OTG_MAJOR 240 -#define FSL_OTG_NAME "fsl-usb2-otg" -/* Command to OTG driver ioctl */ +/*For SRP and HNP handle*/ +#define FSL_OTG_MAJOR 66 +#define FSL_OTG_NAME "fsl-usb2-otg" +/*Command to OTG driver(ioctl)*/ #define OTG_IOCTL_MAGIC FSL_OTG_MAJOR -/* if otg work as host, it should return 1, otherwise return 0 */ +/*if otg work as host,it should return 1,otherwise it return 0*/ #define GET_OTG_STATUS _IOR(OTG_IOCTL_MAGIC, 1, int) #define SET_A_SUSPEND_REQ _IOW(OTG_IOCTL_MAGIC, 2, int) #define SET_A_BUS_DROP _IOW(OTG_IOCTL_MAGIC, 3, int) @@ -401,6 +409,4 @@ struct fsl_otg_config { #define GET_A_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 8, int) #define GET_B_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 9, int) -void fsl_otg_add_timer(void *timer); -void fsl_otg_del_timer(void *timer); -void fsl_otg_pulse_vbus(void); +extern const char *state_string(enum usb_otg_state state); diff --git a/drivers/usb/otg/otg_fsm.c b/drivers/usb/otg/otg_fsm.c old mode 100644 new mode 100755 index b0cc422f2ff9..9e16afe02de4 --- a/drivers/usb/otg/otg_fsm.c +++ b/drivers/usb/otg/otg_fsm.c @@ -1,10 +1,9 @@ -/* - * OTG Finite State Machine from OTG spec +/* OTG Finite State Machine from OTG spec * - * Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * Copyright (C) 2006-2011 Freescale Semiconductor, Inc. * - * Author: Li Yang - * Jerry Huang + * Author: Li Yang + * Jerry Huang * * 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 @@ -23,15 +22,40 @@ #include #include +#include #include #include #include #include -#include -#include +#include #include "otg_fsm.h" + +/* Defined by device specific driver, for different timer implementation */ +extern void *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, *a_wait_enum_tmr; + +const char *state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; + case OTG_STATE_A_HOST: return "a_host"; + case OTG_STATE_A_SUSPEND: return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: return "a_vbus_err"; + case OTG_STATE_B_IDLE: return "b_idle"; + case OTG_STATE_B_SRP_INIT: return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: return "b_wait_acon"; + case OTG_STATE_B_HOST: return "b_host"; + default: return "UNDEFINED"; + } +} + /* Change USB protocol when there is a protocol change */ static int otg_set_protocol(struct otg_fsm *fsm, int protocol) { @@ -39,7 +63,7 @@ static int otg_set_protocol(struct otg_fsm *fsm, int protocol) if (fsm->protocol != protocol) { VDBG("Changing role fsm->protocol= %d; new protocol= %d\n", - fsm->protocol, protocol); + fsm->protocol, protocol); /* stop old protocol */ if (fsm->protocol == PROTO_HOST) ret = fsm->ops->start_host(fsm, 0); @@ -120,7 +144,7 @@ int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) state_changed = 1; if (fsm->transceiver->state == new_state) return 0; - VDBG("Set state: %s\n", otg_state_string(new_state)); + VDBG("Set state: %s \n", state_string(new_state)); otg_leave_state(fsm, fsm->transceiver->state); switch (new_state) { case OTG_STATE_B_IDLE: @@ -185,10 +209,8 @@ int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) otg_loc_conn(fsm, 0); otg_loc_sof(fsm, 1); otg_set_protocol(fsm, PROTO_HOST); - /* - * When HNP is triggered while a_bus_req = 0, a_host will - * suspend too fast to complete a_set_b_hnp_en - */ + /* When HNP is triggered while a_bus_req = 0, a_host will + * suspend too fast to complete a_set_b_hnp_en */ if (!fsm->a_bus_req || fsm->a_suspend_req) otg_add_timer(fsm, a_wait_enum_tmr); break; @@ -240,7 +262,7 @@ int otg_statemachine(struct otg_fsm *fsm) switch (state) { case OTG_STATE_UNDEFINED: - VDBG("fsm->id = %d\n", fsm->id); + VDBG("fsm->id = %d \n", fsm->id); if (fsm->id) otg_set_state(fsm, OTG_STATE_B_IDLE); else @@ -344,6 +366,6 @@ int otg_statemachine(struct otg_fsm *fsm) } spin_unlock_irqrestore(&fsm->lock, flags); - VDBG("quit statemachine, changed = %d\n", state_changed); + /* VDBG("quit statemachine, changed = %d \n", state_changed); */ return state_changed; } diff --git a/drivers/usb/otg/otg_fsm.h b/drivers/usb/otg/otg_fsm.h old mode 100644 new mode 100755 index 0cecf1d593a0..aa629b5028ba --- a/drivers/usb/otg/otg_fsm.h +++ b/drivers/usb/otg/otg_fsm.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. +/* Copyright (C) 2006-2011 Freescale Semiconductor, 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 @@ -15,11 +15,13 @@ * 675 Mass Ave, Cambridge, MA 02139, USA. */ -#undef DEBUG -#undef VERBOSE +#if 0 +#define DEBUG 1 +#define VERBOSE 1 +#endif #ifdef DEBUG -#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt , \ +#define DBG(fmt, args...) printk(KERN_DEBUG "j=%lu [%s] " fmt "\n", jiffies, \ __func__, ## args) #else #define DBG(fmt, args...) do {} while (0) @@ -147,8 +149,3 @@ static inline void otg_del_timer(struct otg_fsm *fsm, void *timer) } int otg_statemachine(struct otg_fsm *fsm); - -/* Defined by device specific driver, for different timer implementation */ -extern struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, - *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, - *a_wait_enum_tmr;