/* ========================================================================== * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_linux.c $ * $Revision: #20 $ * $Date: 2011/10/26 $ * $Change: 1872981 $ * * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless * otherwise expressly agreed to in writing between Synopsys and you. * * The Software IS NOT an item of Licensed Software or Licensed Product under * any End User Software License Agreement or Agreement for Licensed Product * with Synopsys or any supplement thereto. You are permitted to use and * redistribute this Software in source and binary forms, with or without * modification, provided that redistributions of source code must retain this * notice. You may not view, use, disclose, copy or distribute this file or * any information contained herein except pursuant to this license grant from * Synopsys. If you do not agree with this notice, including the disclaimer * below, then you are not authorized to use the Software. * * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * ========================================================================== */ #ifndef DWC_DEVICE_ONLY /** * @file * * This file contains the implementation of the HCD. In Linux, the HCD * implements the hc_driver API. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ARM #include #endif #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) #include <../drivers/usb/core/hcd.h> #else #include #endif #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)) #define USB_URB_EP_LINKING 1 #else #define USB_URB_EP_LINKING 0 #endif #include "dwc_otg_hcd_if.h" #include "dwc_otg_dbg.h" #include "dwc_otg_driver.h" #include "dwc_otg_hcd.h" #ifndef __virt_to_bus #define __virt_to_bus __virt_to_phys #define __bus_to_virt __phys_to_virt #define __pfn_to_bus(x) __pfn_to_phys(x) #define __bus_to_pfn(x) __phys_to_pfn(x) #endif extern unsigned char _dwc_otg_fiq_stub, _dwc_otg_fiq_stub_end; /** * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is * qualified with its direction (possible 32 endpoints per device). */ #define dwc_ep_addr_to_endpoint(_bEndpointAddress_) ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \ ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4) static const char dwc_otg_hcd_name[] = "dwc_otg_hcd"; extern bool fiq_enable; /** @name Linux HC Driver API Functions */ /** @{ */ /* manage i/o requests, device state */ static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) struct usb_host_endpoint *ep, #endif struct urb *urb, gfp_t mem_flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb); #endif #else /* kernels at or post 2.6.30 */ static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) */ static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep); #endif static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd); extern int hcd_start(struct usb_hcd *hcd); extern void hcd_stop(struct usb_hcd *hcd); static int get_frame_number(struct usb_hcd *hcd); extern int hub_status_data(struct usb_hcd *hcd, char *buf); extern int hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); struct wrapper_priv_data { dwc_otg_hcd_t *dwc_otg_hcd; }; /** @} */ static struct hc_driver dwc_otg_hc_driver = { .description = dwc_otg_hcd_name, .product_desc = "DWC OTG Controller", .hcd_priv_size = sizeof(struct wrapper_priv_data), .irq = dwc_otg_hcd_irq, .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, //.reset = .start = hcd_start, //.suspend = //.resume = .stop = hcd_stop, .urb_enqueue = dwc_otg_urb_enqueue, .urb_dequeue = dwc_otg_urb_dequeue, .endpoint_disable = endpoint_disable, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) .endpoint_reset = endpoint_reset, #endif .get_frame_number = get_frame_number, .hub_status_data = hub_status_data, .hub_control = hub_control, //.bus_suspend = //.bus_resume = }; /** Gets the dwc_otg_hcd from a struct usb_hcd */ static inline dwc_otg_hcd_t *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd) { struct wrapper_priv_data *p; p = (struct wrapper_priv_data *)(hcd->hcd_priv); return p->dwc_otg_hcd; } /** Gets the struct usb_hcd that contains a dwc_otg_hcd_t. */ static inline struct usb_hcd *dwc_otg_hcd_to_hcd(dwc_otg_hcd_t * dwc_otg_hcd) { return dwc_otg_hcd_get_priv_data(dwc_otg_hcd); } /** Gets the usb_host_endpoint associated with an URB. */ inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *urb) { struct usb_device *dev = urb->dev; int ep_num = usb_pipeendpoint(urb->pipe); if (usb_pipein(urb->pipe)) return dev->ep_in[ep_num]; else return dev->ep_out[ep_num]; } static int _disconnect(dwc_otg_hcd_t * hcd) { struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); usb_hcd->self.is_b_host = 0; return 0; } static int _start(dwc_otg_hcd_t * hcd) { struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); usb_hcd->self.is_b_host = dwc_otg_hcd_is_b_host(hcd); hcd_start(usb_hcd); return 0; } static int _hub_info(dwc_otg_hcd_t * hcd, void *urb_handle, uint32_t * hub_addr, uint32_t * port_addr) { struct urb *urb = (struct urb *)urb_handle; struct usb_bus *bus; #if 1 //GRAYG - temporary if (NULL == urb_handle) DWC_ERROR("**** %s - NULL URB handle\n", __func__);//GRAYG if (NULL == urb->dev) DWC_ERROR("**** %s - URB has no device\n", __func__);//GRAYG if (NULL == port_addr) DWC_ERROR("**** %s - NULL port_address\n", __func__);//GRAYG #endif if (urb->dev->tt) { if (NULL == urb->dev->tt->hub) { DWC_ERROR("**** %s - (URB's transactor has no TT - giving no hub)\n", __func__); //GRAYG //*hub_addr = (u8)usb_pipedevice(urb->pipe); //GRAYG *hub_addr = 0; //GRAYG // we probably shouldn't have a transaction translator if // there's no associated hub? } else { bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd)); if (urb->dev->tt->hub == bus->root_hub) *hub_addr = 0; else *hub_addr = urb->dev->tt->hub->devnum; } *port_addr = urb->dev->ttport; } else { *hub_addr = 0; *port_addr = urb->dev->ttport; } return 0; } static int _speed(dwc_otg_hcd_t * hcd, void *urb_handle) { struct urb *urb = (struct urb *)urb_handle; return urb->dev->speed; } static int _get_b_hnp_enable(dwc_otg_hcd_t * hcd) { struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); return usb_hcd->self.b_hnp_enable; } static void allocate_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, struct urb *urb) { hcd_to_bus(hcd)->bandwidth_allocated += bw / urb->interval; if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { hcd_to_bus(hcd)->bandwidth_isoc_reqs++; } else { hcd_to_bus(hcd)->bandwidth_int_reqs++; } } static void free_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, struct urb *urb) { hcd_to_bus(hcd)->bandwidth_allocated -= bw / urb->interval; if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { hcd_to_bus(hcd)->bandwidth_isoc_reqs--; } else { hcd_to_bus(hcd)->bandwidth_int_reqs--; } } /** * Sets the final status of an URB and returns it to the device driver. Any * required cleanup of the URB is performed. The HCD lock should be held on * entry. */ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, dwc_otg_hcd_urb_t * dwc_otg_urb, int32_t status) { struct urb *urb = (struct urb *)urb_handle; urb_tq_entry_t *new_entry; int rc = 0; if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { DWC_PRINTF("%s: urb %p, device %d, ep %d %s, status=%d\n", __func__, urb, usb_pipedevice(urb->pipe), usb_pipeendpoint(urb->pipe), usb_pipein(urb->pipe) ? "IN" : "OUT", status); if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { int i; for (i = 0; i < urb->number_of_packets; i++) { DWC_PRINTF(" ISO Desc %d status: %d\n", i, urb->iso_frame_desc[i].status); } } } new_entry = DWC_ALLOC_ATOMIC(sizeof(urb_tq_entry_t)); urb->actual_length = dwc_otg_hcd_urb_get_actual_length(dwc_otg_urb); /* Convert status value. */ switch (status) { case -DWC_E_PROTOCOL: status = -EPROTO; break; case -DWC_E_IN_PROGRESS: status = -EINPROGRESS; break; case -DWC_E_PIPE: status = -EPIPE; break; case -DWC_E_IO: status = -EIO; break; case -DWC_E_TIMEOUT: status = -ETIMEDOUT; break; case -DWC_E_OVERFLOW: status = -EOVERFLOW; break; case -DWC_E_SHUTDOWN: status = -ESHUTDOWN; break; default: if (status) { DWC_PRINTF("Uknown urb status %d\n", status); } } if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { int i; urb->error_count = dwc_otg_hcd_urb_get_error_count(dwc_otg_urb); urb->actual_length = 0; for (i = 0; i < urb->number_of_packets; ++i) { urb->iso_frame_desc[i].actual_length = dwc_otg_hcd_urb_get_iso_desc_actual_length (dwc_otg_urb, i); urb->actual_length += urb->iso_frame_desc[i].actual_length; urb->iso_frame_desc[i].status = dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_urb, i); } } urb->status = status; urb->hcpriv = NULL; if (!status) { if ((urb->transfer_flags & URB_SHORT_NOT_OK) && (urb->actual_length < urb->transfer_buffer_length)) { urb->status = -EREMOTEIO; } } if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) || (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { struct usb_host_endpoint *ep = dwc_urb_to_endpoint(urb); if (ep) { free_bus_bandwidth(dwc_otg_hcd_to_hcd(hcd), dwc_otg_hcd_get_ep_bandwidth(hcd, ep->hcpriv), urb); } } DWC_FREE(dwc_otg_urb); if (!new_entry) { DWC_ERROR("dwc_otg_hcd: complete: cannot allocate URB TQ entry\n"); urb->status = -EPROTO; /* don't schedule the tasklet - * directly return the packet here with error. */ #if USB_URB_EP_LINKING usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); #else usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); #endif } else { new_entry->urb = urb; #if USB_URB_EP_LINKING rc = usb_hcd_check_unlink_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); if(0 == rc) { usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); } #endif if(0 == rc) { DWC_TAILQ_INSERT_TAIL(&hcd->completed_urb_list, new_entry, urb_tq_entries); DWC_TASK_HI_SCHEDULE(hcd->completion_tasklet); } } return 0; } static struct dwc_otg_hcd_function_ops hcd_fops = { .start = _start, .disconnect = _disconnect, .hub_info = _hub_info, .speed = _speed, .complete = _complete, .get_b_hnp_enable = _get_b_hnp_enable, }; #ifdef CONFIG_ARM64 static int simfiq_irq = -1; void local_fiq_enable(void) { if (simfiq_irq >= 0) enable_irq(simfiq_irq); } void local_fiq_disable(void) { if (simfiq_irq >= 0) disable_irq(simfiq_irq); } irqreturn_t fiq_irq_handler(int irq, void *dev_id) { dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *)dev_id; if (fiq_fsm_enable) dwc_otg_fiq_fsm(dwc_otg_hcd->fiq_state, dwc_otg_hcd->core_if->core_params->host_channels); else dwc_otg_fiq_nop(dwc_otg_hcd->fiq_state); return IRQ_HANDLED; } #else static struct fiq_handler fh = { .name = "usb_fiq", }; #endif static void hcd_init_fiq(void *cookie) { dwc_otg_device_t *otg_dev = cookie; dwc_otg_hcd_t *dwc_otg_hcd = otg_dev->hcd; #ifdef CONFIG_ARM64 int retval = 0; int irq; #else struct pt_regs regs; int irq; if (claim_fiq(&fh)) { DWC_ERROR("Can't claim FIQ"); BUG(); } DWC_WARN("FIQ on core %d", smp_processor_id()); DWC_WARN("FIQ ASM at %px length %d", &_dwc_otg_fiq_stub, (int)(&_dwc_otg_fiq_stub_end - &_dwc_otg_fiq_stub)); set_fiq_handler((void *) &_dwc_otg_fiq_stub, &_dwc_otg_fiq_stub_end - &_dwc_otg_fiq_stub); memset(®s,0,sizeof(regs)); regs.ARM_r8 = (long) dwc_otg_hcd->fiq_state; if (fiq_fsm_enable) { regs.ARM_r9 = dwc_otg_hcd->core_if->core_params->host_channels; //regs.ARM_r10 = dwc_otg_hcd->dma; regs.ARM_fp = (long) dwc_otg_fiq_fsm; } else { regs.ARM_fp = (long) dwc_otg_fiq_nop; } regs.ARM_sp = (long) dwc_otg_hcd->fiq_stack + (sizeof(struct fiq_stack) - 4); // __show_regs(®s); set_fiq_regs(®s); #endif dwc_otg_hcd->fiq_state->dwc_regs_base = otg_dev->os_dep.base; //Set the mphi periph to the required registers dwc_otg_hcd->fiq_state->mphi_regs.base = otg_dev->os_dep.mphi_base; if (otg_dev->os_dep.use_swirq) { dwc_otg_hcd->fiq_state->mphi_regs.swirq_set = otg_dev->os_dep.mphi_base + 0x1f0; dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr = otg_dev->os_dep.mphi_base + 0x1f4; DWC_WARN("Fake MPHI regs_base at %px", dwc_otg_hcd->fiq_state->mphi_regs.base); } else { dwc_otg_hcd->fiq_state->mphi_regs.ctrl = otg_dev->os_dep.mphi_base + 0x4c; dwc_otg_hcd->fiq_state->mphi_regs.outdda = otg_dev->os_dep.mphi_base + 0x28; dwc_otg_hcd->fiq_state->mphi_regs.outddb = otg_dev->os_dep.mphi_base + 0x2c; dwc_otg_hcd->fiq_state->mphi_regs.intstat = otg_dev->os_dep.mphi_base + 0x50; DWC_WARN("MPHI regs_base at %px", dwc_otg_hcd->fiq_state->mphi_regs.base); //Enable mphi peripheral writel((1<<31),dwc_otg_hcd->fiq_state->mphi_regs.ctrl); #ifdef DEBUG if (readl(dwc_otg_hcd->fiq_state->mphi_regs.ctrl) & 0x80000000) DWC_WARN("MPHI periph has been enabled"); else DWC_WARN("MPHI periph has NOT been enabled"); #endif } // Enable FIQ interrupt from USB peripheral #ifdef CONFIG_ARM64 irq = otg_dev->os_dep.fiq_num; if (irq < 0) { DWC_ERROR("Can't get SIM-FIQ irq"); return; } retval = request_irq(irq, fiq_irq_handler, 0, "dwc_otg_sim-fiq", dwc_otg_hcd); if (retval < 0) { DWC_ERROR("Unable to request SIM-FIQ irq\n"); return; } simfiq_irq = irq; #else #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER irq = otg_dev->os_dep.fiq_num; #else irq = INTERRUPT_VC_USB; #endif if (irq < 0) { DWC_ERROR("Can't get FIQ irq"); return; } /* * We could take an interrupt immediately after enabling the FIQ. * Ensure coherency of hcd->fiq_state. */ smp_mb(); enable_fiq(irq); local_fiq_enable(); #endif } /** * Initializes the HCD. This function allocates memory for and initializes the * static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the * USB bus with the core and calls the hc_driver->start() function. It returns * a negative error on failure. */ int hcd_init(dwc_bus_dev_t *_dev) { struct usb_hcd *hcd = NULL; dwc_otg_hcd_t *dwc_otg_hcd = NULL; dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); int retval = 0; u64 dmamask; DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT otg_dev=%p\n", otg_dev); /* Set device flags indicating whether the HCD supports DMA. */ if (dwc_otg_is_dma_enable(otg_dev->core_if)) dmamask = DMA_BIT_MASK(32); else dmamask = 0; #if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE) dma_set_mask(&_dev->dev, dmamask); dma_set_coherent_mask(&_dev->dev, dmamask); #elif defined(PCI_INTERFACE) pci_set_dma_mask(_dev, dmamask); pci_set_consistent_dma_mask(_dev, dmamask); #endif /* * Allocate memory for the base HCD plus the DWC OTG HCD. * Initialize the base HCD. */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, _dev->dev.bus_id); #else hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, dev_name(&_dev->dev)); hcd->has_tt = 1; // hcd->uses_new_polling = 1; // hcd->poll_rh = 0; #endif if (!hcd) { retval = -ENOMEM; goto error1; } hcd->regs = otg_dev->os_dep.base; /* Initialize the DWC OTG HCD. */ dwc_otg_hcd = dwc_otg_hcd_alloc_hcd(); if (!dwc_otg_hcd) { goto error2; } ((struct wrapper_priv_data *)(hcd->hcd_priv))->dwc_otg_hcd = dwc_otg_hcd; otg_dev->hcd = dwc_otg_hcd; otg_dev->hcd->otg_dev = otg_dev; #ifdef CONFIG_ARM64 if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) goto error2; if (fiq_enable) hcd_init_fiq(otg_dev); #else if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) { goto error2; } if (fiq_enable) { if (num_online_cpus() > 1) { /* * bcm2709: can run the FIQ on a separate core to IRQs. * Ensure driver state is visible to other cores before setting up the FIQ. */ smp_mb(); smp_call_function_single(1, hcd_init_fiq, otg_dev, 1); } else { smp_call_function_single(0, hcd_init_fiq, otg_dev, 1); } } #endif hcd->self.otg_port = dwc_otg_hcd_otg_port(dwc_otg_hcd); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) //don't support for LM(with 2.6.20.1 kernel) #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) //version field absent later hcd->self.otg_version = dwc_otg_get_otg_version(otg_dev->core_if); #endif /* Don't support SG list at this point */ hcd->self.sg_tablesize = 0; #endif /* * Finish generic HCD initialization and start the HCD. This function * allocates the DMA buffer pool, registers the USB bus, requests the * IRQ line, and calls hcd_start method. */ retval = usb_add_hcd(hcd, otg_dev->os_dep.irq_num, IRQF_SHARED); if (retval < 0) { goto error2; } dwc_otg_hcd_set_priv_data(dwc_otg_hcd, hcd); return 0; error2: usb_put_hcd(hcd); error1: return retval; } /** * Removes the HCD. * Frees memory and resources associated with the HCD and deregisters the bus. */ void hcd_remove(dwc_bus_dev_t *_dev) { dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); dwc_otg_hcd_t *dwc_otg_hcd; struct usb_hcd *hcd; DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD REMOVE otg_dev=%p\n", otg_dev); if (!otg_dev) { DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__); return; } dwc_otg_hcd = otg_dev->hcd; if (!dwc_otg_hcd) { DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__); return; } hcd = dwc_otg_hcd_to_hcd(dwc_otg_hcd); if (!hcd) { DWC_DEBUGPL(DBG_ANY, "%s: dwc_otg_hcd_to_hcd(dwc_otg_hcd) NULL!\n", __func__); return; } usb_remove_hcd(hcd); dwc_otg_hcd_set_priv_data(dwc_otg_hcd, NULL); dwc_otg_hcd_remove(dwc_otg_hcd); usb_put_hcd(hcd); } /* ========================================================================= * Linux HC Driver Functions * ========================================================================= */ /** Initializes the DWC_otg controller and its root hub and prepares it for host * mode operation. Activates the root port. Returns 0 on success and a negative * error code on failure. */ int hcd_start(struct usb_hcd *hcd) { dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); struct usb_bus *bus; DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD START\n"); bus = hcd_to_bus(hcd); hcd->state = HC_STATE_RUNNING; if (dwc_otg_hcd_start(dwc_otg_hcd, &hcd_fops)) { return 0; } /* Initialize and connect root hub if one is not already attached */ if (bus->root_hub) { DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Has Root Hub\n"); /* Inform the HUB driver to resume. */ usb_hcd_resume_root_hub(hcd); } return 0; } /** * Halts the DWC_otg host mode operations in a clean manner. USB transfers are * stopped. */ void hcd_stop(struct usb_hcd *hcd) { dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); dwc_otg_hcd_stop(dwc_otg_hcd); } /** Returns the current frame number. */ static int get_frame_number(struct usb_hcd *hcd) { hprt0_data_t hprt0; dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) return dwc_otg_hcd_get_frame_number(dwc_otg_hcd) >> 3; else return dwc_otg_hcd_get_frame_number(dwc_otg_hcd); } #ifdef DEBUG static void dump_urb_info(struct urb *urb, char *fn_name) { DWC_PRINTF("%s, urb %p\n", fn_name, urb); DWC_PRINTF(" Device address: %d\n", usb_pipedevice(urb->pipe)); DWC_PRINTF(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), (usb_pipein(urb->pipe) ? "IN" : "OUT")); DWC_PRINTF(" Endpoint type: %s\n", ( { char *pipetype; switch (usb_pipetype(urb->pipe)) { case PIPE_CONTROL: pipetype = "CONTROL"; break; case PIPE_BULK: pipetype = "BULK"; break; case PIPE_INTERRUPT: pipetype = "INTERRUPT"; break; case PIPE_ISOCHRONOUS: pipetype = "ISOCHRONOUS"; break; default: pipetype = "UNKNOWN"; break;}; pipetype;} )) ; DWC_PRINTF(" Speed: %s\n", ( { char *speed; switch (urb->dev->speed) { case USB_SPEED_HIGH: speed = "HIGH"; break; case USB_SPEED_FULL: speed = "FULL"; break; case USB_SPEED_LOW: speed = "LOW"; break; default: speed = "UNKNOWN"; break;}; speed;} )) ; DWC_PRINTF(" Max packet size: %d\n", usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); DWC_PRINTF(" Data buffer length: %d\n", urb->transfer_buffer_length); DWC_PRINTF(" Transfer buffer: %p, Transfer DMA: %p\n", urb->transfer_buffer, (void *)urb->transfer_dma); DWC_PRINTF(" Setup buffer: %p, Setup DMA: %p\n", urb->setup_packet, (void *)urb->setup_dma); DWC_PRINTF(" Interval: %d\n", urb->interval); if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { int i; for (i = 0; i < urb->number_of_packets; i++) { DWC_PRINTF(" ISO Desc %d:\n", i); DWC_PRINTF(" offset: %d, length %d\n", urb->iso_frame_desc[i].offset, urb->iso_frame_desc[i].length); } } } #endif /** Starts processing a USB transfer request specified by a USB Request Block * (URB). mem_flags indicates the type of memory allocation to use while * processing this URB. */ static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) struct usb_host_endpoint *ep, #endif struct urb *urb, gfp_t mem_flags) { int retval = 0; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) struct usb_host_endpoint *ep = urb->ep; #endif dwc_irqflags_t irqflags; void **ref_ep_hcpriv = &ep->hcpriv; dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); dwc_otg_hcd_urb_t *dwc_otg_urb; int i; int alloc_bandwidth = 0; uint8_t ep_type = 0; uint32_t flags = 0; void *buf; #ifdef DEBUG if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { dump_urb_info(urb, "dwc_otg_urb_enqueue"); } #endif if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) || (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { if (!dwc_otg_hcd_is_bandwidth_allocated (dwc_otg_hcd, ref_ep_hcpriv)) { alloc_bandwidth = 1; } } switch (usb_pipetype(urb->pipe)) { case PIPE_CONTROL: ep_type = USB_ENDPOINT_XFER_CONTROL; break; case PIPE_ISOCHRONOUS: ep_type = USB_ENDPOINT_XFER_ISOC; break; case PIPE_BULK: ep_type = USB_ENDPOINT_XFER_BULK; break; case PIPE_INTERRUPT: ep_type = USB_ENDPOINT_XFER_INT; break; default: DWC_WARN("Wrong EP type - %d\n", usb_pipetype(urb->pipe)); } /* # of packets is often 0 - do we really need to call this then? */ dwc_otg_urb = dwc_otg_hcd_urb_alloc(dwc_otg_hcd, urb->number_of_packets, mem_flags == GFP_ATOMIC ? 1 : 0); if(dwc_otg_urb == NULL) return -ENOMEM; if (!dwc_otg_urb && urb->number_of_packets) return -ENOMEM; dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_urb, usb_pipedevice(urb->pipe), usb_pipeendpoint(urb->pipe), ep_type, usb_pipein(urb->pipe), usb_maxpacket(urb->dev, urb->pipe, !(usb_pipein(urb->pipe)))); buf = urb->transfer_buffer; if (hcd_uses_dma(hcd) && !buf && urb->transfer_buffer_length) { /* * Calculate virtual address from physical address, * because some class driver may not fill transfer_buffer. * In Buffer DMA mode virual address is used, * when handling non DWORD aligned buffers. */ buf = (void *)__bus_to_virt((unsigned long)urb->transfer_dma); dev_warn_once(&urb->dev->dev, "USB transfer_buffer was NULL, will use __bus_to_virt(%pad)=%p\n", &urb->transfer_dma, buf); } if (!buf && urb->transfer_buffer_length) { DWC_FREE(dwc_otg_urb); DWC_ERROR("transfer_buffer is NULL in PIO mode or both " "transfer_buffer and transfer_dma are NULL in DMA mode\n"); return -EINVAL; } if (!(urb->transfer_flags & URB_NO_INTERRUPT)) flags |= URB_GIVEBACK_ASAP; if (urb->transfer_flags & URB_ZERO_PACKET) flags |= URB_SEND_ZERO_PACKET; dwc_otg_hcd_urb_set_params(dwc_otg_urb, urb, buf, urb->transfer_dma, urb->transfer_buffer_length, urb->setup_packet, urb->setup_dma, flags, urb->interval); for (i = 0; i < urb->number_of_packets; ++i) { dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_urb, i, urb-> iso_frame_desc[i].offset, urb-> iso_frame_desc[i].length); } DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags); urb->hcpriv = dwc_otg_urb; #if USB_URB_EP_LINKING retval = usb_hcd_link_urb_to_ep(hcd, urb); if (0 == retval) #endif { retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb, /*(dwc_otg_qh_t **)*/ ref_ep_hcpriv, 1); if (0 == retval) { if (alloc_bandwidth) { allocate_bus_bandwidth(hcd, dwc_otg_hcd_get_ep_bandwidth( dwc_otg_hcd, *ref_ep_hcpriv), urb); } } else { DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval); #if USB_URB_EP_LINKING usb_hcd_unlink_urb_from_ep(hcd, urb); #endif DWC_FREE(dwc_otg_urb); urb->hcpriv = NULL; if (retval == -DWC_E_NO_DEVICE) retval = -ENODEV; } } #if USB_URB_EP_LINKING else { DWC_FREE(dwc_otg_urb); urb->hcpriv = NULL; } #endif DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags); return retval; } /** Aborts/cancels a USB transfer request. Always returns 0 to indicate * success. */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) #else static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) #endif { dwc_irqflags_t flags; dwc_otg_hcd_t *dwc_otg_hcd; int rc; DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue\n"); dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); #ifdef DEBUG if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { dump_urb_info(urb, "dwc_otg_urb_dequeue"); } #endif DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); rc = usb_hcd_check_unlink_urb(hcd, urb, status); if (0 == rc) { if(urb->hcpriv != NULL) { dwc_otg_hcd_urb_dequeue(dwc_otg_hcd, (dwc_otg_hcd_urb_t *)urb->hcpriv); DWC_FREE(urb->hcpriv); urb->hcpriv = NULL; } } if (0 == rc) { /* Higher layer software sets URB status. */ #if USB_URB_EP_LINKING usb_hcd_unlink_urb_from_ep(hcd, urb); #endif DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) usb_hcd_giveback_urb(hcd, urb); #else usb_hcd_giveback_urb(hcd, urb, status); #endif if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { DWC_PRINTF("Called usb_hcd_giveback_urb() \n"); DWC_PRINTF(" 1urb->status = %d\n", urb->status); } DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue OK\n"); } else { DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue failed - rc %d\n", rc); } return rc; } /* Frees resources in the DWC_otg controller related to a given endpoint. Also * clears state in the HCD related to the endpoint. Any URBs for the endpoint * must already be dequeued. */ static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP DISABLE: _bEndpointAddress=0x%02x, " "endpoint=%d\n", ep->desc.bEndpointAddress, dwc_ep_addr_to_endpoint(ep->desc.bEndpointAddress)); dwc_otg_hcd_endpoint_disable(dwc_otg_hcd, ep->hcpriv, 250); ep->hcpriv = NULL; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) /* Resets endpoint specific parameter values, in current version used to reset * the data toggle(as a WA). This function can be called from usb_clear_halt routine */ static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { dwc_irqflags_t flags; dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP RESET: Endpoint Num=0x%02d\n", ep->desc.bEndpointAddress); DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); if (ep->hcpriv) { dwc_otg_hcd_endpoint_reset(dwc_otg_hcd, ep->hcpriv); } DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); } #endif /** Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid * interrupt. * * This function is called by the USB core when an interrupt occurs */ static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd) { dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); int32_t retval = dwc_otg_hcd_handle_intr(dwc_otg_hcd); if (retval != 0) { S3C2410X_CLEAR_EINTPEND(); } return IRQ_RETVAL(retval); } /** Creates Status Change bitmap for the root hub and root port. The bitmap is * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1 * is the status change indicator for the single root port. Returns 1 if either * change indicator is 1, otherwise returns 0. */ int hub_status_data(struct usb_hcd *hcd, char *buf) { dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); buf[0] = 0; buf[0] |= (dwc_otg_hcd_is_status_changed(dwc_otg_hcd, 1)) << 1; return (buf[0] != 0); } /** Handles hub class-specific requests. */ int hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) { int retval; retval = dwc_otg_hcd_hub_control(hcd_to_dwc_otg_hcd(hcd), typeReq, wValue, wIndex, buf, wLength); switch (retval) { case -DWC_E_INVALID: retval = -EINVAL; break; } return retval; } #endif /* DWC_DEVICE_ONLY */