2009-01-10 03:03:21 +01:00
|
|
|
/*
|
|
|
|
* Freescale QUICC Engine USB Host Controller Driver
|
|
|
|
*
|
|
|
|
* Copyright (c) Freescale Semicondutor, Inc. 2006.
|
|
|
|
* Shlomi Gridish <gridish@freescale.com>
|
|
|
|
* Jerry Huang <Chang-Ming.Huang@freescale.com>
|
|
|
|
* Copyright (c) Logic Product Development, Inc. 2007
|
|
|
|
* Peter Barada <peterb@logicpd.com>
|
|
|
|
* Copyright (c) MontaVista Software, Inc. 2008.
|
|
|
|
* Anton Vorontsov <avorontsov@ru.mvista.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
* option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/usb.h>
|
2010-04-24 23:21:52 +02:00
|
|
|
#include <linux/usb/hcd.h>
|
2009-01-10 03:03:21 +01:00
|
|
|
#include <linux/of_platform.h>
|
|
|
|
#include <linux/of_gpio.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 09:04:11 +01:00
|
|
|
#include <linux/slab.h>
|
2009-01-10 03:03:21 +01:00
|
|
|
#include <asm/qe.h>
|
|
|
|
#include <asm/fsl_gtm.h>
|
|
|
|
#include "fhci.h"
|
|
|
|
|
|
|
|
void fhci_start_sof_timer(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
fhci_dbg(fhci, "-> %s\n", __func__);
|
|
|
|
|
|
|
|
/* clear frame_n */
|
|
|
|
out_be16(&fhci->pram->frame_num, 0);
|
|
|
|
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&fhci->regs->usb_ussft, 0);
|
|
|
|
setbits8(&fhci->regs->usb_usmod, USB_MODE_SFTE);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
fhci_dbg(fhci, "<- %s\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void fhci_stop_sof_timer(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
fhci_dbg(fhci, "-> %s\n", __func__);
|
|
|
|
|
2012-06-24 02:24:30 +02:00
|
|
|
clrbits8(&fhci->regs->usb_usmod, USB_MODE_SFTE);
|
2009-01-10 03:03:21 +01:00
|
|
|
gtm_stop_timer16(fhci->timer);
|
|
|
|
|
|
|
|
fhci_dbg(fhci, "<- %s\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
u16 fhci_get_sof_timer_count(struct fhci_usb *usb)
|
|
|
|
{
|
2012-06-24 02:24:30 +02:00
|
|
|
return be16_to_cpu(in_be16(&usb->fhci->regs->usb_ussft) / 12);
|
2009-01-10 03:03:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the endpoint zero */
|
|
|
|
static u32 endpoint_zero_init(struct fhci_usb *usb,
|
|
|
|
enum fhci_mem_alloc data_mem,
|
|
|
|
u32 ring_len)
|
|
|
|
{
|
|
|
|
u32 rc;
|
|
|
|
|
|
|
|
rc = fhci_create_ep(usb, data_mem, ring_len);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
/* inilialize endpoint registers */
|
|
|
|
fhci_init_ep_registers(usb, usb->ep0, data_mem);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enable the USB interrupts */
|
|
|
|
void fhci_usb_enable_interrupt(struct fhci_usb *usb)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci = usb->fhci;
|
|
|
|
|
|
|
|
if (usb->intr_nesting_cnt == 1) {
|
|
|
|
/* initialize the USB interrupt */
|
|
|
|
enable_irq(fhci_to_hcd(fhci)->irq);
|
|
|
|
|
|
|
|
/* initialize the event register and mask register */
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&usb->fhci->regs->usb_usber, 0xffff);
|
|
|
|
out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
/* enable the timer interrupts */
|
|
|
|
enable_irq(fhci->timer->irq);
|
|
|
|
} else if (usb->intr_nesting_cnt > 1)
|
|
|
|
fhci_info(fhci, "unbalanced USB interrupts nesting\n");
|
|
|
|
usb->intr_nesting_cnt--;
|
|
|
|
}
|
|
|
|
|
2010-12-31 00:07:58 +01:00
|
|
|
/* disable the usb interrupt */
|
2009-01-10 03:03:21 +01:00
|
|
|
void fhci_usb_disable_interrupt(struct fhci_usb *usb)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci = usb->fhci;
|
|
|
|
|
|
|
|
if (usb->intr_nesting_cnt == 0) {
|
2010-12-31 00:07:58 +01:00
|
|
|
/* disable the timer interrupt */
|
2009-01-10 03:03:21 +01:00
|
|
|
disable_irq_nosync(fhci->timer->irq);
|
|
|
|
|
|
|
|
/* disable the usb interrupt */
|
|
|
|
disable_irq_nosync(fhci_to_hcd(fhci)->irq);
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&usb->fhci->regs->usb_usbmr, 0);
|
2009-01-10 03:03:21 +01:00
|
|
|
}
|
|
|
|
usb->intr_nesting_cnt++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enable the USB controller */
|
|
|
|
static u32 fhci_usb_enable(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
struct fhci_usb *usb = fhci->usb_lld;
|
|
|
|
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&usb->fhci->regs->usb_usber, 0xffff);
|
|
|
|
out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk);
|
|
|
|
setbits8(&usb->fhci->regs->usb_usmod, USB_MODE_EN);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
mdelay(100);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* disable the USB controller */
|
|
|
|
static u32 fhci_usb_disable(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
struct fhci_usb *usb = fhci->usb_lld;
|
|
|
|
|
|
|
|
fhci_usb_disable_interrupt(usb);
|
|
|
|
fhci_port_disable(fhci);
|
|
|
|
|
|
|
|
/* disable the usb controller */
|
|
|
|
if (usb->port_status == FHCI_PORT_FULL ||
|
|
|
|
usb->port_status == FHCI_PORT_LOW)
|
|
|
|
fhci_device_disconnected_interrupt(fhci);
|
|
|
|
|
2012-06-24 02:24:30 +02:00
|
|
|
clrbits8(&usb->fhci->regs->usb_usmod, USB_MODE_EN);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check the bus state by polling the QE bit on the IO ports */
|
|
|
|
int fhci_ioports_check_bus_state(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
u8 bits = 0;
|
|
|
|
|
|
|
|
/* check USBOE,if transmitting,exit */
|
|
|
|
if (!gpio_get_value(fhci->gpios[GPIO_USBOE]))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* check USBRP */
|
|
|
|
if (gpio_get_value(fhci->gpios[GPIO_USBRP]))
|
|
|
|
bits |= 0x2;
|
|
|
|
|
|
|
|
/* check USBRN */
|
|
|
|
if (gpio_get_value(fhci->gpios[GPIO_USBRN]))
|
|
|
|
bits |= 0x1;
|
|
|
|
|
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fhci_mem_free(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
struct ed *ed;
|
|
|
|
struct ed *next_ed;
|
|
|
|
struct td *td;
|
|
|
|
struct td *next_td;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(ed, next_ed, &fhci->empty_eds, node) {
|
|
|
|
list_del(&ed->node);
|
|
|
|
kfree(ed);
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry_safe(td, next_td, &fhci->empty_tds, node) {
|
|
|
|
list_del(&td->node);
|
|
|
|
kfree(td);
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(fhci->vroot_hub);
|
|
|
|
fhci->vroot_hub = NULL;
|
|
|
|
|
|
|
|
kfree(fhci->hc_list);
|
|
|
|
fhci->hc_list = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fhci_mem_init(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
fhci->hc_list = kzalloc(sizeof(*fhci->hc_list), GFP_KERNEL);
|
|
|
|
if (!fhci->hc_list)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&fhci->hc_list->ctrl_list);
|
|
|
|
INIT_LIST_HEAD(&fhci->hc_list->bulk_list);
|
|
|
|
INIT_LIST_HEAD(&fhci->hc_list->iso_list);
|
|
|
|
INIT_LIST_HEAD(&fhci->hc_list->intr_list);
|
|
|
|
INIT_LIST_HEAD(&fhci->hc_list->done_list);
|
|
|
|
|
|
|
|
fhci->vroot_hub = kzalloc(sizeof(*fhci->vroot_hub), GFP_KERNEL);
|
|
|
|
if (!fhci->vroot_hub)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&fhci->empty_eds);
|
|
|
|
INIT_LIST_HEAD(&fhci->empty_tds);
|
|
|
|
|
|
|
|
/* initialize work queue to handle done list */
|
|
|
|
fhci_tasklet.data = (unsigned long)fhci;
|
|
|
|
fhci->process_done_task = &fhci_tasklet;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_TDS; i++) {
|
|
|
|
struct td *td;
|
|
|
|
|
|
|
|
td = kmalloc(sizeof(*td), GFP_KERNEL);
|
|
|
|
if (!td)
|
|
|
|
goto err;
|
|
|
|
fhci_recycle_empty_td(fhci, td);
|
|
|
|
}
|
|
|
|
for (i = 0; i < MAX_EDS; i++) {
|
|
|
|
struct ed *ed;
|
|
|
|
|
|
|
|
ed = kmalloc(sizeof(*ed), GFP_KERNEL);
|
|
|
|
if (!ed)
|
|
|
|
goto err;
|
|
|
|
fhci_recycle_empty_ed(fhci, ed);
|
|
|
|
}
|
|
|
|
|
|
|
|
fhci->active_urbs = 0;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
fhci_mem_free(fhci);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* destroy the fhci_usb structure */
|
|
|
|
static void fhci_usb_free(void *lld)
|
|
|
|
{
|
|
|
|
struct fhci_usb *usb = lld;
|
2010-01-07 02:17:32 +01:00
|
|
|
struct fhci_hcd *fhci;
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
if (usb) {
|
2010-01-07 02:17:32 +01:00
|
|
|
fhci = usb->fhci;
|
2009-01-10 03:03:21 +01:00
|
|
|
fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF);
|
|
|
|
fhci_ep0_free(usb);
|
|
|
|
kfree(usb->actual_frame);
|
|
|
|
kfree(usb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the USB */
|
|
|
|
static int fhci_usb_init(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
struct fhci_usb *usb = fhci->usb_lld;
|
|
|
|
|
|
|
|
memset_io(usb->fhci->pram, 0, FHCI_PRAM_SIZE);
|
|
|
|
|
|
|
|
usb->port_status = FHCI_PORT_DISABLED;
|
|
|
|
usb->max_frame_usage = FRAME_TIME_USAGE;
|
|
|
|
usb->sw_transaction_time = SW_FIX_TIME_BETWEEN_TRANSACTION;
|
|
|
|
|
|
|
|
usb->actual_frame = kzalloc(sizeof(*usb->actual_frame), GFP_KERNEL);
|
|
|
|
if (!usb->actual_frame) {
|
|
|
|
fhci_usb_free(usb);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&usb->actual_frame->tds_list);
|
|
|
|
|
|
|
|
/* initializing registers on chip, clear frame number */
|
|
|
|
out_be16(&fhci->pram->frame_num, 0);
|
|
|
|
|
|
|
|
/* clear rx state */
|
|
|
|
out_be32(&fhci->pram->rx_state, 0);
|
|
|
|
|
|
|
|
/* set mask register */
|
|
|
|
usb->saved_msk = (USB_E_TXB_MASK |
|
|
|
|
USB_E_TXE1_MASK |
|
|
|
|
USB_E_IDLE_MASK |
|
|
|
|
USB_E_RESET_MASK | USB_E_SFT_MASK | USB_E_MSF_MASK);
|
|
|
|
|
2012-06-24 02:24:30 +02:00
|
|
|
out_8(&usb->fhci->regs->usb_usmod, USB_MODE_HOST | USB_MODE_EN);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
/* clearing the mask register */
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&usb->fhci->regs->usb_usbmr, 0);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
/* initialing the event register */
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&usb->fhci->regs->usb_usber, 0xffff);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
if (endpoint_zero_init(usb, DEFAULT_DATA_MEM, DEFAULT_RING_LEN) != 0) {
|
|
|
|
fhci_usb_free(usb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the fhci_usb struct and the corresponding data staruct */
|
|
|
|
static struct fhci_usb *fhci_create_lld(struct fhci_hcd *fhci)
|
|
|
|
{
|
|
|
|
struct fhci_usb *usb;
|
|
|
|
|
|
|
|
/* allocate memory for SCC data structure */
|
|
|
|
usb = kzalloc(sizeof(*usb), GFP_KERNEL);
|
|
|
|
if (!usb) {
|
|
|
|
fhci_err(fhci, "no memory for SCC data struct\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
usb->fhci = fhci;
|
|
|
|
usb->hc_list = fhci->hc_list;
|
|
|
|
usb->vroot_hub = fhci->vroot_hub;
|
|
|
|
|
|
|
|
usb->transfer_confirm = fhci_transfer_confirm_callback;
|
|
|
|
|
|
|
|
return usb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fhci_start(struct usb_hcd *hcd)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct fhci_hcd *fhci = hcd_to_fhci(hcd);
|
|
|
|
|
|
|
|
ret = fhci_mem_init(fhci);
|
|
|
|
if (ret) {
|
|
|
|
fhci_err(fhci, "failed to allocate memory\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
fhci->usb_lld = fhci_create_lld(fhci);
|
|
|
|
if (!fhci->usb_lld) {
|
|
|
|
fhci_err(fhci, "low level driver config failed\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = fhci_usb_init(fhci);
|
|
|
|
if (ret) {
|
|
|
|
fhci_err(fhci, "low level driver initialize failed\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_init(&fhci->lock);
|
|
|
|
|
|
|
|
/* connect the virtual root hub */
|
|
|
|
fhci->vroot_hub->dev_num = 1; /* this field may be needed to fix */
|
|
|
|
fhci->vroot_hub->hub.wHubStatus = 0;
|
|
|
|
fhci->vroot_hub->hub.wHubChange = 0;
|
|
|
|
fhci->vroot_hub->port.wPortStatus = 0;
|
|
|
|
fhci->vroot_hub->port.wPortChange = 0;
|
|
|
|
|
|
|
|
hcd->state = HC_STATE_RUNNING;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* From here on, khubd concurrently accesses the root
|
|
|
|
* hub; drivers will be talking to enumerated devices.
|
|
|
|
* (On restart paths, khubd already knows about the root
|
|
|
|
* hub and could find work as soon as we wrote FLAG_CF.)
|
|
|
|
*
|
|
|
|
* Before this point the HC was idle/ready. After, khubd
|
|
|
|
* and device drivers may start it running.
|
|
|
|
*/
|
|
|
|
fhci_usb_enable(fhci);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
fhci_mem_free(fhci);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fhci_stop(struct usb_hcd *hcd)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci = hcd_to_fhci(hcd);
|
|
|
|
|
|
|
|
fhci_usb_disable_interrupt(fhci->usb_lld);
|
|
|
|
fhci_usb_disable(fhci);
|
|
|
|
|
|
|
|
fhci_usb_free(fhci->usb_lld);
|
|
|
|
fhci->usb_lld = NULL;
|
|
|
|
fhci_mem_free(fhci);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
|
|
|
|
gfp_t mem_flags)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci = hcd_to_fhci(hcd);
|
|
|
|
u32 pipe = urb->pipe;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
int size = 0;
|
|
|
|
struct urb_priv *urb_priv;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
switch (usb_pipetype(pipe)) {
|
|
|
|
case PIPE_CONTROL:
|
|
|
|
/* 1 td fro setup,1 for ack */
|
|
|
|
size = 2;
|
|
|
|
case PIPE_BULK:
|
2011-03-31 03:57:33 +02:00
|
|
|
/* one td for every 4096 bytes(can be up to 8k) */
|
2009-01-10 03:03:21 +01:00
|
|
|
size += urb->transfer_buffer_length / 4096;
|
|
|
|
/* ...add for any remaining bytes... */
|
|
|
|
if ((urb->transfer_buffer_length % 4096) != 0)
|
|
|
|
size++;
|
|
|
|
/* ..and maybe a zero length packet to wrap it up */
|
|
|
|
if (size == 0)
|
|
|
|
size++;
|
|
|
|
else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0
|
|
|
|
&& (urb->transfer_buffer_length
|
|
|
|
% usb_maxpacket(urb->dev, pipe,
|
|
|
|
usb_pipeout(pipe))) != 0)
|
|
|
|
size++;
|
|
|
|
break;
|
|
|
|
case PIPE_ISOCHRONOUS:
|
|
|
|
size = urb->number_of_packets;
|
|
|
|
if (size <= 0)
|
|
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
|
|
urb->iso_frame_desc[i].actual_length = 0;
|
|
|
|
urb->iso_frame_desc[i].status = (u32) (-EXDEV);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PIPE_INTERRUPT:
|
|
|
|
size = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate the private part of the URB */
|
|
|
|
urb_priv = kzalloc(sizeof(*urb_priv), mem_flags);
|
|
|
|
if (!urb_priv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* allocate the private part of the URB */
|
2009-12-30 15:34:37 +01:00
|
|
|
urb_priv->tds = kcalloc(size, sizeof(*urb_priv->tds), mem_flags);
|
2009-01-10 03:03:21 +01:00
|
|
|
if (!urb_priv->tds) {
|
|
|
|
kfree(urb_priv);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irqsave(&fhci->lock, flags);
|
|
|
|
|
|
|
|
ret = usb_hcd_link_urb_to_ep(hcd, urb);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
/* fill the private part of the URB */
|
|
|
|
urb_priv->num_of_tds = size;
|
|
|
|
|
|
|
|
urb->status = -EINPROGRESS;
|
|
|
|
urb->actual_length = 0;
|
|
|
|
urb->error_count = 0;
|
|
|
|
urb->hcpriv = urb_priv;
|
|
|
|
|
|
|
|
fhci_queue_urb(fhci, urb);
|
|
|
|
err:
|
|
|
|
if (ret) {
|
|
|
|
kfree(urb_priv->tds);
|
|
|
|
kfree(urb_priv);
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&fhci->lock, flags);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* dequeue FHCI URB */
|
|
|
|
static int fhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci = hcd_to_fhci(hcd);
|
|
|
|
struct fhci_usb *usb = fhci->usb_lld;
|
|
|
|
int ret = -EINVAL;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (!urb || !urb->dev || !urb->dev->bus)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&fhci->lock, flags);
|
|
|
|
|
|
|
|
ret = usb_hcd_check_unlink_urb(hcd, urb, status);
|
|
|
|
if (ret)
|
|
|
|
goto out2;
|
|
|
|
|
|
|
|
if (usb->port_status != FHCI_PORT_DISABLED) {
|
|
|
|
struct urb_priv *urb_priv;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* flag the urb's data for deletion in some upcoming
|
|
|
|
* SF interrupt's delete list processing
|
|
|
|
*/
|
|
|
|
urb_priv = urb->hcpriv;
|
|
|
|
|
|
|
|
if (!urb_priv || (urb_priv->state == URB_DEL))
|
|
|
|
goto out2;
|
|
|
|
|
|
|
|
urb_priv->state = URB_DEL;
|
|
|
|
|
|
|
|
/* already pending? */
|
|
|
|
urb_priv->ed->state = FHCI_ED_URB_DEL;
|
|
|
|
} else {
|
|
|
|
fhci_urb_complete_free(fhci, urb);
|
|
|
|
}
|
|
|
|
|
|
|
|
out2:
|
|
|
|
spin_unlock_irqrestore(&fhci->lock, flags);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fhci_endpoint_disable(struct usb_hcd *hcd,
|
|
|
|
struct usb_host_endpoint *ep)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci;
|
|
|
|
struct ed *ed;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
fhci = hcd_to_fhci(hcd);
|
|
|
|
spin_lock_irqsave(&fhci->lock, flags);
|
|
|
|
ed = ep->hcpriv;
|
|
|
|
if (ed) {
|
|
|
|
while (ed->td_head != NULL) {
|
|
|
|
struct td *td = fhci_remove_td_from_ed(ed);
|
|
|
|
fhci_urb_complete_free(fhci, td->urb);
|
|
|
|
}
|
|
|
|
fhci_recycle_empty_ed(fhci, ed);
|
|
|
|
ep->hcpriv = NULL;
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&fhci->lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fhci_get_frame_number(struct usb_hcd *hcd)
|
|
|
|
{
|
|
|
|
struct fhci_hcd *fhci = hcd_to_fhci(hcd);
|
|
|
|
|
|
|
|
return get_frame_num(fhci);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct hc_driver fhci_driver = {
|
|
|
|
.description = "fsl,usb-fhci",
|
|
|
|
.product_desc = "FHCI HOST Controller",
|
|
|
|
.hcd_priv_size = sizeof(struct fhci_hcd),
|
|
|
|
|
|
|
|
/* generic hardware linkage */
|
|
|
|
.irq = fhci_irq,
|
|
|
|
.flags = HCD_USB11 | HCD_MEMORY,
|
|
|
|
|
|
|
|
/* basic lifecycle operation */
|
|
|
|
.start = fhci_start,
|
|
|
|
.stop = fhci_stop,
|
|
|
|
|
|
|
|
/* managing i/o requests and associated device resources */
|
|
|
|
.urb_enqueue = fhci_urb_enqueue,
|
|
|
|
.urb_dequeue = fhci_urb_dequeue,
|
|
|
|
.endpoint_disable = fhci_endpoint_disable,
|
|
|
|
|
|
|
|
/* scheduling support */
|
|
|
|
.get_frame_number = fhci_get_frame_number,
|
|
|
|
|
|
|
|
/* root hub support */
|
|
|
|
.hub_status_data = fhci_hub_status_data,
|
|
|
|
.hub_control = fhci_hub_control,
|
|
|
|
};
|
|
|
|
|
2012-11-19 19:21:48 +01:00
|
|
|
static int of_fhci_probe(struct platform_device *ofdev)
|
2009-01-10 03:03:21 +01:00
|
|
|
{
|
|
|
|
struct device *dev = &ofdev->dev;
|
2010-04-14 01:12:29 +02:00
|
|
|
struct device_node *node = dev->of_node;
|
2009-01-10 03:03:21 +01:00
|
|
|
struct usb_hcd *hcd;
|
|
|
|
struct fhci_hcd *fhci;
|
|
|
|
struct resource usb_regs;
|
|
|
|
unsigned long pram_addr;
|
|
|
|
unsigned int usb_irq;
|
|
|
|
const char *sprop;
|
|
|
|
const u32 *iprop;
|
|
|
|
int size;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
if (usb_disabled())
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
sprop = of_get_property(node, "mode", NULL);
|
|
|
|
if (sprop && strcmp(sprop, "host"))
|
|
|
|
return -ENODEV;
|
|
|
|
|
2009-02-16 14:40:11 +01:00
|
|
|
hcd = usb_create_hcd(&fhci_driver, dev, dev_name(dev));
|
2009-01-10 03:03:21 +01:00
|
|
|
if (!hcd) {
|
|
|
|
dev_err(dev, "could not create hcd\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
fhci = hcd_to_fhci(hcd);
|
|
|
|
hcd->self.controller = dev;
|
|
|
|
dev_set_drvdata(dev, hcd);
|
|
|
|
|
|
|
|
iprop = of_get_property(node, "hub-power-budget", &size);
|
|
|
|
if (iprop && size == sizeof(*iprop))
|
|
|
|
hcd->power_budget = *iprop;
|
|
|
|
|
|
|
|
/* FHCI registers. */
|
|
|
|
ret = of_address_to_resource(node, 0, &usb_regs);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "could not get regs\n");
|
|
|
|
goto err_regs;
|
|
|
|
}
|
|
|
|
|
2011-06-09 18:13:32 +02:00
|
|
|
hcd->regs = ioremap(usb_regs.start, resource_size(&usb_regs));
|
2009-01-10 03:03:21 +01:00
|
|
|
if (!hcd->regs) {
|
|
|
|
dev_err(dev, "could not ioremap regs\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err_regs;
|
|
|
|
}
|
|
|
|
fhci->regs = hcd->regs;
|
|
|
|
|
|
|
|
/* Parameter RAM. */
|
|
|
|
iprop = of_get_property(node, "reg", &size);
|
|
|
|
if (!iprop || size < sizeof(*iprop) * 4) {
|
|
|
|
dev_err(dev, "can't get pram offset\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err_pram;
|
|
|
|
}
|
|
|
|
|
2011-08-23 15:23:46 +02:00
|
|
|
pram_addr = cpm_muram_alloc(FHCI_PRAM_SIZE, 64);
|
2009-01-10 03:03:21 +01:00
|
|
|
if (IS_ERR_VALUE(pram_addr)) {
|
|
|
|
dev_err(dev, "failed to allocate usb pram\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err_pram;
|
|
|
|
}
|
2011-08-23 15:23:46 +02:00
|
|
|
|
|
|
|
qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, QE_CR_SUBBLOCK_USB,
|
|
|
|
QE_CR_PROTOCOL_UNSPECIFIED, pram_addr);
|
2009-01-10 03:03:21 +01:00
|
|
|
fhci->pram = cpm_muram_addr(pram_addr);
|
|
|
|
|
|
|
|
/* GPIOs and pins */
|
|
|
|
for (i = 0; i < NUM_GPIOS; i++) {
|
|
|
|
int gpio;
|
|
|
|
enum of_gpio_flags flags;
|
|
|
|
|
|
|
|
gpio = of_get_gpio_flags(node, i, &flags);
|
|
|
|
fhci->gpios[i] = gpio;
|
|
|
|
fhci->alow_gpios[i] = flags & OF_GPIO_ACTIVE_LOW;
|
|
|
|
|
|
|
|
if (!gpio_is_valid(gpio)) {
|
|
|
|
if (i < GPIO_SPEED) {
|
|
|
|
dev_err(dev, "incorrect GPIO%d: %d\n",
|
|
|
|
i, gpio);
|
|
|
|
goto err_gpios;
|
|
|
|
} else {
|
|
|
|
dev_info(dev, "assuming board doesn't have "
|
|
|
|
"%s gpio\n", i == GPIO_SPEED ?
|
|
|
|
"speed" : "power");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-02-16 14:40:11 +01:00
|
|
|
ret = gpio_request(gpio, dev_name(dev));
|
2009-01-10 03:03:21 +01:00
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "failed to request gpio %d", i);
|
|
|
|
goto err_gpios;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i >= GPIO_SPEED) {
|
|
|
|
ret = gpio_direction_output(gpio, 0);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "failed to set gpio %d as "
|
|
|
|
"an output\n", i);
|
|
|
|
i++;
|
|
|
|
goto err_gpios;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 0; j < NUM_PINS; j++) {
|
2010-04-14 01:12:29 +02:00
|
|
|
fhci->pins[j] = qe_pin_request(node, j);
|
2009-01-10 03:03:21 +01:00
|
|
|
if (IS_ERR(fhci->pins[j])) {
|
|
|
|
ret = PTR_ERR(fhci->pins[j]);
|
|
|
|
dev_err(dev, "can't get pin %d: %d\n", j, ret);
|
|
|
|
goto err_pins;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Frame limit timer and its interrupt. */
|
|
|
|
fhci->timer = gtm_get_timer16();
|
|
|
|
if (IS_ERR(fhci->timer)) {
|
|
|
|
ret = PTR_ERR(fhci->timer);
|
|
|
|
dev_err(dev, "failed to request qe timer: %i", ret);
|
|
|
|
goto err_get_timer;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = request_irq(fhci->timer->irq, fhci_frame_limit_timer_irq,
|
2011-09-07 10:10:52 +02:00
|
|
|
0, "qe timer (usb)", hcd);
|
2009-01-10 03:03:21 +01:00
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "failed to request timer irq");
|
|
|
|
goto err_timer_irq;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* USB Host interrupt. */
|
|
|
|
usb_irq = irq_of_parse_and_map(node, 0);
|
|
|
|
if (usb_irq == NO_IRQ) {
|
|
|
|
dev_err(dev, "could not get usb irq\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err_usb_irq;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clocks. */
|
|
|
|
sprop = of_get_property(node, "fsl,fullspeed-clock", NULL);
|
|
|
|
if (sprop) {
|
|
|
|
fhci->fullspeed_clk = qe_clock_source(sprop);
|
|
|
|
if (fhci->fullspeed_clk == QE_CLK_DUMMY) {
|
|
|
|
dev_err(dev, "wrong fullspeed-clock\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err_clocks;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sprop = of_get_property(node, "fsl,lowspeed-clock", NULL);
|
|
|
|
if (sprop) {
|
|
|
|
fhci->lowspeed_clk = qe_clock_source(sprop);
|
|
|
|
if (fhci->lowspeed_clk == QE_CLK_DUMMY) {
|
|
|
|
dev_err(dev, "wrong lowspeed-clock\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err_clocks;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fhci->fullspeed_clk == QE_CLK_NONE &&
|
|
|
|
fhci->lowspeed_clk == QE_CLK_NONE) {
|
|
|
|
dev_err(dev, "no clocks specified\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err_clocks;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_info(dev, "at 0x%p, irq %d\n", hcd->regs, usb_irq);
|
|
|
|
|
|
|
|
fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF);
|
|
|
|
|
|
|
|
/* Start with full-speed, if possible. */
|
|
|
|
if (fhci->fullspeed_clk != QE_CLK_NONE) {
|
|
|
|
fhci_config_transceiver(fhci, FHCI_PORT_FULL);
|
|
|
|
qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK);
|
|
|
|
} else {
|
|
|
|
fhci_config_transceiver(fhci, FHCI_PORT_LOW);
|
|
|
|
qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear and disable any pending interrupts. */
|
2012-06-24 02:24:30 +02:00
|
|
|
out_be16(&fhci->regs->usb_usber, 0xffff);
|
|
|
|
out_be16(&fhci->regs->usb_usbmr, 0);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
2011-09-07 10:10:52 +02:00
|
|
|
ret = usb_add_hcd(hcd, usb_irq, 0);
|
2009-01-10 03:03:21 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto err_add_hcd;
|
|
|
|
|
|
|
|
fhci_dfs_create(fhci);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_add_hcd:
|
|
|
|
err_clocks:
|
|
|
|
irq_dispose_mapping(usb_irq);
|
|
|
|
err_usb_irq:
|
|
|
|
free_irq(fhci->timer->irq, hcd);
|
|
|
|
err_timer_irq:
|
|
|
|
gtm_put_timer16(fhci->timer);
|
|
|
|
err_get_timer:
|
|
|
|
err_pins:
|
|
|
|
while (--j >= 0)
|
|
|
|
qe_pin_free(fhci->pins[j]);
|
|
|
|
err_gpios:
|
|
|
|
while (--i >= 0) {
|
|
|
|
if (gpio_is_valid(fhci->gpios[i]))
|
|
|
|
gpio_free(fhci->gpios[i]);
|
|
|
|
}
|
|
|
|
cpm_muram_free(pram_addr);
|
|
|
|
err_pram:
|
|
|
|
iounmap(hcd->regs);
|
|
|
|
err_regs:
|
|
|
|
usb_put_hcd(hcd);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-11-19 19:26:20 +01:00
|
|
|
static int fhci_remove(struct device *dev)
|
2009-01-10 03:03:21 +01:00
|
|
|
{
|
|
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
|
|
struct fhci_hcd *fhci = hcd_to_fhci(hcd);
|
|
|
|
int i;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
usb_remove_hcd(hcd);
|
|
|
|
free_irq(fhci->timer->irq, hcd);
|
|
|
|
gtm_put_timer16(fhci->timer);
|
|
|
|
cpm_muram_free(cpm_muram_offset(fhci->pram));
|
|
|
|
for (i = 0; i < NUM_GPIOS; i++) {
|
|
|
|
if (!gpio_is_valid(fhci->gpios[i]))
|
|
|
|
continue;
|
|
|
|
gpio_free(fhci->gpios[i]);
|
|
|
|
}
|
|
|
|
for (j = 0; j < NUM_PINS; j++)
|
|
|
|
qe_pin_free(fhci->pins[j]);
|
|
|
|
fhci_dfs_destroy(fhci);
|
|
|
|
usb_put_hcd(hcd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-11-19 19:26:20 +01:00
|
|
|
static int of_fhci_remove(struct platform_device *ofdev)
|
2009-01-10 03:03:21 +01:00
|
|
|
{
|
|
|
|
return fhci_remove(&ofdev->dev);
|
|
|
|
}
|
|
|
|
|
2010-01-10 15:35:03 +01:00
|
|
|
static const struct of_device_id of_fhci_match[] = {
|
2009-01-10 03:03:21 +01:00
|
|
|
{ .compatible = "fsl,mpc8323-qe-usb", },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, of_fhci_match);
|
|
|
|
|
2011-02-23 05:08:34 +01:00
|
|
|
static struct platform_driver of_fhci_driver = {
|
2010-04-14 01:13:02 +02:00
|
|
|
.driver = {
|
|
|
|
.name = "fsl,usb-fhci",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.of_match_table = of_fhci_match,
|
|
|
|
},
|
2009-01-10 03:03:21 +01:00
|
|
|
.probe = of_fhci_probe,
|
2012-11-19 19:21:08 +01:00
|
|
|
.remove = of_fhci_remove,
|
2009-01-10 03:03:21 +01:00
|
|
|
};
|
|
|
|
|
2011-11-27 13:16:27 +01:00
|
|
|
module_platform_driver(of_fhci_driver);
|
2009-01-10 03:03:21 +01:00
|
|
|
|
|
|
|
MODULE_DESCRIPTION("USB Freescale Host Controller Interface Driver");
|
|
|
|
MODULE_AUTHOR("Shlomi Gridish <gridish@freescale.com>, "
|
|
|
|
"Jerry Huang <Chang-Ming.Huang@freescale.com>, "
|
|
|
|
"Anton Vorontsov <avorontsov@ru.mvista.com>");
|
|
|
|
MODULE_LICENSE("GPL");
|