Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/usb/host/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 45 kB image not shown  

Quelle  sl811-hcd.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * 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
 * - 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/errno.h>
#include <linux/timer.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <linux/usb/sl811.h>
#include <linux/usb/hcd.h>
#include <linux/platform_device.h>
#include <linux/prefetch.h>
#include <linux/string_choices.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/byteorder.h>
#include <linux/unaligned.h>

#include "sl811.h"


MODULE_DESCRIPTION("SL811HS USB Host Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:sl811-hcd");

#define DRIVER_VERSION "19 May 2005"

/* for now, use only one transfer register bank */
#undef USE_B

// #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 & USB_PORT_STAT_POWER)
   return;

  sl811->port1 = USB_PORT_STAT_POWER;
  sl811->irq_enable = SL11H_INTMASK_INSRMV;
 } else {
  sl811->port1 = 0;
  sl811->irq_enable = 0;
  hcd->state = HC_STATE_HALT;
 }
 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 */
  dev_dbg(hcd->self.controller, "power %s\n",
   str_on_off(is_on));
  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_t(u32, 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_t(u32, 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;
 dev_dbg(sl811_to_hcd(sl811)->self.controller, "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;
 dev_dbg(sl811_to_hcd(sl811)->self.controller, "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))) {
  dev_dbg(sl811_to_hcd(sl811)->self.controller,
   "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:
  dev_dbg(sl811_to_hcd(sl811)->self.controller,
   "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 & USB_PORT_STAT_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;

 usb_hcd_unlink_urb_from_ep(sl811_to_hcd(sl811), urb);
 spin_unlock(&sl811->lock);
 usb_hcd_giveback_urb(sl811_to_hcd(sl811), urb, status);
 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 */
 dev_dbg(sl811_to_hcd(sl811)->self.controller,
  "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)
{
 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;
    urbstat = -EOVERFLOW;
   }
   urb->actual_length += len;
   sl811_read_buf(sl811, SL811HS_PACKET_BUF(bank == 0),
     buf, len);
   usb_dotoggle(udev, ep->epnum, 0);
   if (urbstat == -EINPROGRESS &&
     (len < ep->maxpacket ||
      urb->actual_length ==
      urb->transfer_buffer_length)) {
    if (usb_pipecontrol(urb->pipe))
     ep->nextpid = USB_PID_ACK;
    else
     urbstat = 0;
   }
   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 = -ETIME;
  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 (urbstat != -EINPROGRESS || urb->unlinked)
  finish_request(sl811, ep, urb, urbstat);
}

#ifdef QUIRK2
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);
  dev_dbg(sl811_to_hcd(sl811)->self.controller,
   "%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);
  dev_dbg(sl811_to_hcd(sl811)->self.controller,
   "%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;
}
#endif

static irqreturn_t sl811h_irq(struct usb_hcd *hcd)
{
 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) {
  irqstat = checkdone(sl811);
  if (irqstat)
   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));
  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));
  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) {
   // dev_err(hcd->self.controller, "overrun to slot %d\n", index);
   sl811->stat_overrun++;
  }
  if (sl811->periodic[index])
   sl811->next_periodic = sl811->periodic[index];
 }

 /* hub_wq 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),
    -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 weird until after reset, so
 * force the reset and make hub_wq clean up later.
 */

  if (irqstat & SL11H_INTMASK_RD)
   sl811->port1 &= ~USB_PORT_STAT_CONNECTION;
  else
   sl811->port1 |= USB_PORT_STAT_CONNECTION;

  sl811->port1 |= USB_PORT_STAT_C_CONNECTION << 16;

 } else if (irqstat & SL11H_INTMASK_RD) {
  if (sl811->port1 & USB_PORT_STAT_SUSPEND) {
   dev_dbg(hcd->self.controller, "wakeup\n");
   sl811->port1 |= USB_PORT_STAT_C_SUSPEND << 16;
   sl811->stat_wake++;
  } else
   irqstat &= ~SL11H_INTMASK_RD;
 }

 if (irqstat) {
  if (sl811->port1 & USB_PORT_STAT_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.
 *
 * NOTE:  the periodic schedule is a sparse tree, with the load for
 * each branch minimized.  see fig 3.5 in the OHCI spec for example.
 */

#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 urb  *urb,
 gfp_t   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;
 struct usb_host_endpoint *hep = urb->ep;

#ifndef CONFIG_USB_SL811_HCD_ISO
 if (type == PIPE_ISOCHRONOUS)
  return -ENOSPC;
#endif

 /* avoid all allocations within spinlocks */
 if (!hep->hcpriv) {
  ep = kzalloc(sizeof *ep, mem_flags);
  if (ep == NULL)
   return -ENOMEM;
 }

 spin_lock_irqsave(&sl811->lock, flags);

 /* don't submit to a dead or disabled port */
 if (!(sl811->port1 & USB_PORT_STAT_ENABLE)
   || !HC_IS_RUNNING(hcd->state)) {
  retval = -ENODEV;
  kfree(ep);
  goto fail_not_linked;
 }
 retval = usb_hcd_link_urb_to_ep(hcd, urb);
 if (retval) {
  kfree(ep);
  goto fail_not_linked;
 }

 if (hep->hcpriv) {
  kfree(ep);
  ep = hep->hcpriv;
 } else if (!ep) {
  retval = -ENOMEM;
  goto fail;

 } else {
  INIT_LIST_HEAD(&ep->schedule);
  ep->udev = udev;
  ep->epnum = epnum;
  ep->maxpacket = usb_maxpacket(udev, urb->pipe);
  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... */
   dev_dbg(hcd->self.controller,
    "dev %d ep%d maxpacket %d\n", udev->devnum,
    epnum, ep->maxpacket);
   retval = -EINVAL;
   kfree(ep);
   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))
     / 1000;
   break;
  }

  ep->hep = hep;
  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) {
   /* NOTE:  the phase is correct here, but the value
 * needs offsetting by the transfer queue depth.
 * All current drivers ignore start_frame, so this
 * is unlikely to ever matter...
 */

   urb->start_frame = (sl811->frame & (PERIODIC_SIZE - 1))
      + ep->branch;
   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
 */

  dev_dbg(hcd->self.controller, "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);
 }

 urb->hcpriv = hep;
 start_transfer(sl811);
 sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable);
