blob: dcf81d7f62e058d5354bb2f7a470352aa7ff93b5 [file] [log] [blame]
/* ==========================================================================
* $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_linux.c $
* $Revision: #22 $
* $Date: 2012/12/21 $
* $Change: 2131568 $
*
* 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>
#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 "dwc_otg_hcd_if.h"
#include "dwc_otg_dbg.h"
#include "dwc_otg_driver.h"
#include "dwc_otg_hcd.h"
/**
* 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";
/** @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 dwc_otg_hcd_irq(struct usb_hcd *hcd);
extern int hcd_start(struct usb_hcd *hcd);
extern void hcd_stop(struct usb_hcd *hcd);
extern int hcd_suspend(struct usb_hcd *hcd);
extern int hcd_resume(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_USB2 | HCD_BH,
.start = hcd_start,
.stop = hcd_stop,
.shutdown = hcd_shutdown,
.bus_suspend = hcd_suspend,
.bus_resume = hcd_resume,
.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,
};
/** 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;
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(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.
*/
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;
#ifdef DEBUG
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);
}
}
#endif
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);
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->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);
}
#if 0
usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
#endif
DWC_FREE(dwc_otg_urb);
dwc_otg_urb = NULL;
DWC_SPINUNLOCK(hcd->lock);
#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, status);
#endif
DWC_SPINLOCK(hcd->lock);
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,
};
/**
* 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(struct platform_device *pdev)
{
struct usb_hcd *hcd = NULL;
dwc_otg_hcd_t *dwc_otg_hcd = NULL;
dwc_otg_device_t *otg_dev = g_dwc_otg_device[pdev->id];
int retval = 0;
int irq = 0;
int tt = 0;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT\n");
/*
* 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, &pdev->dev, pdev->dev.bus_id);
tt = 0;
#else
hcd = usb_create_hcd(&dwc_otg_hc_driver, &pdev->dev, dev_name(&pdev->dev));
tt = 1;
#endif
if (!hcd) {
retval = -ENOMEM;
goto error1;
}
hcd->has_tt = tt;
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;
if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if))
goto error2;
otg_dev->hcd->otg_dev = otg_dev;
hcd->self.otg_port = dwc_otg_hcd_otg_port(dwc_otg_hcd);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)
/* Don't support SG list at this point */
hcd->self.sg_tablesize = 0;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
/* Do not to do HNP polling if not capable */
#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.
*/
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENODEV;
retval = usb_add_hcd(hcd, irq, 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(struct platform_device *pdev)
{
dwc_otg_device_t *otg_dev = g_dwc_otg_device[pdev->id];
dwc_otg_hcd_t *dwc_otg_hcd;
struct usb_hcd *hcd;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD REMOVE\n");
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)) {
if (dwc_otg_hcd->core_if->otg_ver)
dwc_otg_hcd->core_if->op_state = B_PERIPHERAL;
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);
}
/** HCD shutdown */
void hcd_shutdown(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
dwc_otg_hcd->auto_pm_suspend_flag = (hcd->flags>>31)&1;
dwc_otg_hcd->pm_freeze_flag = (hcd->flags >> 30) & 1;
DWC_DEBUGPL(DBG_HCD, "HCD SUSPEND\n");
dwc_otg_hcd_suspend(dwc_otg_hcd);
hcd->flags &= (~(1<<31));
hcd->flags &= (~(1<<30));
return;
}
/** HCD Suspend */
int hcd_suspend(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
dwc_otg_hcd->auto_pm_suspend_flag = (hcd->flags>>31)&1;
dwc_otg_hcd->pm_freeze_flag = (hcd->flags >> 30) & 1;
DWC_DEBUGPL(DBG_HCD, "HCD SUSPEND\n");
dwc_otg_hcd_suspend(dwc_otg_hcd);
hcd->flags &= (~(1<<31));
hcd->flags &= (~(1<<30));
return 0;
}
/** HCD resume */
int hcd_resume(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
dwc_otg_hcd->auto_pm_suspend_flag = (hcd->flags>>31)&1;
dwc_otg_hcd->pm_freeze_flag = (hcd->flags >> 30) & 1;
DWC_DEBUGPL(DBG_HCD, "HCD RESUME\n");
dwc_otg_hcd_resume(dwc_otg_hcd);
hcd->flags &= (~(1<<31));
hcd->flags &= (~(1<<30));
return 0;
}
/** Returns the current frame number. */
static int get_frame_number(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
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;
};
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 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_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;
dwc_irqflags_t flags_lock;
#ifdef DEBUG
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB))
dump_urb_info(urb, "urb_enqueue");
#endif
DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags_lock);
DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags_lock);
if (unlikely(atomic_read(&urb->reject))) {
DWC_PRINTF("%s:urb(%p) had been killed\n", __func__, urb);
return -EPERM;
}
#if 0
retval = usb_hcd_link_urb_to_ep(hcd, urb);
if (unlikely(retval)) {
DWC_ERROR("DWC OTG HCD URB Enqueue failed linking urb. "
"Error status %d\n", retval);
return retval;
}
#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;
}
dwc_otg_urb = dwc_otg_hcd_urb_alloc(dwc_otg_hcd,
urb->number_of_packets,
mem_flags == GFP_ATOMIC ? 1 : 0);
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;
/*phys_to_virt api can not geting virt address,we can not use it.*/
#if 0
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);
#endif
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);
if (unlikely(atomic_read(&urb->reject))) {
DWC_PRINTF("%s:urb(%p) had been killed2\n", __func__, urb);
DWC_FREE(dwc_otg_urb);
return -EPERM;
}
urb->hcpriv = dwc_otg_urb;
retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb, &ep->hcpriv,
/* mem_flags == GFP_ATOMIC ? 1 : 0*/1);
if (!retval) {
if (alloc_bandwidth)
allocate_bus_bandwidth(hcd,
dwc_otg_hcd_get_ep_bandwidth
(dwc_otg_hcd, ep->hcpriv), urb);
} else {
DWC_FREE(dwc_otg_urb);
urb->hcpriv = NULL;
#if 0
usb_hcd_unlink_urb_from_ep(hcd, urb);
#endif
if (retval == -DWC_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
{
int retval = 0;
dwc_irqflags_t flags;
dwc_otg_hcd_t *dwc_otg_hcd;
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, "urb_dequeue");
#endif
DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
if (!((hcd->flags >> 29) & 1)) {
retval = usb_hcd_check_unlink_urb(hcd, urb, status);
if (unlikely(retval))
goto EXIT;
}
if (usb_pipeint(urb->pipe) && (dwc_otg_hcd->ssplit_lock == usb_pipedevice(urb->pipe))) {
DWC_DEBUGPL(DBG_HCD, "addr=%d(%p)\n", usb_pipedevice(urb->pipe), urb->hcpriv);
dwc_otg_hcd->ssplit_lock = 0;
}
if (urb->hcpriv == NULL) {
DWC_WARN("urb->hcpriv == NULL! urb = %p status=%d\n", urb, status);
goto EXIT;
}
retval = dwc_otg_hcd_urb_dequeue(dwc_otg_hcd, urb->hcpriv);
if (!retval)
DWC_FREE(urb->hcpriv);
EXIT:
DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
if (retval)
return retval;
/* Higher layer software sets URB status. */
if (urb->hcpriv)
#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(" urb->status = %d\n", urb->status);
}
return 0;
}
/* 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,is_intr=%d\n", ep->desc.bEndpointAddress,
dwc_ep_addr_to_endpoint(ep->desc.bEndpointAddress),
usb_endpoint_xfer_int(&ep->desc));
if (usb_endpoint_xfer_int(&ep->desc))
dwc_otg_hcd->ssplit_lock = 0;
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_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
if (usb_endpoint_xfer_int(&ep->desc))
dwc_otg_hcd->ssplit_lock = 0;
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 */