Commit 4808a1c0 authored by Olav Kongas's avatar Olav Kongas Committed by Greg Kroah-Hartman

[PATCH] USB: Add isp116x-hcd USB host controller driver

This patch provides an "isp116x-hcd" driver for Philips'
ISP1160/ISP1161 USB host controllers.

The driver:
 - is relatively small, meant for use on embedded platforms.
 - runs usbtests 1-14 without problems for days.
 - has been in use by 6-7 different people on ARM and PPC platforms,
   running a range of devices including USB hubs.
 - supports suspend/resume of both the platform device and the root hub;
   supports remote wakeup of the root hub (but NOT the platform device)
   by USB devices.
 - does NOT support ISO transfers (nobody has asked for them).
 - is PIO-only.
Signed-off-by: default avatarOlav Kongas <ok@artecdesign.ee>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 313980c9
...@@ -9,6 +9,7 @@ obj-$(CONFIG_USB) += core/ ...@@ -9,6 +9,7 @@ obj-$(CONFIG_USB) += core/
obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_USB_MON) += mon/
obj-$(CONFIG_USB_EHCI_HCD) += host/ obj-$(CONFIG_USB_EHCI_HCD) += host/
obj-$(CONFIG_USB_ISP116X_HCD) += host/
obj-$(CONFIG_USB_OHCI_HCD) += host/ obj-$(CONFIG_USB_OHCI_HCD) += host/
obj-$(CONFIG_USB_UHCI_HCD) += host/ obj-$(CONFIG_USB_UHCI_HCD) += host/
obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_SL811_HCD) += host/
......
...@@ -49,6 +49,19 @@ config USB_EHCI_ROOT_HUB_TT ...@@ -49,6 +49,19 @@ config USB_EHCI_ROOT_HUB_TT
This supports the EHCI implementation from TransDimension Inc. This supports the EHCI implementation from TransDimension Inc.
config USB_ISP116X_HCD
tristate "ISP116X HCD support"
depends on USB
default N
---help---
The ISP1160 and ISP1161 chips are USB host controllers. Enable this
option if your board has this chip. If unsure, say N.
This driver does not support isochronous transfers.
To compile this driver as a module, choose M here: the
module will be called isp116x-hcd.
config USB_OHCI_HCD config USB_OHCI_HCD
tristate "OHCI HCD support" tristate "OHCI HCD support"
depends on USB && USB_ARCH_HAS_OHCI depends on USB && USB_ARCH_HAS_OHCI
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
# #
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o
obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o
obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o
......
/*
* ISP116x HCD (Host Controller Driver) for USB.
*
* Derived from the SL811 HCD, rewritten for ISP116x.
* Copyright (C) 2005 Olav Kongas <ok@artecdesign.ee>
*
* Portions:
* Copyright (C) 2004 Psion Teklogix (for NetBook PRO)
* Copyright (C) 2004 David Brownell
*
* Periodic scheduling is based on Roman's OHCI code
* Copyright (C) 1999 Roman Weissgaerber
*
*/
/*
* The driver basically works. A number of people have used it with a range
* of devices.
*
*The driver passes all usbtests 1-14.
*
* Suspending/resuming of root hub via sysfs works. Remote wakeup works too.
* And suspending/resuming of platform device works too. Suspend/resume
* via HCD operations vector is not implemented.
*
* Iso transfer support is not implemented. Adding this would include
* implementing recovery from the failure to service the processed ITL
* fifo ram in time, which will involve chip reset.
*
* TODO:
+ More testing of suspend/resume.
*/
/*
ISP116x chips require certain delays between accesses to its
registers. The following timing options exist.
1. Configure your memory controller (the best)
2. Implement platform-specific delay function possibly
combined with configuring the memory controller; see
include/linux/usb-isp116x.h for more info. Some broken
memory controllers line LH7A400 SMC need this. Also,
uncomment for that to work the following
USE_PLATFORM_DELAY macro.
3. Use ndelay (easiest, poorest). For that, uncomment
the following USE_NDELAY macro.
*/
#define USE_PLATFORM_DELAY
//#define USE_NDELAY
//#define DEBUG
//#define VERBOSE
/* Transfer descriptors. See dump_ptd() for printout format */
//#define PTD_TRACE
/* enqueuing/finishing log of urbs */
//#define URB_TRACE
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <linux/usb_isp116x.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/byteorder.h>
#ifndef DEBUG
# define STUB_DEBUG_FILE
#endif
#include "../core/hcd.h"
#include "isp116x.h"
#define DRIVER_VERSION "08 Apr 2005"
#define DRIVER_DESC "ISP116x USB Host Controller Driver"
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
static const char hcd_name[] = "isp116x-hcd";
/*-----------------------------------------------------------------*/
/*
Write len bytes to fifo, pad till 32-bit boundary
*/
static void write_ptddata_to_fifo(struct isp116x *isp116x, void *buf, int len)
{
u8 *dp = (u8 *) buf;
u16 *dp2 = (u16 *) buf;
u16 w;
int quot = len % 4;
if ((unsigned long)dp2 & 1) {
/* not aligned */
for (; len > 1; len -= 2) {
w = *dp++;
w |= *dp++ << 8;
isp116x_raw_write_data16(isp116x, w);
}
if (len)
isp116x_write_data16(isp116x, (u16) * dp);
} else {
/* aligned */
for (; len > 1; len -= 2)
isp116x_raw_write_data16(isp116x, *dp2++);
if (len)
isp116x_write_data16(isp116x, 0xff & *((u8 *) dp2));
}
if (quot == 1 || quot == 2)
isp116x_raw_write_data16(isp116x, 0);
}
/*
Read len bytes from fifo and then read till 32-bit boundary.
*/
static void read_ptddata_from_fifo(struct isp116x *isp116x, void *buf, int len)
{
u8 *dp = (u8 *) buf;
u16 *dp2 = (u16 *) buf;
u16 w;
int quot = len % 4;
if ((unsigned long)dp2 & 1) {
/* not aligned */
for (; len > 1; len -= 2) {
w = isp116x_raw_read_data16(isp116x);
*dp++ = w & 0xff;
*dp++ = (w >> 8) & 0xff;
}
if (len)
*dp = 0xff & isp116x_read_data16(isp116x);
} else {
/* aligned */
for (; len > 1; len -= 2)
*dp2++ = isp116x_raw_read_data16(isp116x);
if (len)
*(u8 *) dp2 = 0xff & isp116x_read_data16(isp116x);
}
if (quot == 1 || quot == 2)
isp116x_raw_read_data16(isp116x);
}
/*
Write ptd's and data for scheduled transfers into
the fifo ram. Fifo must be empty and ready.
*/
static void pack_fifo(struct isp116x *isp116x)
{
struct isp116x_ep *ep;
struct ptd *ptd;
int buflen = isp116x->atl_last_dir == PTD_DIR_IN
? isp116x->atl_bufshrt : isp116x->atl_buflen;
int ptd_count = 0;
isp116x_write_reg16(isp116x, HCuPINT, HCuPINT_AIIEOT);
isp116x_write_reg16(isp116x, HCXFERCTR, buflen);
isp116x_write_addr(isp116x, HCATLPORT | ISP116x_WRITE_OFFSET);
for (ep = isp116x->atl_active; ep; ep = ep->active) {
++ptd_count;
ptd = &ep->ptd;
dump_ptd(ptd);
dump_ptd_out_data(ptd, ep->data);
isp116x_write_data16(isp116x, ptd->count);
isp116x_write_data16(isp116x, ptd->mps);
isp116x_write_data16(isp116x, ptd->len);
isp116x_write_data16(isp116x, ptd->faddr);
buflen -= sizeof(struct ptd);
/* Skip writing data for last IN PTD */
if (ep->active || (isp116x->atl_last_dir != PTD_DIR_IN)) {
write_ptddata_to_fifo(isp116x, ep->data, ep->length);
buflen -= ALIGN(ep->length, 4);
}
}
BUG_ON(buflen);
}
/*
Read the processed ptd's and data from fifo ram back to
URBs' buffers. Fifo must be full and done
*/
static void unpack_fifo(struct isp116x *isp116x)
{
struct isp116x_ep *ep;
struct ptd *ptd;
int buflen = isp116x->atl_last_dir == PTD_DIR_IN
? isp116x->atl_buflen : isp116x->atl_bufshrt;
isp116x_write_reg16(isp116x, HCuPINT, HCuPINT_AIIEOT);
isp116x_write_reg16(isp116x, HCXFERCTR, buflen);
isp116x_write_addr(isp116x, HCATLPORT);
for (ep = isp116x->atl_active; ep; ep = ep->active) {
ptd = &ep->ptd;
ptd->count = isp116x_read_data16(isp116x);
ptd->mps = isp116x_read_data16(isp116x);
ptd->len = isp116x_read_data16(isp116x);
ptd->faddr = isp116x_read_data16(isp116x);
buflen -= sizeof(struct ptd);
/* Skip reading data for last Setup or Out PTD */
if (ep->active || (isp116x->atl_last_dir == PTD_DIR_IN)) {
read_ptddata_from_fifo(isp116x, ep->data, ep->length);
buflen -= ALIGN(ep->length, 4);
}
dump_ptd(ptd);
dump_ptd_in_data(ptd, ep->data);
}
BUG_ON(buflen);
}
/*---------------------------------------------------------------*/
/*
Set up PTD's.
*/
static void preproc_atl_queue(struct isp116x *isp116x)
{
struct isp116x_ep *ep;
struct urb *urb;
struct ptd *ptd;
u16 toggle, dir, len;
for (ep = isp116x->atl_active; ep; ep = ep->active) {
BUG_ON(list_empty(&ep->hep->urb_list));
urb = container_of(ep->hep->urb_list.next,
struct urb, urb_list);
ptd = &ep->ptd;
len = ep->length;
spin_lock(&urb->lock);
ep->data = (unsigned char *)urb->transfer_buffer
+ urb->actual_length;
switch (ep->nextpid) {
case USB_PID_IN:
toggle = usb_gettoggle(urb->dev, ep->epnum, 0);
dir = PTD_DIR_IN;
break;
case USB_PID_OUT:
toggle = usb_gettoggle(urb->dev, ep->epnum, 1);
dir = PTD_DIR_OUT;
break;
case USB_PID_SETUP:
toggle = 0;
dir = PTD_DIR_SETUP;
len = sizeof(struct usb_ctrlrequest);
ep->data = urb->setup_packet;
break;
case USB_PID_ACK:
toggle = 1;
len = 0;
dir = (urb->transfer_buffer_length
&& usb_pipein(urb->pipe))
? PTD_DIR_OUT : PTD_DIR_IN;
break;
default:
/* To please gcc */
toggle = dir = 0;
ERR("%s %d: ep->nextpid %d\n", __func__, __LINE__,
ep->nextpid);
BUG_ON(1);
}
ptd->count = PTD_CC_MSK | PTD_ACTIVE_MSK | PTD_TOGGLE(toggle);
ptd->mps = PTD_MPS(ep->maxpacket)
| PTD_SPD(urb->dev->speed == USB_SPEED_LOW)
| PTD_EP(ep->epnum);
ptd->len = PTD_LEN(len) | PTD_DIR(dir);
ptd->faddr = PTD_FA(usb_pipedevice(urb->pipe));
spin_unlock(&urb->lock);
if (!ep->active) {
ptd->mps |= PTD_LAST_MSK;
isp116x->atl_last_dir = dir;
}
isp116x->atl_bufshrt = sizeof(struct ptd) + isp116x->atl_buflen;
isp116x->atl_buflen = isp116x->atl_bufshrt + ALIGN(len, 4);
}
}
/*
Analyze transfer results, handle partial transfers and errors
*/
static void postproc_atl_queue(struct isp116x *isp116x)
{
struct isp116x_ep *ep;
struct urb *urb;
struct usb_device *udev;
struct ptd *ptd;
int short_not_ok;
u8 cc;
for (ep = isp116x->atl_active; ep; ep = ep->active) {
BUG_ON(list_empty(&ep->hep->urb_list));
urb =
container_of(ep->hep->urb_list.next, struct urb, urb_list);
udev = urb->dev;
ptd = &ep->ptd;
cc = PTD_GET_CC(ptd);
spin_lock(&urb->lock);
short_not_ok = 1;
/* Data underrun is special. For allowed underrun
we clear the error and continue as normal. For
forbidden underrun we finish the DATA stage
immediately while for control transfer,
we do a STATUS stage. */
if (cc == TD_DATAUNDERRUN) {
if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) {
DBG("Allowed data underrun\n");
cc = TD_CC_NOERROR;
short_not_ok = 0;
} else {
ep->error_count = 1;
if (usb_pipecontrol(urb->pipe))
ep->nextpid = USB_PID_ACK;
else
usb_settoggle(udev, ep->epnum,
ep->nextpid ==
USB_PID_OUT,
PTD_GET_TOGGLE(ptd) ^ 1);
urb->status = cc_to_error[TD_DATAUNDERRUN];
spin_unlock(&urb->lock);
continue;
}
}
/* Keep underrun error through the STATUS stage */
if (urb->status == cc_to_error[TD_DATAUNDERRUN])
cc = TD_DATAUNDERRUN;
if (cc != TD_CC_NOERROR && cc != TD_NOTACCESSED
&& (++ep->error_count >= 3 || cc == TD_CC_STALL
|| cc == TD_DATAOVERRUN)) {
if (urb->status == -EINPROGRESS)
urb->status = cc_to_error[cc];
if (ep->nextpid == USB_PID_ACK)
ep->nextpid = 0;
spin_unlock(&urb->lock);
continue;
}
/* According to usb spec, zero-length Int transfer signals
finishing of the urb. Hey, does this apply only
for IN endpoints? */
if (usb_pipeint(urb->pipe) && !PTD_GET_LEN(ptd)) {
if (urb->status == -EINPROGRESS)
urb->status = 0;
spin_unlock(&urb->lock);
continue;
}
/* Relax after previously failed, but later succeeded
or correctly NAK'ed retransmission attempt */
if (ep->error_count
&& (cc == TD_CC_NOERROR || cc == TD_NOTACCESSED))
ep->error_count = 0;
/* Take into account idiosyncracies of the isp116x chip
regarding toggle bit for failed transfers */
if (ep->nextpid == USB_PID_OUT)
usb_settoggle(udev, ep->epnum, 1, PTD_GET_TOGGLE(ptd)
^ (ep->error_count > 0));
else if (ep->nextpid == USB_PID_IN)
usb_settoggle(udev, ep->epnum, 0, PTD_GET_TOGGLE(ptd)
^ (ep->error_count > 0));
switch (ep->nextpid) {
case USB_PID_IN:
case USB_PID_OUT:
urb->actual_length += PTD_GET_COUNT(ptd);
if (PTD_GET_ACTIVE(ptd)
|| (cc != TD_CC_NOERROR && cc < 0x0E))
break;
if (urb->transfer_buffer_length != urb->actual_length) {
if (short_not_ok)
break;
} else {
if (urb->transfer_flags & URB_ZERO_PACKET
&& ep->nextpid == USB_PID_OUT
&& !(PTD_GET_COUNT(ptd) % ep->maxpacket)) {
DBG("Zero packet requested\n");
break;
}
}
/* All data for this URB is transferred, let's finish */
if (usb_pipecontrol(urb->pipe))
ep->nextpid = USB_PID_ACK;
else if (urb->status == -EINPROGRESS)
urb->status = 0;
break;
case USB_PID_SETUP:
if (PTD_GET_ACTIVE(ptd)
|| (cc != TD_CC_NOERROR && cc < 0x0E))
break;
if (urb->transfer_buffer_length == urb->actual_length)
ep->nextpid = USB_PID_ACK;
else if (usb_pipeout(urb->pipe)) {
usb_settoggle(udev, 0, 1, 1);
ep->nextpid = USB_PID_OUT;
} else {
usb_settoggle(udev, 0, 0, 1);
ep->nextpid = USB_PID_IN;
}
break;
case USB_PID_ACK:
if (PTD_GET_ACTIVE(ptd)
|| (cc != TD_CC_NOERROR && cc < 0x0E))
break;
if (urb->status == -EINPROGRESS)
urb->status = 0;
ep->nextpid = 0;
break;
default:
BUG_ON(1);
}
spin_unlock(&urb->lock);
}
}
/*
Take done or failed requests out of schedule. Give back
processed urbs.
*/
static void finish_request(struct isp116x *isp116x, struct isp116x_ep *ep,
struct urb *urb, struct pt_regs *regs)
__releases(isp116x->lock) __acquires(isp116x->lock)
{
unsigned i;
urb->hcpriv = NULL;
ep->error_count = 0;
if (usb_pipecontrol(urb->pipe))
ep->nextpid = USB_PID_SETUP;
urb_dbg(urb, "Finish");
spin_unlock(&isp116x->lock);
usb_hcd_giveback_urb(isp116x_to_hcd(isp116x), urb, regs);
spin_lock(&isp116x->lock);
/* take idle endpoints out of the schedule */
if (!list_empty(&ep->hep->urb_list))
return;
/* async deschedule */
if (!list_empty(&ep->schedule)) {
list_del_init(&ep->schedule);
return;
}
/* periodic deschedule */
DBG("deschedule qh%d/%p branch %d\n", ep->period, ep, ep->branch);
for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) {
struct isp116x_ep *temp;
struct isp116x_ep **prev = &isp116x->periodic[i];
while (*prev && ((temp = *prev) != ep))
prev = &temp->next;
if (*prev)
*prev = ep->next;
isp116x->load[i] -= ep->load;
}
ep->branch = PERIODIC_SIZE;
isp116x_to_hcd(isp116x)->self.bandwidth_allocated -=
ep->load / ep->period;
/* switch irq type? */
if (!--isp116x->periodic_count) {
isp116x->irqenb &= ~HCuPINT_SOF;
isp116x->irqenb |= HCuPINT_ATL;
}
}
/*
Scan transfer lists, schedule transfers, send data off
to chip.
*/
static void start_atl_transfers(struct isp116x *isp116x)
{
struct isp116x_ep *last_ep = NULL, *ep;
struct urb *urb;
u16 load = 0;
int len, index, speed, byte_time;
if (atomic_read(&isp116x->atl_finishing))
return;
if (!HC_IS_RUNNING(isp116x_to_hcd(isp116x)->state))
return;
/* FIFO not empty? */
if (isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_FULL)
return;
isp116x->atl_active = NULL;
isp116x->atl_buflen = isp116x->atl_bufshrt = 0;
/* Schedule int transfers */
if (isp116x->periodic_count) {
isp116x->fmindex = index =
(isp116x->fmindex + 1) & (PERIODIC_SIZE - 1);
if ((load = isp116x->load[index])) {
/* Bring all int transfers for this frame
into the active queue */
isp116x->atl_active = last_ep =
isp116x->periodic[index];
while (last_ep->next)
last_ep = (last_ep->active = last_ep->next);
last_ep->active = NULL;
}
}
/* Schedule control/bulk transfers */
list_for_each_entry(ep, &isp116x->async, schedule) {
urb = container_of(ep->hep->urb_list.next,
struct urb, urb_list);
speed = urb->dev->speed;
byte_time = speed == USB_SPEED_LOW
? BYTE_TIME_LOWSPEED : BYTE_TIME_FULLSPEED;
if (ep->nextpid == USB_PID_SETUP) {
len = sizeof(struct usb_ctrlrequest);
} else if (ep->nextpid == USB_PID_ACK) {
len = 0;
} else {
/* Find current free length ... */
len = (MAX_LOAD_LIMIT - load) / byte_time;
/* ... then limit it to configured max size ... */
len = min(len, speed == USB_SPEED_LOW ?
MAX_TRANSFER_SIZE_LOWSPEED :
MAX_TRANSFER_SIZE_FULLSPEED);
/* ... and finally cut to the multiple of MaxPacketSize,
or to the real length if there's enough room. */
if (len <
(urb->transfer_buffer_length -
urb->actual_length)) {
len -= len % ep->maxpacket;
if (!len)
continue;
} else
len = urb->transfer_buffer_length -
urb->actual_length;
BUG_ON(len < 0);
}
load += len * byte_time;
if (load > MAX_LOAD_LIMIT)
break;
ep->active = NULL;
ep->length = len;
if (last_ep)
last_ep->active = ep;
else
isp116x->atl_active = ep;
last_ep = ep;
}
/* Avoid starving of endpoints */
if ((&isp116x->async)->next != (&isp116x->async)->prev)
list_move(&isp116x->async, (&isp116x->async)->next);
if (isp116x->atl_active) {
preproc_atl_queue(isp116x);
pack_fifo(isp116x);
}
}
/*
Finish the processed transfers
*/
static void finish_atl_transfers(struct isp116x *isp116x, struct pt_regs *regs)
{
struct isp116x_ep *ep;
struct urb *urb;
if (!isp116x->atl_active)
return;
/* Fifo not ready? */
if (!(isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_DONE))
return;
atomic_inc(&isp116x->atl_finishing);
unpack_fifo(isp116x);
postproc_atl_queue(isp116x);
for (ep = isp116x->atl_active; ep; ep = ep->active) {
urb =
container_of(ep->hep->urb_list.next, struct urb, urb_list);
/* USB_PID_ACK check here avoids finishing of
control transfers, for which TD_DATAUNDERRUN
occured, while URB_SHORT_NOT_OK was set */
if (urb && urb->status != -EINPROGRESS
&& ep->nextpid != USB_PID_ACK)
finish_request(isp116x, ep, urb, regs);
}
atomic_dec(&isp116x->atl_finishing);
}
static irqreturn_t isp116x_irq(struct usb_hcd *hcd, struct pt_regs *regs)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
u16 irqstat;
irqreturn_t ret = IRQ_NONE;
spin_lock(&isp116x->lock);
isp116x_write_reg16(isp116x, HCuPINTENB, 0);
irqstat = isp116x_read_reg16(isp116x, HCuPINT);
isp116x_write_reg16(isp116x, HCuPINT, irqstat);
if (irqstat & (HCuPINT_ATL | HCuPINT_SOF)) {
ret = IRQ_HANDLED;
finish_atl_transfers(isp116x, regs);
}
if (irqstat & HCuPINT_OPR) {
u32 intstat = isp116x_read_reg32(isp116x, HCINTSTAT);
isp116x_write_reg32(isp116x, HCINTSTAT, intstat);
if (intstat & HCINT_UE) {
ERR("Unrecoverable error\n");
/* What should we do here? Reset? */
}
if (intstat & HCINT_RHSC) {
isp116x->rhstatus =
isp116x_read_reg32(isp116x, HCRHSTATUS);
isp116x->rhport[0] =
isp116x_read_reg32(isp116x, HCRHPORT1);
isp116x->rhport[1] =
isp116x_read_reg32(isp116x, HCRHPORT2);
}
if (intstat & HCINT_RD) {
DBG("---- remote wakeup\n");
schedule_work(&isp116x->rh_resume);
ret = IRQ_HANDLED;
}
irqstat &= ~HCuPINT_OPR;
ret = IRQ_HANDLED;
}
if (irqstat & (HCuPINT_ATL | HCuPINT_SOF)) {
start_atl_transfers(isp116x);
}
isp116x_write_reg16(isp116x, HCuPINTENB, isp116x->irqenb);
spin_unlock(&isp116x->lock);
return ret;
}
/*-----------------------------------------------------------------*/
/* usb 1.1 says max 90% of a frame is available for periodic transfers.
* this driver doesn't promise that much since it's got to handle an
* IRQ per packet; irq handling latencies also use up that time.
*/
/* out of 1000 us */
#define MAX_PERIODIC_LOAD 600
static int balance(struct isp116x *isp116x, u16 period, u16 load)
{
int i, branch = -ENOSPC;
/* search for the least loaded schedule branch of that period
which has enough bandwidth left unreserved. */
for (i = 0; i < period; i++) {
if (branch < 0 || isp116x->load[branch] > isp116x->load[i]) {
int j;
for (j = i; j < PERIODIC_SIZE; j += period) {
if ((isp116x->load[j] + load)
> MAX_PERIODIC_LOAD)
break;
}
if (j < PERIODIC_SIZE)
continue;
branch = i;
}
}
return branch;
}
/* NB! ALL the code above this point runs with isp116x->lock
held, irqs off
*/
/*-----------------------------------------------------------------*/
static int isp116x_urb_enqueue(struct usb_hcd *hcd,
struct usb_host_endpoint *hep, struct urb *urb,
int mem_flags)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
struct usb_device *udev = urb->dev;
unsigned int pipe = urb->pipe;
int is_out = !usb_pipein(pipe);
int type = usb_pipetype(pipe);
int epnum = usb_pipeendpoint(pipe);
struct isp116x_ep *ep = NULL;
unsigned long flags;
int i;
int ret = 0;
urb_dbg(urb, "Enqueue");
if (type == PIPE_ISOCHRONOUS) {
ERR("Isochronous transfers not supported\n");
urb_dbg(urb, "Refused to enqueue");
return -ENXIO;
}
/* avoid all allocations within spinlocks: request or endpoint */
if (!hep->hcpriv) {
ep = kcalloc(1, sizeof *ep, (__force unsigned)mem_flags);
if (!ep)
return -ENOMEM;
}
spin_lock_irqsave(&isp116x->lock, flags);
if (!HC_IS_RUNNING(hcd->state)) {
ret = -ENODEV;
goto fail;
}
if (hep->hcpriv)
ep = hep->hcpriv;
else {
INIT_LIST_HEAD(&ep->schedule);
ep->udev = usb_get_dev(udev);
ep->epnum = epnum;
ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out);
usb_settoggle(udev, epnum, is_out, 0);
if (type == PIPE_CONTROL) {
ep->nextpid = USB_PID_SETUP;
} else if (is_out) {
ep->nextpid = USB_PID_OUT;
} else {
ep->nextpid = USB_PID_IN;
}
if (urb->interval) {
/*
With INT URBs submitted, the driver works with SOF
interrupt enabled and ATL interrupt disabled. After
the PTDs are written to fifo ram, the chip starts
fifo processing and usb transfers after the next
SOF and continues until the transfers are finished
(succeeded or failed) or the frame ends. Therefore,
the transfers occur only in every second frame,
while fifo reading/writing and data processing
occur in every other second frame. */
if (urb->interval < 2)
urb->interval = 2;
if (urb->interval > 2 * PERIODIC_SIZE)
urb->interval = 2 * PERIODIC_SIZE;
ep->period = urb->interval >> 1;
ep->branch = PERIODIC_SIZE;
ep->load = usb_calc_bus_time(udev->speed,
!is_out,
(type == PIPE_ISOCHRONOUS),
usb_maxpacket(udev, pipe,
is_out)) /
1000;
}
hep->hcpriv = ep;
ep->hep = hep;
}
/* maybe put endpoint into schedule */
switch (type) {
case PIPE_CONTROL:
case PIPE_BULK:
if (list_empty(&ep->schedule))
list_add_tail(&ep->schedule, &isp116x->async);
break;
case PIPE_INTERRUPT:
urb->interval = ep->period;
ep->length = min((int)ep->maxpacket,
urb->transfer_buffer_length);
/* urb submitted for already existing endpoint */
if (ep->branch < PERIODIC_SIZE)
break;
ret = ep->branch = balance(isp116x, ep->period, ep->load);
if (ret < 0)
goto fail;
ret = 0;
urb->start_frame = (isp116x->fmindex & (PERIODIC_SIZE - 1))
+ ep->branch;
/* sort each schedule branch by period (slow before fast)
to share the faster parts of the tree without needing
dummy/placeholder nodes */
DBG("schedule qh%d/%p branch %d\n", ep->period, ep, ep->branch);
for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) {
struct isp116x_ep **prev = &isp116x->periodic[i];
struct isp116x_ep *here = *prev;
while (here && ep != here) {
if (ep->period > here->period)
break;
prev = &here->next;
here = *prev;
}
if (ep != here) {
ep->next = here;
*prev = ep;
}
isp116x->load[i] += ep->load;
}
hcd->self.bandwidth_allocated += ep->load / ep->period;
/* switch over to SOFint */
if (!isp116x->periodic_count++) {
isp116x->irqenb &= ~HCuPINT_ATL;
isp116x->irqenb |= HCuPINT_SOF;
isp116x_write_reg16(isp116x, HCuPINTENB,
isp116x->irqenb);
}
}
/* in case of unlink-during-submit */
spin_lock(&urb->lock);
if (urb->status != -EINPROGRESS) {
spin_unlock(&urb->lock);
finish_request(isp116x, ep, urb, NULL);
ret = 0;
goto fail;
}
urb->hcpriv = hep;
spin_unlock(&urb->lock);
start_atl_transfers(isp116x);
fail:
spin_unlock_irqrestore(&isp116x->lock, flags);
return ret;
}
/*
Dequeue URBs.
*/
static int isp116x_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
struct usb_host_endpoint *hep;
struct isp116x_ep *ep, *ep_act;
unsigned long flags;
spin_lock_irqsave(&isp116x->lock, flags);
hep = urb->hcpriv;
/* URB already unlinked (or never linked)? */
if (!hep) {
spin_unlock_irqrestore(&isp116x->lock, flags);
return 0;
}
ep = hep->hcpriv;
WARN_ON(hep != ep->hep);
/* In front of queue? */
if (ep->hep->urb_list.next == &urb->urb_list)
/* active? */
for (ep_act = isp116x->atl_active; ep_act;
ep_act = ep_act->active)
if (ep_act == ep) {
VDBG("dequeue, urb %p active; wait for irq\n",
urb);
urb = NULL;
break;
}
if (urb)
finish_request(isp116x, ep, urb, NULL);
spin_unlock_irqrestore(&isp116x->lock, flags);
return 0;
}
static void isp116x_endpoint_disable(struct usb_hcd *hcd,
struct usb_host_endpoint *hep)
{
int i;
struct isp116x_ep *ep = hep->hcpriv;;
if (!ep)
return;
/* assume we'd just wait for the irq */
for (i = 0; i < 100 && !list_empty(&hep->urb_list); i++)
msleep(3);
if (!list_empty(&hep->urb_list))
WARN("ep %p not empty?\n", ep);
usb_put_dev(ep->udev);
kfree(ep);
hep->hcpriv = NULL;
}
static int isp116x_get_frame(struct usb_hcd *hcd)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
u32 fmnum;
unsigned long flags;
spin_lock_irqsave(&isp116x->lock, flags);
fmnum = isp116x_read_reg32(isp116x, HCFMNUM);
spin_unlock_irqrestore(&isp116x->lock, flags);
return (int)fmnum;
}
/*----------------------------------------------------------------*/
/*
Adapted from ohci-hub.c. Currently we don't support autosuspend.
*/
static int isp116x_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
int ports, i, changed = 0;
if (!HC_IS_RUNNING(hcd->state))
return -ESHUTDOWN;
ports = isp116x->rhdesca & RH_A_NDP;
/* init status */
if (isp116x->rhstatus & (RH_HS_LPSC | RH_HS_OCIC))
buf[0] = changed = 1;
else
buf[0] = 0;
for (i = 0; i < ports; i++) {
u32 status = isp116x->rhport[i];
if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC
| RH_PS_OCIC | RH_PS_PRSC)) {
changed = 1;
buf[0] |= 1 << (i + 1);
continue;
}
}
return changed;
}
static void isp116x_hub_descriptor(struct isp116x *isp116x,
struct usb_hub_descriptor *desc)
{
u32 reg = isp116x->rhdesca;
desc->bDescriptorType = 0x29;
desc->bDescLength = 9;
desc->bHubContrCurrent = 0;
desc->bNbrPorts = (u8) (reg & 0x3);
/* Power switching, device type, overcurrent. */
desc->wHubCharacteristics =
(__force __u16) cpu_to_le16((u16) ((reg >> 8) & 0x1f));
desc->bPwrOn2PwrGood = (u8) ((reg >> 24) & 0xff);
/* two bitmaps: ports removable, and legacy PortPwrCtrlMask */
desc->bitmap[0] = desc->bNbrPorts == 1 ? 1 << 1 : 3 << 1;
desc->bitmap[1] = ~0;
}
/* Perform reset of a given port.
It would be great to just start the reset and let the
USB core to clear the reset in due time. However,
root hub ports should be reset for at least 50 ms, while
our chip stays in reset for about 10 ms. I.e., we must
repeatedly reset it ourself here.
*/
static inline void root_port_reset(struct isp116x *isp116x, unsigned port)
{
u32 tmp;
unsigned long flags, t;
/* Root hub reset should be 50 ms, but some devices
want it even longer. */
t = jiffies + msecs_to_jiffies(100);
while (time_before(jiffies, t)) {
spin_lock_irqsave(&isp116x->lock, flags);
/* spin until any current reset finishes */
for (;;) {
tmp = isp116x_read_reg32(isp116x, port ?
HCRHPORT2 : HCRHPORT1);
if (!(tmp & RH_PS_PRS))
break;
udelay(500);
}
/* Don't reset a disconnected port */
if (!(tmp & RH_PS_CCS)) {
spin_unlock_irqrestore(&isp116x->lock, flags);
break;
}
/* Reset lasts 10ms (claims datasheet) */
isp116x_write_reg32(isp116x, port ? HCRHPORT2 :
HCRHPORT1, (RH_PS_PRS));
spin_unlock_irqrestore(&isp116x->lock, flags);
msleep(10);
}
}
/* Adapted from ohci-hub.c */
static int isp116x_hub_control(struct usb_hcd *hcd,
u16 typeReq,
u16 wValue, u16 wIndex, char *buf, u16 wLength)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
int ret = 0;
unsigned long flags;
int ports = isp116x->rhdesca & RH_A_NDP;
u32 tmp = 0;
switch (typeReq) {
case ClearHubFeature:
DBG("ClearHubFeature: ");
switch (wValue) {
case C_HUB_OVER_CURRENT:
DBG("C_HUB_OVER_CURRENT\n");
spin_lock_irqsave(&isp116x->lock, flags);
isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_OCIC);
spin_unlock_irqrestore(&isp116x->lock, flags);
case C_HUB_LOCAL_POWER:
DBG("C_HUB_LOCAL_POWER\n");
break;
default:
goto error;
}
break;
case SetHubFeature:
DBG("SetHubFeature: ");
switch (wValue) {
case C_HUB_OVER_CURRENT:
case C_HUB_LOCAL_POWER:
DBG("C_HUB_OVER_CURRENT or C_HUB_LOCAL_POWER\n");
break;
default:
goto error;
}
break;
case GetHubDescriptor:
DBG("GetHubDescriptor\n");
isp116x_hub_descriptor(isp116x,
(struct usb_hub_descriptor *)buf);
break;
case GetHubStatus:
DBG("GetHubStatus\n");
*(__le32 *) buf = cpu_to_le32(0);
break;
case GetPortStatus:
DBG("GetPortStatus\n");
if (!wIndex || wIndex > ports)
goto error;
tmp = isp116x->rhport[--wIndex];
*(__le32 *) buf = cpu_to_le32(tmp);
DBG("GetPortStatus: port[%d] %08x\n", wIndex + 1, tmp);
break;
case ClearPortFeature:
DBG("ClearPortFeature: ");
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
switch (wValue) {
case USB_PORT_FEAT_ENABLE:
DBG("USB_PORT_FEAT_ENABLE\n");
tmp = RH_PS_CCS;
break;
case USB_PORT_FEAT_C_ENABLE:
DBG("USB_PORT_FEAT_C_ENABLE\n");
tmp = RH_PS_PESC;
break;
case USB_PORT_FEAT_SUSPEND:
DBG("USB_PORT_FEAT_SUSPEND\n");
tmp = RH_PS_POCI;
break;
case USB_PORT_FEAT_C_SUSPEND:
DBG("USB_PORT_FEAT_C_SUSPEND\n");
tmp = RH_PS_PSSC;
break;
case USB_PORT_FEAT_POWER:
DBG("USB_PORT_FEAT_POWER\n");
tmp = RH_PS_LSDA;
break;
case USB_PORT_FEAT_C_CONNECTION:
DBG("USB_PORT_FEAT_C_CONNECTION\n");
tmp = RH_PS_CSC;
break;
case USB_PORT_FEAT_C_OVER_CURRENT:
DBG("USB_PORT_FEAT_C_OVER_CURRENT\n");
tmp = RH_PS_OCIC;
break;
case USB_PORT_FEAT_C_RESET:
DBG("USB_PORT_FEAT_C_RESET\n");
tmp = RH_PS_PRSC;
break;
default:
goto error;
}
spin_lock_irqsave(&isp116x->lock, flags);
isp116x_write_reg32(isp116x, wIndex
? HCRHPORT2 : HCRHPORT1, tmp);
isp116x->rhport[wIndex] =
isp116x_read_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1);
spin_unlock_irqrestore(&isp116x->lock, flags);
break;
case SetPortFeature:
DBG("SetPortFeature: ");
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
DBG("USB_PORT_FEAT_SUSPEND\n");
spin_lock_irqsave(&isp116x->lock, flags);
isp116x_write_reg32(isp116x, wIndex
? HCRHPORT2 : HCRHPORT1, RH_PS_PSS);
break;
case USB_PORT_FEAT_POWER:
DBG("USB_PORT_FEAT_POWER\n");
spin_lock_irqsave(&isp116x->lock, flags);
isp116x_write_reg32(isp116x, wIndex
? HCRHPORT2 : HCRHPORT1, RH_PS_PPS);
break;
case USB_PORT_FEAT_RESET:
DBG("USB_PORT_FEAT_RESET\n");
root_port_reset(isp116x, wIndex);
spin_lock_irqsave(&isp116x->lock, flags);
break;
default:
goto error;
}
isp116x->rhport[wIndex] =
isp116x_read_reg32(isp116x, wIndex ? HCRHPORT2 : HCRHPORT1);
spin_unlock_irqrestore(&isp116x->lock, flags);
break;
default:
error:
/* "protocol stall" on error */
DBG("PROTOCOL STALL\n");
ret = -EPIPE;
}
return ret;
}
#ifdef CONFIG_PM
static int isp116x_hub_suspend(struct usb_hcd *hcd)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
unsigned long flags;
u32 val;
int ret = 0;
spin_lock_irqsave(&isp116x->lock, flags);
val = isp116x_read_reg32(isp116x, HCCONTROL);
switch (val & HCCONTROL_HCFS) {
case HCCONTROL_USB_OPER:
hcd->state = HC_STATE_QUIESCING;
val &= (~HCCONTROL_HCFS & ~HCCONTROL_RWE);
val |= HCCONTROL_USB_SUSPEND;
if (hcd->remote_wakeup)
val |= HCCONTROL_RWE;
/* Wait for usb transfers to finish */
mdelay(2);
isp116x_write_reg32(isp116x, HCCONTROL, val);
hcd->state = HC_STATE_SUSPENDED;
/* Wait for devices to suspend */
mdelay(5);
case HCCONTROL_USB_SUSPEND:
break;
case HCCONTROL_USB_RESUME:
isp116x_write_reg32(isp116x, HCCONTROL,
(val & ~HCCONTROL_HCFS) |
HCCONTROL_USB_RESET);
case HCCONTROL_USB_RESET:
ret = -EBUSY;
break;
default:
ret = -EINVAL;
}
spin_unlock_irqrestore(&isp116x->lock, flags);
return ret;
}
static int isp116x_hub_resume(struct usb_hcd *hcd)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
u32 val;
int ret = -EINPROGRESS;
msleep(5);
spin_lock_irq(&isp116x->lock);
val = isp116x_read_reg32(isp116x, HCCONTROL);
switch (val & HCCONTROL_HCFS) {
case HCCONTROL_USB_SUSPEND:
val &= ~HCCONTROL_HCFS;
val |= HCCONTROL_USB_RESUME;
isp116x_write_reg32(isp116x, HCCONTROL, val);
case HCCONTROL_USB_RESUME:
break;
case HCCONTROL_USB_OPER:
/* Without setting power_state here the
SUSPENDED state won't be removed from
sysfs/usbN/power.state as a response to remote
wakeup. Maybe in the future. */
hcd->self.root_hub->dev.power.power_state = PMSG_ON;
ret = 0;
break;
default:
ret = -EBUSY;
}
if (ret != -EINPROGRESS) {
spin_unlock_irq(&isp116x->lock);
return ret;
}
val = isp116x->rhdesca & RH_A_NDP;
while (val--) {
u32 stat =
isp116x_read_reg32(isp116x, val ? HCRHPORT2 : HCRHPORT1);
/* force global, not selective, resume */
if (!(stat & RH_PS_PSS))
continue;
DBG("%s: Resuming port %d\n", __func__, val);
isp116x_write_reg32(isp116x, RH_PS_POCI, val
? HCRHPORT2 : HCRHPORT1);
}
spin_unlock_irq(&isp116x->lock);
hcd->state = HC_STATE_RESUMING;
mdelay(20);
/* Go operational */
spin_lock_irq(&isp116x->lock);
val = isp116x_read_reg32(isp116x, HCCONTROL);
isp116x_write_reg32(isp116x, HCCONTROL,
(val & ~HCCONTROL_HCFS) | HCCONTROL_USB_OPER);
spin_unlock_irq(&isp116x->lock);
/* see analogous comment above */
hcd->self.root_hub->dev.power.power_state = PMSG_ON;
hcd->state = HC_STATE_RUNNING;
return 0;
}
static void isp116x_rh_resume(void *_hcd)
{
struct usb_hcd *hcd = _hcd;
usb_resume_device(hcd->self.root_hub);
}
#else
#define isp116x_hub_suspend NULL
#define isp116x_hub_resume NULL
static void isp116x_rh_resume(void *_hcd)
{
}
#endif
/*-----------------------------------------------------------------*/
#ifdef STUB_DEBUG_FILE
static inline void create_debug_file(struct isp116x *isp116x)
{
}
static inline void remove_debug_file(struct isp116x *isp116x)
{
}
#else
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static void dump_irq(struct seq_file *s, char *label, u16 mask)
{
seq_printf(s, "%s %04x%s%s%s%s%s%s\n", label, mask,
mask & HCuPINT_CLKRDY ? " clkrdy" : "",
mask & HCuPINT_SUSP ? " susp" : "",
mask & HCuPINT_OPR ? " opr" : "",
mask & HCuPINT_AIIEOT ? " eot" : "",
mask & HCuPINT_ATL ? " atl" : "",
mask & HCuPINT_SOF ? " sof" : "");
}
static void dump_int(struct seq_file *s, char *label, u32 mask)
{
seq_printf(s, "%s %08x%s%s%s%s%s%s%s\n", label, mask,
mask & HCINT_MIE ? " MIE" : "",
mask & HCINT_RHSC ? " rhsc" : "",
mask & HCINT_FNO ? " fno" : "",
mask & HCINT_UE ? " ue" : "",
mask & HCINT_RD ? " rd" : "",
mask & HCINT_SF ? " sof" : "", mask & HCINT_SO ? " so" : "");
}
static int proc_isp116x_show(struct seq_file *s, void *unused)
{
struct isp116x *isp116x = s->private;
struct isp116x_ep *ep;
struct urb *urb;
unsigned i;
char *str;
seq_printf(s, "%s\n%s version %s\n",
isp116x_to_hcd(isp116x)->product_desc, hcd_name,
DRIVER_VERSION);
if (HC_IS_SUSPENDED(isp116x_to_hcd(isp116x)->state)) {
seq_printf(s, "HCD is suspended\n");
return 0;
}
if (!HC_IS_RUNNING(isp116x_to_hcd(isp116x)->state)) {
seq_printf(s, "HCD not running\n");
return 0;
}
spin_lock_irq(&isp116x->lock);
dump_irq(s, "hc_irq_enable", isp116x_read_reg16(isp116x, HCuPINTENB));
dump_irq(s, "hc_irq_status", isp116x_read_reg16(isp116x, HCuPINT));
dump_int(s, "hc_int_enable", isp116x_read_reg32(isp116x, HCINTENB));
dump_int(s, "hc_int_status", isp116x_read_reg32(isp116x, HCINTSTAT));
list_for_each_entry(ep, &isp116x->async, schedule) {
switch (ep->nextpid) {
case USB_PID_IN:
str = "in";
break;
case USB_PID_OUT:
str = "out";
break;
case USB_PID_SETUP:
str = "setup";
break;
case USB_PID_ACK:
str = "status";
break;
default:
str = "?";
break;
};
seq_printf(s, "%p, ep%d%s, maxpacket %d:\n", ep,
ep->epnum, str, ep->maxpacket);
list_for_each_entry(urb, &ep->hep->urb_list, urb_list) {
seq_printf(s, " urb%p, %d/%d\n", urb,
urb->actual_length,
urb->transfer_buffer_length);
}
}
if (!list_empty(&isp116x->async))
seq_printf(s, "\n");
seq_printf(s, "periodic size= %d\n", PERIODIC_SIZE);
for (i = 0; i < PERIODIC_SIZE; i++) {
ep = isp116x->periodic[i];
if (!ep)
continue;
seq_printf(s, "%2d [%3d]:\n", i, isp116x->load[i]);
/* DUMB: prints shared entries multiple times */
do {
seq_printf(s, " %d/%p (%sdev%d ep%d%s max %d)\n",
ep->period, ep,
(ep->udev->speed ==
USB_SPEED_FULL) ? "" : "ls ",
ep->udev->devnum, ep->epnum,
(ep->epnum ==
0) ? "" : ((ep->nextpid ==
USB_PID_IN) ? "in" : "out"),
ep->maxpacket);
ep = ep->next;
} while (ep);
}
spin_unlock_irq(&isp116x->lock);
seq_printf(s, "\n");
return 0;
}
static int proc_isp116x_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_isp116x_show, PDE(inode)->data);
}
static struct file_operations proc_ops = {
.open = proc_isp116x_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/* expect just one isp116x per system */
static const char proc_filename[] = "driver/isp116x";
static void create_debug_file(struct isp116x *isp116x)
{
struct proc_dir_entry *pde;
pde = create_proc_entry(proc_filename, 0, NULL);
if (pde == NULL)
return;
pde->proc_fops = &proc_ops;
pde->data = isp116x;
isp116x->pde = pde;
}
static void remove_debug_file(struct isp116x *isp116x)
{
if (isp116x->pde)
remove_proc_entry(proc_filename, NULL);
}
#endif
/*-----------------------------------------------------------------*/
/*
Software reset - can be called from any contect.
*/
static int isp116x_sw_reset(struct isp116x *isp116x)
{
int retries = 15;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&isp116x->lock, flags);
isp116x_write_reg16(isp116x, HCSWRES, HCSWRES_MAGIC);
isp116x_write_reg32(isp116x, HCCMDSTAT, HCCMDSTAT_HCR);
while (--retries) {
/* It usually resets within 1 ms */
mdelay(1);
if (!(isp116x_read_reg32(isp116x, HCCMDSTAT) & HCCMDSTAT_HCR))
break;
}
if (!retries) {
ERR("Software reset timeout\n");
ret = -ETIME;
}
spin_unlock_irqrestore(&isp116x->lock, flags);
return ret;
}
/*
Reset. Tries to perform platform-specific hardware
reset first; falls back to software reset.
*/
static int isp116x_reset(struct usb_hcd *hcd)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
unsigned long t;
u16 clkrdy = 0;
int ret = 0, timeout = 15 /* ms */ ;
if (isp116x->board && isp116x->board->reset) {
/* Hardware reset */
isp116x->board->reset(hcd->self.controller, 1);
msleep(10);
if (isp116x->board->clock)
isp116x->board->clock(hcd->self.controller, 1);
msleep(1);
isp116x->board->reset(hcd->self.controller, 0);
} else
ret = isp116x_sw_reset(isp116x);
if (ret)
return ret;
t = jiffies + msecs_to_jiffies(timeout);
while (time_before_eq(jiffies, t)) {
msleep(4);
spin_lock_irq(&isp116x->lock);
clkrdy = isp116x_read_reg16(isp116x, HCuPINT) & HCuPINT_CLKRDY;
spin_unlock_irq(&isp116x->lock);
if (clkrdy)
break;
}
if (!clkrdy) {
ERR("Clock not ready after 20ms\n");
ret = -ENODEV;
}
return ret;
}
static void isp116x_stop(struct usb_hcd *hcd)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
unsigned long flags;
u32 val;
spin_lock_irqsave(&isp116x->lock, flags);
isp116x_write_reg16(isp116x, HCuPINTENB, 0);
/* Switch off ports' power, some devices don't come up
after next 'insmod' without this */
val = isp116x_read_reg32(isp116x, HCRHDESCA);
val &= ~(RH_A_NPS | RH_A_PSM);
isp116x_write_reg32(isp116x, HCRHDESCA, val);
isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_LPS);
spin_unlock_irqrestore(&isp116x->lock, flags);
/* Put the chip into reset state */
if (isp116x->board && isp116x->board->reset)
isp116x->board->reset(hcd->self.controller, 0);
else
isp116x_sw_reset(isp116x);
/* Stop the clock */
if (isp116x->board && isp116x->board->clock)
isp116x->board->clock(hcd->self.controller, 0);
}
/*
Configure the chip. The chip must be successfully reset by now.
*/
static int isp116x_start(struct usb_hcd *hcd)
{
struct isp116x *isp116x = hcd_to_isp116x(hcd);
struct isp116x_platform_data *board = isp116x->board;
struct usb_device *udev;
u32 val;
unsigned long flags;
spin_lock_irqsave(&isp116x->lock, flags);
/* clear interrupt status and disable all interrupt sources */
isp116x_write_reg16(isp116x, HCuPINT, 0xff);
isp116x_write_reg16(isp116x, HCuPINTENB, 0);
val = isp116x_read_reg16(isp116x, HCCHIPID);
if ((val & HCCHIPID_MASK) != HCCHIPID_MAGIC) {
ERR("Invalid chip ID %04x\n", val);
spin_unlock_irqrestore(&isp116x->lock, flags);
return -ENODEV;
}
isp116x_write_reg16(isp116x, HCITLBUFLEN, ISP116x_ITL_BUFSIZE);
isp116x_write_reg16(isp116x, HCATLBUFLEN, ISP116x_ATL_BUFSIZE);
/* ----- HW conf */
val = HCHWCFG_INT_ENABLE | HCHWCFG_DBWIDTH(1);
if (board->sel15Kres)
val |= HCHWCFG_15KRSEL;
/* Remote wakeup won't work without working clock */
if (board->clknotstop || board->remote_wakeup_enable)
val |= HCHWCFG_CLKNOTSTOP;
if (board->oc_enable)
val |= HCHWCFG_ANALOG_OC;
if (board->int_act_high)
val |= HCHWCFG_INT_POL;
if (board->int_edge_triggered)
val |= HCHWCFG_INT_TRIGGER;
isp116x_write_reg16(isp116x, HCHWCFG, val);
/* ----- Root hub conf */
val = 0;
/* AN10003_1.pdf recommends NPS to be always 1 */
if (board->no_power_switching)
val |= RH_A_NPS;
if (board->power_switching_mode)
val |= RH_A_PSM;
if (board->potpg)
val |= (board->potpg << 24) & RH_A_POTPGT;
else
val |= (25 << 24) & RH_A_POTPGT;
isp116x_write_reg32(isp116x, HCRHDESCA, val);
isp116x->rhdesca = isp116x_read_reg32(isp116x, HCRHDESCA);
val = RH_B_PPCM;
isp116x_write_reg32(isp116x, HCRHDESCB, val);
isp116x->rhdescb = isp116x_read_reg32(isp116x, HCRHDESCB);
val = 0;
if (board->remote_wakeup_enable) {
hcd->can_wakeup = 1;
val |= RH_HS_DRWE;
}
isp116x_write_reg32(isp116x, HCRHSTATUS, val);
isp116x->rhstatus = isp116x_read_reg32(isp116x, HCRHSTATUS);
isp116x_write_reg32(isp116x, HCFMINTVL, 0x27782edf);
spin_unlock_irqrestore(&isp116x->lock, flags);
udev = usb_alloc_dev(NULL, &hcd->self, 0);
if (!udev) {
isp116x_stop(hcd);
return -ENOMEM;
}
udev->speed = USB_SPEED_FULL;
hcd->state = HC_STATE_RUNNING;
if (usb_hcd_register_root_hub(udev, hcd) != 0) {
isp116x_stop(hcd);
usb_put_dev(udev);
return -ENODEV;
}
spin_lock_irqsave(&isp116x->lock, flags);
/* Set up interrupts */
isp116x->intenb = HCINT_MIE | HCINT_RHSC | HCINT_UE;
if (board->remote_wakeup_enable)
isp116x->intenb |= HCINT_RD;
isp116x->irqenb = HCuPINT_ATL | HCuPINT_OPR; /* | HCuPINT_SUSP; */
isp116x_write_reg32(isp116x, HCINTENB, isp116x->intenb);
isp116x_write_reg16(isp116x, HCuPINTENB, isp116x->irqenb);
/* Go operational */
val = HCCONTROL_USB_OPER;
/* Remote wakeup connected - NOT SUPPORTED */
/* if (board->remote_wakeup_connected)
val |= HCCONTROL_RWC; */
if (board->remote_wakeup_enable)
val |= HCCONTROL_RWE;
isp116x_write_reg32(isp116x, HCCONTROL, val);
/* Disable ports to avoid race in device enumeration */
isp116x_write_reg32(isp116x, HCRHPORT1, RH_PS_CCS);
isp116x_write_reg32(isp116x, HCRHPORT2, RH_PS_CCS);
isp116x_show_regs(isp116x);
spin_unlock_irqrestore(&isp116x->lock, flags);
return 0;
}
/*-----------------------------------------------------------------*/
static struct hc_driver isp116x_hc_driver = {
.description = hcd_name,
.product_desc = "ISP116x Host Controller",
.hcd_priv_size = sizeof(struct isp116x),
.irq = isp116x_irq,
.flags = HCD_USB11,
.reset = isp116x_reset,
.start = isp116x_start,
.stop = isp116x_stop,
.urb_enqueue = isp116x_urb_enqueue,
.urb_dequeue = isp116x_urb_dequeue,
.endpoint_disable = isp116x_endpoint_disable,
.get_frame_number = isp116x_get_frame,
.hub_status_data = isp116x_hub_status_data,
.hub_control = isp116x_hub_control,
.hub_suspend = isp116x_hub_suspend,
.hub_resume = isp116x_hub_resume,
};
/*----------------------------------------------------------------*/
static int __init_or_module isp116x_remove(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct isp116x *isp116x = hcd_to_isp116x(hcd);
struct platform_device *pdev;
struct resource *res;
pdev = container_of(dev, struct platform_device, dev);
remove_debug_file(isp116x);
usb_remove_hcd(hcd);
iounmap(isp116x->data_reg);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
release_mem_region(res->start, 2);
iounmap(isp116x->addr_reg);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(res->start, 2);
usb_put_hcd(hcd);
return 0;
}
#define resource_len(r) (((r)->end - (r)->start) + 1)
static int __init isp116x_probe(struct device *dev)
{
struct usb_hcd *hcd;
struct isp116x *isp116x;
struct platform_device *pdev;
struct resource *addr, *data;
void __iomem *addr_reg;
void __iomem *data_reg;
int irq;
int ret = 0;
pdev = container_of(dev, struct platform_device, dev);
if (pdev->num_resources < 3) {
ret = -ENODEV;
goto err1;
}
data = platform_get_resource(pdev, IORESOURCE_MEM, 0);
addr = platform_get_resource(pdev, IORESOURCE_MEM, 1);
irq = platform_get_irq(pdev, 0);
if (!addr || !data || irq < 0) {
ret = -ENODEV;
goto err1;
}
if (dev->dma_mask) {
DBG("DMA not supported\n");
ret = -EINVAL;
goto err1;
}
if (!request_mem_region(addr->start, 2, hcd_name)) {
ret = -EBUSY;
goto err1;
}
addr_reg = ioremap(addr->start, resource_len(addr));
if (addr_reg == NULL) {
ret = -ENOMEM;
goto err2;
}
if (!request_mem_region(data->start, 2, hcd_name)) {
ret = -EBUSY;
goto err3;
}
data_reg = ioremap(data->start, resource_len(data));
if (data_reg == NULL) {
ret = -ENOMEM;
goto err4;
}
/* allocate and initialize hcd */
hcd = usb_create_hcd(&isp116x_hc_driver, dev, dev->bus_id);
if (!hcd) {
ret = -ENOMEM;
goto err5;
}
/* this rsrc_start is bogus */
hcd->rsrc_start = addr->start;
isp116x = hcd_to_isp116x(hcd);
isp116x->data_reg = data_reg;
isp116x->addr_reg = addr_reg;
spin_lock_init(&isp116x->lock);
INIT_LIST_HEAD(&isp116x->async);
INIT_WORK(&isp116x->rh_resume, isp116x_rh_resume, hcd);
isp116x->board = dev->platform_data;
if (!isp116x->board) {
ERR("Platform data structure not initialized\n");
ret = -ENODEV;
goto err6;
}
if (isp116x_check_platform_delay(isp116x)) {
ERR("USE_PLATFORM_DELAY defined, but delay function not "
"implemented.\n");
ERR("See comments in drivers/usb/host/isp116x-hcd.c\n");
ret = -ENODEV;
goto err6;
}
ret = usb_add_hcd(hcd, irq, SA_INTERRUPT);
if (ret != 0)
goto err6;
create_debug_file(isp116x);
return 0;
err6:
usb_put_hcd(hcd);
err5:
iounmap(data_reg);
err4:
release_mem_region(data->start, 2);
err3:
iounmap(addr_reg);
err2:
release_mem_region(addr->start, 2);
err1:
ERR("init error, %d\n", ret);
return ret;
}
#ifdef CONFIG_PM
/*
Suspend of platform device
*/
static int isp116x_suspend(struct device *dev, pm_message_t state, u32 phase)
{
int ret = 0;
struct usb_hcd *hcd = dev_get_drvdata(dev);
VDBG("%s: state %x, phase %x\n", __func__, state, phase);
if (phase != SUSPEND_DISABLE && phase != SUSPEND_POWER_DOWN)
return 0;
ret = usb_suspend_device(hcd->self.root_hub, state);
if (!ret) {
dev->power.power_state = state;
INFO("%s suspended\n", (char *)hcd_name);
} else
ERR("%s suspend failed\n", (char *)hcd_name);
return ret;
}
/*
Resume platform device
*/
static int isp116x_resume(struct device *dev, u32 phase)
{
int ret = 0;
struct usb_hcd *hcd = dev_get_drvdata(dev);
VDBG("%s: state %x, phase %x\n", __func__, dev->power.power_state,
phase);
if (phase != RESUME_POWER_ON)
return 0;
ret = usb_resume_device(hcd->self.root_hub);
if (!ret) {
dev->power.power_state = PMSG_ON;
VDBG("%s resumed\n", (char *)hcd_name);
}
return ret;
}
#else
#define isp116x_suspend NULL
#define isp116x_resume NULL
#endif
static struct device_driver isp116x_driver = {
.name = (char *)hcd_name,
.bus = &platform_bus_type,
.probe = isp116x_probe,
.remove = isp116x_remove,
.suspend = isp116x_suspend,
.resume = isp116x_resume,
};
/*-----------------------------------------------------------------*/
static int __init isp116x_init(void)
{
if (usb_disabled())
return -ENODEV;
INFO("driver %s, %s\n", hcd_name, DRIVER_VERSION);
return driver_register(&isp116x_driver);
}
module_init(isp116x_init);
static void __exit isp116x_cleanup(void)
{
driver_unregister(&isp116x_driver);
}
module_exit(isp116x_cleanup);
/*
* ISP116x register declarations and HCD data structures
*
* Copyright (C) 2005 Olav Kongas <ok@artecdesign.ee>
* Portions:
* Copyright (C) 2004 Lothar Wassmann
* Copyright (C) 2004 Psion Teklogix
* Copyright (C) 2004 David Brownell
*/
/* us of 1ms frame */
#define MAX_LOAD_LIMIT 850
/* Full speed: max # of bytes to transfer for a single urb
at a time must be < 1024 && must be multiple of 64.
832 allows transfering 4kiB within 5 frames. */
#define MAX_TRANSFER_SIZE_FULLSPEED 832
/* Low speed: there is no reason to schedule in very big
chunks; often the requested long transfers are for
string descriptors containing short strings. */
#define MAX_TRANSFER_SIZE_LOWSPEED 64
/* Bytetime (us), a rough indication of how much time it
would take to transfer a byte of useful data over USB */
#define BYTE_TIME_FULLSPEED 1
#define BYTE_TIME_LOWSPEED 20
/* Buffer sizes */
#define ISP116x_BUF_SIZE 4096
#define ISP116x_ITL_BUFSIZE 0
#define ISP116x_ATL_BUFSIZE ((ISP116x_BUF_SIZE) - 2*(ISP116x_ITL_BUFSIZE))
#define ISP116x_WRITE_OFFSET 0x80
/*------------ ISP116x registers/bits ------------*/
#define HCREVISION 0x00
#define HCCONTROL 0x01
#define HCCONTROL_HCFS (3 << 6) /* host controller
functional state */
#define HCCONTROL_USB_RESET (0 << 6)
#define HCCONTROL_USB_RESUME (1 << 6)
#define HCCONTROL_USB_OPER (2 << 6)
#define HCCONTROL_USB_SUSPEND (3 << 6)
#define HCCONTROL_RWC (1 << 9) /* remote wakeup connected */
#define HCCONTROL_RWE (1 << 10) /* remote wakeup enable */
#define HCCMDSTAT 0x02
#define HCCMDSTAT_HCR (1 << 0) /* host controller reset */
#define HCCMDSTAT_SOC (3 << 16) /* scheduling overrun count */
#define HCINTSTAT 0x03
#define HCINT_SO (1 << 0) /* scheduling overrun */
#define HCINT_WDH (1 << 1) /* writeback of done_head */
#define HCINT_SF (1 << 2) /* start frame */
#define HCINT_RD (1 << 3) /* resume detect */
#define HCINT_UE (1 << 4) /* unrecoverable error */
#define HCINT_FNO (1 << 5) /* frame number overflow */
#define HCINT_RHSC (1 << 6) /* root hub status change */
#define HCINT_OC (1 << 30) /* ownership change */
#define HCINT_MIE (1 << 31) /* master interrupt enable */
#define HCINTENB 0x04
#define HCINTDIS 0x05
#define HCFMINTVL 0x0d
#define HCFMREM 0x0e
#define HCFMNUM 0x0f
#define HCLSTHRESH 0x11
#define HCRHDESCA 0x12
#define RH_A_NDP (0x3 << 0) /* # downstream ports */
#define RH_A_PSM (1 << 8) /* power switching mode */
#define RH_A_NPS (1 << 9) /* no power switching */
#define RH_A_DT (1 << 10) /* device type (mbz) */
#define RH_A_OCPM (1 << 11) /* overcurrent protection
mode */
#define RH_A_NOCP (1 << 12) /* no overcurrent protection */
#define RH_A_POTPGT (0xff << 24) /* power on -> power good
time */
#define HCRHDESCB 0x13
#define RH_B_DR (0xffff << 0) /* device removable flags */
#define RH_B_PPCM (0xffff << 16) /* port power control mask */
#define HCRHSTATUS 0x14
#define RH_HS_LPS (1 << 0) /* local power status */
#define RH_HS_OCI (1 << 1) /* over current indicator */
#define RH_HS_DRWE (1 << 15) /* device remote wakeup
enable */
#define RH_HS_LPSC (1 << 16) /* local power status change */
#define RH_HS_OCIC (1 << 17) /* over current indicator
change */
#define RH_HS_CRWE (1 << 31) /* clear remote wakeup
enable */
#define HCRHPORT1 0x15
#define RH_PS_CCS (1 << 0) /* current connect status */
#define RH_PS_PES (1 << 1) /* port enable status */
#define RH_PS_PSS (1 << 2) /* port suspend status */
#define RH_PS_POCI (1 << 3) /* port over current
indicator */
#define RH_PS_PRS (1 << 4) /* port reset status */
#define RH_PS_PPS (1 << 8) /* port power status */
#define RH_PS_LSDA (1 << 9) /* low speed device attached */
#define RH_PS_CSC (1 << 16) /* connect status change */
#define RH_PS_PESC (1 << 17) /* port enable status change */
#define RH_PS_PSSC (1 << 18) /* port suspend status
change */
#define RH_PS_OCIC (1 << 19) /* over current indicator
change */
#define RH_PS_PRSC (1 << 20) /* port reset status change */
#define HCRHPORT_CLRMASK (0x1f << 16)
#define HCRHPORT2 0x16
#define HCHWCFG 0x20
#define HCHWCFG_15KRSEL (1 << 12)
#define HCHWCFG_CLKNOTSTOP (1 << 11)
#define HCHWCFG_ANALOG_OC (1 << 10)
#define HCHWCFG_DACK_MODE (1 << 8)
#define HCHWCFG_EOT_POL (1 << 7)
#define HCHWCFG_DACK_POL (1 << 6)
#define HCHWCFG_DREQ_POL (1 << 5)
#define HCHWCFG_DBWIDTH_MASK (0x03 << 3)
#define HCHWCFG_DBWIDTH(n) (((n) << 3) & HCHWCFG_DBWIDTH_MASK)
#define HCHWCFG_INT_POL (1 << 2)
#define HCHWCFG_INT_TRIGGER (1 << 1)
#define HCHWCFG_INT_ENABLE (1 << 0)
#define HCDMACFG 0x21
#define HCDMACFG_BURST_LEN_MASK (0x03 << 5)
#define HCDMACFG_BURST_LEN(n) (((n) << 5) & HCDMACFG_BURST_LEN_MASK)
#define HCDMACFG_BURST_LEN_1 HCDMACFG_BURST_LEN(0)
#define HCDMACFG_BURST_LEN_4 HCDMACFG_BURST_LEN(1)
#define HCDMACFG_BURST_LEN_8 HCDMACFG_BURST_LEN(2)
#define HCDMACFG_DMA_ENABLE (1 << 4)
#define HCDMACFG_BUF_TYPE_MASK (0x07 << 1)
#define HCDMACFG_CTR_SEL (1 << 2)
#define HCDMACFG_ITLATL_SEL (1 << 1)
#define HCDMACFG_DMA_RW_SELECT (1 << 0)
#define HCXFERCTR 0x22
#define HCuPINT 0x24
#define HCuPINT_SOF (1 << 0)
#define HCuPINT_ATL (1 << 1)
#define HCuPINT_AIIEOT (1 << 2)
#define HCuPINT_OPR (1 << 4)
#define HCuPINT_SUSP (1 << 5)
#define HCuPINT_CLKRDY (1 << 6)
#define HCuPINTENB 0x25
#define HCCHIPID 0x27
#define HCCHIPID_MASK 0xff00
#define HCCHIPID_MAGIC 0x6100
#define HCSCRATCH 0x28
#define HCSWRES 0x29
#define HCSWRES_MAGIC 0x00f6
#define HCITLBUFLEN 0x2a
#define HCATLBUFLEN 0x2b
#define HCBUFSTAT 0x2c
#define HCBUFSTAT_ITL0_FULL (1 << 0)
#define HCBUFSTAT_ITL1_FULL (1 << 1)
#define HCBUFSTAT_ATL_FULL (1 << 2)
#define HCBUFSTAT_ITL0_DONE (1 << 3)
#define HCBUFSTAT_ITL1_DONE (1 << 4)
#define HCBUFSTAT_ATL_DONE (1 << 5)
#define HCRDITL0LEN 0x2d
#define HCRDITL1LEN 0x2e
#define HCITLPORT 0x40
#define HCATLPORT 0x41
/* Philips transfer descriptor */
struct ptd {
u16 count;
#define PTD_COUNT_MSK (0x3ff << 0)
#define PTD_TOGGLE_MSK (1 << 10)
#define PTD_ACTIVE_MSK (1 << 11)
#define PTD_CC_MSK (0xf << 12)
u16 mps;
#define PTD_MPS_MSK (0x3ff << 0)
#define PTD_SPD_MSK (1 << 10)
#define PTD_LAST_MSK (1 << 11)
#define PTD_EP_MSK (0xf << 12)
u16 len;
#define PTD_LEN_MSK (0x3ff << 0)
#define PTD_DIR_MSK (3 << 10)
#define PTD_DIR_SETUP (0)
#define PTD_DIR_OUT (1)
#define PTD_DIR_IN (2)
#define PTD_B5_5_MSK (1 << 13)
u16 faddr;
#define PTD_FA_MSK (0x7f << 0)
#define PTD_FMT_MSK (1 << 7)
} __attribute__ ((packed, aligned(2)));
/* PTD accessor macros. */
#define PTD_GET_COUNT(p) (((p)->count & PTD_COUNT_MSK) >> 0)
#define PTD_COUNT(v) (((v) << 0) & PTD_COUNT_MSK)
#define PTD_GET_TOGGLE(p) (((p)->count & PTD_TOGGLE_MSK) >> 10)
#define PTD_TOGGLE(v) (((v) << 10) & PTD_TOGGLE_MSK)
#define PTD_GET_ACTIVE(p) (((p)->count & PTD_ACTIVE_MSK) >> 11)
#define PTD_ACTIVE(v) (((v) << 11) & PTD_ACTIVE_MSK)
#define PTD_GET_CC(p) (((p)->count & PTD_CC_MSK) >> 12)
#define PTD_CC(v) (((v) << 12) & PTD_CC_MSK)
#define PTD_GET_MPS(p) (((p)->mps & PTD_MPS_MSK) >> 0)
#define PTD_MPS(v) (((v) << 0) & PTD_MPS_MSK)
#define PTD_GET_SPD(p) (((p)->mps & PTD_SPD_MSK) >> 10)
#define PTD_SPD(v) (((v) << 10) & PTD_SPD_MSK)
#define PTD_GET_LAST(p) (((p)->mps & PTD_LAST_MSK) >> 11)
#define PTD_LAST(v) (((v) << 11) & PTD_LAST_MSK)
#define PTD_GET_EP(p) (((p)->mps & PTD_EP_MSK) >> 12)
#define PTD_EP(v) (((v) << 12) & PTD_EP_MSK)
#define PTD_GET_LEN(p) (((p)->len & PTD_LEN_MSK) >> 0)
#define PTD_LEN(v) (((v) << 0) & PTD_LEN_MSK)
#define PTD_GET_DIR(p) (((p)->len & PTD_DIR_MSK) >> 10)
#define PTD_DIR(v) (((v) << 10) & PTD_DIR_MSK)
#define PTD_GET_B5_5(p) (((p)->len & PTD_B5_5_MSK) >> 13)
#define PTD_B5_5(v) (((v) << 13) & PTD_B5_5_MSK)
#define PTD_GET_FA(p) (((p)->faddr & PTD_FA_MSK) >> 0)
#define PTD_FA(v) (((v) << 0) & PTD_FA_MSK)
#define PTD_GET_FMT(p) (((p)->faddr & PTD_FMT_MSK) >> 7)
#define PTD_FMT(v) (((v) << 7) & PTD_FMT_MSK)
/* Hardware transfer status codes -- CC from ptd->count */
#define TD_CC_NOERROR 0x00
#define TD_CC_CRC 0x01
#define TD_CC_BITSTUFFING 0x02
#define TD_CC_DATATOGGLEM 0x03
#define TD_CC_STALL 0x04
#define TD_DEVNOTRESP 0x05
#define TD_PIDCHECKFAIL 0x06
#define TD_UNEXPECTEDPID 0x07
#define TD_DATAOVERRUN 0x08
#define TD_DATAUNDERRUN 0x09
/* 0x0A, 0x0B reserved for hardware */
#define TD_BUFFEROVERRUN 0x0C
#define TD_BUFFERUNDERRUN 0x0D
/* 0x0E, 0x0F reserved for HCD */
#define TD_NOTACCESSED 0x0F
/* map PTD status codes (CC) to errno values */
static const int cc_to_error[16] = {
/* No Error */ 0,
/* CRC Error */ -EILSEQ,
/* Bit Stuff */ -EPROTO,
/* Data Togg */ -EILSEQ,
/* Stall */ -EPIPE,
/* DevNotResp */ -ETIMEDOUT,
/* PIDCheck */ -EPROTO,
/* UnExpPID */ -EPROTO,
/* DataOver */ -EOVERFLOW,
/* DataUnder */ -EREMOTEIO,
/* (for hw) */ -EIO,
/* (for hw) */ -EIO,
/* BufferOver */ -ECOMM,
/* BuffUnder */ -ENOSR,
/* (for HCD) */ -EALREADY,
/* (for HCD) */ -EALREADY
};
/*--------------------------------------------------------------*/
#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */
#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE)
struct isp116x {
spinlock_t lock;
struct work_struct rh_resume;
void __iomem *addr_reg;
void __iomem *data_reg;
struct isp116x_platform_data *board;
struct proc_dir_entry *pde;
unsigned long stat1, stat2, stat4, stat8, stat16;
/* HC registers */
u32 intenb; /* "OHCI" interrupts */
u16 irqenb; /* uP interrupts */
/* Root hub registers */
u32 rhdesca;
u32 rhdescb;
u32 rhstatus;
u32 rhport[2];
/* async schedule: control, bulk */
struct list_head async;
/* periodic schedule: int */
u16 load[PERIODIC_SIZE];
struct isp116x_ep *periodic[PERIODIC_SIZE];
unsigned periodic_count;
u16 fmindex;
/* Schedule for the current frame */
struct isp116x_ep *atl_active;
int atl_buflen;
int atl_bufshrt;
int atl_last_dir;
atomic_t atl_finishing;
};
static inline struct isp116x *hcd_to_isp116x(struct usb_hcd *hcd)
{
return (struct isp116x *)(hcd->hcd_priv);
}
static inline struct usb_hcd *isp116x_to_hcd(struct isp116x *isp116x)
{
return container_of((void *)isp116x, struct usb_hcd, hcd_priv);
}
struct isp116x_ep {
struct usb_host_endpoint *hep;
struct usb_device *udev;
struct ptd ptd;
u8 maxpacket;
u8 epnum;
u8 nextpid;
u16 error_count;
u16 length; /* of current packet */
unsigned char *data; /* to databuf */
/* queue of active EP's (the ones scheduled for the
current frame) */
struct isp116x_ep *active;
/* periodic schedule */
u16 period;
u16 branch;
u16 load;
struct isp116x_ep *next;
/* async schedule */
struct list_head schedule;
};
/*-------------------------------------------------------------------------*/
#ifdef DEBUG
#define DBG(stuff...) printk(KERN_DEBUG "116x: " stuff)
#else
#define DBG(stuff...) do{}while(0)
#endif
#ifdef VERBOSE
# define VDBG DBG
#else
# define VDBG(stuff...) do{}while(0)
#endif
#define ERR(stuff...) printk(KERN_ERR "116x: " stuff)
#define WARN(stuff...) printk(KERN_WARNING "116x: " stuff)
#define INFO(stuff...) printk(KERN_INFO "116x: " stuff)
/* ------------------------------------------------- */
#if defined(USE_PLATFORM_DELAY)
#if defined(USE_NDELAY)
#error USE_PLATFORM_DELAY and USE_NDELAY simultaneously defined.
#endif
#define isp116x_delay(h,d) (h)->board->delay( \
isp116x_to_hcd(h)->self.controller,d)
#define isp116x_check_platform_delay(h) ((h)->board->delay == NULL)
#elif defined(USE_NDELAY)
#define isp116x_delay(h,d) ndelay(d)
#define isp116x_check_platform_delay(h) 0
#else
#define isp116x_delay(h,d) do{}while(0)
#define isp116x_check_platform_delay(h) 0
#endif
#if defined(DEBUG)
#define IRQ_TEST() BUG_ON(!irqs_disabled())
#else
#define IRQ_TEST() do{}while(0)
#endif
static inline void isp116x_write_addr(struct isp116x *isp116x, unsigned reg)
{
IRQ_TEST();
writew(reg & 0xff, isp116x->addr_reg);
isp116x_delay(isp116x, 300);
}
static inline void isp116x_write_data16(struct isp116x *isp116x, u16 val)
{
writew(val, isp116x->data_reg);
isp116x_delay(isp116x, 150);
}
static inline void isp116x_raw_write_data16(struct isp116x *isp116x, u16 val)
{
__raw_writew(val, isp116x->data_reg);
isp116x_delay(isp116x, 150);
}
static inline u16 isp116x_read_data16(struct isp116x *isp116x)
{
u16 val;
val = readw(isp116x->data_reg);
isp116x_delay(isp116x, 150);
return val;
}
static inline u16 isp116x_raw_read_data16(struct isp116x *isp116x)
{
u16 val;
val = __raw_readw(isp116x->data_reg);
isp116x_delay(isp116x, 150);
return val;
}
static inline void isp116x_write_data32(struct isp116x *isp116x, u32 val)
{
writew(val & 0xffff, isp116x->data_reg);
isp116x_delay(isp116x, 150);
writew(val >> 16, isp116x->data_reg);
isp116x_delay(isp116x, 150);
}
static inline u32 isp116x_read_data32(struct isp116x *isp116x)
{
u32 val;
val = (u32) readw(isp116x->data_reg);
isp116x_delay(isp116x, 150);
val |= ((u32) readw(isp116x->data_reg)) << 16;
isp116x_delay(isp116x, 150);
return val;
}
/* Let's keep register access functions out of line. Hint:
we wait at least 150 ns at every access.
*/
static u16 isp116x_read_reg16(struct isp116x *isp116x, unsigned reg)
{
isp116x_write_addr(isp116x, reg);
return isp116x_read_data16(isp116x);
}
static u32 isp116x_read_reg32(struct isp116x *isp116x, unsigned reg)
{
isp116x_write_addr(isp116x, reg);
return isp116x_read_data32(isp116x);
}
static void isp116x_write_reg16(struct isp116x *isp116x, unsigned reg,
unsigned val)
{
isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET);
isp116x_write_data16(isp116x, (u16) (val & 0xffff));
}
static void isp116x_write_reg32(struct isp116x *isp116x, unsigned reg,
unsigned val)
{
isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET);
isp116x_write_data32(isp116x, (u32) val);
}
#define isp116x_show_reg(d,r) { \
if ((r) < 0x20) { \
DBG("%-12s[%02x]: %08x\n", #r, \
r, isp116x_read_reg32(d, r)); \
} else { \
DBG("%-12s[%02x]: %04x\n", #r, \
r, isp116x_read_reg16(d, r)); \
} \
}
static inline void isp116x_show_regs(struct isp116x *isp116x)
{
isp116x_show_reg(isp116x, HCREVISION);
isp116x_show_reg(isp116x, HCCONTROL);
isp116x_show_reg(isp116x, HCCMDSTAT);
isp116x_show_reg(isp116x, HCINTSTAT);
isp116x_show_reg(isp116x, HCINTENB);
isp116x_show_reg(isp116x, HCFMINTVL);
isp116x_show_reg(isp116x, HCFMREM);
isp116x_show_reg(isp116x, HCFMNUM);
isp116x_show_reg(isp116x, HCLSTHRESH);
isp116x_show_reg(isp116x, HCRHDESCA);
isp116x_show_reg(isp116x, HCRHDESCB);
isp116x_show_reg(isp116x, HCRHSTATUS);
isp116x_show_reg(isp116x, HCRHPORT1);
isp116x_show_reg(isp116x, HCRHPORT2);
isp116x_show_reg(isp116x, HCHWCFG);
isp116x_show_reg(isp116x, HCDMACFG);
isp116x_show_reg(isp116x, HCXFERCTR);
isp116x_show_reg(isp116x, HCuPINT);
isp116x_show_reg(isp116x, HCuPINTENB);
isp116x_show_reg(isp116x, HCCHIPID);
isp116x_show_reg(isp116x, HCSCRATCH);
isp116x_show_reg(isp116x, HCITLBUFLEN);
isp116x_show_reg(isp116x, HCATLBUFLEN);
isp116x_show_reg(isp116x, HCBUFSTAT);
isp116x_show_reg(isp116x, HCRDITL0LEN);
isp116x_show_reg(isp116x, HCRDITL1LEN);
}
#if defined(URB_TRACE)
#define PIPETYPE(pipe) ({ char *__s; \
if (usb_pipecontrol(pipe)) __s = "ctrl"; \
else if (usb_pipeint(pipe)) __s = "int"; \
else if (usb_pipebulk(pipe)) __s = "bulk"; \
else __s = "iso"; \
__s;})
#define PIPEDIR(pipe) ({ usb_pipein(pipe) ? "in" : "out"; })
#define URB_NOTSHORT(urb) ({ (urb)->transfer_flags & URB_SHORT_NOT_OK ? \
"short_not_ok" : ""; })
/* print debug info about the URB */
static void urb_dbg(struct urb *urb, char *msg)
{
unsigned int pipe;
if (!urb) {
DBG("%s: zero urb\n", msg);
return;
}
pipe = urb->pipe;
DBG("%s: FA %d ep%d%s %s: len %d/%d %s\n", msg,
usb_pipedevice(pipe), usb_pipeendpoint(pipe),
PIPEDIR(pipe), PIPETYPE(pipe),
urb->transfer_buffer_length, urb->actual_length, URB_NOTSHORT(urb));
}
#else
#define urb_dbg(urb,msg) do{}while(0)
#endif /* ! defined(URB_TRACE) */
#if defined(PTD_TRACE)
#define PTD_DIR_STR(ptd) ({char __c; \
switch(PTD_GET_DIR(ptd)){ \
case 0: __c = 's'; break; \
case 1: __c = 'o'; break; \
default: __c = 'i'; break; \
}; __c;})
/*
Dump PTD info. The code documents the format
perfectly, right :)
*/
static inline void dump_ptd(struct ptd *ptd)
{
printk("td: %x %d%c%d %d,%d,%d %x %x%x%x\n",
PTD_GET_CC(ptd), PTD_GET_FA(ptd),
PTD_DIR_STR(ptd), PTD_GET_EP(ptd),
PTD_GET_COUNT(ptd), PTD_GET_LEN(ptd), PTD_GET_MPS(ptd),
PTD_GET_TOGGLE(ptd), PTD_GET_ACTIVE(ptd),
PTD_GET_SPD(ptd), PTD_GET_LAST(ptd));
}
static inline void dump_ptd_out_data(struct ptd *ptd, u8 * buf)
{
int k;
if (PTD_GET_DIR(ptd) != PTD_DIR_IN && PTD_GET_LEN(ptd)) {
printk("-> ");
for (k = 0; k < PTD_GET_LEN(ptd); ++k)
printk("%02x ", ((u8 *) buf)[k]);
printk("\n");
}
}
static inline void dump_ptd_in_data(struct ptd *ptd, u8 * buf)
{
int k;
if (PTD_GET_DIR(ptd) == PTD_DIR_IN && PTD_GET_COUNT(ptd)) {
printk("<- ");
for (k = 0; k < PTD_GET_COUNT(ptd); ++k)
printk("%02x ", ((u8 *) buf)[k]);
printk("\n");
}
if (PTD_GET_LAST(ptd))
printk("-\n");
}
#else
#define dump_ptd(ptd) do{}while(0)
#define dump_ptd_in_data(ptd,buf) do{}while(0)
#define dump_ptd_out_data(ptd,buf) do{}while(0)
#endif /* ! defined(PTD_TRACE) */
/*
* Board initialization code should put one of these into dev->platform_data
* and place the isp116x onto platform_bus.
*/
struct isp116x_platform_data {
/* Enable internal resistors on downstream ports */
unsigned sel15Kres:1;
/* Chip's internal clock won't be stopped in suspended state.
Setting/unsetting this bit takes effect only if
'remote_wakeup_enable' below is not set. */
unsigned clknotstop:1;
/* On-chip overcurrent protection */
unsigned oc_enable:1;
/* INT output polarity */
unsigned int_act_high:1;
/* INT edge or level triggered */
unsigned int_edge_triggered:1;
/* WAKEUP pin connected - NOT SUPPORTED */
/* unsigned remote_wakeup_connected:1; */
/* Wakeup by devices on usb bus enabled */
unsigned remote_wakeup_enable:1;
/* Switch or not to switch (keep always powered) */
unsigned no_power_switching:1;
/* Ganged port power switching (0) or individual port
power switching (1) */
unsigned power_switching_mode:1;
/* Given port_power, msec/2 after power on till power good */
u8 potpg;
/* Hardware reset set/clear. If implemented, this function must:
if set == 0, deassert chip's HW reset pin
otherwise, assert chip's HW reset pin */
void (*reset) (struct device * dev, int set);
/* Hardware clock start/stop. If implemented, this function must:
if start == 0, stop the external clock
otherwise, start the external clock
*/
void (*clock) (struct device * dev, int start);
/* Inter-io delay (ns). The chip is picky about access timings; it
expects at least:
150ns delay between consecutive accesses to DATA_REG,
300ns delay between access to ADDR_REG and DATA_REG
OE, WE MUST NOT be changed during these intervals
*/
void (*delay) (struct device * dev, int delay);
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment