summaryrefslogtreecommitdiff
path: root/drivers/usb/host/sl811-hcd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/sl811-hcd.c')
-rw-r--r--drivers/usb/host/sl811-hcd.c1851
1 files changed, 1851 insertions, 0 deletions
diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c
new file mode 100644
index 000000000000..d309e292198e
--- /dev/null
+++ b/drivers/usb/host/sl811-hcd.c
@@ -0,0 +1,1851 @@
+/*
+ * SL811HS HCD (Host Controller Driver) for USB.
+ *
+ * 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 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/config.h>
+
+#ifdef CONFIG_USB_DEBUG
+# define DEBUG
+#else
+# undef DEBUG
+#endif
+
+#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 <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 "15 Dec 2004"
+
+
+#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 QUIRK3
+
+static 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);
+
+ 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 cancelation.
+ *
+ * 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,
+ struct pt_regs *regs,
+ 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;
+ spin_unlock(&urb->lock);
+
+ spin_unlock(&sl811->lock);
+ usb_hcd_giveback_urb(sl811_to_hcd(sl811), urb, regs);
+ 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];
+
+ while (*prev && ((temp = *prev) != ep))
+ prev = &temp->next;
+ if (*prev)
+ *prev = ep->next;
+ sl811->load[i] -= ep->load;
+ }
+ ep->branch = PERIODIC_SIZE;
+ sl811->periodic_count--;
+ sl811_to_hcd(sl811)->self.bandwidth_allocated
+ -= ep->load / ep->period;
+ if (ep == sl811->next_periodic)
+ sl811->next_periodic = ep->next;
+
+ /* we might turn SOFs back on again for the async schedule */
+ if (sl811->periodic_count == 0)
+ sofirq_off(sl811);
+}
+
+static void
+done(struct sl811 *sl811, struct sl811h_ep *ep, u8 bank, struct pt_regs *regs)
+{
+ u8 status;
+ struct urb *urb;
+ int urbstat = -EINPROGRESS;
+
+ if (unlikely(!ep))
+ return;
+
+ status = sl811_read(sl811, bank + SL11H_PKTSTATREG);
+
+ urb = container_of(ep->hep->urb_list.next, struct urb, urb_list);
+
+ /* we can safely ignore NAKs */
+ if (status & SL11H_STATMASK_NAK) {
+ // PACKET("...NAK_%02x qh%p\n", bank, ep);
+ if (!ep->period)
+ ep->nak_count++;
+ ep->error_count = 0;
+
+ /* ACK advances transfer, toggle, and maybe queue */
+ } else if (status & SL11H_STATMASK_ACK) {
+ struct usb_device *udev = urb->dev;
+ int len;
+ unsigned char *buf;
+
+ /* urb->iso_frame_desc is currently ignored here... */
+
+ ep->nak_count = ep->error_count = 0;
+ switch (ep->nextpid) {
+ case USB_PID_OUT:
+ // PACKET("...ACK/out_%02x qh%p\n", bank, ep);
+ urb->actual_length += ep->length;
+ usb_dotoggle(udev, ep->epnum, 1);
+ if (urb->actual_length
+ == urb->transfer_buffer_length) {
+ if (usb_pipecontrol(urb->pipe))
+ ep->nextpid = USB_PID_ACK;
+
+ /* some bulk protocols terminate OUT transfers
+ * by a short packet, using ZLPs not padding.
+ */
+ else if (ep->length < ep->maxpacket
+ || !(urb->transfer_flags
+ & URB_ZERO_PACKET))
+ urbstat = 0;
+ }
+ break;
+ case USB_PID_IN:
+ // PACKET("...ACK/in_%02x qh%p\n", bank, ep);
+ buf = urb->transfer_buffer + urb->actual_length;
+ prefetchw(buf);
+ len = ep->maxpacket - sl811_read(sl811,
+ bank + SL11H_XFERCNTREG);
+ if (len > ep->length) {
+ len = ep->length;
+ urb->status = -EOVERFLOW;
+ }
+ urb->actual_length += len;
+ sl811_read_buf(sl811, SL811HS_PACKET_BUF(bank == 0),
+ buf, len);
+ usb_dotoggle(udev, ep->epnum, 0);
+ if (urb->actual_length == urb->transfer_buffer_length)
+ urbstat = 0;
+ else if (len < ep->maxpacket) {
+ if (urb->transfer_flags & URB_SHORT_NOT_OK)
+ urbstat = -EREMOTEIO;
+ else
+ urbstat = 0;
+ }
+ if (usb_pipecontrol(urb->pipe)
+ && (urbstat == -EREMOTEIO
+ || urbstat == 0)) {
+
+ /* NOTE if the status stage STALLs (why?),
+ * this reports the wrong urb status.
+ */
+ spin_lock(&urb->lock);
+ if (urb->status == -EINPROGRESS)
+ urb->status = urbstat;
+ spin_unlock(&urb->lock);
+
+ urb = NULL;
+ ep->nextpid = USB_PID_ACK;
+ }
+ break;
+ case USB_PID_SETUP:
+ // PACKET("...ACK/setup_%02x qh%p\n", bank, ep);
+ 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:
+ // PACKET("...ACK/status_%02x qh%p\n", bank, ep);
+ urbstat = 0;
+ break;
+ }
+
+ /* STALL stops all transfers */
+ } else if (status & SL11H_STATMASK_STALL) {
+ PACKET("...STALL_%02x qh%p\n", bank, ep);
+ ep->nak_count = ep->error_count = 0;
+ urbstat = -EPIPE;
+
+ /* error? retry, until "3 strikes" */
+ } else if (++ep->error_count >= 3) {
+ if (status & SL11H_STATMASK_TMOUT)
+ urbstat = -ETIMEDOUT;
+ else if (status & SL11H_STATMASK_OVF)
+ urbstat = -EOVERFLOW;
+ else
+ urbstat = -EPROTO;
+ ep->error_count = 0;
+ PACKET("...3STRIKES_%02x %02x qh%p stat %d\n",
+ bank, status, ep, urbstat);
+ }
+
+ if (urb && (urbstat != -EINPROGRESS || urb->status != -EINPROGRESS))
+ finish_request(sl811, ep, urb, regs, urbstat);
+}
+
+static inline u8 checkdone(struct sl811 *sl811)
+{
+ u8 ctl;
+ u8 irqstat = 0;
+
+ if (sl811->active_a && time_before_eq(sl811->jiffies_a, jiffies)) {
+ ctl = sl811_read(sl811, SL811_EP_A(SL11H_HOSTCTLREG));
+ if (ctl & SL11H_HCTLMASK_ARM)
+ sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0);
+ DBG("%s DONE_A: ctrl %02x sts %02x\n",
+ (ctl & SL11H_HCTLMASK_ARM) ? "timeout" : "lost",
+ ctl,
+ sl811_read(sl811, SL811_EP_A(SL11H_PKTSTATREG)));
+ irqstat |= SL11H_INTMASK_DONE_A;
+ }
+#ifdef USE_B
+ if (sl811->active_b && time_before_eq(sl811->jiffies_b, jiffies)) {
+ ctl = sl811_read(sl811, SL811_EP_B(SL11H_HOSTCTLREG));
+ if (ctl & SL11H_HCTLMASK_ARM)
+ sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0);
+ DBG("%s DONE_B: ctrl %02x sts %02x\n",
+ (ctl & SL11H_HCTLMASK_ARM) ? "timeout" : "lost",
+ ctl,
+ sl811_read(sl811, SL811_EP_B(SL11H_PKTSTATREG)));
+ irqstat |= SL11H_INTMASK_DONE_A;
+ }
+#endif
+ return irqstat;
+}
+
+static irqreturn_t sl811h_irq(struct usb_hcd *hcd, struct pt_regs *regs)
+{
+ struct sl811 *sl811 = hcd_to_sl811(hcd);
+ u8 irqstat;
+ irqreturn_t ret = IRQ_NONE;
+ unsigned retries = 5;
+
+ spin_lock(&sl811->lock);
+
+retry:
+ irqstat = sl811_read(sl811, SL11H_IRQ_STATUS) & ~SL11H_INTMASK_DP;
+ if (irqstat) {
+ sl811_write(sl811, SL11H_IRQ_STATUS, irqstat);
+ irqstat &= sl811->irq_enable;
+ }
+
+#ifdef QUIRK2
+ /* this may no longer be necessary ... */
+ if (irqstat == 0 && ret == IRQ_NONE) {
+ irqstat = checkdone(sl811);
+ if (irqstat /* && irq != ~0 */ )
+ sl811->stat_lost++;
+ }
+#endif
+
+ /* USB packets, not necessarily handled in the order they're
+ * issued ... that's fine if they're different endpoints.
+ */
+ if (irqstat & SL11H_INTMASK_DONE_A) {
+ done(sl811, sl811->active_a, SL811_EP_A(SL811_HOST_BUF), regs);
+ sl811->active_a = NULL;
+ sl811->stat_a++;
+ }
+#ifdef USE_B
+ if (irqstat & SL11H_INTMASK_DONE_B) {
+ done(sl811, sl811->active_b, SL811_EP_B(SL811_HOST_BUF), regs);
+ sl811->active_b = NULL;
+ sl811->stat_b++;
+ }
+#endif
+ if (irqstat & SL11H_INTMASK_SOFINTR) {
+ unsigned index;
+
+ index = sl811->frame++ % (PERIODIC_SIZE - 1);
+ sl811->stat_sof++;
+
+ /* be graceful about almost-inevitable periodic schedule
+ * overruns: continue the previous frame's transfers iff
+ * this one has nothing scheduled.
+ */
+ if (sl811->next_periodic) {
+ // ERR("overrun to slot %d\n", index);
+ sl811->stat_overrun++;
+ }
+ if (sl811->periodic[index])
+ sl811->next_periodic = sl811->periodic[index];
+ }
+
+ /* khubd manages debouncing and wakeup */
+ if (irqstat & SL11H_INTMASK_INSRMV) {
+ sl811->stat_insrmv++;
+
+ /* most stats are reset for each VBUS session */
+ sl811->stat_wake = 0;
+ sl811->stat_sof = 0;
+ sl811->stat_a = 0;
+ sl811->stat_b = 0;
+ sl811->stat_lost = 0;
+
+ sl811->ctrl1 = 0;
+ sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);
+
+ sl811->irq_enable = SL11H_INTMASK_INSRMV;
+ sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable);
+
+ /* usbcore nukes other pending transactions on disconnect */
+ if (sl811->active_a) {
+ sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0);
+ finish_request(sl811, sl811->active_a,
+ container_of(sl811->active_a->hep->urb_list.next,
+ struct urb, urb_list),
+ NULL, -ESHUTDOWN);
+ sl811->active_a = NULL;
+ }
+#ifdef USE_B
+ if (sl811->active_b) {
+ sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0);
+ finish_request(sl811, sl811->active_b,
+ container_of(sl811->active_b->hep->urb_list.next,
+ struct urb, urb_list),
+ NULL, -ESHUTDOWN);
+ sl811->active_b = NULL;
+ }
+#endif
+
+ /* port status seems wierd until after reset, so
+ * force the reset and make khubd clean up later.
+ */
+ sl811->port1 |= (1 << USB_PORT_FEAT_C_CONNECTION)
+ | (1 << USB_PORT_FEAT_CONNECTION);
+
+ } else if (irqstat & SL11H_INTMASK_RD) {
+ if (sl811->port1 & (1 << USB_PORT_FEAT_SUSPEND)) {
+ DBG("wakeup\n");
+ sl811->port1 |= 1 << USB_PORT_FEAT_C_SUSPEND;
+ sl811->stat_wake++;
+ } else
+ irqstat &= ~SL11H_INTMASK_RD;
+ }
+
+ if (irqstat) {
+ if (sl811->port1 & (1 << USB_PORT_FEAT_ENABLE))
+ start_transfer(sl811);
+ ret = IRQ_HANDLED;
+ if (retries--)
+ goto retry;
+ }
+
+ if (sl811->periodic_count == 0 && list_empty(&sl811->async))
+ sofirq_off(sl811);
+ sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable);
+
+ spin_unlock(&sl811->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.
+ */
+#define MAX_PERIODIC_LOAD 500 /* out of 1000 usec */
+
+static int balance(struct sl811 *sl811, 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 || sl811->load[branch] > sl811->load[i]) {
+ int j;
+
+ for (j = i; j < PERIODIC_SIZE; j += period) {
+ if ((sl811->load[j] + load)
+ > MAX_PERIODIC_LOAD)
+ break;
+ }
+ if (j < PERIODIC_SIZE)
+ continue;
+ branch = i;
+ }
+ }
+ return branch;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int sl811h_urb_enqueue(
+ struct usb_hcd *hcd,
+ struct usb_host_endpoint *hep,
+ struct urb *urb,
+ int mem_flags
+) {
+ struct sl811 *sl811 = hcd_to_sl811(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 sl811h_ep *ep = NULL;
+ unsigned long flags;
+ int i;
+ int retval = 0;
+
+#ifdef DISABLE_ISO
+ if (type == PIPE_ISOCHRONOUS)
+ return -ENOSPC;
+#endif
+
+ /* avoid all allocations within spinlocks */
+ if (!hep->hcpriv)
+ ep = kcalloc(1, sizeof *ep, mem_flags);
+
+ spin_lock_irqsave(&sl811->lock, flags);
+
+ /* don't submit to a dead or disabled port */
+ if (!(sl811->port1 & (1 << USB_PORT_FEAT_ENABLE))
+ || !HC_IS_RUNNING(hcd->state)) {
+ retval = -ENODEV;
+ goto fail;
+ }
+
+ if (hep->hcpriv) {
+ kfree(ep);
+ ep = hep->hcpriv;
+ } else if (!ep) {
+ retval = -ENOMEM;
+ goto fail;
+
+ } else {
+ INIT_LIST_HEAD(&ep->schedule);
+ ep->udev = usb_get_dev(udev);
+ ep->epnum = epnum;
+ ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out);
+ ep->defctrl = SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENABLE;
+ 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 (ep->maxpacket > H_MAXPACKET) {
+ /* iso packets up to 240 bytes could work... */
+ DBG("dev %d ep%d maxpacket %d\n",
+ udev->devnum, epnum, ep->maxpacket);
+ retval = -EINVAL;
+ goto fail;
+ }
+
+ if (udev->speed == USB_SPEED_LOW) {
+ /* send preamble for external hub? */
+ if (!(sl811->ctrl1 & SL11H_CTL1MASK_LSPD))
+ ep->defctrl |= SL11H_HCTLMASK_PREAMBLE;
+ }
+ switch (type) {
+ case PIPE_ISOCHRONOUS:
+ case PIPE_INTERRUPT:
+ if (urb->interval > PERIODIC_SIZE)
+ urb->interval = PERIODIC_SIZE;
+ ep->period = urb->interval;
+ ep->branch = PERIODIC_SIZE;
+ if (type == PIPE_ISOCHRONOUS)
+ ep->defctrl |= SL11H_HCTLMASK_ISOCH;
+ ep->load = usb_calc_bus_time(udev->speed, !is_out,
+ (type == PIPE_ISOCHRONOUS),
+ usb_maxpacket(udev, pipe, is_out))
+ / 1000;
+ break;
+ }
+
+ hep->hcpriv = ep;
+ }
+
+ /* maybe put endpoint into schedule */
+ switch (type) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ if (list_empty(&ep->schedule))
+ list_add_tail(&ep->schedule, &sl811->async);
+ break;
+ case PIPE_ISOCHRONOUS:
+ case PIPE_INTERRUPT:
+ urb->interval = ep->period;
+ if (ep->branch < PERIODIC_SIZE)
+ break;
+
+ retval = balance(sl811, ep->period, ep->load);
+ if (retval < 0)
+ goto fail;
+ ep->branch = retval;
+ retval = 0;
+ urb->start_frame = (sl811->frame & (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 sl811h_ep **prev = &sl811->periodic[i];
+ struct sl811h_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;
+ }
+ sl811->load[i] += ep->load;
+ }
+ sl811->periodic_count++;
+ hcd->self.bandwidth_allocated += ep->load / ep->period;
+ sofirq_on(sl811);
+ }
+
+ /* in case of unlink-during-submit */
+ spin_lock(&urb->lock);
+ if (urb->status != -EINPROGRESS) {
+ spin_unlock(&urb->lock);
+ finish_request(sl811, ep, urb, NULL, 0);
+ retval = 0;
+ goto fail;
+ }
+ urb->hcpriv = hep;
+ spin_unlock(&urb->lock);
+
+ start_transfer(sl811);
+ sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable);
+fail:
+ spin_unlock_irqrestore(&sl811->lock, flags);
+ return retval;
+}
+
+static int sl811h_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
+{
+ struct sl811 *sl811 = hcd_to_sl811(hcd);
+ struct usb_host_endpoint *hep = urb->hcpriv;
+ unsigned long flags;
+ struct sl811h_ep *ep;
+ int retval = 0;
+
+ if (!hep)
+ return -EINVAL;
+
+ spin_lock_irqsave(&sl811->lock, flags);
+ ep = hep->hcpriv;
+ if (ep) {
+ /* finish right away if this urb can't be active ...
+ * note that some drivers wrongly expect delays
+ */
+ if (ep->hep->urb_list.next != &urb->urb_list) {
+ /* not front of queue? never active */
+
+ /* for active transfers, we expect an IRQ */
+ } else if (sl811->active_a == ep) {
+ if (time_before_eq(sl811->jiffies_a, jiffies)) {
+ /* happens a lot with lowspeed?? */
+ DBG("giveup on DONE_A: ctrl %02x sts %02x\n",
+ sl811_read(sl811,
+ SL811_EP_A(SL11H_HOSTCTLREG)),
+ sl811_read(sl811,
+ SL811_EP_A(SL11H_PKTSTATREG)));
+ sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG),
+ 0);
+ sl811->active_a = NULL;
+ } else
+ urb = NULL;
+#ifdef USE_B
+ } else if (sl811->active_b == ep) {
+ if (time_before_eq(sl811->jiffies_a, jiffies)) {
+ /* happens a lot with lowspeed?? */
+ DBG("giveup on DONE_B: ctrl %02x sts %02x\n",
+ sl811_read(sl811,
+ SL811_EP_B(SL11H_HOSTCTLREG)),
+ sl811_read(sl811,
+ SL811_EP_B(SL11H_PKTSTATREG)));
+ sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG),
+ 0);
+ sl811->active_b = NULL;
+ } else
+ urb = NULL;
+#endif
+ } else {
+ /* front of queue for inactive endpoint */
+ }
+
+ if (urb)
+ finish_request(sl811, ep, urb, NULL, 0);
+ else
+ VDBG("dequeue, urb %p active %s; wait4irq\n", urb,
+ (sl811->active_a == ep) ? "A" : "B");
+ } else
+ retval = -EINVAL;
+ spin_unlock_irqrestore(&sl811->lock, flags);
+ return retval;
+}
+
+static void
+sl811h_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep)
+{
+ struct sl811h_ep *ep = hep->hcpriv;
+
+ if (!ep)
+ return;
+
+ /* assume we'd just wait for the irq */
+ if (!list_empty(&hep->urb_list))
+ 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
+sl811h_get_frame(struct usb_hcd *hcd)
+{
+ struct sl811 *sl811 = hcd_to_sl811(hcd);
+
+ /* wrong except while periodic transfers are scheduled;
+ * never matches the on-the-wire frame;
+ * subject to overruns.
+ */
+ return sl811->frame;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* the virtual root hub timer IRQ checks for hub status */
+static int
+sl811h_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+ struct sl811 *sl811 = hcd_to_sl811(hcd);
+#ifdef QUIRK3
+ unsigned long flags;
+
+ /* non-SMP HACK: use root hub timer as i/o watchdog
+ * this seems essential when SOF IRQs aren't in use...
+ */
+ local_irq_save(flags);
+ if (!timer_pending(&sl811->timer)) {
+ if (sl811h_irq( /* ~0, */ hcd, NULL) != IRQ_NONE)
+ sl811->stat_lost++;
+ }
+ local_irq_restore(flags);
+#endif
+
+ if (!(sl811->port1 & (0xffff << 16)))
+ return 0;
+
+ /* tell khubd port 1 changed */
+ *buf = (1 << 1);
+ return 1;
+}
+
+static void
+sl811h_hub_descriptor (
+ struct sl811 *sl811,
+ struct usb_hub_descriptor *desc
+) {
+ u16 temp = 0;
+
+ desc->bDescriptorType = 0x29;
+ desc->bHubContrCurrent = 0;
+
+ desc->bNbrPorts = 1;
+ desc->bDescLength = 9;
+
+ /* per-port power switching (gang of one!), or none */
+ desc->bPwrOn2PwrGood = 0;
+ if (sl811->board && sl811->board->port_power) {
+ desc->bPwrOn2PwrGood = sl811->board->potpg;
+ if (!desc->bPwrOn2PwrGood)
+ desc->bPwrOn2PwrGood = 10;
+ temp = 0x0001;
+ } else
+ temp = 0x0002;
+
+ /* no overcurrent errors detection/handling */
+ temp |= 0x0010;
+
+ desc->wHubCharacteristics = (__force __u16)cpu_to_le16(temp);
+
+ /* two bitmaps: ports removable, and legacy PortPwrCtrlMask */
+ desc->bitmap[0] = 1 << 1;
+ desc->bitmap[1] = ~0;
+}
+
+static void
+sl811h_timer(unsigned long _sl811)
+{
+ struct sl811 *sl811 = (void *) _sl811;
+ unsigned long flags;
+ u8 irqstat;
+ u8 signaling = sl811->ctrl1 & SL11H_CTL1MASK_FORCE;
+ const u32 mask = (1 << USB_PORT_FEAT_CONNECTION)
+ | (1 << USB_PORT_FEAT_ENABLE)
+ | (1 << USB_PORT_FEAT_LOWSPEED);
+
+ spin_lock_irqsave(&sl811->lock, flags);
+
+ /* stop special signaling */
+ sl811->ctrl1 &= ~SL11H_CTL1MASK_FORCE;
+ sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);
+ udelay(3);
+
+ irqstat = sl811_read(sl811, SL11H_IRQ_STATUS);
+
+ switch (signaling) {
+ case SL11H_CTL1MASK_SE0:
+ DBG("end reset\n");
+ sl811->port1 = (1 << USB_PORT_FEAT_C_RESET)
+ | (1 << USB_PORT_FEAT_POWER);
+ sl811->ctrl1 = 0;
+ /* don't wrongly ack RD */
+ if (irqstat & SL11H_INTMASK_INSRMV)
+ irqstat &= ~SL11H_INTMASK_RD;
+ break;
+ case SL11H_CTL1MASK_K:
+ DBG("end resume\n");
+ sl811->port1 &= ~(1 << USB_PORT_FEAT_SUSPEND);
+ break;
+ default:
+ DBG("odd timer signaling: %02x\n", signaling);
+ break;
+ }
+ sl811_write(sl811, SL11H_IRQ_STATUS, irqstat);
+
+ if (irqstat & SL11H_INTMASK_RD) {
+ /* usbcore nukes all pending transactions on disconnect */
+ if (sl811->por