root/drivers/usb/host/sl811-hcd.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_obj(*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_POWEROFF:
        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);