fail:
 if (retval)
  usb_hcd_unlink_urb_from_ep(hcd, urb);
fail_not_linked:
 spin_unlock_irqrestore(&sl811->lock, flags);
 return retval;
}

static int sl811h_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
 struct sl811  *sl811 = hcd_to_sl811(hcd);
 struct usb_host_endpoint *hep;
 unsigned long  flags;
 struct sl811h_ep *ep;
 int   retval;

 spin_lock_irqsave(&sl811->lock, flags);
 retval = usb_hcd_check_unlink_urb(hcd, urb, status);
 if (retval)
  goto fail;

 hep = urb->hcpriv;
 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?? */
    dev_dbg(hcd->self.controller,
     "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?? */
    dev_dbg(hcd->self.controller,
     "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, 0);
  else
   dev_dbg(sl811_to_hcd(sl811)->self.controller,
    "dequeue, urb %p active %s; wait4irq\n", urb,
    (sl811->active_a == ep) ? "A" : "B");
 } else
  retval = -EINVAL;
 fail:
 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))
  dev_warn(hcd->self.controller, "ep %p not empty?\n", ep);

 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) != IRQ_NONE)
   sl811->stat_lost++;
 }
 local_irq_restore(flags);
#endif

 if (!(sl811->port1 & (0xffff << 16)))
  return 0;

 /* tell hub_wq 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 = USB_DT_HUB;
 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 = HUB_CHAR_INDV_PORT_LPSM;
 } else
  temp = HUB_CHAR_NO_LPSM;

 /* no overcurrent errors detection/handling */
 temp |= HUB_CHAR_NO_OCPM;

 desc->wHubCharacteristics = cpu_to_le16(temp);

 /* ports removable, and legacy PortPwrCtrlMask */
 desc->u.hs.DeviceRemovable[0] = 0 << 1;
 desc->u.hs.DeviceRemovable[1] = ~0;
}

static void
sl811h_timer(struct timer_list *t)
{
 struct sl811  *sl811 = timer_container_of(sl811, t, timer);
 unsigned long flags;
 u8  irqstat;
 u8  signaling = sl811->ctrl1 & SL11H_CTL1MASK_FORCE;
 const u32 mask = USB_PORT_STAT_CONNECTION
    | USB_PORT_STAT_ENABLE
    | USB_PORT_STAT_LOW_SPEED;

 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:
  dev_dbg(sl811_to_hcd(sl811)->self.controller, "end reset\n");
  sl811->port1 = (USB_PORT_STAT_C_RESET << 16)
     | USB_PORT_STAT_POWER;
  sl811->ctrl1 = 0;
  /* don't wrongly ack RD */
  if (irqstat & SL11H_INTMASK_INSRMV)
   irqstat &= ~SL11H_INTMASK_RD;
  break;
 case SL11H_CTL1MASK_K:
  dev_dbg(sl811_to_hcd(sl811)->self.controller, "end resume\n");
  sl811->port1 &= ~USB_PORT_STAT_SUSPEND;
  break;
 default:
  dev_dbg(sl811_to_hcd(sl811)->self.controller,
   "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->port1 & USB_PORT_STAT_CONNECTION)
   sl811->port1 |= (USB_PORT_STAT_C_CONNECTION << 16)
     | (USB_PORT_STAT_C_ENABLE << 16);
  sl811->port1 &= ~mask;
  sl811->irq_enable = SL11H_INTMASK_INSRMV;
 } else {
  sl811->port1 |= mask;
  if (irqstat & SL11H_INTMASK_DP)
   sl811->port1 &= ~USB_PORT_STAT_LOW_SPEED;
  sl811->irq_enable = SL11H_INTMASK_INSRMV | SL11H_INTMASK_RD;
 }

 if (sl811->port1 & USB_PORT_STAT_CONNECTION) {
  u8 ctrl2 = SL811HS_CTL2_INIT;

  sl811->irq_enable |= SL11H_INTMASK_DONE_A;
#ifdef USE_B
  sl811->irq_enable |= SL11H_INTMASK_DONE_B;
#endif
  if (sl811->port1 & USB_PORT_STAT_LOW_SPEED) {
   sl811->ctrl1 |= SL11H_CTL1MASK_LSPD;
   ctrl2 |= SL811HS_CTL2MASK_DSWAP;
  }

  /* start SOFs flowing, kickstarting with A registers */
  sl811->ctrl1 |= SL11H_CTL1MASK_SOF_ENA;
  sl811_write(sl811, SL11H_SOFLOWREG, 0xe0);
  sl811_write(sl811, SL811HS_CTLREG2, ctrl2);

  /* autoincrementing */
  sl811_write(sl811, SL811_EP_A(SL11H_BUFLNTHREG), 0);
  writeb(SL_SOF, sl811->data_reg);
  writeb(0, sl811->data_reg);
  sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG),
    SL11H_HCTLMASK_ARM);

  /* hub_wq provides debounce delay */
 } else {
  sl811->ctrl1 = 0;
 }
 sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);

 /* reenable irqs */
 sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable);
 spin_unlock_irqrestore(&sl811->lock, flags);
}

