blob: 5c09aff92a26818471b2f0205102b305f8e2b336 [file] [log] [blame]
/* ==========================================================================
* $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>
#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";
/** @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);
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;
};
/** @} */
int comcerto_dwc_dummy_bus_suspend(struct usb_hcd *hcd)
{
printk("\n comcerto_dwc_dummy_bus_suspend...");
return 0;
}
int comcerto_dwc_dummy_bus_resume(struct usb_hcd *hcd)
{
printk("\n comcerto_dwc_dummy_bus_resume...");
return 0;
}
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,
//.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 = comcerto_dwc_dummy_bus_suspend,
.bus_resume = comcerto_dwc_dummy_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;
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;
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);
}
}
DWC_FREE(dwc_otg_urb);
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,
};
/*
* Controller Port Suspend Routiene.
*/
void dwc_otg_host_port_suspend(struct usb_hcd *hcd)
{
dwc_otg_host_if_t host_if;
hprt0_data_t hprt;
host_if.hprt0 = hcd->regs + DWC_OTG_HOST_PORT_REGS_OFFSET;
hprt.d32= DWC_READ_REG32(host_if.hprt0);
hprt.b.prtsusp = 1;
DWC_WRITE_REG32(host_if.hprt0, hprt.d32);
}
/*
* Controller Port Resume Routiene.
*/
void dwc_otg_host_port_resume(struct usb_hcd *hcd)
{
dwc_otg_host_if_t host_if;
hprt0_data_t hprt;
host_if.hprt0 = hcd->regs + DWC_OTG_HOST_PORT_REGS_OFFSET;
hprt.d32= DWC_READ_REG32(host_if.hprt0);
hprt.b.prtsusp = 0;
DWC_WRITE_REG32(host_if.hprt0, hprt.d32);
}
/**
* 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(
#ifdef LM_INTERFACE
struct lm_device *_dev
#elif defined(PCI_INTERFACE)
struct pci_dev *_dev
#else
struct platform_device *_dev
#endif
)
{
struct usb_hcd *hcd = NULL;
dwc_otg_hcd_t *dwc_otg_hcd = NULL;
#ifdef LM_INTERFACE
dwc_otg_device_t *otg_dev = lm_get_drvdata(_dev);
#elif defined(PCI_INTERFACE)
dwc_otg_device_t *otg_dev = pci_get_drvdata(_dev);
#else
dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev);
#endif
int retval = 0;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT\n");
/* Set device flags indicating whether the HCD supports DMA. */
if (dwc_otg_is_dma_enable(otg_dev->core_if)) {
#ifdef LM_INTERFACE
_dev->dev.dma_mask = (void *)~0;
_dev->dev.coherent_dma_mask = ~0;
#elif defined(PCI_INTERFACE)
pci_set_dma_mask(_dev, DMA_32BIT_MASK);
pci_set_consistent_dma_mask(_dev, DMA_32BIT_MASK);
#endif
} else {
#ifdef LM_INTERFACE
_dev->dev.dma_mask = (void *)0;
_dev->dev.coherent_dma_mask = 0;
#elif defined(PCI_INTERFACE)
pci_set_dma_mask(_dev, 0);
pci_set_consistent_dma_mask(_dev, 0);
#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;
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 for LM(with 2.6.20.1 kernel)
/* 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->irq, IRQF_SHARED | IRQF_DISABLED);
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(
#ifdef LM_INTERFACE
struct lm_device *_dev
#elif defined(PCI_INTERFACE)
struct pci_dev *_dev
#else
struct platform_device *_dev
#endif
)
{
#ifdef LM_INTERFACE
dwc_otg_device_t *otg_dev = lm_get_drvdata(_dev);
#elif defined(PCI_INTERFACE)
dwc_otg_device_t *otg_dev = pci_get_drvdata(_dev);
#else
dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev);
#endif
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)) {
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)
{
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; 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 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;
#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 (!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\n");
}
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;
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;
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);
}
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);
if (!retval) {
if (alloc_bandwidth) {
allocate_bus_bandwidth(hcd,
dwc_otg_hcd_get_ep_bandwidth
(dwc_otg_hcd, ep->hcpriv), urb);
}
} else {
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
{
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);
dwc_otg_hcd_urb_dequeue(dwc_otg_hcd, urb->hcpriv);
DWC_FREE(urb->hcpriv);
urb->hcpriv = NULL;
DWC_SPINUNLOCK_IRQRESTORE(dwc_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)) {
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\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;
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);
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
#ifdef LM_INTERFACE
struct lm_device *_dev = dwc_otg_hcd->otg_dev->os_dep.lmdev;
#elif defined(PCI_INTERFACE)
struct pci_dev *_dev = dwc_otg_hcd->otg_dev->os_dep.pcidev;
#else
struct device *_dev = dwc_otg_hcd->otg_dev->os_dep.parent;
#endif
if (_dev)
udev = to_usb_device(_dev);
else
return;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP RESET: Endpoint Num=0x%02d\n", epnum);
DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
usb_settoggle(udev, epnum, is_out, 0);
if (is_control)
usb_settoggle(udev, epnum, !is_out, 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 */