?? isp116x-hcd.c
字號:
/* * 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/module.h>#include <linux/delay.h>#include <linux/debugfs.h>#include <linux/seq_file.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/list.h>#include <linux/usb.h>#include <linux/usb/isp116x.h>#include <linux/platform_device.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/byteorder.h>#include "../core/hcd.h"#include "isp116x.h"#define DRIVER_VERSION "03 Nov 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; 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 = &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 len; for (ep = isp116x->atl_active; ep; ep = ep->active) { u16 toggle = 0, dir = PTD_DIR_SETUP; 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: 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: ERR("%s %d: ep->nextpid %d\n", __func__, __LINE__, ep->nextpid); BUG(); } 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); short_not_ok = 1; spin_lock(&urb->lock); /* 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)); urb->actual_length += PTD_GET_COUNT(ptd); 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(); } 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)__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); 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 isp116x_ep *ep; struct urb *urb; if (!isp116x->atl_active) return; /* Fifo not ready? */ if (!(isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_DONE)) return;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -