Brooklyn/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c

1088 lines
31 KiB
C

/* ==========================================================================
* $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);
}
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(&regs,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(&regs);
set_fiq_regs(&regs);
#endif
dwc_otg_hcd->fiq_state->dwc_regs_base = otg_dev->os_dep.base;
//Set the mphi periph to the required registers
dwc_otg_hcd->fiq_state->mphi_regs.base = otg_dev->os_dep.mphi_base;
if (otg_dev->os_dep.use_swirq) {
dwc_otg_hcd->fiq_state->mphi_regs.swirq_set =
otg_dev->os_dep.mphi_base + 0x1f0;
dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr =
otg_dev->os_dep.mphi_base + 0x1f4;
DWC_WARN("Fake MPHI regs_base at %px",
dwc_otg_hcd->fiq_state->mphi_regs.base);
} else {
dwc_otg_hcd->fiq_state->mphi_regs.ctrl =
otg_dev->os_dep.mphi_base + 0x4c;
dwc_otg_hcd->fiq_state->mphi_regs.outdda
= otg_dev->os_dep.mphi_base + 0x28;
dwc_otg_hcd->fiq_state->mphi_regs.outddb
= otg_dev->os_dep.mphi_base + 0x2c;
dwc_otg_hcd->fiq_state->mphi_regs.intstat
= otg_dev->os_dep.mphi_base + 0x50;
DWC_WARN("MPHI regs_base at %px",
dwc_otg_hcd->fiq_state->mphi_regs.base);
//Enable mphi peripheral
writel((1<<31),dwc_otg_hcd->fiq_state->mphi_regs.ctrl);
#ifdef DEBUG
if (readl(dwc_otg_hcd->fiq_state->mphi_regs.ctrl) & 0x80000000)
DWC_WARN("MPHI periph has been enabled");
else
DWC_WARN("MPHI periph has NOT been enabled");
#endif
}
// Enable FIQ interrupt from USB peripheral
#ifdef CONFIG_ARM64
irq = otg_dev->os_dep.fiq_num;
if (irq < 0) {
DWC_ERROR("Can't get SIM-FIQ irq");
return;
}
retval = request_irq(irq, fiq_irq_handler, 0, "dwc_otg_sim-fiq", dwc_otg_hcd);
if (retval < 0) {
DWC_ERROR("Unable to request SIM-FIQ irq\n");
return;
}
simfiq_irq = irq;
#else
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
irq = otg_dev->os_dep.fiq_num;
#else
irq = INTERRUPT_VC_USB;
#endif
if (irq < 0) {
DWC_ERROR("Can't get FIQ irq");
return;
}
/*
* We could take an interrupt immediately after enabling the FIQ.
* Ensure coherency of hcd->fiq_state.
*/
smp_mb();
enable_fiq(irq);
local_fiq_enable();
#endif
}
/**
* Initializes the HCD. This function allocates memory for and initializes the
* static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the
* USB bus with the core and calls the hc_driver->start() function. It returns
* a negative error on failure.
*/
int hcd_init(dwc_bus_dev_t *_dev)
{
struct usb_hcd *hcd = NULL;
dwc_otg_hcd_t *dwc_otg_hcd = NULL;
dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
int retval = 0;
u64 dmamask;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT otg_dev=%p\n", otg_dev);
/* Set device flags indicating whether the HCD supports DMA. */
if (dwc_otg_is_dma_enable(otg_dev->core_if))
dmamask = DMA_BIT_MASK(32);
else
dmamask = 0;
#if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE)
dma_set_mask(&_dev->dev, dmamask);
dma_set_coherent_mask(&_dev->dev, dmamask);
#elif defined(PCI_INTERFACE)
pci_set_dma_mask(_dev, dmamask);
pci_set_consistent_dma_mask(_dev, dmamask);
#endif
/*
* Allocate memory for the base HCD plus the DWC OTG HCD.
* Initialize the base HCD.
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, _dev->dev.bus_id);
#else
hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, dev_name(&_dev->dev));
hcd->has_tt = 1;
// hcd->uses_new_polling = 1;
// hcd->poll_rh = 0;
#endif
if (!hcd) {
retval = -ENOMEM;
goto error1;
}
hcd->regs = otg_dev->os_dep.base;
/* Initialize the DWC OTG HCD. */
dwc_otg_hcd = dwc_otg_hcd_alloc_hcd();
if (!dwc_otg_hcd) {
goto error2;
}
((struct wrapper_priv_data *)(hcd->hcd_priv))->dwc_otg_hcd =
dwc_otg_hcd;
otg_dev->hcd = dwc_otg_hcd;
otg_dev->hcd->otg_dev = otg_dev;
#ifdef CONFIG_ARM64
if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if))
goto error2;
if (fiq_enable)
hcd_init_fiq(otg_dev);
#else
if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) {
goto error2;
}
if (fiq_enable) {
if (num_online_cpus() > 1) {
/*
* bcm2709: can run the FIQ on a separate core to IRQs.
* Ensure driver state is visible to other cores before setting up the FIQ.
*/
smp_mb();
smp_call_function_single(1, hcd_init_fiq, otg_dev, 1);
} else {
smp_call_function_single(0, hcd_init_fiq, otg_dev, 1);
}
}
#endif
hcd->self.otg_port = dwc_otg_hcd_otg_port(dwc_otg_hcd);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) //don't support for LM(with 2.6.20.1 kernel)
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) //version field absent later
hcd->self.otg_version = dwc_otg_get_otg_version(otg_dev->core_if);
#endif
/* Don't support SG list at this point */
hcd->self.sg_tablesize = 0;
#endif
/*
* Finish generic HCD initialization and start the HCD. This function
* allocates the DMA buffer pool, registers the USB bus, requests the
* IRQ line, and calls hcd_start method.
*/
retval = usb_add_hcd(hcd, otg_dev->os_dep.irq_num, IRQF_SHARED);
if (retval < 0) {
goto error2;
}
dwc_otg_hcd_set_priv_data(dwc_otg_hcd, hcd);
return 0;
error2:
usb_put_hcd(hcd);
error1:
return retval;
}
/**
* Removes the HCD.
* Frees memory and resources associated with the HCD and deregisters the bus.
*/
void hcd_remove(dwc_bus_dev_t *_dev)
{
dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
dwc_otg_hcd_t *dwc_otg_hcd;
struct usb_hcd *hcd;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD REMOVE otg_dev=%p\n", otg_dev);
if (!otg_dev) {
DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__);
return;
}
dwc_otg_hcd = otg_dev->hcd;
if (!dwc_otg_hcd) {
DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__);
return;
}
hcd = dwc_otg_hcd_to_hcd(dwc_otg_hcd);
if (!hcd) {
DWC_DEBUGPL(DBG_ANY,
"%s: dwc_otg_hcd_to_hcd(dwc_otg_hcd) NULL!\n",
__func__);
return;
}
usb_remove_hcd(hcd);
dwc_otg_hcd_set_priv_data(dwc_otg_hcd, NULL);
dwc_otg_hcd_remove(dwc_otg_hcd);
usb_put_hcd(hcd);
}
/* =========================================================================
* Linux HC Driver Functions
* ========================================================================= */
/** Initializes the DWC_otg controller and its root hub and prepares it for host
* mode operation. Activates the root port. Returns 0 on success and a negative
* error code on failure. */
int hcd_start(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
struct usb_bus *bus;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD START\n");
bus = hcd_to_bus(hcd);
hcd->state = HC_STATE_RUNNING;
if (dwc_otg_hcd_start(dwc_otg_hcd, &hcd_fops)) {
return 0;
}
/* Initialize and connect root hub if one is not already attached */
if (bus->root_hub) {
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Has Root Hub\n");
/* Inform the HUB driver to resume. */
usb_hcd_resume_root_hub(hcd);
}
return 0;
}
/**
* Halts the DWC_otg host mode operations in a clean manner. USB transfers are
* stopped.
*/
void hcd_stop(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
dwc_otg_hcd_stop(dwc_otg_hcd);
}
/** Returns the current frame number. */
static int get_frame_number(struct usb_hcd *hcd)
{
hprt0_data_t hprt0;
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0);
if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED)
return dwc_otg_hcd_get_frame_number(dwc_otg_hcd) >> 3;
else
return dwc_otg_hcd_get_frame_number(dwc_otg_hcd);
}
#ifdef DEBUG
static void dump_urb_info(struct urb *urb, char *fn_name)
{
DWC_PRINTF("%s, urb %p\n", fn_name, urb);
DWC_PRINTF(" Device address: %d\n", usb_pipedevice(urb->pipe));
DWC_PRINTF(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe),
(usb_pipein(urb->pipe) ? "IN" : "OUT"));
DWC_PRINTF(" Endpoint type: %s\n", ( {
char *pipetype;
switch (usb_pipetype(urb->pipe)) {
case PIPE_CONTROL:
pipetype = "CONTROL"; break; case PIPE_BULK:
pipetype = "BULK"; break; case PIPE_INTERRUPT:
pipetype = "INTERRUPT"; break; case PIPE_ISOCHRONOUS:
pipetype = "ISOCHRONOUS"; break; default:
pipetype = "UNKNOWN"; break;};
pipetype;}
)) ;
DWC_PRINTF(" Speed: %s\n", ( {
char *speed; switch (urb->dev->speed) {
case USB_SPEED_HIGH:
speed = "HIGH"; break; case USB_SPEED_FULL:
speed = "FULL"; break; case USB_SPEED_LOW:
speed = "LOW"; break; default:
speed = "UNKNOWN"; break;};
speed;}
)) ;
DWC_PRINTF(" Max packet size: %d\n",
usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)));
DWC_PRINTF(" Data buffer length: %d\n", urb->transfer_buffer_length);
DWC_PRINTF(" Transfer buffer: %p, Transfer DMA: %p\n",
urb->transfer_buffer, (void *)urb->transfer_dma);
DWC_PRINTF(" Setup buffer: %p, Setup DMA: %p\n",
urb->setup_packet, (void *)urb->setup_dma);
DWC_PRINTF(" Interval: %d\n", urb->interval);
if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
int i;
for (i = 0; i < urb->number_of_packets; i++) {
DWC_PRINTF(" ISO Desc %d:\n", i);
DWC_PRINTF(" offset: %d, length %d\n",
urb->iso_frame_desc[i].offset,
urb->iso_frame_desc[i].length);
}
}
}
#endif
/** Starts processing a USB transfer request specified by a USB Request Block
* (URB). mem_flags indicates the type of memory allocation to use while
* processing this URB. */
static int dwc_otg_urb_enqueue(struct usb_hcd *hcd,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
struct usb_host_endpoint *ep,
#endif
struct urb *urb, gfp_t mem_flags)
{
int retval = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
struct usb_host_endpoint *ep = urb->ep;
#endif
dwc_irqflags_t irqflags;
void **ref_ep_hcpriv = &ep->hcpriv;
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
dwc_otg_hcd_urb_t *dwc_otg_urb;
int i;
int alloc_bandwidth = 0;
uint8_t ep_type = 0;
uint32_t flags = 0;
void *buf;
#ifdef DEBUG
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
dump_urb_info(urb, "dwc_otg_urb_enqueue");
}
#endif
if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS)
|| (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) {
if (!dwc_otg_hcd_is_bandwidth_allocated
(dwc_otg_hcd, ref_ep_hcpriv)) {
alloc_bandwidth = 1;
}
}
switch (usb_pipetype(urb->pipe)) {
case PIPE_CONTROL:
ep_type = USB_ENDPOINT_XFER_CONTROL;
break;
case PIPE_ISOCHRONOUS:
ep_type = USB_ENDPOINT_XFER_ISOC;
break;
case PIPE_BULK:
ep_type = USB_ENDPOINT_XFER_BULK;
break;
case PIPE_INTERRUPT:
ep_type = USB_ENDPOINT_XFER_INT;
break;
default:
DWC_WARN("Wrong EP type - %d\n", usb_pipetype(urb->pipe));
}
/* # of packets is often 0 - do we really need to call this then? */
dwc_otg_urb = dwc_otg_hcd_urb_alloc(dwc_otg_hcd,
urb->number_of_packets,
mem_flags == GFP_ATOMIC ? 1 : 0);
if(dwc_otg_urb == NULL)
return -ENOMEM;
if (!dwc_otg_urb && urb->number_of_packets)
return -ENOMEM;
dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_urb, usb_pipedevice(urb->pipe),
usb_pipeendpoint(urb->pipe), ep_type,
usb_pipein(urb->pipe),
usb_maxpacket(urb->dev, urb->pipe,
!(usb_pipein(urb->pipe))));
buf = urb->transfer_buffer;
if (hcd_uses_dma(hcd) && !buf && urb->transfer_buffer_length) {
/*
* Calculate virtual address from physical address,
* because some class driver may not fill transfer_buffer.
* In Buffer DMA mode virual address is used,
* when handling non DWORD aligned buffers.
*/
buf = (void *)__bus_to_virt((unsigned long)urb->transfer_dma);
dev_warn_once(&urb->dev->dev,
"USB transfer_buffer was NULL, will use __bus_to_virt(%pad)=%p\n",
&urb->transfer_dma, buf);
}
if (!buf && urb->transfer_buffer_length) {
DWC_FREE(dwc_otg_urb);
DWC_ERROR("transfer_buffer is NULL in PIO mode or both "
"transfer_buffer and transfer_dma are NULL in DMA mode\n");
return -EINVAL;
}
if (!(urb->transfer_flags & URB_NO_INTERRUPT))
flags |= URB_GIVEBACK_ASAP;
if (urb->transfer_flags & URB_ZERO_PACKET)
flags |= URB_SEND_ZERO_PACKET;
dwc_otg_hcd_urb_set_params(dwc_otg_urb, urb, buf,
urb->transfer_dma,
urb->transfer_buffer_length,
urb->setup_packet,
urb->setup_dma, flags, urb->interval);
for (i = 0; i < urb->number_of_packets; ++i) {
dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_urb, i,
urb->
iso_frame_desc[i].offset,
urb->
iso_frame_desc[i].length);
}
DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags);
urb->hcpriv = dwc_otg_urb;
#if USB_URB_EP_LINKING
retval = usb_hcd_link_urb_to_ep(hcd, urb);
if (0 == retval)
#endif
{
retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb,
/*(dwc_otg_qh_t **)*/
ref_ep_hcpriv, 1);
if (0 == retval) {
if (alloc_bandwidth) {
allocate_bus_bandwidth(hcd,
dwc_otg_hcd_get_ep_bandwidth(
dwc_otg_hcd, *ref_ep_hcpriv),
urb);
}
} else {
DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval);
#if USB_URB_EP_LINKING
usb_hcd_unlink_urb_from_ep(hcd, urb);
#endif
DWC_FREE(dwc_otg_urb);
urb->hcpriv = NULL;
if (retval == -DWC_E_NO_DEVICE)
retval = -ENODEV;
}
}
#if USB_URB_EP_LINKING
else
{
DWC_FREE(dwc_otg_urb);
urb->hcpriv = NULL;
}
#endif
DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags);
return retval;
}
/** Aborts/cancels a USB transfer request. Always returns 0 to indicate
* success. */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
#else
static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
#endif
{
dwc_irqflags_t flags;
dwc_otg_hcd_t *dwc_otg_hcd;
int rc;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue\n");
dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
#ifdef DEBUG
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
dump_urb_info(urb, "dwc_otg_urb_dequeue");
}
#endif
DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
rc = usb_hcd_check_unlink_urb(hcd, urb, status);
if (0 == rc) {
if(urb->hcpriv != NULL) {
dwc_otg_hcd_urb_dequeue(dwc_otg_hcd,
(dwc_otg_hcd_urb_t *)urb->hcpriv);
DWC_FREE(urb->hcpriv);
urb->hcpriv = NULL;
}
}
if (0 == rc) {
/* Higher layer software sets URB status. */
#if USB_URB_EP_LINKING
usb_hcd_unlink_urb_from_ep(hcd, urb);
#endif
DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
usb_hcd_giveback_urb(hcd, urb);
#else
usb_hcd_giveback_urb(hcd, urb, status);
#endif
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
DWC_PRINTF("Called usb_hcd_giveback_urb() \n");
DWC_PRINTF(" 1urb->status = %d\n", urb->status);
}
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue OK\n");
} else {
DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue failed - rc %d\n",
rc);
}
return rc;
}
/* Frees resources in the DWC_otg controller related to a given endpoint. Also
* clears state in the HCD related to the endpoint. Any URBs for the endpoint
* must already be dequeued. */
static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
DWC_DEBUGPL(DBG_HCD,
"DWC OTG HCD EP DISABLE: _bEndpointAddress=0x%02x, "
"endpoint=%d\n", ep->desc.bEndpointAddress,
dwc_ep_addr_to_endpoint(ep->desc.bEndpointAddress));
dwc_otg_hcd_endpoint_disable(dwc_otg_hcd, ep->hcpriv, 250);
ep->hcpriv = NULL;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
/* Resets endpoint specific parameter values, in current version used to reset
* the data toggle(as a WA). This function can be called from usb_clear_halt routine */
static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
{
dwc_irqflags_t flags;
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP RESET: Endpoint Num=0x%02d\n",
ep->desc.bEndpointAddress);
DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
if (ep->hcpriv) {
dwc_otg_hcd_endpoint_reset(dwc_otg_hcd, ep->hcpriv);
}
DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
}
#endif
/** Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
* there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid
* interrupt.
*
* This function is called by the USB core when an interrupt occurs */
static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
int32_t retval = dwc_otg_hcd_handle_intr(dwc_otg_hcd);
if (retval != 0) {
S3C2410X_CLEAR_EINTPEND();
}
return IRQ_RETVAL(retval);
}
/** Creates Status Change bitmap for the root hub and root port. The bitmap is
* returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1
* is the status change indicator for the single root port. Returns 1 if either
* change indicator is 1, otherwise returns 0. */
int hub_status_data(struct usb_hcd *hcd, char *buf)
{
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
buf[0] = 0;
buf[0] |= (dwc_otg_hcd_is_status_changed(dwc_otg_hcd, 1)) << 1;
return (buf[0] != 0);
}
/** Handles hub class-specific requests. */
int hub_control(struct usb_hcd *hcd,
u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength)
{
int retval;
retval = dwc_otg_hcd_hub_control(hcd_to_dwc_otg_hcd(hcd),
typeReq, wValue, wIndex, buf, wLength);
switch (retval) {
case -DWC_E_INVALID:
retval = -EINVAL;
break;
}
return retval;
}
#endif /* DWC_DEVICE_ONLY */