mirror of
https://github.com/torvalds/linux.git
synced 2024-11-19 02:21:47 +00:00
e99e88a9d2
This converts all remaining cases of the old setup_timer() API into using timer_setup(), where the callback argument is the structure already holding the struct timer_list. These should have no behavioral changes, since they just change which pointer is passed into the callback with the same available pointers after conversion. It handles the following examples, in addition to some other variations. Casting from unsigned long: void my_callback(unsigned long data) { struct something *ptr = (struct something *)data; ... } ... setup_timer(&ptr->my_timer, my_callback, ptr); and forced object casts: void my_callback(struct something *ptr) { ... } ... setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr); become: void my_callback(struct timer_list *t) { struct something *ptr = from_timer(ptr, t, my_timer); ... } ... timer_setup(&ptr->my_timer, my_callback, 0); Direct function assignments: void my_callback(unsigned long data) { struct something *ptr = (struct something *)data; ... } ... ptr->my_timer.function = my_callback; have a temporary cast added, along with converting the args: void my_callback(struct timer_list *t) { struct something *ptr = from_timer(ptr, t, my_timer); ... } ... ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback; And finally, callbacks without a data assignment: void my_callback(unsigned long data) { ... } ... setup_timer(&ptr->my_timer, my_callback, 0); have their argument renamed to verify they're unused during conversion: void my_callback(struct timer_list *unused) { ... } ... timer_setup(&ptr->my_timer, my_callback, 0); The conversion is done with the following Coccinelle script: spatch --very-quiet --all-includes --include-headers \ -I ./arch/x86/include -I ./arch/x86/include/generated \ -I ./include -I ./arch/x86/include/uapi \ -I ./arch/x86/include/generated/uapi -I ./include/uapi \ -I ./include/generated/uapi --include ./include/linux/kconfig.h \ --dir . \ --cocci-file ~/src/data/timer_setup.cocci @fix_address_of@ expression e; @@ setup_timer( -&(e) +&e , ...) // Update any raw setup_timer() usages that have a NULL callback, but // would otherwise match change_timer_function_usage, since the latter // will update all function assignments done in the face of a NULL // function initialization in setup_timer(). @change_timer_function_usage_NULL@ expression _E; identifier _timer; type _cast_data; @@ ( -setup_timer(&_E->_timer, NULL, _E); +timer_setup(&_E->_timer, NULL, 0); | -setup_timer(&_E->_timer, NULL, (_cast_data)_E); +timer_setup(&_E->_timer, NULL, 0); | -setup_timer(&_E._timer, NULL, &_E); +timer_setup(&_E._timer, NULL, 0); | -setup_timer(&_E._timer, NULL, (_cast_data)&_E); +timer_setup(&_E._timer, NULL, 0); ) @change_timer_function_usage@ expression _E; identifier _timer; struct timer_list _stl; identifier _callback; type _cast_func, _cast_data; @@ ( -setup_timer(&_E->_timer, _callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, &_callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, _callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, &_callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)_callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)&_callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E._timer, _callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, _callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, &_callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, &_callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | _E->_timer@_stl.function = _callback; | _E->_timer@_stl.function = &_callback; | _E->_timer@_stl.function = (_cast_func)_callback; | _E->_timer@_stl.function = (_cast_func)&_callback; | _E._timer@_stl.function = _callback; | _E._timer@_stl.function = &_callback; | _E._timer@_stl.function = (_cast_func)_callback; | _E._timer@_stl.function = (_cast_func)&_callback; ) // callback(unsigned long arg) @change_callback_handle_cast depends on change_timer_function_usage@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _origtype; identifier _origarg; type _handletype; identifier _handle; @@ void _callback( -_origtype _origarg +struct timer_list *t ) { ( ... when != _origarg _handletype *_handle = -(_handletype *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg | ... when != _origarg _handletype *_handle = -(void *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg | ... when != _origarg _handletype *_handle; ... when != _handle _handle = -(_handletype *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg | ... when != _origarg _handletype *_handle; ... when != _handle _handle = -(void *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg ) } // callback(unsigned long arg) without existing variable @change_callback_handle_cast_no_arg depends on change_timer_function_usage && !change_callback_handle_cast@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _origtype; identifier _origarg; type _handletype; @@ void _callback( -_origtype _origarg +struct timer_list *t ) { + _handletype *_origarg = from_timer(_origarg, t, _timer); + ... when != _origarg - (_handletype *)_origarg + _origarg ... when != _origarg } // Avoid already converted callbacks. @match_callback_converted depends on change_timer_function_usage && !change_callback_handle_cast && !change_callback_handle_cast_no_arg@ identifier change_timer_function_usage._callback; identifier t; @@ void _callback(struct timer_list *t) { ... } // callback(struct something *handle) @change_callback_handle_arg depends on change_timer_function_usage && !match_callback_converted && !change_callback_handle_cast && !change_callback_handle_cast_no_arg@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _handletype; identifier _handle; @@ void _callback( -_handletype *_handle +struct timer_list *t ) { + _handletype *_handle = from_timer(_handle, t, _timer); ... } // If change_callback_handle_arg ran on an empty function, remove // the added handler. @unchange_callback_handle_arg depends on change_timer_function_usage && change_callback_handle_arg@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _handletype; identifier _handle; identifier t; @@ void _callback(struct timer_list *t) { - _handletype *_handle = from_timer(_handle, t, _timer); } // We only want to refactor the setup_timer() data argument if we've found // the matching callback. This undoes changes in change_timer_function_usage. @unchange_timer_function_usage depends on change_timer_function_usage && !change_callback_handle_cast && !change_callback_handle_cast_no_arg && !change_callback_handle_arg@ expression change_timer_function_usage._E; identifier change_timer_function_usage._timer; identifier change_timer_function_usage._callback; type change_timer_function_usage._cast_data; @@ ( -timer_setup(&_E->_timer, _callback, 0); +setup_timer(&_E->_timer, _callback, (_cast_data)_E); | -timer_setup(&_E._timer, _callback, 0); +setup_timer(&_E._timer, _callback, (_cast_data)&_E); ) // If we fixed a callback from a .function assignment, fix the // assignment cast now. @change_timer_function_assignment depends on change_timer_function_usage && (change_callback_handle_cast || change_callback_handle_cast_no_arg || change_callback_handle_arg)@ expression change_timer_function_usage._E; identifier change_timer_function_usage._timer; identifier change_timer_function_usage._callback; type _cast_func; typedef TIMER_FUNC_TYPE; @@ ( _E->_timer.function = -_callback +(TIMER_FUNC_TYPE)_callback ; | _E->_timer.function = -&_callback +(TIMER_FUNC_TYPE)_callback ; | _E->_timer.function = -(_cast_func)_callback; +(TIMER_FUNC_TYPE)_callback ; | _E->_timer.function = -(_cast_func)&_callback +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -_callback +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -&_callback; +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -(_cast_func)_callback +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -(_cast_func)&_callback +(TIMER_FUNC_TYPE)_callback ; ) // Sometimes timer functions are called directly. Replace matched args. @change_timer_function_calls depends on change_timer_function_usage && (change_callback_handle_cast || change_callback_handle_cast_no_arg || change_callback_handle_arg)@ expression _E; identifier change_timer_function_usage._timer; identifier change_timer_function_usage._callback; type _cast_data; @@ _callback( ( -(_cast_data)_E +&_E->_timer | -(_cast_data)&_E +&_E._timer | -_E +&_E->_timer ) ) // If a timer has been configured without a data argument, it can be // converted without regard to the callback argument, since it is unused. @match_timer_function_unused_data@ expression _E; identifier _timer; identifier _callback; @@ ( -setup_timer(&_E->_timer, _callback, 0); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, _callback, 0L); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, _callback, 0UL); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E._timer, _callback, 0); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, _callback, 0L); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, _callback, 0UL); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_timer, _callback, 0); +timer_setup(&_timer, _callback, 0); | -setup_timer(&_timer, _callback, 0L); +timer_setup(&_timer, _callback, 0); | -setup_timer(&_timer, _callback, 0UL); +timer_setup(&_timer, _callback, 0); | -setup_timer(_timer, _callback, 0); +timer_setup(_timer, _callback, 0); | -setup_timer(_timer, _callback, 0L); +timer_setup(_timer, _callback, 0); | -setup_timer(_timer, _callback, 0UL); +timer_setup(_timer, _callback, 0); ) @change_callback_unused_data depends on match_timer_function_unused_data@ identifier match_timer_function_unused_data._callback; type _origtype; identifier _origarg; @@ void _callback( -_origtype _origarg +struct timer_list *unused ) { ... when != _origarg } Signed-off-by: Kees Cook <keescook@chromium.org>
1818 lines
46 KiB
C
1818 lines
46 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, 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_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;
|
|
}
|
|
|
|
static int sl811h_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, sl811h_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations debug_ops = {
|
|
.open = sl811h_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
/* 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,
|
|
&debug_ops);
|
|
}
|
|
|
|
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;
|
|
|
|
/* refuse to confuse usbcore */
|
|
if (dev->dev.dma_mask) {
|
|
dev_dbg(&dev->dev, "no we won't dma\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 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 = (char *) hcd_name,
|
|
},
|
|
};
|
|
EXPORT_SYMBOL(sl811h_driver);
|
|
|
|
module_platform_driver(sl811h_driver);
|