Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
David Brownell | 7061 | 89.10% | 7 | 10.94% |
Alan Stern | 404 | 5.10% | 13 | 20.31% |
Greg Kroah-Hartman | 239 | 3.02% | 5 | 7.81% |
Russell King | 34 | 0.43% | 2 | 3.12% |
Marc Zyngier | 33 | 0.42% | 1 | 1.56% |
Michael Hennerich | 29 | 0.37% | 3 | 4.69% |
Kees Cook | 14 | 0.18% | 1 | 1.56% |
Vasiliy Kulikov | 12 | 0.15% | 1 | 1.56% |
Tobias Klauser | 10 | 0.13% | 1 | 1.56% |
Kay Sievers | 9 | 0.11% | 2 | 3.12% |
Peter Chen | 9 | 0.11% | 1 | 1.56% |
John Youn | 8 | 0.10% | 1 | 1.56% |
Rafael J. Wysocki | 5 | 0.06% | 2 | 3.12% |
Denis V. Lunev | 5 | 0.06% | 1 | 1.56% |
Jesper Juhl | 5 | 0.06% | 1 | 1.56% |
Andy Shevchenko | 5 | 0.06% | 1 | 1.56% |
Jingoo Han | 4 | 0.05% | 1 | 1.56% |
Julia Lawall | 4 | 0.05% | 2 | 3.12% |
Petr Mladek | 4 | 0.05% | 1 | 1.56% |
Pavel Machek | 4 | 0.05% | 2 | 3.12% |
Sergei Shtylyov | 4 | 0.05% | 2 | 3.12% |
Eric Lescouet | 3 | 0.04% | 1 | 1.56% |
Linus Torvalds | 3 | 0.04% | 1 | 1.56% |
Sage Sharp | 3 | 0.04% | 1 | 1.56% |
Mike Frysinger | 3 | 0.04% | 1 | 1.56% |
Harvey Harrison | 2 | 0.03% | 1 | 1.56% |
Joe Perches | 2 | 0.03% | 1 | 1.56% |
Robert P. J. Day | 1 | 0.01% | 1 | 1.56% |
Steven Cole | 1 | 0.01% | 1 | 1.56% |
Pekka J Enberg | 1 | 0.01% | 1 | 1.56% |
Alexey Dobriyan | 1 | 0.01% | 1 | 1.56% |
Al Viro | 1 | 0.01% | 1 | 1.56% |
Felipe Balbi | 1 | 0.01% | 1 | 1.56% |
Pete Zaitcev | 1 | 0.01% | 1 | 1.56% |
Total | 7925 | 64 |
// 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/debugfs.h> #include <linux/seq_file.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/byteorder.h> #include <asm/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", is_on ? "on" : "off"); sl811->board->port_power(hcd->self.controller, is_on); } /* reset as thoroughly as we can */ if (sl811->board && sl811->board->reset) sl811->board->reset(hcd->self.controller); else { sl811_write(sl811, SL11H_CTLREG1, SL11H_CTL1MASK_SE0); mdelay(20); } sl811_write(sl811, SL11H_IRQ_ENABLE, 0); sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); sl811_write(sl811, SL811HS_CTLREG2, SL811HS_CTL2_INIT); sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); // if !is_on, put into lowpower mode now } /*-------------------------------------------------------------------------*/ /* This is a PIO-only HCD. Queueing appends URBs to the endpoint's queue, * and may start I/O. Endpoint queues are scanned during completion irq * handlers (one per packet: ACK, NAK, faults, etc) and urb cancellation. * * Using an external DMA engine to copy a packet at a time could work, * though setup/teardown costs may be too big to make it worthwhile. */ /* SETUP starts a new control request. Devices are not allowed to * STALL or NAK these; they must cancel any pending control requests. */ static void setup_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control ) { u8 addr; u8 len; void __iomem *data_reg; addr = SL811HS_PACKET_BUF(bank == 0); len = sizeof(struct usb_ctrlrequest); data_reg = sl811->data_reg; sl811_write_buf(sl811, addr, urb->setup_packet, len); /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); writeb(len, data_reg); writeb(SL_SETUP /* | ep->epnum */, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); /* always OUT/data0 */ sl811_write(sl811, bank + SL11H_HOSTCTLREG, control | SL11H_HCTLMASK_OUT); ep->length = 0; PACKET("SETUP qh%p\n", ep); } /* STATUS finishes control requests, often after IN or OUT data packets */ static void status_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control ) { int do_out; void __iomem *data_reg; do_out = urb->transfer_buffer_length && usb_pipein(urb->pipe); data_reg = sl811->data_reg; /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, 0); writeb(0, data_reg); writeb((do_out ? SL_OUT : SL_IN) /* | ep->epnum */, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); /* always data1; sometimes IN */ control |= SL11H_HCTLMASK_TOGGLE; if (do_out) control |= SL11H_HCTLMASK_OUT; sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); ep->length = 0; PACKET("STATUS%s/%s qh%p\n", ep->nak_count ? "/retry" : "", do_out ? "out" : "in", ep); } /* IN packets can be used with any type of endpoint. here we just * start the transfer, data from the peripheral may arrive later. * urb->iso_frame_desc is currently ignored here... */ static void in_packet( struct sl811 *sl811, struct sl811h_ep *ep, struct urb *urb, u8 bank, u8 control ) { u8 addr; u8 len; void __iomem *data_reg; /* avoid losing data on overflow */ len = ep->maxpacket; addr = SL811HS_PACKET_BUF(bank == 0); if (!(control & SL11H_HCTLMASK_ISOCH) && usb_gettoggle(urb->dev, ep->epnum, 0)) control |= SL11H_HCTLMASK_TOGGLE; data_reg = sl811->data_reg; /* autoincrementing */ sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); writeb(len, data_reg); writeb(SL_IN | ep->epnum, data_reg); writeb(usb_pipedevice(urb->pipe), data_reg); sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); ep->length = min_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); } 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; } 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, is_out); ep->defctrl = SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENABLE; usb_settoggle(udev, epnum, is_out, 0); if (type == PIPE_CONTROL) ep->nextpid = USB_PID_SETUP; else if (is_out) ep->nextpid = USB_PID_OUT; else ep->nextpid = USB_PID_IN; if (ep->maxpacket > H_MAXPACKET) { /* iso packets up to 240 bytes could work... */ 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, is_out)) / 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 = from_timer(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); #ifndef VERBOSE if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ #endif 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) { sl811->debug_file = debugfs_create_file("sl811h", S_IRUGO, usb_debug_root, sl811, &sl811h_debug_fops); } static void remove_debug_file(struct sl811 *sl811) { debugfs_remove(sl811->debug_file); } /*-------------------------------------------------------------------------*/ static void sl811h_stop(struct usb_hcd *hcd) { struct sl811 *sl811 = hcd_to_sl811(hcd); unsigned long flags; del_timer_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 int 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); return 0; } 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 = 0; unsigned long irqflags; if (usb_disabled()) 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; /* the chip may be wired for either kind of addressing */ addr = platform_get_resource(dev, IORESOURCE_MEM, 0); data = platform_get_resource(dev, IORESOURCE_MEM, 1); retval = -EBUSY; if (!addr || !data) { addr = platform_get_resource(dev, IORESOURCE_IO, 0); data = platform_get_resource(dev, IORESOURCE_IO, 1); if (!addr || !data) return -ENODEV; ioaddr = 1; /* * 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);
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1