static int
sl811h_hub_control(
 struct usb_hcd *hcd,
 u16  typeReq,
 u16  wValue,
 u16  wIndex,
 char  *buf,
 u16  wLength
) {
 struct sl811 *sl811 = hcd_to_sl811(hcd);
 int  retval = 0;
 unsigned long flags;

 spin_lock_irqsave(&sl811->lock, flags);

 switch (typeReq) {
 case ClearHubFeature:
 case SetHubFeature:
  switch (wValue) {
  case C_HUB_OVER_CURRENT:
  case C_HUB_LOCAL_POWER:
   break;
  default:
   goto error;
  }
  break;
 case ClearPortFeature:
  if (wIndex != 1 || wLength != 0)
   goto error;

  switch (wValue) {
  case USB_PORT_FEAT_ENABLE:
   sl811->port1 &= USB_PORT_STAT_POWER;
   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);
   break;
  case USB_PORT_FEAT_SUSPEND:
   if (!(sl811->port1 & USB_PORT_STAT_SUSPEND))
    break;

   /* 20 msec of resume/K signaling, other irqs blocked */
   dev_dbg(hcd->self.controller, "start resume...\n");
   sl811->irq_enable = 0;
   sl811_write(sl811, SL11H_IRQ_ENABLE,
      sl811->irq_enable);
   sl811->ctrl1 |= SL11H_CTL1MASK_K;
   sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);

   mod_timer(&sl811->timer, jiffies
     + msecs_to_jiffies(USB_RESUME_TIMEOUT));
   break;
  case USB_PORT_FEAT_POWER:
   port_power(sl811, 0);
   break;
  case USB_PORT_FEAT_C_ENABLE:
  case USB_PORT_FEAT_C_SUSPEND:
  case USB_PORT_FEAT_C_CONNECTION:
  case USB_PORT_FEAT_C_OVER_CURRENT:
  case USB_PORT_FEAT_C_RESET:
   break;
  default:
   goto error;
  }
  sl811->port1 &= ~(1 << wValue);
  break;
 case GetHubDescriptor:
  sl811h_hub_descriptor(sl811, (struct usb_hub_descriptor *) buf);
  break;
 case GetHubStatus:
  put_unaligned_le32(0, buf);
  break;
 case GetPortStatus:
  if (wIndex != 1)
   goto error;
  put_unaligned_le32(sl811->port1, buf);

  if (__is_defined(VERBOSE) ||
      *(u16*)(buf+2)) /* only if wPortChange is interesting */
   dev_dbg(hcd->self.controller, "GetPortStatus %08x\n",
    sl811->port1);
  break;
 case SetPortFeature:
  if (wIndex != 1 || wLength != 0)
   goto error;
  switch (wValue) {
  case USB_PORT_FEAT_SUSPEND:
   if (sl811->port1 & USB_PORT_STAT_RESET)
    goto error;
   if (!(sl811->port1 & USB_PORT_STAT_ENABLE))
    goto error;

   dev_dbg(hcd->self.controller,"suspend...\n");
   sl811->ctrl1 &= ~SL11H_CTL1MASK_SOF_ENA;
   sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);
   break;
  case USB_PORT_FEAT_POWER:
   port_power(sl811, 1);
   break;
  case USB_PORT_FEAT_RESET:
   if (sl811->port1 & USB_PORT_STAT_SUSPEND)
    goto error;
   if (!(sl811->port1 & USB_PORT_STAT_POWER))
    break;

   /* 50 msec of reset/SE0 signaling, irqs blocked */
   sl811->irq_enable = 0;
   sl811_write(sl811, SL11H_IRQ_ENABLE,
      sl811->irq_enable);
   sl811->ctrl1 = SL11H_CTL1MASK_SE0;
   sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);
   sl811->port1 |= USB_PORT_STAT_RESET;
   mod_timer(&sl811->timer, jiffies
     + msecs_to_jiffies(50));
   break;
  default:
   goto error;
  }
  sl811->port1 |= 1 << wValue;
  break;

 default:
error:
  /* "protocol stall" on error */
  retval = -EPIPE;
 }

 spin_unlock_irqrestore(&sl811->lock, flags);
 return retval;
}

#ifdef CONFIG_PM

static int
sl811h_bus_suspend(struct usb_hcd *hcd)
{
 // SOFs off
 dev_dbg(hcd->self.controller, "%s\n", __func__);
 return 0;
}

static int
sl811h_bus_resume(struct usb_hcd *hcd)
{
 // SOFs on
 dev_dbg(hcd->self.controller, "%s\n", __func__);
 return 0;
}

#else

#define sl811h_bus_suspend NULL
#define sl811h_bus_resume NULL

#endif


/*-------------------------------------------------------------------------*/

static void dump_irq(struct seq_file *s, char *label, u8 mask)
{
 seq_printf(s, "%s %02x%s%s%s%s%s%s\n", label, mask,
  (mask & SL11H_INTMASK_DONE_A) ? " done_a" : "",
  (mask & SL11H_INTMASK_DONE_B) ? " done_b" : "",
  (mask & SL11H_INTMASK_SOFINTR) ? " sof" : "",
  (mask & SL11H_INTMASK_INSRMV) ? " ins/rmv" : "",
  (mask & SL11H_INTMASK_RD) ? " rd" : "",
  (mask & SL11H_INTMASK_DP) ? " dp" : "");
}

