?? sl811-hcd.c
字號:
/* * SL811HS HCD (Host Controller Driver) for USB. * * Copyright (C) 2004 Psion Teklogix (for NetBook PRO) * Copyright (C) 2004-2005 David Brownell * * Periodic scheduling is based on Roman's OHCI code * Copyright (C) 1999 Roman Weissgaerber * * The SL811HS controller handles host side USB (like the SL11H, but with * another register set and SOF generation) as well as peripheral side USB * (like the SL811S). This driver version doesn't implement the Gadget API * for the peripheral role; or OTG (that'd need much external circuitry). * * For documentation, see the SL811HS spec and the "SL811HS Embedded Host" * document (providing significant pieces missing from that spec); plus * the SL811S spec if you want peripheral side info. *//* * Status: Passed basic stress testing, works with hubs, mice, keyboards, * and usb-storage. * * TODO: * - usb suspend/resume triggered by sl811 (with USB_SUSPEND) * - various issues noted in the code * - performance work; use both register banks; ... * - use urb->iso_frame_desc[] with ISO transfers */#undef VERBOSE#undef PACKET_TRACE#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/timer.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/usb.h>#include <linux/usb/sl811.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 "sl811.h"MODULE_DESCRIPTION("SL811HS USB Host Controller Driver");MODULE_LICENSE("GPL");#define DRIVER_VERSION "19 May 2005"#ifndef DEBUG# define STUB_DEBUG_FILE#endif/* for now, use only one transfer register bank */#undef USE_B/* this doesn't understand urb->iso_frame_desc[], but if you had a driver * that just queued one ISO frame per URB then iso transfers "should" work * using the normal urb status fields. */#define DISABLE_ISO// #define QUIRK2#define QUIRK3static const char hcd_name[] = "sl811-hcd";/*-------------------------------------------------------------------------*/static void port_power(struct sl811 *sl811, int is_on){ struct usb_hcd *hcd = sl811_to_hcd(sl811); /* hub is inactive unless the port is powered */ if (is_on) { if (sl811->port1 & (1 << USB_PORT_FEAT_POWER)) return; sl811->port1 = (1 << USB_PORT_FEAT_POWER); sl811->irq_enable = SL11H_INTMASK_INSRMV; hcd->self.controller->power.power_state = PMSG_ON; } else { sl811->port1 = 0; sl811->irq_enable = 0; hcd->state = HC_STATE_HALT; hcd->self.controller->power.power_state = PMSG_SUSPEND; } sl811->ctrl1 = 0; sl811_write(sl811, SL11H_IRQ_ENABLE, 0); sl811_write(sl811, SL11H_IRQ_STATUS, ~0); if (sl811->board && sl811->board->port_power) { /* switch VBUS, at 500mA unless hub power budget gets set */ DBG("power %s\n", is_on ? "on" : "off"); sl811->board->port_power(hcd->self.controller, is_on); } /* reset as thoroughly as we can */ if (sl811->board && sl811->board->reset) sl811->board->reset(hcd->self.controller); else { sl811_write(sl811, SL11H_CTLREG1, SL11H_CTL1MASK_SE0); mdelay(20); } sl811_write(sl811, SL11H_IRQ_ENABLE, 0); sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); sl811_write(sl811, SL811HS_CTLREG2, SL811HS_CTL2_INIT); sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); // if !is_on, put into lowpower mode now}/*-------------------------------------------------------------------------*//* This is a PIO-only HCD. Queueing appends URBs to the endpoint's queue, * and may start I/O. Endpoint queues are scanned during completion irq * handlers (one per packet: ACK, NAK, faults, etc) and urb cancellation. * * Using an external DMA engine to copy a packet at a time could work, * though setup/teardown costs may be too big to make it worthwhile. *//* SETUP starts a new control request. Devices are not allowed to * STALL or NAK these; they must cancel any pending control requests. */static void setup_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control){ u8 addr; u8 len; void __iomem *data_reg; addr = SL811HS_PACKET_BUF(bank == 0); len = sizeof(struct usb_ctrlrequest); data_reg = sl811->data_reg; sl811_write_buf(sl811, addr, urb->setup_packet, len); /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); writeb(len, data_reg); writeb(SL_SETUP /* | ep->epnum */, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); /* always OUT/data0 */ ; sl811_write(sl811, bank + SL11H_HOSTCTLREG, control | SL11H_HCTLMASK_OUT); ep->length = 0; PACKET("SETUP qh%p\n", ep);}/* STATUS finishes control requests, often after IN or OUT data packets */static void status_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control){ int do_out; void __iomem *data_reg; do_out = urb->transfer_buffer_length && usb_pipein(urb->pipe); data_reg = sl811->data_reg; /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, 0); writeb(0, data_reg); writeb((do_out ? SL_OUT : SL_IN) /* | ep->epnum */, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); /* always data1; sometimes IN */ control |= SL11H_HCTLMASK_TOGGLE; if (do_out) control |= SL11H_HCTLMASK_OUT; sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); ep->length = 0; PACKET("STATUS%s/%s qh%p\n", ep->nak_count ? "/retry" : "", do_out ? "out" : "in", ep);}/* IN packets can be used with any type of endpoint. here we just * start the transfer, data from the peripheral may arrive later. * urb->iso_frame_desc is currently ignored here... */static void in_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control){ u8 addr; u8 len; void __iomem *data_reg; /* avoid losing data on overflow */ len = ep->maxpacket; addr = SL811HS_PACKET_BUF(bank == 0); if (!(control & SL11H_HCTLMASK_ISOCH) && usb_gettoggle(urb->dev, ep->epnum, 0)) control |= SL11H_HCTLMASK_TOGGLE; data_reg = sl811->data_reg; /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); writeb(len, data_reg); writeb(SL_IN | ep->epnum, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); ep->length = min((int)len, urb->transfer_buffer_length - urb->actual_length); PACKET("IN%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", !!usb_gettoggle(urb->dev, ep->epnum, 0), ep, len);}/* OUT packets can be used with any type of endpoint. * urb->iso_frame_desc is currently ignored here... */static void out_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control){ void *buf; u8 addr; u8 len; void __iomem *data_reg; buf = urb->transfer_buffer + urb->actual_length; prefetch(buf); len = min((int)ep->maxpacket, urb->transfer_buffer_length - urb->actual_length); if (!(control & SL11H_HCTLMASK_ISOCH) && usb_gettoggle(urb->dev, ep->epnum, 1)) control |= SL11H_HCTLMASK_TOGGLE; addr = SL811HS_PACKET_BUF(bank == 0); data_reg = sl811->data_reg; sl811_write_buf(sl811, addr, buf, len); /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); writeb(len, data_reg); writeb(SL_OUT | ep->epnum, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); sl811_write(sl811, bank + SL11H_HOSTCTLREG, control | SL11H_HCTLMASK_OUT); ep->length = len; PACKET("OUT%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", !!usb_gettoggle(urb->dev, ep->epnum, 1), ep, len);}/*-------------------------------------------------------------------------*//* caller updates on-chip enables later */static inline void sofirq_on(struct sl811 *sl811){ if (sl811->irq_enable & SL11H_INTMASK_SOFINTR) return; VDBG("sof irq on\n"); sl811->irq_enable |= SL11H_INTMASK_SOFINTR;}static inline void sofirq_off(struct sl811 *sl811){ if (!(sl811->irq_enable & SL11H_INTMASK_SOFINTR)) return; VDBG("sof irq off\n"); sl811->irq_enable &= ~SL11H_INTMASK_SOFINTR;}/*-------------------------------------------------------------------------*//* pick the next endpoint for a transaction, and issue it. * frames start with periodic transfers (after whatever is pending * from the previous frame), and the rest of the time is async * transfers, scheduled round-robin. */static struct sl811h_ep *start(struct sl811 *sl811, u8 bank){ struct sl811h_ep *ep; struct urb *urb; int fclock; u8 control; /* use endpoint at schedule head */ if (sl811->next_periodic) { ep = sl811->next_periodic; sl811->next_periodic = ep->next; } else { if (sl811->next_async) ep = sl811->next_async; else if (!list_empty(&sl811->async)) ep = container_of(sl811->async.next, struct sl811h_ep, schedule); else { /* could set up the first fullspeed periodic * transfer for the next frame ... */ return NULL; }#ifdef USE_B if ((bank && sl811->active_b == ep) || sl811->active_a == ep) return NULL;#endif if (ep->schedule.next == &sl811->async) sl811->next_async = NULL; else sl811->next_async = container_of(ep->schedule.next, struct sl811h_ep, schedule); } if (unlikely(list_empty(&ep->hep->urb_list))) { DBG("empty %p queue?\n", ep); return NULL; } urb = container_of(ep->hep->urb_list.next, struct urb, urb_list); control = ep->defctrl; /* if this frame doesn't have enough time left to transfer this * packet, wait till the next frame. too-simple algorithm... */ fclock = sl811_read(sl811, SL11H_SOFTMRREG) << 6; fclock -= 100; /* setup takes not much time */ if (urb->dev->speed == USB_SPEED_LOW) { if (control & SL11H_HCTLMASK_PREAMBLE) { /* also note erratum 1: some hubs won't work */ fclock -= 800; } fclock -= ep->maxpacket << 8; /* erratum 2: AFTERSOF only works for fullspeed */ if (fclock < 0) { if (ep->period) sl811->stat_overrun++; sofirq_on(sl811); return NULL; } } else { fclock -= 12000 / 19; /* 19 64byte packets/msec */ if (fclock < 0) { if (ep->period) sl811->stat_overrun++; control |= SL11H_HCTLMASK_AFTERSOF; /* throttle bulk/control irq noise */ } else if (ep->nak_count) control |= SL11H_HCTLMASK_AFTERSOF; } switch (ep->nextpid) { case USB_PID_IN: in_packet(sl811, ep, urb, bank, control); break; case USB_PID_OUT: out_packet(sl811, ep, urb, bank, control); break; case USB_PID_SETUP: setup_packet(sl811, ep, urb, bank, control); break; case USB_PID_ACK: /* for control status */ status_packet(sl811, ep, urb, bank, control); break; default: DBG("bad ep%p pid %02x\n", ep, ep->nextpid); ep = NULL; } return ep;}#define MIN_JIFFIES ((msecs_to_jiffies(2) > 1) ? msecs_to_jiffies(2) : 2)static inline void start_transfer(struct sl811 *sl811){ if (sl811->port1 & (1 << USB_PORT_FEAT_SUSPEND)) return; if (sl811->active_a == NULL) { sl811->active_a = start(sl811, SL811_EP_A(SL811_HOST_BUF)); if (sl811->active_a != NULL) sl811->jiffies_a = jiffies + MIN_JIFFIES; }#ifdef USE_B if (sl811->active_b == NULL) { sl811->active_b = start(sl811, SL811_EP_B(SL811_HOST_BUF)); if (sl811->active_b != NULL) sl811->jiffies_b = jiffies + MIN_JIFFIES; }#endif}static void finish_request( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, int status) __releases(sl811->lock) __acquires(sl811->lock){ unsigned i; if (usb_pipecontrol(urb->pipe)) ep->nextpid = USB_PID_SETUP; spin_lock(&urb->lock); if (urb->status == -EINPROGRESS) urb->status = status; urb->hcpriv = NULL; spin_unlock(&urb->lock); spin_unlock(&sl811->lock); usb_hcd_giveback_urb(sl811_to_hcd(sl811), urb); spin_lock(&sl811->lock); /* leave active endpoints in the schedule */ if (!list_empty(&ep->hep->urb_list)) return; /* async deschedule? */ if (!list_empty(&ep->schedule)) { list_del_init(&ep->schedule); if (ep == sl811->next_async) sl811->next_async = NULL; 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 sl811h_ep *temp; struct sl811h_ep **prev = &sl811->periodic[i];
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -