mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-05 18:39:59 +00:00
![Vincent Mailhol](/assets/img/avatar_default.png)
The third argument of usb_maxpacket(): in_out has been deprecated because it could be derived from the second argument (e.g. using usb_pipeout(pipe)). N.B. function usb_maxpacket() was made variadic to accommodate the transition from the old prototype with three arguments to the new one with only two arguments (so that no renaming is needed). The variadic argument is to be removed once all users of usb_maxpacket() get migrated. CC: Duncan Sands <duncan.sands@free.fr> CC: Alan Stern <stern@rowland.harvard.edu> CC: Olav Kongas <ok@artecdesign.ee> CC: Rui Miguel Silva <rui.silva@linaro.org> Signed-off-by: Vincent Mailhol <mailhol.vincent@wanadoo.fr> Link: https://lore.kernel.org/r/20220317035514.6378-7-mailhol.vincent@wanadoo.fr Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1796 lines
45 KiB
C
1796 lines
45 KiB
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/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);
|
|
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 = 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);
|
|
|
|
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_remove(debugfs_lookup("sl811h", usb_debug_root));
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
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;
|
|
unsigned long irqflags;
|
|
|
|
if (usb_disabled())
|
|
return -ENODEV;
|
|
|
|
/* the chip may be wired for either kind of addressing */
|
|
addr = platform_get_mem_or_io(dev, 0);
|
|
data = platform_get_mem_or_io(dev, 1);
|
|
if (!addr || !data || resource_type(addr) != resource_type(data))
|
|
return -ENODEV;
|
|
|
|
/* basic sanity checks first. board-specific init logic should
|
|
* have initialized these three resources and probably board
|
|
* specific platform_data. we don't probe for IRQs, and do only
|
|
* minimal sanity checking.
|
|
*/
|
|
ires = platform_get_resource(dev, IORESOURCE_IRQ, 0);
|
|
if (dev->num_resources < 3 || !ires)
|
|
return -ENODEV;
|
|
|
|
irq = ires->start;
|
|
irqflags = ires->flags & IRQF_TRIGGER_MASK;
|
|
|
|
ioaddr = resource_type(addr) == IORESOURCE_IO;
|
|
if (ioaddr) {
|
|
/*
|
|
* NOTE: 64-bit resource->start is getting truncated
|
|
* to avoid compiler warning, assuming that ->start
|
|
* is always 32-bit for this case
|
|
*/
|
|
addr_reg = (void __iomem *) (unsigned long) addr->start;
|
|
data_reg = (void __iomem *) (unsigned long) data->start;
|
|
} else {
|
|
addr_reg = ioremap(addr->start, 1);
|
|
if (addr_reg == NULL) {
|
|
retval = -ENOMEM;
|
|
goto err2;
|
|
}
|
|
|
|
data_reg = ioremap(data->start, 1);
|
|
if (data_reg == NULL) {
|
|
retval = -ENOMEM;
|
|
goto err4;
|
|
}
|
|
}
|
|
|
|
/* allocate and initialize hcd */
|
|
hcd = usb_create_hcd(&sl811h_hc_driver, &dev->dev, dev_name(&dev->dev));
|
|
if (!hcd) {
|
|
retval = -ENOMEM;
|
|
goto err5;
|
|
}
|
|
hcd->rsrc_start = addr->start;
|
|
sl811 = hcd_to_sl811(hcd);
|
|
|
|
spin_lock_init(&sl811->lock);
|
|
INIT_LIST_HEAD(&sl811->async);
|
|
sl811->board = dev_get_platdata(&dev->dev);
|
|
timer_setup(&sl811->timer, sl811h_timer, 0);
|
|
sl811->addr_reg = addr_reg;
|
|
sl811->data_reg = data_reg;
|
|
|
|
spin_lock_irq(&sl811->lock);
|
|
port_power(sl811, 0);
|
|
spin_unlock_irq(&sl811->lock);
|
|
msleep(200);
|
|
|
|
tmp = sl811_read(sl811, SL11H_HWREVREG);
|
|
switch (tmp >> 4) {
|
|
case 1:
|
|
hcd->product_desc = "SL811HS v1.2";
|
|
break;
|
|
case 2:
|
|
hcd->product_desc = "SL811HS v1.5";
|
|
break;
|
|
default:
|
|
/* reject case 0, SL11S is less functional */
|
|
dev_dbg(&dev->dev, "chiprev %02x\n", tmp);
|
|
retval = -ENXIO;
|
|
goto err6;
|
|
}
|
|
|
|
/* The chip's IRQ is level triggered, active high. A requirement
|
|
* for platform device setup is to cope with things like signal
|
|
* inverters (e.g. CF is active low) or working only with edge
|
|
* triggers (e.g. most ARM CPUs). Initial driver stress testing
|
|
* was on a system with single edge triggering, so most sorts of
|
|
* triggering arrangement should work.
|
|
*
|
|
* Use resource IRQ flags if set by platform device setup.
|
|
*/
|
|
irqflags |= IRQF_SHARED;
|
|
retval = usb_add_hcd(hcd, irq, irqflags);
|
|
if (retval != 0)
|
|
goto err6;
|
|
|
|
device_wakeup_enable(hcd->self.controller);
|
|
|
|
create_debug_file(sl811);
|
|
return retval;
|
|
|
|
err6:
|
|
usb_put_hcd(hcd);
|
|
err5:
|
|
if (!ioaddr)
|
|
iounmap(data_reg);
|
|
err4:
|
|
if (!ioaddr)
|
|
iounmap(addr_reg);
|
|
err2:
|
|
dev_dbg(&dev->dev, "init error, %d\n", retval);
|
|
return retval;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
/* for this device there's no useful distinction between the controller
|
|
* and its root hub.
|
|
*/
|
|
|
|
static int
|
|
sl811h_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
|
struct sl811 *sl811 = hcd_to_sl811(hcd);
|
|
int retval = 0;
|
|
|
|
switch (state.event) {
|
|
case PM_EVENT_FREEZE:
|
|
retval = sl811h_bus_suspend(hcd);
|
|
break;
|
|
case PM_EVENT_SUSPEND:
|
|
case PM_EVENT_HIBERNATE:
|
|
case PM_EVENT_PRETHAW: /* explicitly discard hw state */
|
|
port_power(sl811, 0);
|
|
break;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
sl811h_resume(struct platform_device *dev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
|
struct sl811 *sl811 = hcd_to_sl811(hcd);
|
|
|
|
/* with no "check to see if VBUS is still powered" board hook,
|
|
* let's assume it'd only be powered to enable remote wakeup.
|
|
*/
|
|
if (!sl811->port1 || !device_can_wakeup(&hcd->self.root_hub->dev)) {
|
|
sl811->port1 = 0;
|
|
port_power(sl811, 1);
|
|
usb_root_hub_lost_power(hcd->self.root_hub);
|
|
return 0;
|
|
}
|
|
|
|
return sl811h_bus_resume(hcd);
|
|
}
|
|
|
|
#else
|
|
|
|
#define sl811h_suspend NULL
|
|
#define sl811h_resume NULL
|
|
|
|
#endif
|
|
|
|
|
|
/* this driver is exported so sl811_cs can depend on it */
|
|
struct platform_driver sl811h_driver = {
|
|
.probe = sl811h_probe,
|
|
.remove = sl811h_remove,
|
|
|
|
.suspend = sl811h_suspend,
|
|
.resume = sl811h_resume,
|
|
.driver = {
|
|
.name = hcd_name,
|
|
},
|
|
};
|
|
EXPORT_SYMBOL(sl811h_driver);
|
|
|
|
module_platform_driver(sl811h_driver);
|