static int sl811h_debug_show(struct seq_file *s, void *unused)
{
 struct sl811  *sl811 = s->private;
 struct sl811h_ep *ep;
 unsigned  i;

 seq_printf(s, "%s\n%s version %s\nportstatus[1] = %08x\n",
  sl811_to_hcd(sl811)->product_desc,
  hcd_name, DRIVER_VERSION,
  sl811->port1);

 seq_printf(s, "insert/remove: %ld\n", sl811->stat_insrmv);
 seq_printf(s, "current session: done_a %ld done_b %ld "
   "wake %ld sof %ld overrun %ld lost %ld\n\n",
  sl811->stat_a, sl811->stat_b,
  sl811->stat_wake, sl811->stat_sof,
  sl811->stat_overrun, sl811->stat_lost);

 spin_lock_irq(&sl811->lock);

 if (sl811->ctrl1 & SL11H_CTL1MASK_SUSPEND)
  seq_printf(s, "(suspended)\n\n");
 else {
  u8 t = sl811_read(sl811, SL11H_CTLREG1);

  seq_printf(s, "ctrl1 %02x%s%s%s%s\n", t,
   (t & SL11H_CTL1MASK_SOF_ENA) ? " sofgen" : "",
   ({char *s; switch (t & SL11H_CTL1MASK_FORCE) {
   case SL11H_CTL1MASK_NORMAL: s = ""break;
   case SL11H_CTL1MASK_SE0: s = " se0/reset"break;
   case SL11H_CTL1MASK_K: s = " k/resume"break;
   default: s = "j"break;
   } s; }),
   (t & SL11H_CTL1MASK_LSPD) ? " lowspeed" : "",
   (t & SL11H_CTL1MASK_SUSPEND) ? " suspend" : "");

  dump_irq(s, "irq_enable",
    sl811_read(sl811, SL11H_IRQ_ENABLE));
  dump_irq(s, "irq_status",
    sl811_read(sl811, SL11H_IRQ_STATUS));
  seq_printf(s, "frame clocks remaining: %d\n",
    sl811_read(sl811, SL11H_SOFTMRREG) << 6);
 }

 seq_printf(s, "A: qh%p ctl %02x sts %02x\n", sl811->active_a,
  sl811_read(sl811, SL811_EP_A(SL11H_HOSTCTLREG)),
  sl811_read(sl811, SL811_EP_A(SL11H_PKTSTATREG)));
 seq_printf(s, "B: qh%p ctl %02x sts %02x\n", sl811->active_b,
  sl811_read(sl811, SL811_EP_B(SL11H_HOSTCTLREG)),
  sl811_read(sl811, SL811_EP_B(SL11H_PKTSTATREG)));
 seq_printf(s, "\n");
 list_for_each_entry (ep, &sl811->async, schedule) {
  struct urb  *urb;

  seq_printf(s, "%s%sqh%p, ep%d%s, maxpacket %d"
     " nak %d err %d\n",
   (ep == sl811->active_a) ? "(A) " : "",
   (ep == sl811->active_b) ? "(B) " : "",
   ep, ep->epnum,
   ({ char *s; switch (ep->nextpid) {
   case USB_PID_IN: s = "in"break;
   case USB_PID_OUT: s = "out"break;
   case USB_PID_SETUP: s = "setup"break;
   case USB_PID_ACK: s = "status"break;
   default: s = "?"break;
   } s;}),
   ep->maxpacket,
   ep->nak_count, ep->error_count);
  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(&sl811->async))
  seq_printf(s, "\n");

 seq_printf(s, "periodic size= %d\n", PERIODIC_SIZE);

 for (i = 0; i < PERIODIC_SIZE; i++) {
  ep = sl811->periodic[i];
  if (!ep)
   continue;
  seq_printf(s, "%2d [%3d]:\n", i, sl811->load[i]);

  /* DUMB: prints shared entries multiple times */
  do {
   seq_printf(s,
    " %s%sqh%d/%p (%sdev%d ep%d%s max %d) "
     "err %d\n",
    (ep == sl811->active_a) ? "(A) " : "",
    (ep == sl811->active_b) ? "(B) " : "",
    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->error_count);
   ep = ep->next;
  } while (ep);
 }

 spin_unlock_irq(&sl811->lock);
 seq_printf(s, "\n");

 return 0;
}
DEFINE_SHOW_ATTRIBUTE(sl811h_debug);

/* expect just one sl811 per system */
static void create_debug_file(struct sl811 *sl811)
{
 debugfs_create_file("sl811h", S_IRUGO, usb_debug_root, sl811,
       &sl811h_debug_fops);
}

static void remove_debug_file(struct sl811 *sl811)
{
 debugfs_lookup_and_remove("sl811h", usb_debug_root);
}

