/* ========================================================================== * BLX HS OTG Linux Software Driver and documentation (hereinafter, * "Software") is an Unsupported proprietary work of BLX, Inc. unless * otherwise expressly agreed to in writing between BLX 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 BLX 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 * BLX. 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 BLX 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 BLX 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 GSC3280_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 #include #include #include "gsc3280_otg_hcd_if.h" #include "gsc3280_otg_dbg.h" #include "gsc3280_otg_driver.h" #include "gsc3280_otg_hcd.h" /** * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is * qualified with its direction (possible 32 endpoints per device). */ #define gsc3280_ep_addr_to_endpoint(_bEndpointAddress_) ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \ ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4) static const char gsc3280_otg_hcd_name[] = "gsc3280_otg_hcd"; /** @name Linux HC Driver API Functions */ /** @{ */ static int 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,28) static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb); #else static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); #endif 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 gsc3280_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 { gsc3280_otg_hcd_t *gsc3280_otg_hcd; }; /** @} */ static struct hc_driver gsc3280_otg_hc_driver = { .description = gsc3280_otg_hcd_name, .product_desc = "GSC3280 OTG Controller", .hcd_priv_size = sizeof(struct wrapper_priv_data), .irq = gsc3280_otg_hcd_irq, .flags = HCD_MEMORY | HCD_USB2, //.reset = .start = hcd_start, //.suspend = //.resume = .stop = hcd_stop, .urb_enqueue = urb_enqueue, .urb_dequeue = 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 gsc3280_otg_hcd from a struct usb_hcd */ static inline gsc3280_otg_hcd_t *hcd_to_gsc3280_otg_hcd(struct usb_hcd *hcd) { struct wrapper_priv_data *p; p = (struct wrapper_priv_data *)(hcd->hcd_priv); return p->gsc3280_otg_hcd; } /** Gets the struct usb_hcd that contains a gsc3280_otg_hcd_t. */ static inline struct usb_hcd *gsc3280_otg_hcd_to_hcd(gsc3280_otg_hcd_t * gsc3280_otg_hcd) { return gsc3280_otg_hcd_get_priv_data(gsc3280_otg_hcd); } /** Gets the usb_host_endpoint associated with an URB. */ inline struct usb_host_endpoint *gsc3280_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(gsc3280_otg_hcd_t * hcd) { struct usb_hcd *usb_hcd = gsc3280_otg_hcd_to_hcd(hcd); usb_hcd->self.is_b_host = 0; return 0; } static int _start(gsc3280_otg_hcd_t * hcd) { struct usb_hcd *usb_hcd = gsc3280_otg_hcd_to_hcd(hcd); usb_hcd->self.is_b_host = gsc3280_otg_hcd_is_b_host(hcd); hcd_start(usb_hcd); return 0; } static int _hub_info(gsc3280_otg_hcd_t * hcd, void *urb_handle, uint32_t * hub_addr, uint32_t * port_addr) { struct urb *urb = (struct urb *)urb_handle; if (urb->dev->tt) { *hub_addr = urb->dev->tt->hub->devnum; } else { *hub_addr = 0; } *port_addr = urb->dev->ttport; return 0; } static int _speed(gsc3280_otg_hcd_t * hcd, void *urb_handle) { struct urb *urb = (struct urb *)urb_handle; return urb->dev->speed; } static int _get_b_hnp_enable(gsc3280_otg_hcd_t * hcd) { struct usb_hcd *usb_hcd = gsc3280_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. */ static int _complete(gsc3280_otg_hcd_t * hcd, void *urb_handle, gsc3280_otg_hcd_urb_t * gsc3280_otg_urb, int32_t status) { struct urb *urb = (struct urb *)urb_handle; #ifdef DEBUG if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { GSC3280_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++) { GSC3280_PRINTF(" ISO Desc %d status: %d\n", i, urb->iso_frame_desc[i].status); } } } #endif urb->actual_length = gsc3280_otg_hcd_urb_get_actual_length(gsc3280_otg_urb); /* Convert status value. */ switch (status) { case -GSC3280_E_PROTOCOL: status = -EPROTO; break; case -GSC3280_E_IN_PROGRESS: status = -EINPROGRESS; break; case -GSC3280_E_PIPE: status = -EPIPE; break; case -GSC3280_E_IO: status = -EIO; break; case -GSC3280_E_TIMEOUT: status = -ETIMEDOUT; break; case -GSC3280_E_OVERFLOW: status = -EOVERFLOW; break; default: if (status) { GSC3280_PRINTF("Uknown urb status %d\n", status); } } if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { int i; urb->error_count = gsc3280_otg_hcd_urb_get_error_count(gsc3280_otg_urb); for (i = 0; i < urb->number_of_packets; ++i) { urb->iso_frame_desc[i].actual_length = gsc3280_otg_hcd_urb_get_iso_desc_actual_length (gsc3280_otg_urb, i); urb->iso_frame_desc[i].status = gsc3280_otg_hcd_urb_get_iso_desc_status(gsc3280_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 = gsc3280_urb_to_endpoint(urb); if (ep) { free_bus_bandwidth(gsc3280_otg_hcd_to_hcd(hcd), gsc3280_otg_hcd_get_ep_bandwidth(hcd, ep->hcpriv), urb); } } GSC3280_FREE(gsc3280_otg_urb); GSC3280_SPINUNLOCK(hcd->lock); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) usb_hcd_giveback_urb(gsc3280_otg_hcd_to_hcd(hcd), urb); #else usb_hcd_giveback_urb(gsc3280_otg_hcd_to_hcd(hcd), urb, status); #endif GSC3280_SPINLOCK(hcd->lock); return 0; } static struct gsc3280_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, }; /** * Initializes the HCD. This function allocates memory for and initializes the * static parts of the usb_hcd and gsc3280_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(struct platform_device *pdev) { struct usb_hcd *hcd = NULL; gsc3280_otg_hcd_t *gsc3280_otg_hcd = NULL; gsc3280_otg_device_t *otg_dev = platform_get_drvdata(pdev); int retval = 0; GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD INIT\n"); /* Set device flags indicating whether the HCD supports DMA. */ if (gsc3280_otg_is_dma_enable(otg_dev->core_if)) { pdev->dev.dma_mask = (void *)~0; pdev->dev.coherent_dma_mask = ~0; } else { pdev->dev.dma_mask = (void *)0; pdev->dev.coherent_dma_mask = 0; } /* * Allocate memory for the base HCD plus the GSC3280 OTG HCD. * Initialize the base HCD. */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) hcd = usb_create_hcd(&gsc3280_otg_hc_driver, &pdev->dev, pdev->dev.bus_id); #else hcd = usb_create_hcd(&gsc3280_otg_hc_driver, &pdev->dev, dev_name(&pdev->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 GSC3280 OTG HCD. */ gsc3280_otg_hcd = gsc3280_otg_hcd_alloc_hcd(); if (!gsc3280_otg_hcd) { goto error2; } ((struct wrapper_priv_data *)(hcd->hcd_priv))->gsc3280_otg_hcd = gsc3280_otg_hcd; otg_dev->hcd = gsc3280_otg_hcd; if (gsc3280_otg_hcd_init(gsc3280_otg_hcd, otg_dev->core_if)) { goto error2; } otg_dev->hcd->otg_dev = otg_dev; hcd->self.otg_port = gsc3280_otg_hcd_otg_port(gsc3280_otg_hcd); // otg_main (); /* * 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->irq, IRQF_SHARED | IRQF_DISABLED); if (retval < 0) { goto error2; } gsc3280_otg_hcd_set_priv_data(gsc3280_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(struct platform_device *pdev) { gsc3280_otg_device_t *otg_dev = platform_get_drvdata(pdev); gsc3280_otg_hcd_t *gsc3280_otg_hcd; struct usb_hcd *hcd; GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD REMOVE\n"); if (!otg_dev) { GSC3280_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__); return; } gsc3280_otg_hcd = otg_dev->hcd; if (!gsc3280_otg_hcd) { GSC3280_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__); return; } hcd = gsc3280_otg_hcd_to_hcd(gsc3280_otg_hcd); if (!hcd) { GSC3280_DEBUGPL(DBG_ANY, "%s: gsc3280_otg_hcd_to_hcd(gsc3280_otg_hcd) NULL!\n", __func__); return; } usb_remove_hcd(hcd); gsc3280_otg_hcd_set_priv_data(gsc3280_otg_hcd, NULL); gsc3280_otg_hcd_remove(gsc3280_otg_hcd); usb_put_hcd(hcd); } /* ========================================================================= * Linux HC Driver Functions * ========================================================================= */ /** Initializes the GSC3280_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) { gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); struct usb_bus *bus; GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD START\n"); bus = hcd_to_bus(hcd); hcd->state = HC_STATE_RUNNING; if (gsc3280_otg_hcd_start(gsc3280_otg_hcd, &hcd_fops)) { return 0; } /* Initialize and connect root hub if one is not already attached */ if (bus->root_hub) { GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD Has Root Hub\n"); /* Inform the HUB driver to resume. */ usb_hcd_resume_root_hub(hcd); } return 0; } /** * Halts the GSC3280_otg host mode operations in a clean manner. USB transfers are * stopped. */ void hcd_stop(struct usb_hcd *hcd) { gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); gsc3280_otg_hcd_stop(gsc3280_otg_hcd); } /** Returns the current frame number. */ static int get_frame_number(struct usb_hcd *hcd) { gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); return gsc3280_otg_hcd_get_frame_number(gsc3280_otg_hcd); } #ifdef DEBUG static void dump_urb_info(struct urb *urb, char *fn_name) { GSC3280_PRINTF("%s, urb %p\n", fn_name, urb); GSC3280_PRINTF(" Device address: %d\n", usb_pipedevice(urb->pipe)); GSC3280_PRINTF(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), (usb_pipein(urb->pipe) ? "IN" : "OUT")); GSC3280_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;} )) ; GSC3280_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;} )) ; GSC3280_PRINTF(" Max packet size: %d\n", usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); GSC3280_PRINTF(" Data buffer length: %d\n", urb->transfer_buffer_length); GSC3280_PRINTF(" Transfer buffer: %p, Transfer DMA: %p\n", urb->transfer_buffer, (void *)urb->transfer_dma); GSC3280_PRINTF(" Setup buffer: %p, Setup DMA: %p\n", urb->setup_packet, (void *)urb->setup_dma); GSC3280_PRINTF(" Interval: %d\n", urb->interval); if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { int i; for (i = 0; i < urb->number_of_packets; i++) { GSC3280_PRINTF(" ISO Desc %d:\n", i); GSC3280_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. */ int len_flag = 0; static int 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 gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); gsc3280_otg_hcd_urb_t *gsc3280_otg_urb; int i; int alloc_bandwidth = 0; uint8_t ep_type = 0; uint32_t flags = 0; void *buf; if (urb->transfer_buffer_length == 4096) len_flag ++; #ifdef DEBUG if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { dump_urb_info(urb, "urb_enqueue"); } #endif if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) || (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { if (!gsc3280_otg_hcd_is_bandwidth_allocated (gsc3280_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; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) if (urb->sg) { ;// GSC3280_WARN("SG LIST received - we don't support it\n"); } #endif break; case PIPE_INTERRUPT: ep_type = USB_ENDPOINT_XFER_INT; break; default: GSC3280_WARN("Wrong ep type\n"); } gsc3280_otg_urb = gsc3280_otg_hcd_urb_alloc(gsc3280_otg_hcd, urb->number_of_packets, mem_flags == GFP_ATOMIC ? 1 : 0); gsc3280_otg_hcd_urb_set_pipeinfo(gsc3280_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->self.uses_dma) { /* * 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 = phys_to_virt(urb->transfer_dma); } if (!(urb->transfer_flags & URB_NO_INTERRUPT)) flags |= URB_GIVEBACK_ASAP; if (urb->transfer_flags & URB_ZERO_PACKET) flags |= URB_SEND_ZERO_PACKET; gsc3280_otg_hcd_urb_set_params(gsc3280_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) { gsc3280_otg_hcd_urb_set_iso_desc_params(gsc3280_otg_urb, i, urb-> iso_frame_desc[i].offset, urb-> iso_frame_desc[i].length); } urb->hcpriv = gsc3280_otg_urb; retval = gsc3280_otg_hcd_urb_enqueue(gsc3280_otg_hcd, gsc3280_otg_urb, &ep->hcpriv, mem_flags == GFP_ATOMIC ? 1 : 0); // if (!retval) { if (alloc_bandwidth) { allocate_bus_bandwidth(hcd, gsc3280_otg_hcd_get_ep_bandwidth (gsc3280_otg_hcd, ep->hcpriv), urb); } } else { if (retval == -GSC3280_E_NO_DEVICE) { retval = -ENODEV; } } 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 urb_dequeue(struct usb_hcd *hcd, struct urb *urb) #else static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) #endif { gsc3280_irqflags_t flags; gsc3280_otg_hcd_t *gsc3280_otg_hcd; GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD URB Dequeue\n"); gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); #ifdef DEBUG if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { dump_urb_info(urb, "urb_dequeue"); } #endif GSC3280_SPINLOCK_IRQSAVE(gsc3280_otg_hcd->lock, &flags); gsc3280_otg_hcd_urb_dequeue(gsc3280_otg_hcd, urb->hcpriv); GSC3280_FREE(urb->hcpriv); urb->hcpriv = NULL; GSC3280_SPINUNLOCK_IRQRESTORE(gsc3280_otg_hcd->lock, flags); /* Higher layer software sets URB status. */ #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)) { GSC3280_PRINTF("Called usb_hcd_giveback_urb()\n"); GSC3280_PRINTF(" urb->status = %d\n", urb->status); } return 0; } /* Frees resources in the GSC3280_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) { gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD EP DISABLE: _bEndpointAddress=0x%02x, " "endpoint=%d\n", ep->desc.bEndpointAddress, gsc3280_ep_addr_to_endpoint(ep->desc.bEndpointAddress)); gsc3280_otg_hcd_endpoint_disable(gsc3280_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) { gsc3280_irqflags_t flags; struct usb_device *udev = NULL; int epnum = usb_endpoint_num(&ep->desc); int is_out = usb_endpoint_dir_out(&ep->desc); int is_control = usb_endpoint_xfer_control(&ep->desc); gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); struct platform_device *pdev = to_platform_device(gsc3280_otg_hcd->otg_dev); if (pdev) udev = to_usb_device(&pdev->dev); else return; GSC3280_DEBUGPL(DBG_HCD, "GSC3280 OTG HCD EP RESET: Endpoint Num=0x%02d, " "endpoint=%d\n", epnum); GSC3280_SPINLOCK_IRQSAVE(gsc3280_otg_hcd->lock, &flags); usb_settoggle(udev, epnum, is_out, 0); if (is_control) usb_settoggle(udev, epnum, !is_out, 0); if (ep->hcpriv) { gsc3280_otg_hcd_endpoint_reset(gsc3280_otg_hcd, ep->hcpriv); } GSC3280_SPINUNLOCK_IRQRESTORE(gsc3280_otg_hcd->lock, flags); } #endif /** Handles host mode interrupts for the GSC3280_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 gsc3280_otg_hcd_irq(struct usb_hcd *hcd) { gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); int32_t retval = gsc3280_otg_hcd_handle_intr(gsc3280_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) { gsc3280_otg_hcd_t *gsc3280_otg_hcd = hcd_to_gsc3280_otg_hcd(hcd); buf[0] = 0; buf[0] |= (gsc3280_otg_hcd_is_status_changed(gsc3280_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 = gsc3280_otg_hcd_hub_control(hcd_to_gsc3280_otg_hcd(hcd), typeReq, wValue, wIndex, buf, wLength); switch (retval) { case -GSC3280_E_INVALID: retval = -EINVAL; break; } return retval; } #endif /* GSC3280_DEVICE_ONLY */