diff options
Diffstat (limited to 'drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c')
| -rw-r--r-- | drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c | 1084 |
1 files changed, 1084 insertions, 0 deletions
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c new file mode 100644 index 000000000000..298ca842003f --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c @@ -0,0 +1,1084 @@ + +/* ========================================================================== + * $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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/dma-mapping.h> +#include <linux/version.h> +#include <asm/io.h> +#ifdef CONFIG_ARM +#include <asm/fiq.h> +#endif +#include <linux/usb.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) +#include <../drivers/usb/core/hcd.h> +#else +#include <linux/usb/hcd.h> +#endif +#include <asm/bug.h> + +#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); +} + +static 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); + 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; + 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, 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)); + + 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, + &ep->hcpriv, 1); + if (0 == retval) { + if (alloc_bandwidth) { + allocate_bus_bandwidth(hcd, + dwc_otg_hcd_get_ep_bandwidth( + dwc_otg_hcd, 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 */ |