/*-------------------------------------------------------------------------*/

static void
sl811h_stop(struct usb_hcd *hcd)
{
 struct sl811 *sl811 = hcd_to_sl811(hcd);
 unsigned long flags;

 timer_delete_sync(&hcd->rh_timer);

 spin_lock_irqsave(&sl811->lock, flags);
 port_power(sl811, 0);
 spin_unlock_irqrestore(&sl811->lock, flags);
}

static int
sl811h_start(struct usb_hcd *hcd)
{
 struct sl811  *sl811 = hcd_to_sl811(hcd);

 /* chip has been reset, VBUS power is off */
 hcd->state = HC_STATE_RUNNING;

 if (sl811->board) {
  if (!device_can_wakeup(hcd->self.controller))
   device_init_wakeup(hcd->self.controller,
    sl811->board->can_wakeup);
  hcd->power_budget = sl811->board->power * 2;
 }

 /* enable power and interrupts */
 port_power(sl811, 1);

 return 0;
}

/*-------------------------------------------------------------------------*/

static const struct hc_driver sl811h_hc_driver = {
 .description =  hcd_name,
 .hcd_priv_size = sizeof(struct sl811),

 /*
 * generic hardware linkage
 */

 .irq =   sl811h_irq,
 .flags =  HCD_USB11 | HCD_MEMORY,

 /* Basic lifecycle operations */
 .start =  sl811h_start,
 .stop =   sl811h_stop,

 /*
 * managing i/o requests and associated device resources
 */

 .urb_enqueue =  sl811h_urb_enqueue,
 .urb_dequeue =  sl811h_urb_dequeue,
 .endpoint_disable = sl811h_endpoint_disable,

 /*
 * periodic schedule support
 */

 .get_frame_number = sl811h_get_frame,

 /*
 * root hub support
 */

 .hub_status_data = sl811h_hub_status_data,
 .hub_control =  sl811h_hub_control,
 .bus_suspend =  sl811h_bus_suspend,
 .bus_resume =  sl811h_bus_resume,
};

/*-------------------------------------------------------------------------*/

static void
sl811h_remove(struct platform_device *dev)
{
 struct usb_hcd  *hcd = platform_get_drvdata(dev);
 struct sl811  *sl811 = hcd_to_sl811(hcd);
 struct resource  *res;

 remove_debug_file(sl811);
 usb_remove_hcd(hcd);

 /* some platforms may use IORESOURCE_IO */
 res = platform_get_resource(dev, IORESOURCE_MEM, 1);
 if (res)
  iounmap(sl811->data_reg);

 res = platform_get_resource(dev, IORESOURCE_MEM, 0);
 if (res)
  iounmap(sl811->addr_reg);

 usb_put_hcd(hcd);
}

static int
sl811h_probe(struct platform_device *dev)
{
 struct usb_hcd  *hcd;
 struct sl811  *sl811;
 struct resource  *addr, *data, *ires;
 int   irq;
 void __iomem  *addr_reg;
 void __iomem  *data_reg;
 int   retval;
 u8   tmp, ioaddr;
 unsigned long  irqflags;

 if (usb_disabled())
  return -ENODEV;

 /* the chip may be wired for either kind of addressing */
 addr = platform_get_mem_or_io(dev, 0);
 data = platform_get_mem_or_io(dev, 1);
 if (!addr || !data || resource_type(addr) != resource_type(data))
  return -ENODEV;

 /* basic sanity checks first.  board-specific init logic should
 * have initialized these three resources and probably board
 * specific platform_data.  we don't probe for IRQs, and do only
 * minimal sanity checking.
 */

 ires = platform_get_resource(dev, IORESOURCE_IRQ, 0);
 if (dev->num_resources < 3 || !ires)
  return -ENODEV;

 irq = ires->start;
 irqflags = ires->flags & IRQF_TRIGGER_MASK;

 ioaddr = resource_type(addr) == IORESOURCE_IO;
 if (ioaddr) {
  /*
 * NOTE: 64-bit resource->start is getting truncated
 * to avoid compiler warning, assuming that ->start
 * is always 32-bit for this case
 */

  addr_reg = (void __iomem *) (unsigned long) addr->start;
  data_reg = (void __iomem *) (unsigned long) data->start;
 } else {
  addr_reg = ioremap(addr->start, 1);
  if (addr_reg == NULL) {
   retval = -ENOMEM;
   goto err2;
  }

  data_reg = ioremap(data->start, 1);
  if (data_reg == NULL) {
   retval = -ENOMEM;
   goto err4;
  }
 }

 /* allocate and initialize hcd */
 hcd = usb_create_hcd(&sl811h_hc_driver, &dev->dev, dev_name(&dev->dev));
 if (!hcd) {
  retval = -ENOMEM;
  goto err5;
 }
 hcd->rsrc_start = addr->start;
 sl811 = hcd_to_sl811(hcd);

 spin_lock_init(&sl811->lock);
 INIT_LIST_HEAD(&sl811->async);
 sl811->board = dev_get_platdata(&dev->dev);
 timer_setup(&sl811->timer, sl811h_timer, 0);
 sl811->addr_reg = addr_reg;
 sl811->data_reg = data_reg;

 spin_lock_irq(&sl811->lock);
 port_power(sl811, 0);
 spin_unlock_irq(&sl811->lock);
 msleep(200);

 tmp = sl811_read(sl811, SL11H_HWREVREG);
 switch (tmp >> 4) {
 case 1:
  hcd->product_desc = "SL811HS v1.2";
  break;
 case 2:
  hcd->product_desc = "SL811HS v1.5";
  break;
 default:
  /* reject case 0, SL11S is less functional */
  dev_dbg(&dev->dev, "chiprev %02x\n", tmp);
  retval = -ENXIO;
  goto err6;
 }

 /* The chip's IRQ is level triggered, active high.  A requirement
 * for platform device setup is to cope with things like signal
 * inverters (e.g. CF is active low) or working only with edge
 * triggers (e.g. most ARM CPUs).  Initial driver stress testing
 * was on a system with single edge triggering, so most sorts of
 * triggering arrangement should work.
 *
 * Use resource IRQ flags if set by platform device setup.
 */

 irqflags |= IRQF_SHARED;
 retval = usb_add_hcd(hcd, irq, irqflags);
 if (retval != 0)
  goto err6;

 device_wakeup_enable(hcd->self.controller);

 create_debug_file(sl811);
 return retval;

 err6:
 usb_put_hcd(hcd);
 err5:
 if (!ioaddr)
  iounmap(data_reg);
 err4:
 if (!ioaddr)
  iounmap(addr_reg);
 err2:
 dev_dbg(&dev->dev, "init error, %d\n", retval);
 return retval;
}

#ifdef CONFIG_PM

/* for this device there's no useful distinction between the controller
 * and its root hub.
 */


static int
sl811h_suspend(struct platform_device *dev, pm_message_t state)
{
 struct usb_hcd *hcd = platform_get_drvdata(dev);
 struct sl811 *sl811 = hcd_to_sl811(hcd);
 int  retval = 0;

 switch (state.event) {
 case PM_EVENT_FREEZE:
  retval = sl811h_bus_suspend(hcd);
  break;
 case PM_EVENT_SUSPEND:
 case PM_EVENT_HIBERNATE:
 case PM_EVENT_PRETHAW:  /* explicitly discard hw state */
  port_power(sl811, 0);
  break;
 }
 return retval;
}

static int
sl811h_resume(struct platform_device *dev)
{
 struct usb_hcd *hcd = platform_get_drvdata(dev);
 struct sl811 *sl811 = hcd_to_sl811(hcd);

 /* with no "check to see if VBUS is still powered" board hook,
 * let's assume it'd only be powered to enable remote wakeup.
 */

 if (!sl811->port1 || !device_can_wakeup(&hcd->self.root_hub->dev)) {
  sl811->port1 = 0;
  port_power(sl811, 1);
  usb_root_hub_lost_power(hcd->self.root_hub);
  return 0;
 }

 return sl811h_bus_resume(hcd);
}

#else

#define sl811h_suspend NULL
#define sl811h_resume NULL

#endif


/* this driver is exported so sl811_cs can depend on it */
struct platform_driver sl811h_driver = {
 .probe = sl811h_probe,
 .remove = sl811h_remove,

 .suspend = sl811h_suspend,
 .resume = sl811h_resume,
 .driver = {
  .name = hcd_name,
 },
};
EXPORT_SYMBOL(sl811h_driver);

module_platform_driver(sl811h_driver);

Messung V0.5
C=95 H=90 G=92

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.