Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
此提交包含在:
126
drivers/usb/host/Kconfig
一般檔案
126
drivers/usb/host/Kconfig
一般檔案
@@ -0,0 +1,126 @@
|
||||
#
|
||||
# USB Host Controller Drivers
|
||||
#
|
||||
comment "USB Host Controller Drivers"
|
||||
depends on USB
|
||||
|
||||
config USB_EHCI_HCD
|
||||
tristate "EHCI HCD (USB 2.0) support"
|
||||
depends on USB && PCI
|
||||
---help---
|
||||
The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0
|
||||
"high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware.
|
||||
If your USB host controller supports USB 2.0, you will likely want to
|
||||
configure this Host Controller Driver. At this writing, the primary
|
||||
implementation of EHCI is a chip from NEC, widely available in add-on
|
||||
PCI cards, but implementations are in the works from other vendors
|
||||
including Intel and Philips. Motherboard support is appearing.
|
||||
|
||||
EHCI controllers are packaged with "companion" host controllers (OHCI
|
||||
or UHCI) to handle USB 1.1 devices connected to root hub ports. Ports
|
||||
will connect to EHCI if it the device is high speed, otherwise they
|
||||
connect to a companion controller. If you configure EHCI, you should
|
||||
probably configure the OHCI (for NEC and some other vendors) USB Host
|
||||
Controller Driver or UHCI (for Via motherboards) Host Controller
|
||||
Driver too.
|
||||
|
||||
You may want to read <file:Documentation/usb/ehci.txt>.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ehci-hcd.
|
||||
|
||||
config USB_EHCI_SPLIT_ISO
|
||||
bool "Full speed ISO transactions (EXPERIMENTAL)"
|
||||
depends on USB_EHCI_HCD && EXPERIMENTAL
|
||||
default n
|
||||
---help---
|
||||
This code is new and hasn't been used with many different
|
||||
EHCI or USB 2.0 transaction translator implementations.
|
||||
It should work for ISO-OUT transfers, like audio.
|
||||
|
||||
config USB_EHCI_ROOT_HUB_TT
|
||||
bool "Root Hub Transaction Translators (EXPERIMENTAL)"
|
||||
depends on USB_EHCI_HCD && EXPERIMENTAL
|
||||
---help---
|
||||
Some EHCI chips have vendor-specific extensions to integrate
|
||||
transaction translators, so that no OHCI or UHCI companion
|
||||
controller is needed. It's safe to say "y" even if your
|
||||
controller doesn't support this feature.
|
||||
|
||||
This supports the EHCI implementation from TransDimension Inc.
|
||||
|
||||
config USB_OHCI_HCD
|
||||
tristate "OHCI HCD support"
|
||||
depends on USB && USB_ARCH_HAS_OHCI
|
||||
select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3
|
||||
---help---
|
||||
The Open Host Controller Interface (OHCI) is a standard for accessing
|
||||
USB 1.1 host controller hardware. It does more in hardware than Intel's
|
||||
UHCI specification. If your USB host controller follows the OHCI spec,
|
||||
say Y. On most non-x86 systems, and on x86 hardware that's not using a
|
||||
USB controller from Intel or VIA, this is appropriate. If your host
|
||||
controller doesn't use PCI, this is probably appropriate. For a PCI
|
||||
based system where you're not sure, the "lspci -v" entry will list the
|
||||
right "prog-if" for your USB controller(s): EHCI, OHCI, or UHCI.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ohci-hcd.
|
||||
|
||||
config USB_OHCI_HCD_PPC_SOC
|
||||
bool "OHCI support for on-chip PPC USB controller"
|
||||
depends on USB_OHCI_HCD && (STB03xxx || PPC_MPC52xx)
|
||||
default y
|
||||
select USB_OHCI_BIG_ENDIAN
|
||||
---help---
|
||||
Enables support for the USB controller on the MPC52xx or
|
||||
STB03xxx processor chip. If unsure, say Y.
|
||||
|
||||
config USB_OHCI_HCD_PCI
|
||||
bool "OHCI support for PCI-bus USB controllers"
|
||||
depends on USB_OHCI_HCD && PCI && (STB03xxx || PPC_MPC52xx)
|
||||
default y
|
||||
select USB_OHCI_LITTLE_ENDIAN
|
||||
---help---
|
||||
Enables support for PCI-bus plug-in USB controller cards.
|
||||
If unsure, say Y.
|
||||
|
||||
config USB_OHCI_BIG_ENDIAN
|
||||
bool
|
||||
depends on USB_OHCI_HCD
|
||||
default n
|
||||
|
||||
config USB_OHCI_LITTLE_ENDIAN
|
||||
bool
|
||||
depends on USB_OHCI_HCD
|
||||
default n if STB03xxx || PPC_MPC52xx
|
||||
default y
|
||||
|
||||
config USB_UHCI_HCD
|
||||
tristate "UHCI HCD (most Intel and VIA) support"
|
||||
depends on USB && PCI
|
||||
---help---
|
||||
The Universal Host Controller Interface is a standard by Intel for
|
||||
accessing the USB hardware in the PC (which is also called the USB
|
||||
host controller). If your USB host controller conforms to this
|
||||
standard, you may want to say Y, but see below. All recent boards
|
||||
with Intel PCI chipsets (like intel 430TX, 440FX, 440LX, 440BX,
|
||||
i810, i820) conform to this standard. Also all VIA PCI chipsets
|
||||
(like VIA VP2, VP3, MVP3, Apollo Pro, Apollo Pro II or Apollo Pro
|
||||
133). If unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called uhci-hcd.
|
||||
|
||||
config USB_SL811_HCD
|
||||
tristate "SL811HS HCD support"
|
||||
depends on USB
|
||||
default N
|
||||
help
|
||||
The SL811HS is a single-port USB controller that supports either
|
||||
host side or peripheral side roles. Enable this option if your
|
||||
board has this chip, and you want to use it as a host controller.
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called sl811-hcd.
|
||||
|
10
drivers/usb/host/Makefile
一般檔案
10
drivers/usb/host/Makefile
一般檔案
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Makefile for USB Host Controller Driver
|
||||
# framework and drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
|
||||
obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o
|
||||
obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
|
||||
obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o
|
||||
obj-$(CONFIG_ETRAX_ARCH_V10) += hc_crisv10.o
|
755
drivers/usb/host/ehci-dbg.c
一般檔案
755
drivers/usb/host/ehci-dbg.c
一般檔案
@@ -0,0 +1,755 @@
|
||||
/*
|
||||
* Copyright (c) 2001-2002 by David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/* this file is part of ehci-hcd.c */
|
||||
|
||||
#define ehci_dbg(ehci, fmt, args...) \
|
||||
dev_dbg (ehci_to_hcd(ehci)->self.controller , fmt , ## args )
|
||||
#define ehci_err(ehci, fmt, args...) \
|
||||
dev_err (ehci_to_hcd(ehci)->self.controller , fmt , ## args )
|
||||
#define ehci_info(ehci, fmt, args...) \
|
||||
dev_info (ehci_to_hcd(ehci)->self.controller , fmt , ## args )
|
||||
#define ehci_warn(ehci, fmt, args...) \
|
||||
dev_warn (ehci_to_hcd(ehci)->self.controller , fmt , ## args )
|
||||
|
||||
#ifdef EHCI_VERBOSE_DEBUG
|
||||
# define vdbg dbg
|
||||
# define ehci_vdbg ehci_dbg
|
||||
#else
|
||||
# define vdbg(fmt,args...) do { } while (0)
|
||||
# define ehci_vdbg(ehci, fmt, args...) do { } while (0)
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
/* check the values in the HCSPARAMS register
|
||||
* (host controller _Structural_ parameters)
|
||||
* see EHCI spec, Table 2-4 for each value
|
||||
*/
|
||||
static void dbg_hcs_params (struct ehci_hcd *ehci, char *label)
|
||||
{
|
||||
u32 params = readl (&ehci->caps->hcs_params);
|
||||
|
||||
ehci_dbg (ehci,
|
||||
"%s hcs_params 0x%x dbg=%d%s cc=%d pcc=%d%s%s ports=%d\n",
|
||||
label, params,
|
||||
HCS_DEBUG_PORT (params),
|
||||
HCS_INDICATOR (params) ? " ind" : "",
|
||||
HCS_N_CC (params),
|
||||
HCS_N_PCC (params),
|
||||
HCS_PORTROUTED (params) ? "" : " ordered",
|
||||
HCS_PPC (params) ? "" : " !ppc",
|
||||
HCS_N_PORTS (params)
|
||||
);
|
||||
/* Port routing, per EHCI 0.95 Spec, Section 2.2.5 */
|
||||
if (HCS_PORTROUTED (params)) {
|
||||
int i;
|
||||
char buf [46], tmp [7], byte;
|
||||
|
||||
buf[0] = 0;
|
||||
for (i = 0; i < HCS_N_PORTS (params); i++) {
|
||||
// FIXME MIPS won't readb() ...
|
||||
byte = readb (&ehci->caps->portroute[(i>>1)]);
|
||||
sprintf(tmp, "%d ",
|
||||
((i & 0x1) ? ((byte)&0xf) : ((byte>>4)&0xf)));
|
||||
strcat(buf, tmp);
|
||||
}
|
||||
ehci_dbg (ehci, "%s portroute %s\n",
|
||||
label, buf);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
static inline void dbg_hcs_params (struct ehci_hcd *ehci, char *label) {}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
/* check the values in the HCCPARAMS register
|
||||
* (host controller _Capability_ parameters)
|
||||
* see EHCI Spec, Table 2-5 for each value
|
||||
* */
|
||||
static void dbg_hcc_params (struct ehci_hcd *ehci, char *label)
|
||||
{
|
||||
u32 params = readl (&ehci->caps->hcc_params);
|
||||
|
||||
if (HCC_ISOC_CACHE (params)) {
|
||||
ehci_dbg (ehci,
|
||||
"%s hcc_params %04x caching frame %s%s%s\n",
|
||||
label, params,
|
||||
HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024",
|
||||
HCC_CANPARK (params) ? " park" : "",
|
||||
HCC_64BIT_ADDR (params) ? " 64 bit addr" : "");
|
||||
} else {
|
||||
ehci_dbg (ehci,
|
||||
"%s hcc_params %04x thresh %d uframes %s%s%s\n",
|
||||
label,
|
||||
params,
|
||||
HCC_ISOC_THRES (params),
|
||||
HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024",
|
||||
HCC_CANPARK (params) ? " park" : "",
|
||||
HCC_64BIT_ADDR (params) ? " 64 bit addr" : "");
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
static void __attribute__((__unused__))
|
||||
dbg_qtd (const char *label, struct ehci_hcd *ehci, struct ehci_qtd *qtd)
|
||||
{
|
||||
ehci_dbg (ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd,
|
||||
le32_to_cpup (&qtd->hw_next),
|
||||
le32_to_cpup (&qtd->hw_alt_next),
|
||||
le32_to_cpup (&qtd->hw_token),
|
||||
le32_to_cpup (&qtd->hw_buf [0]));
|
||||
if (qtd->hw_buf [1])
|
||||
ehci_dbg (ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n",
|
||||
le32_to_cpup (&qtd->hw_buf [1]),
|
||||
le32_to_cpup (&qtd->hw_buf [2]),
|
||||
le32_to_cpup (&qtd->hw_buf [3]),
|
||||
le32_to_cpup (&qtd->hw_buf [4]));
|
||||
}
|
||||
|
||||
static void __attribute__((__unused__))
|
||||
dbg_qh (const char *label, struct ehci_hcd *ehci, struct ehci_qh *qh)
|
||||
{
|
||||
ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label,
|
||||
qh, qh->hw_next, qh->hw_info1, qh->hw_info2,
|
||||
qh->hw_current);
|
||||
dbg_qtd ("overlay", ehci, (struct ehci_qtd *) &qh->hw_qtd_next);
|
||||
}
|
||||
|
||||
static void __attribute__((__unused__))
|
||||
dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd)
|
||||
{
|
||||
ehci_dbg (ehci, "%s [%d] itd %p, next %08x, urb %p\n",
|
||||
label, itd->frame, itd, le32_to_cpu(itd->hw_next), itd->urb);
|
||||
ehci_dbg (ehci,
|
||||
" trans: %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||
le32_to_cpu(itd->hw_transaction[0]),
|
||||
le32_to_cpu(itd->hw_transaction[1]),
|
||||
le32_to_cpu(itd->hw_transaction[2]),
|
||||
le32_to_cpu(itd->hw_transaction[3]),
|
||||
le32_to_cpu(itd->hw_transaction[4]),
|
||||
le32_to_cpu(itd->hw_transaction[5]),
|
||||
le32_to_cpu(itd->hw_transaction[6]),
|
||||
le32_to_cpu(itd->hw_transaction[7]));
|
||||
ehci_dbg (ehci,
|
||||
" buf: %08x %08x %08x %08x %08x %08x %08x\n",
|
||||
le32_to_cpu(itd->hw_bufp[0]),
|
||||
le32_to_cpu(itd->hw_bufp[1]),
|
||||
le32_to_cpu(itd->hw_bufp[2]),
|
||||
le32_to_cpu(itd->hw_bufp[3]),
|
||||
le32_to_cpu(itd->hw_bufp[4]),
|
||||
le32_to_cpu(itd->hw_bufp[5]),
|
||||
le32_to_cpu(itd->hw_bufp[6]));
|
||||
ehci_dbg (ehci, " index: %d %d %d %d %d %d %d %d\n",
|
||||
itd->index[0], itd->index[1], itd->index[2],
|
||||
itd->index[3], itd->index[4], itd->index[5],
|
||||
itd->index[6], itd->index[7]);
|
||||
}
|
||||
|
||||
static void __attribute__((__unused__))
|
||||
dbg_sitd (const char *label, struct ehci_hcd *ehci, struct ehci_sitd *sitd)
|
||||
{
|
||||
ehci_dbg (ehci, "%s [%d] sitd %p, next %08x, urb %p\n",
|
||||
label, sitd->frame, sitd, le32_to_cpu(sitd->hw_next), sitd->urb);
|
||||
ehci_dbg (ehci,
|
||||
" addr %08x sched %04x result %08x buf %08x %08x\n",
|
||||
le32_to_cpu(sitd->hw_fullspeed_ep),
|
||||
le32_to_cpu(sitd->hw_uframe),
|
||||
le32_to_cpu(sitd->hw_results),
|
||||
le32_to_cpu(sitd->hw_buf [0]),
|
||||
le32_to_cpu(sitd->hw_buf [1]));
|
||||
}
|
||||
|
||||
static int __attribute__((__unused__))
|
||||
dbg_status_buf (char *buf, unsigned len, const char *label, u32 status)
|
||||
{
|
||||
return scnprintf (buf, len,
|
||||
"%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s",
|
||||
label, label [0] ? " " : "", status,
|
||||
(status & STS_ASS) ? " Async" : "",
|
||||
(status & STS_PSS) ? " Periodic" : "",
|
||||
(status & STS_RECL) ? " Recl" : "",
|
||||
(status & STS_HALT) ? " Halt" : "",
|
||||
(status & STS_IAA) ? " IAA" : "",
|
||||
(status & STS_FATAL) ? " FATAL" : "",
|
||||
(status & STS_FLR) ? " FLR" : "",
|
||||
(status & STS_PCD) ? " PCD" : "",
|
||||
(status & STS_ERR) ? " ERR" : "",
|
||||
(status & STS_INT) ? " INT" : ""
|
||||
);
|
||||
}
|
||||
|
||||
static int __attribute__((__unused__))
|
||||
dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable)
|
||||
{
|
||||
return scnprintf (buf, len,
|
||||
"%s%sintrenable %02x%s%s%s%s%s%s",
|
||||
label, label [0] ? " " : "", enable,
|
||||
(enable & STS_IAA) ? " IAA" : "",
|
||||
(enable & STS_FATAL) ? " FATAL" : "",
|
||||
(enable & STS_FLR) ? " FLR" : "",
|
||||
(enable & STS_PCD) ? " PCD" : "",
|
||||
(enable & STS_ERR) ? " ERR" : "",
|
||||
(enable & STS_INT) ? " INT" : ""
|
||||
);
|
||||
}
|
||||
|
||||
static const char *const fls_strings [] =
|
||||
{ "1024", "512", "256", "??" };
|
||||
|
||||
static int
|
||||
dbg_command_buf (char *buf, unsigned len, const char *label, u32 command)
|
||||
{
|
||||
return scnprintf (buf, len,
|
||||
"%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s",
|
||||
label, label [0] ? " " : "", command,
|
||||
(command & CMD_PARK) ? "park" : "(park)",
|
||||
CMD_PARK_CNT (command),
|
||||
(command >> 16) & 0x3f,
|
||||
(command & CMD_LRESET) ? " LReset" : "",
|
||||
(command & CMD_IAAD) ? " IAAD" : "",
|
||||
(command & CMD_ASE) ? " Async" : "",
|
||||
(command & CMD_PSE) ? " Periodic" : "",
|
||||
fls_strings [(command >> 2) & 0x3],
|
||||
(command & CMD_RESET) ? " Reset" : "",
|
||||
(command & CMD_RUN) ? "RUN" : "HALT"
|
||||
);
|
||||
}
|
||||
|
||||
static int
|
||||
dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status)
|
||||
{
|
||||
char *sig;
|
||||
|
||||
/* signaling state */
|
||||
switch (status & (3 << 10)) {
|
||||
case 0 << 10: sig = "se0"; break;
|
||||
case 1 << 10: sig = "k"; break; /* low speed */
|
||||
case 2 << 10: sig = "j"; break;
|
||||
default: sig = "?"; break;
|
||||
}
|
||||
|
||||
return scnprintf (buf, len,
|
||||
"%s%sport %d status %06x%s%s sig=%s %s%s%s%s%s%s%s%s%s",
|
||||
label, label [0] ? " " : "", port, status,
|
||||
(status & PORT_POWER) ? " POWER" : "",
|
||||
(status & PORT_OWNER) ? " OWNER" : "",
|
||||
sig,
|
||||
(status & PORT_RESET) ? " RESET" : "",
|
||||
(status & PORT_SUSPEND) ? " SUSPEND" : "",
|
||||
(status & PORT_RESUME) ? " RESUME" : "",
|
||||
(status & PORT_OCC) ? " OCC" : "",
|
||||
(status & PORT_OC) ? " OC" : "",
|
||||
(status & PORT_PEC) ? " PEC" : "",
|
||||
(status & PORT_PE) ? " PE" : "",
|
||||
(status & PORT_CSC) ? " CSC" : "",
|
||||
(status & PORT_CONNECT) ? " CONNECT" : ""
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
static inline void __attribute__((__unused__))
|
||||
dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh)
|
||||
{}
|
||||
|
||||
static inline int __attribute__((__unused__))
|
||||
dbg_status_buf (char *buf, unsigned len, const char *label, u32 status)
|
||||
{ return 0; }
|
||||
|
||||
static inline int __attribute__((__unused__))
|
||||
dbg_command_buf (char *buf, unsigned len, const char *label, u32 command)
|
||||
{ return 0; }
|
||||
|
||||
static inline int __attribute__((__unused__))
|
||||
dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable)
|
||||
{ return 0; }
|
||||
|
||||
static inline int __attribute__((__unused__))
|
||||
dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status)
|
||||
{ return 0; }
|
||||
|
||||
#endif /* DEBUG */
|
||||
|
||||
/* functions have the "wrong" filename when they're output... */
|
||||
#define dbg_status(ehci, label, status) { \
|
||||
char _buf [80]; \
|
||||
dbg_status_buf (_buf, sizeof _buf, label, status); \
|
||||
ehci_dbg (ehci, "%s\n", _buf); \
|
||||
}
|
||||
|
||||
#define dbg_cmd(ehci, label, command) { \
|
||||
char _buf [80]; \
|
||||
dbg_command_buf (_buf, sizeof _buf, label, command); \
|
||||
ehci_dbg (ehci, "%s\n", _buf); \
|
||||
}
|
||||
|
||||
#define dbg_port(ehci, label, port, status) { \
|
||||
char _buf [80]; \
|
||||
dbg_port_buf (_buf, sizeof _buf, label, port, status); \
|
||||
ehci_dbg (ehci, "%s\n", _buf); \
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef STUB_DEBUG_FILES
|
||||
|
||||
static inline void create_debug_files (struct ehci_hcd *bus) { }
|
||||
static inline void remove_debug_files (struct ehci_hcd *bus) { }
|
||||
|
||||
#else
|
||||
|
||||
/* troubleshooting help: expose state in driverfs */
|
||||
|
||||
#define speed_char(info1) ({ char tmp; \
|
||||
switch (info1 & (3 << 12)) { \
|
||||
case 0 << 12: tmp = 'f'; break; \
|
||||
case 1 << 12: tmp = 'l'; break; \
|
||||
case 2 << 12: tmp = 'h'; break; \
|
||||
default: tmp = '?'; break; \
|
||||
}; tmp; })
|
||||
|
||||
static inline char token_mark (__le32 token)
|
||||
{
|
||||
__u32 v = le32_to_cpu (token);
|
||||
if (v & QTD_STS_ACTIVE)
|
||||
return '*';
|
||||
if (v & QTD_STS_HALT)
|
||||
return '-';
|
||||
if (!IS_SHORT_READ (v))
|
||||
return ' ';
|
||||
/* tries to advance through hw_alt_next */
|
||||
return '/';
|
||||
}
|
||||
|
||||
static void qh_lines (
|
||||
struct ehci_hcd *ehci,
|
||||
struct ehci_qh *qh,
|
||||
char **nextp,
|
||||
unsigned *sizep
|
||||
)
|
||||
{
|
||||
u32 scratch;
|
||||
u32 hw_curr;
|
||||
struct list_head *entry;
|
||||
struct ehci_qtd *td;
|
||||
unsigned temp;
|
||||
unsigned size = *sizep;
|
||||
char *next = *nextp;
|
||||
char mark;
|
||||
|
||||
if (qh->hw_qtd_next == EHCI_LIST_END) /* NEC does this */
|
||||
mark = '@';
|
||||
else
|
||||
mark = token_mark (qh->hw_token);
|
||||
if (mark == '/') { /* qh_alt_next controls qh advance? */
|
||||
if ((qh->hw_alt_next & QTD_MASK) == ehci->async->hw_alt_next)
|
||||
mark = '#'; /* blocked */
|
||||
else if (qh->hw_alt_next == EHCI_LIST_END)
|
||||
mark = '.'; /* use hw_qtd_next */
|
||||
/* else alt_next points to some other qtd */
|
||||
}
|
||||
scratch = le32_to_cpup (&qh->hw_info1);
|
||||
hw_curr = (mark == '*') ? le32_to_cpup (&qh->hw_current) : 0;
|
||||
temp = scnprintf (next, size,
|
||||
"qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)",
|
||||
qh, scratch & 0x007f,
|
||||
speed_char (scratch),
|
||||
(scratch >> 8) & 0x000f,
|
||||
scratch, le32_to_cpup (&qh->hw_info2),
|
||||
le32_to_cpup (&qh->hw_token), mark,
|
||||
(__constant_cpu_to_le32 (QTD_TOGGLE) & qh->hw_token)
|
||||
? "data1" : "data0",
|
||||
(le32_to_cpup (&qh->hw_alt_next) >> 1) & 0x0f);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
/* hc may be modifying the list as we read it ... */
|
||||
list_for_each (entry, &qh->qtd_list) {
|
||||
td = list_entry (entry, struct ehci_qtd, qtd_list);
|
||||
scratch = le32_to_cpup (&td->hw_token);
|
||||
mark = ' ';
|
||||
if (hw_curr == td->qtd_dma)
|
||||
mark = '*';
|
||||
else if (qh->hw_qtd_next == td->qtd_dma)
|
||||
mark = '+';
|
||||
else if (QTD_LENGTH (scratch)) {
|
||||
if (td->hw_alt_next == ehci->async->hw_alt_next)
|
||||
mark = '#';
|
||||
else if (td->hw_alt_next != EHCI_LIST_END)
|
||||
mark = '/';
|
||||
}
|
||||
temp = snprintf (next, size,
|
||||
"\n\t%p%c%s len=%d %08x urb %p",
|
||||
td, mark, ({ char *tmp;
|
||||
switch ((scratch>>8)&0x03) {
|
||||
case 0: tmp = "out"; break;
|
||||
case 1: tmp = "in"; break;
|
||||
case 2: tmp = "setup"; break;
|
||||
default: tmp = "?"; break;
|
||||
} tmp;}),
|
||||
(scratch >> 16) & 0x7fff,
|
||||
scratch,
|
||||
td->urb);
|
||||
if (temp < 0)
|
||||
temp = 0;
|
||||
else if (size < temp)
|
||||
temp = size;
|
||||
size -= temp;
|
||||
next += temp;
|
||||
if (temp == size)
|
||||
goto done;
|
||||
}
|
||||
|
||||
temp = snprintf (next, size, "\n");
|
||||
if (temp < 0)
|
||||
temp = 0;
|
||||
else if (size < temp)
|
||||
temp = size;
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
done:
|
||||
*sizep = size;
|
||||
*nextp = next;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
show_async (struct class_device *class_dev, char *buf)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
unsigned long flags;
|
||||
unsigned temp, size;
|
||||
char *next;
|
||||
struct ehci_qh *qh;
|
||||
|
||||
*buf = 0;
|
||||
|
||||
bus = to_usb_bus(class_dev);
|
||||
hcd = bus->hcpriv;
|
||||
ehci = hcd_to_ehci (hcd);
|
||||
next = buf;
|
||||
size = PAGE_SIZE;
|
||||
|
||||
/* dumps a snapshot of the async schedule.
|
||||
* usually empty except for long-term bulk reads, or head.
|
||||
* one QH per line, and TDs we know about
|
||||
*/
|
||||
spin_lock_irqsave (&ehci->lock, flags);
|
||||
for (qh = ehci->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh)
|
||||
qh_lines (ehci, qh, &next, &size);
|
||||
if (ehci->reclaim && size > 0) {
|
||||
temp = scnprintf (next, size, "\nreclaim =\n");
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
for (qh = ehci->reclaim; size > 0 && qh; qh = qh->reclaim)
|
||||
qh_lines (ehci, qh, &next, &size);
|
||||
}
|
||||
spin_unlock_irqrestore (&ehci->lock, flags);
|
||||
|
||||
return strlen (buf);
|
||||
}
|
||||
static CLASS_DEVICE_ATTR (async, S_IRUGO, show_async, NULL);
|
||||
|
||||
#define DBG_SCHED_LIMIT 64
|
||||
|
||||
static ssize_t
|
||||
show_periodic (struct class_device *class_dev, char *buf)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
unsigned long flags;
|
||||
union ehci_shadow p, *seen;
|
||||
unsigned temp, size, seen_count;
|
||||
char *next;
|
||||
unsigned i;
|
||||
__le32 tag;
|
||||
|
||||
if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, SLAB_ATOMIC)))
|
||||
return 0;
|
||||
seen_count = 0;
|
||||
|
||||
bus = to_usb_bus(class_dev);
|
||||
hcd = bus->hcpriv;
|
||||
ehci = hcd_to_ehci (hcd);
|
||||
next = buf;
|
||||
size = PAGE_SIZE;
|
||||
|
||||
temp = scnprintf (next, size, "size = %d\n", ehci->periodic_size);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
/* dump a snapshot of the periodic schedule.
|
||||
* iso changes, interrupt usually doesn't.
|
||||
*/
|
||||
spin_lock_irqsave (&ehci->lock, flags);
|
||||
for (i = 0; i < ehci->periodic_size; i++) {
|
||||
p = ehci->pshadow [i];
|
||||
if (likely (!p.ptr))
|
||||
continue;
|
||||
tag = Q_NEXT_TYPE (ehci->periodic [i]);
|
||||
|
||||
temp = scnprintf (next, size, "%4d: ", i);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
do {
|
||||
switch (tag) {
|
||||
case Q_TYPE_QH:
|
||||
temp = scnprintf (next, size, " qh%d-%04x/%p",
|
||||
p.qh->period,
|
||||
le32_to_cpup (&p.qh->hw_info2)
|
||||
/* uframe masks */
|
||||
& 0xffff,
|
||||
p.qh);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
/* don't repeat what follows this qh */
|
||||
for (temp = 0; temp < seen_count; temp++) {
|
||||
if (seen [temp].ptr != p.ptr)
|
||||
continue;
|
||||
if (p.qh->qh_next.ptr)
|
||||
temp = scnprintf (next, size,
|
||||
" ...");
|
||||
p.ptr = NULL;
|
||||
break;
|
||||
}
|
||||
/* show more info the first time around */
|
||||
if (temp == seen_count && p.ptr) {
|
||||
u32 scratch = le32_to_cpup (
|
||||
&p.qh->hw_info1);
|
||||
struct ehci_qtd *qtd;
|
||||
char *type = "";
|
||||
|
||||
/* count tds, get ep direction */
|
||||
temp = 0;
|
||||
list_for_each_entry (qtd,
|
||||
&p.qh->qtd_list,
|
||||
qtd_list) {
|
||||
temp++;
|
||||
switch (0x03 & (le32_to_cpu (
|
||||
qtd->hw_token) >> 8)) {
|
||||
case 0: type = "out"; continue;
|
||||
case 1: type = "in"; continue;
|
||||
}
|
||||
}
|
||||
|
||||
temp = scnprintf (next, size,
|
||||
" (%c%d ep%d%s "
|
||||
"[%d/%d] q%d p%d)",
|
||||
speed_char (scratch),
|
||||
scratch & 0x007f,
|
||||
(scratch >> 8) & 0x000f, type,
|
||||
p.qh->usecs, p.qh->c_usecs,
|
||||
temp,
|
||||
0x7ff & (scratch >> 16));
|
||||
|
||||
if (seen_count < DBG_SCHED_LIMIT)
|
||||
seen [seen_count++].qh = p.qh;
|
||||
} else
|
||||
temp = 0;
|
||||
if (p.qh) {
|
||||
tag = Q_NEXT_TYPE (p.qh->hw_next);
|
||||
p = p.qh->qh_next;
|
||||
}
|
||||
break;
|
||||
case Q_TYPE_FSTN:
|
||||
temp = scnprintf (next, size,
|
||||
" fstn-%8x/%p", p.fstn->hw_prev,
|
||||
p.fstn);
|
||||
tag = Q_NEXT_TYPE (p.fstn->hw_next);
|
||||
p = p.fstn->fstn_next;
|
||||
break;
|
||||
case Q_TYPE_ITD:
|
||||
temp = scnprintf (next, size,
|
||||
" itd/%p", p.itd);
|
||||
tag = Q_NEXT_TYPE (p.itd->hw_next);
|
||||
p = p.itd->itd_next;
|
||||
break;
|
||||
case Q_TYPE_SITD:
|
||||
temp = scnprintf (next, size,
|
||||
" sitd%d-%04x/%p",
|
||||
p.sitd->stream->interval,
|
||||
le32_to_cpup (&p.sitd->hw_uframe)
|
||||
& 0x0000ffff,
|
||||
p.sitd);
|
||||
tag = Q_NEXT_TYPE (p.sitd->hw_next);
|
||||
p = p.sitd->sitd_next;
|
||||
break;
|
||||
}
|
||||
size -= temp;
|
||||
next += temp;
|
||||
} while (p.ptr);
|
||||
|
||||
temp = scnprintf (next, size, "\n");
|
||||
size -= temp;
|
||||
next += temp;
|
||||
}
|
||||
spin_unlock_irqrestore (&ehci->lock, flags);
|
||||
kfree (seen);
|
||||
|
||||
return PAGE_SIZE - size;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR (periodic, S_IRUGO, show_periodic, NULL);
|
||||
|
||||
#undef DBG_SCHED_LIMIT
|
||||
|
||||
static ssize_t
|
||||
show_registers (struct class_device *class_dev, char *buf)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
unsigned long flags;
|
||||
unsigned temp, size, i;
|
||||
char *next, scratch [80];
|
||||
static char fmt [] = "%*s\n";
|
||||
static char label [] = "";
|
||||
|
||||
bus = to_usb_bus(class_dev);
|
||||
hcd = bus->hcpriv;
|
||||
ehci = hcd_to_ehci (hcd);
|
||||
next = buf;
|
||||
size = PAGE_SIZE;
|
||||
|
||||
spin_lock_irqsave (&ehci->lock, flags);
|
||||
|
||||
if (bus->controller->power.power_state) {
|
||||
size = scnprintf (next, size,
|
||||
"bus %s, device %s (driver " DRIVER_VERSION ")\n"
|
||||
"SUSPENDED (no register access)\n",
|
||||
hcd->self.controller->bus->name,
|
||||
hcd->self.controller->bus_id);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Capability Registers */
|
||||
i = HC_VERSION(readl (&ehci->caps->hc_capbase));
|
||||
temp = scnprintf (next, size,
|
||||
"bus %s, device %s (driver " DRIVER_VERSION ")\n"
|
||||
"EHCI %x.%02x, hcd state %d\n",
|
||||
hcd->self.controller->bus->name,
|
||||
hcd->self.controller->bus_id,
|
||||
i >> 8, i & 0x0ff, hcd->state);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
// FIXME interpret both types of params
|
||||
i = readl (&ehci->caps->hcs_params);
|
||||
temp = scnprintf (next, size, "structural params 0x%08x\n", i);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
i = readl (&ehci->caps->hcc_params);
|
||||
temp = scnprintf (next, size, "capability params 0x%08x\n", i);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
/* Operational Registers */
|
||||
temp = dbg_status_buf (scratch, sizeof scratch, label,
|
||||
readl (&ehci->regs->status));
|
||||
temp = scnprintf (next, size, fmt, temp, scratch);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
temp = dbg_command_buf (scratch, sizeof scratch, label,
|
||||
readl (&ehci->regs->command));
|
||||
temp = scnprintf (next, size, fmt, temp, scratch);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
temp = dbg_intr_buf (scratch, sizeof scratch, label,
|
||||
readl (&ehci->regs->intr_enable));
|
||||
temp = scnprintf (next, size, fmt, temp, scratch);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
temp = scnprintf (next, size, "uframe %04x\n",
|
||||
readl (&ehci->regs->frame_index));
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
for (i = 0; i < HCS_N_PORTS (ehci->hcs_params); i++) {
|
||||
temp = dbg_port_buf (scratch, sizeof scratch, label, i + 1,
|
||||
readl (&ehci->regs->port_status [i]));
|
||||
temp = scnprintf (next, size, fmt, temp, scratch);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
}
|
||||
|
||||
if (ehci->reclaim) {
|
||||
temp = scnprintf (next, size, "reclaim qh %p%s\n",
|
||||
ehci->reclaim,
|
||||
ehci->reclaim_ready ? " ready" : "");
|
||||
size -= temp;
|
||||
next += temp;
|
||||
}
|
||||
|
||||
#ifdef EHCI_STATS
|
||||
temp = scnprintf (next, size,
|
||||
"irq normal %ld err %ld reclaim %ld (lost %ld)\n",
|
||||
ehci->stats.normal, ehci->stats.error, ehci->stats.reclaim,
|
||||
ehci->stats.lost_iaa);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
temp = scnprintf (next, size, "complete %ld unlink %ld\n",
|
||||
ehci->stats.complete, ehci->stats.unlink);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
#endif
|
||||
|
||||
done:
|
||||
spin_unlock_irqrestore (&ehci->lock, flags);
|
||||
|
||||
return PAGE_SIZE - size;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL);
|
||||
|
||||
static inline void create_debug_files (struct ehci_hcd *ehci)
|
||||
{
|
||||
struct class_device *cldev = &ehci_to_hcd(ehci)->self.class_dev;
|
||||
|
||||
class_device_create_file(cldev, &class_device_attr_async);
|
||||
class_device_create_file(cldev, &class_device_attr_periodic);
|
||||
class_device_create_file(cldev, &class_device_attr_registers);
|
||||
}
|
||||
|
||||
static inline void remove_debug_files (struct ehci_hcd *ehci)
|
||||
{
|
||||
struct class_device *cldev = &ehci_to_hcd(ehci)->self.class_dev;
|
||||
|
||||
class_device_remove_file(cldev, &class_device_attr_async);
|
||||
class_device_remove_file(cldev, &class_device_attr_periodic);
|
||||
class_device_remove_file(cldev, &class_device_attr_registers);
|
||||
}
|
||||
|
||||
#endif /* STUB_DEBUG_FILES */
|
||||
|
1261
drivers/usb/host/ehci-hcd.c
一般檔案
1261
drivers/usb/host/ehci-hcd.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
553
drivers/usb/host/ehci-hub.c
一般檔案
553
drivers/usb/host/ehci-hub.c
一般檔案
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* Copyright (c) 2001-2002 by David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/* this file is part of ehci-hcd.c */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Root Hub ... the nonsharable stuff
|
||||
*
|
||||
* Registers don't need cpu_to_le32, that happens transparently
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int ehci_hub_suspend (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
||||
int port;
|
||||
|
||||
if (time_before (jiffies, ehci->next_statechange))
|
||||
msleep(5);
|
||||
|
||||
port = HCS_N_PORTS (ehci->hcs_params);
|
||||
spin_lock_irq (&ehci->lock);
|
||||
|
||||
/* stop schedules, clean any completed work */
|
||||
if (HC_IS_RUNNING(hcd->state)) {
|
||||
ehci_quiesce (ehci);
|
||||
hcd->state = HC_STATE_QUIESCING;
|
||||
}
|
||||
ehci->command = readl (&ehci->regs->command);
|
||||
if (ehci->reclaim)
|
||||
ehci->reclaim_ready = 1;
|
||||
ehci_work(ehci, NULL);
|
||||
|
||||
/* suspend any active/unsuspended ports, maybe allow wakeup */
|
||||
while (port--) {
|
||||
u32 __iomem *reg = &ehci->regs->port_status [port];
|
||||
u32 t1 = readl (reg);
|
||||
u32 t2 = t1;
|
||||
|
||||
if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
|
||||
t2 |= PORT_SUSPEND;
|
||||
if (hcd->remote_wakeup)
|
||||
t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E;
|
||||
else
|
||||
t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
|
||||
|
||||
if (t1 != t2) {
|
||||
ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
|
||||
port + 1, t1, t2);
|
||||
writel (t2, reg);
|
||||
}
|
||||
}
|
||||
|
||||
/* turn off now-idle HC */
|
||||
ehci_halt (ehci);
|
||||
hcd->state = HC_STATE_SUSPENDED;
|
||||
|
||||
ehci->next_statechange = jiffies + msecs_to_jiffies(10);
|
||||
spin_unlock_irq (&ehci->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* caller has locked the root hub, and should reset/reinit on error */
|
||||
static int ehci_hub_resume (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
||||
u32 temp;
|
||||
int i;
|
||||
int intr_enable;
|
||||
|
||||
if (time_before (jiffies, ehci->next_statechange))
|
||||
msleep(5);
|
||||
spin_lock_irq (&ehci->lock);
|
||||
|
||||
/* re-init operational registers in case we lost power */
|
||||
if (readl (&ehci->regs->intr_enable) == 0) {
|
||||
/* at least some APM implementations will try to deliver
|
||||
* IRQs right away, so delay them until we're ready.
|
||||
*/
|
||||
intr_enable = 1;
|
||||
writel (0, &ehci->regs->segment);
|
||||
writel (ehci->periodic_dma, &ehci->regs->frame_list);
|
||||
writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
|
||||
} else
|
||||
intr_enable = 0;
|
||||
ehci_dbg(ehci, "resume root hub%s\n",
|
||||
intr_enable ? " after power loss" : "");
|
||||
|
||||
/* restore CMD_RUN, framelist size, and irq threshold */
|
||||
writel (ehci->command, &ehci->regs->command);
|
||||
|
||||
/* take ports out of suspend */
|
||||
i = HCS_N_PORTS (ehci->hcs_params);
|
||||
while (i--) {
|
||||
temp = readl (&ehci->regs->port_status [i]);
|
||||
temp &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
|
||||
if (temp & PORT_SUSPEND) {
|
||||
ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
|
||||
temp |= PORT_RESUME;
|
||||
}
|
||||
writel (temp, &ehci->regs->port_status [i]);
|
||||
}
|
||||
i = HCS_N_PORTS (ehci->hcs_params);
|
||||
mdelay (20);
|
||||
while (i--) {
|
||||
temp = readl (&ehci->regs->port_status [i]);
|
||||
if ((temp & PORT_SUSPEND) == 0)
|
||||
continue;
|
||||
temp &= ~PORT_RESUME;
|
||||
writel (temp, &ehci->regs->port_status [i]);
|
||||
ehci_vdbg (ehci, "resumed port %d\n", i + 1);
|
||||
}
|
||||
(void) readl (&ehci->regs->command);
|
||||
|
||||
/* maybe re-activate the schedule(s) */
|
||||
temp = 0;
|
||||
if (ehci->async->qh_next.qh)
|
||||
temp |= CMD_ASE;
|
||||
if (ehci->periodic_sched)
|
||||
temp |= CMD_PSE;
|
||||
if (temp) {
|
||||
ehci->command |= temp;
|
||||
writel (ehci->command, &ehci->regs->command);
|
||||
}
|
||||
|
||||
ehci->next_statechange = jiffies + msecs_to_jiffies(5);
|
||||
hcd->state = HC_STATE_RUNNING;
|
||||
|
||||
/* Now we can safely re-enable irqs */
|
||||
if (intr_enable)
|
||||
writel (INTR_MASK, &ehci->regs->intr_enable);
|
||||
|
||||
spin_unlock_irq (&ehci->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define ehci_hub_suspend NULL
|
||||
#define ehci_hub_resume NULL
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int check_reset_complete (
|
||||
struct ehci_hcd *ehci,
|
||||
int index,
|
||||
int port_status
|
||||
) {
|
||||
if (!(port_status & PORT_CONNECT)) {
|
||||
ehci->reset_done [index] = 0;
|
||||
return port_status;
|
||||
}
|
||||
|
||||
/* if reset finished and it's still not enabled -- handoff */
|
||||
if (!(port_status & PORT_PE)) {
|
||||
|
||||
/* with integrated TT, there's nobody to hand it to! */
|
||||
if (ehci_is_TDI(ehci)) {
|
||||
ehci_dbg (ehci,
|
||||
"Failed to enable port %d on root hub TT\n",
|
||||
index+1);
|
||||
return port_status;
|
||||
}
|
||||
|
||||
ehci_dbg (ehci, "port %d full speed --> companion\n",
|
||||
index + 1);
|
||||
|
||||
// what happens if HCS_N_CC(params) == 0 ?
|
||||
port_status |= PORT_OWNER;
|
||||
writel (port_status, &ehci->regs->port_status [index]);
|
||||
|
||||
} else
|
||||
ehci_dbg (ehci, "port %d high speed\n", index + 1);
|
||||
|
||||
return port_status;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* build "status change" packet (one or two bytes) from HC registers */
|
||||
|
||||
static int
|
||||
ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
|
||||
{
|
||||
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
||||
u32 temp, status = 0;
|
||||
int ports, i, retval = 1;
|
||||
unsigned long flags;
|
||||
|
||||
/* if !USB_SUSPEND, root hub timers won't get shut down ... */
|
||||
if (!HC_IS_RUNNING(hcd->state))
|
||||
return 0;
|
||||
|
||||
/* init status to no-changes */
|
||||
buf [0] = 0;
|
||||
ports = HCS_N_PORTS (ehci->hcs_params);
|
||||
if (ports > 7) {
|
||||
buf [1] = 0;
|
||||
retval++;
|
||||
}
|
||||
|
||||
/* no hub change reports (bit 0) for now (power, ...) */
|
||||
|
||||
/* port N changes (bit N)? */
|
||||
spin_lock_irqsave (&ehci->lock, flags);
|
||||
for (i = 0; i < ports; i++) {
|
||||
temp = readl (&ehci->regs->port_status [i]);
|
||||
if (temp & PORT_OWNER) {
|
||||
/* don't report this in GetPortStatus */
|
||||
if (temp & PORT_CSC) {
|
||||
temp &= ~PORT_CSC;
|
||||
writel (temp, &ehci->regs->port_status [i]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!(temp & PORT_CONNECT))
|
||||
ehci->reset_done [i] = 0;
|
||||
if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0
|
||||
// PORT_STAT_C_SUSPEND?
|
||||
|| ((temp & PORT_RESUME) != 0
|
||||
&& time_after (jiffies,
|
||||
ehci->reset_done [i]))) {
|
||||
if (i < 7)
|
||||
buf [0] |= 1 << (i + 1);
|
||||
else
|
||||
buf [1] |= 1 << (i - 7);
|
||||
status = STS_PCD;
|
||||
}
|
||||
}
|
||||
/* FIXME autosuspend idle root hubs */
|
||||
spin_unlock_irqrestore (&ehci->lock, flags);
|
||||
return status ? retval : 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
ehci_hub_descriptor (
|
||||
struct ehci_hcd *ehci,
|
||||
struct usb_hub_descriptor *desc
|
||||
) {
|
||||
int ports = HCS_N_PORTS (ehci->hcs_params);
|
||||
u16 temp;
|
||||
|
||||
desc->bDescriptorType = 0x29;
|
||||
desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */
|
||||
desc->bHubContrCurrent = 0;
|
||||
|
||||
desc->bNbrPorts = ports;
|
||||
temp = 1 + (ports / 8);
|
||||
desc->bDescLength = 7 + 2 * temp;
|
||||
|
||||
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
|
||||
memset (&desc->bitmap [0], 0, temp);
|
||||
memset (&desc->bitmap [temp], 0xff, temp);
|
||||
|
||||
temp = 0x0008; /* per-port overcurrent reporting */
|
||||
if (HCS_PPC (ehci->hcs_params))
|
||||
temp |= 0x0001; /* per-port power control */
|
||||
#if 0
|
||||
// re-enable when we support USB_PORT_FEAT_INDICATOR below.
|
||||
if (HCS_INDICATOR (ehci->hcs_params))
|
||||
temp |= 0x0080; /* per-port indicators (LEDs) */
|
||||
#endif
|
||||
desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
|
||||
|
||||
static int ehci_hub_control (
|
||||
struct usb_hcd *hcd,
|
||||
u16 typeReq,
|
||||
u16 wValue,
|
||||
u16 wIndex,
|
||||
char *buf,
|
||||
u16 wLength
|
||||
) {
|
||||
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
||||
int ports = HCS_N_PORTS (ehci->hcs_params);
|
||||
u32 temp, status;
|
||||
unsigned long flags;
|
||||
int retval = 0;
|
||||
|
||||
/*
|
||||
* FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
|
||||
* HCS_INDICATOR may say we can change LEDs to off/amber/green.
|
||||
* (track current state ourselves) ... blink for diagnostics,
|
||||
* power, "this is the one", etc. EHCI spec supports this.
|
||||
*/
|
||||
|
||||
spin_lock_irqsave (&ehci->lock, flags);
|
||||
switch (typeReq) {
|
||||
case ClearHubFeature:
|
||||
switch (wValue) {
|
||||
case C_HUB_LOCAL_POWER:
|
||||
case C_HUB_OVER_CURRENT:
|
||||
/* no hub-wide feature/status flags */
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
if (!wIndex || wIndex > ports)
|
||||
goto error;
|
||||
wIndex--;
|
||||
temp = readl (&ehci->regs->port_status [wIndex]);
|
||||
if (temp & PORT_OWNER)
|
||||
break;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
writel (temp & ~PORT_PE,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_ENABLE:
|
||||
writel (temp | PORT_PEC,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
if (temp & PORT_RESET)
|
||||
goto error;
|
||||
if (temp & PORT_SUSPEND) {
|
||||
if ((temp & PORT_PE) == 0)
|
||||
goto error;
|
||||
/* resume signaling for 20 msec */
|
||||
writel ((temp & ~PORT_WAKE_BITS) | PORT_RESUME,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
ehci->reset_done [wIndex] = jiffies
|
||||
+ msecs_to_jiffies (20);
|
||||
}
|
||||
break;
|
||||
case USB_PORT_FEAT_C_SUSPEND:
|
||||
/* we auto-clear this feature */
|
||||
break;
|
||||
case USB_PORT_FEAT_POWER:
|
||||
if (HCS_PPC (ehci->hcs_params))
|
||||
writel (temp & ~PORT_POWER,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_CONNECTION:
|
||||
writel (temp | PORT_CSC,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_OVER_CURRENT:
|
||||
writel (temp | PORT_OCC,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_RESET:
|
||||
/* GetPortStatus clears reset */
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
readl (&ehci->regs->command); /* unblock posted write */
|
||||
break;
|
||||
case GetHubDescriptor:
|
||||
ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
|
||||
buf);
|
||||
break;
|
||||
case GetHubStatus:
|
||||
/* no hub-wide feature/status flags */
|
||||
memset (buf, 0, 4);
|
||||
//cpu_to_le32s ((u32 *) buf);
|
||||
break;
|
||||
case GetPortStatus:
|
||||
if (!wIndex || wIndex > ports)
|
||||
goto error;
|
||||
wIndex--;
|
||||
status = 0;
|
||||
temp = readl (&ehci->regs->port_status [wIndex]);
|
||||
|
||||
// wPortChange bits
|
||||
if (temp & PORT_CSC)
|
||||
status |= 1 << USB_PORT_FEAT_C_CONNECTION;
|
||||
if (temp & PORT_PEC)
|
||||
status |= 1 << USB_PORT_FEAT_C_ENABLE;
|
||||
if (temp & PORT_OCC)
|
||||
status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
|
||||
|
||||
/* whoever resumes must GetPortStatus to complete it!! */
|
||||
if ((temp & PORT_RESUME)
|
||||
&& time_after (jiffies,
|
||||
ehci->reset_done [wIndex])) {
|
||||
status |= 1 << USB_PORT_FEAT_C_SUSPEND;
|
||||
ehci->reset_done [wIndex] = 0;
|
||||
|
||||
/* stop resume signaling */
|
||||
temp = readl (&ehci->regs->port_status [wIndex]);
|
||||
writel (temp & ~PORT_RESUME,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
retval = handshake (
|
||||
&ehci->regs->port_status [wIndex],
|
||||
PORT_RESUME, 0, 2000 /* 2msec */);
|
||||
if (retval != 0) {
|
||||
ehci_err (ehci, "port %d resume error %d\n",
|
||||
wIndex + 1, retval);
|
||||
goto error;
|
||||
}
|
||||
temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
|
||||
}
|
||||
|
||||
/* whoever resets must GetPortStatus to complete it!! */
|
||||
if ((temp & PORT_RESET)
|
||||
&& time_after (jiffies,
|
||||
ehci->reset_done [wIndex])) {
|
||||
status |= 1 << USB_PORT_FEAT_C_RESET;
|
||||
ehci->reset_done [wIndex] = 0;
|
||||
|
||||
/* force reset to complete */
|
||||
writel (temp & ~PORT_RESET,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
retval = handshake (
|
||||
&ehci->regs->port_status [wIndex],
|
||||
PORT_RESET, 0, 500);
|
||||
if (retval != 0) {
|
||||
ehci_err (ehci, "port %d reset error %d\n",
|
||||
wIndex + 1, retval);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* see what we found out */
|
||||
temp = check_reset_complete (ehci, wIndex,
|
||||
readl (&ehci->regs->port_status [wIndex]));
|
||||
}
|
||||
|
||||
// don't show wPortStatus if it's owned by a companion hc
|
||||
if (!(temp & PORT_OWNER)) {
|
||||
if (temp & PORT_CONNECT) {
|
||||
status |= 1 << USB_PORT_FEAT_CONNECTION;
|
||||
// status may be from integrated TT
|
||||
status |= ehci_port_speed(ehci, temp);
|
||||
}
|
||||
if (temp & PORT_PE)
|
||||
status |= 1 << USB_PORT_FEAT_ENABLE;
|
||||
if (temp & (PORT_SUSPEND|PORT_RESUME))
|
||||
status |= 1 << USB_PORT_FEAT_SUSPEND;
|
||||
if (temp & PORT_OC)
|
||||
status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
|
||||
if (temp & PORT_RESET)
|
||||
status |= 1 << USB_PORT_FEAT_RESET;
|
||||
if (temp & PORT_POWER)
|
||||
status |= 1 << USB_PORT_FEAT_POWER;
|
||||
}
|
||||
|
||||
#ifndef EHCI_VERBOSE_DEBUG
|
||||
if (status & ~0xffff) /* only if wPortChange is interesting */
|
||||
#endif
|
||||
dbg_port (ehci, "GetStatus", wIndex + 1, temp);
|
||||
// we "know" this alignment is good, caller used kmalloc()...
|
||||
*((__le32 *) buf) = cpu_to_le32 (status);
|
||||
break;
|
||||
case SetHubFeature:
|
||||
switch (wValue) {
|
||||
case C_HUB_LOCAL_POWER:
|
||||
case C_HUB_OVER_CURRENT:
|
||||
/* no hub-wide feature/status flags */
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case SetPortFeature:
|
||||
if (!wIndex || wIndex > ports)
|
||||
goto error;
|
||||
wIndex--;
|
||||
temp = readl (&ehci->regs->port_status [wIndex]);
|
||||
if (temp & PORT_OWNER)
|
||||
break;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
if ((temp & PORT_PE) == 0
|
||||
|| (temp & PORT_RESET) != 0)
|
||||
goto error;
|
||||
if (hcd->remote_wakeup)
|
||||
temp |= PORT_WAKE_BITS;
|
||||
writel (temp | PORT_SUSPEND,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_POWER:
|
||||
if (HCS_PPC (ehci->hcs_params))
|
||||
writel (temp | PORT_POWER,
|
||||
&ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_RESET:
|
||||
if (temp & PORT_RESUME)
|
||||
goto error;
|
||||
/* line status bits may report this as low speed,
|
||||
* which can be fine if this root hub has a
|
||||
* transaction translator built in.
|
||||
*/
|
||||
if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
|
||||
&& !ehci_is_TDI(ehci)
|
||||
&& PORT_USB11 (temp)) {
|
||||
ehci_dbg (ehci,
|
||||
"port %d low speed --> companion\n",
|
||||
wIndex + 1);
|
||||
temp |= PORT_OWNER;
|
||||
} else {
|
||||
ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
|
||||
temp |= PORT_RESET;
|
||||
temp &= ~PORT_PE;
|
||||
|
||||
/*
|
||||
* caller must wait, then call GetPortStatus
|
||||
* usb 2.0 spec says 50 ms resets on root
|
||||
*/
|
||||
ehci->reset_done [wIndex] = jiffies
|
||||
+ msecs_to_jiffies (50);
|
||||
}
|
||||
writel (temp, &ehci->regs->port_status [wIndex]);
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
readl (&ehci->regs->command); /* unblock posted writes */
|
||||
break;
|
||||
|
||||
default:
|
||||
error:
|
||||
/* "stall" on error */
|
||||
retval = -EPIPE;
|
||||
}
|
||||
spin_unlock_irqrestore (&ehci->lock, flags);
|
||||
return retval;
|
||||
}
|
237
drivers/usb/host/ehci-mem.c
一般檔案
237
drivers/usb/host/ehci-mem.c
一般檔案
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (c) 2001 by David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/* this file is part of ehci-hcd.c */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* There's basically three types of memory:
|
||||
* - data used only by the HCD ... kmalloc is fine
|
||||
* - async and periodic schedules, shared by HC and HCD ... these
|
||||
* need to use dma_pool or dma_alloc_coherent
|
||||
* - driver buffers, read/written by HC ... single shot DMA mapped
|
||||
*
|
||||
* There's also PCI "register" data, which is memory mapped.
|
||||
* No memory seen by this driver is pageable.
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Allocate the key transfer structures from the previously allocated pool */
|
||||
|
||||
static inline void ehci_qtd_init (struct ehci_qtd *qtd, dma_addr_t dma)
|
||||
{
|
||||
memset (qtd, 0, sizeof *qtd);
|
||||
qtd->qtd_dma = dma;
|
||||
qtd->hw_token = cpu_to_le32 (QTD_STS_HALT);
|
||||
qtd->hw_next = EHCI_LIST_END;
|
||||
qtd->hw_alt_next = EHCI_LIST_END;
|
||||
INIT_LIST_HEAD (&qtd->qtd_list);
|
||||
}
|
||||
|
||||
static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, int flags)
|
||||
{
|
||||
struct ehci_qtd *qtd;
|
||||
dma_addr_t dma;
|
||||
|
||||
qtd = dma_pool_alloc (ehci->qtd_pool, flags, &dma);
|
||||
if (qtd != NULL) {
|
||||
ehci_qtd_init (qtd, dma);
|
||||
}
|
||||
return qtd;
|
||||
}
|
||||
|
||||
static inline void ehci_qtd_free (struct ehci_hcd *ehci, struct ehci_qtd *qtd)
|
||||
{
|
||||
dma_pool_free (ehci->qtd_pool, qtd, qtd->qtd_dma);
|
||||
}
|
||||
|
||||
|
||||
static void qh_destroy (struct kref *kref)
|
||||
{
|
||||
struct ehci_qh *qh = container_of(kref, struct ehci_qh, kref);
|
||||
struct ehci_hcd *ehci = qh->ehci;
|
||||
|
||||
/* clean qtds first, and know this is not linked */
|
||||
if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) {
|
||||
ehci_dbg (ehci, "unused qh not empty!\n");
|
||||
BUG ();
|
||||
}
|
||||
if (qh->dummy)
|
||||
ehci_qtd_free (ehci, qh->dummy);
|
||||
usb_put_dev (qh->dev);
|
||||
dma_pool_free (ehci->qh_pool, qh, qh->qh_dma);
|
||||
}
|
||||
|
||||
static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, int flags)
|
||||
{
|
||||
struct ehci_qh *qh;
|
||||
dma_addr_t dma;
|
||||
|
||||
qh = (struct ehci_qh *)
|
||||
dma_pool_alloc (ehci->qh_pool, flags, &dma);
|
||||
if (!qh)
|
||||
return qh;
|
||||
|
||||
memset (qh, 0, sizeof *qh);
|
||||
kref_init(&qh->kref);
|
||||
qh->ehci = ehci;
|
||||
qh->qh_dma = dma;
|
||||
// INIT_LIST_HEAD (&qh->qh_list);
|
||||
INIT_LIST_HEAD (&qh->qtd_list);
|
||||
|
||||
/* dummy td enables safe urb queuing */
|
||||
qh->dummy = ehci_qtd_alloc (ehci, flags);
|
||||
if (qh->dummy == NULL) {
|
||||
ehci_dbg (ehci, "no dummy td\n");
|
||||
dma_pool_free (ehci->qh_pool, qh, qh->qh_dma);
|
||||
qh = NULL;
|
||||
}
|
||||
return qh;
|
||||
}
|
||||
|
||||
/* to share a qh (cpu threads, or hc) */
|
||||
static inline struct ehci_qh *qh_get (struct ehci_qh *qh)
|
||||
{
|
||||
kref_get(&qh->kref);
|
||||
return qh;
|
||||
}
|
||||
|
||||
static inline void qh_put (struct ehci_qh *qh)
|
||||
{
|
||||
kref_put(&qh->kref, qh_destroy);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* The queue heads and transfer descriptors are managed from pools tied
|
||||
* to each of the "per device" structures.
|
||||
* This is the initialisation and cleanup code.
|
||||
*/
|
||||
|
||||
static void ehci_mem_cleanup (struct ehci_hcd *ehci)
|
||||
{
|
||||
if (ehci->async)
|
||||
qh_put (ehci->async);
|
||||
ehci->async = NULL;
|
||||
|
||||
/* DMA consistent memory and pools */
|
||||
if (ehci->qtd_pool)
|
||||
dma_pool_destroy (ehci->qtd_pool);
|
||||
ehci->qtd_pool = NULL;
|
||||
|
||||
if (ehci->qh_pool) {
|
||||
dma_pool_destroy (ehci->qh_pool);
|
||||
ehci->qh_pool = NULL;
|
||||
}
|
||||
|
||||
if (ehci->itd_pool)
|
||||
dma_pool_destroy (ehci->itd_pool);
|
||||
ehci->itd_pool = NULL;
|
||||
|
||||
if (ehci->sitd_pool)
|
||||
dma_pool_destroy (ehci->sitd_pool);
|
||||
ehci->sitd_pool = NULL;
|
||||
|
||||
if (ehci->periodic)
|
||||
dma_free_coherent (ehci_to_hcd(ehci)->self.controller,
|
||||
ehci->periodic_size * sizeof (u32),
|
||||
ehci->periodic, ehci->periodic_dma);
|
||||
ehci->periodic = NULL;
|
||||
|
||||
/* shadow periodic table */
|
||||
if (ehci->pshadow)
|
||||
kfree (ehci->pshadow);
|
||||
ehci->pshadow = NULL;
|
||||
}
|
||||
|
||||
/* remember to add cleanup code (above) if you add anything here */
|
||||
static int ehci_mem_init (struct ehci_hcd *ehci, int flags)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* QTDs for control/bulk/intr transfers */
|
||||
ehci->qtd_pool = dma_pool_create ("ehci_qtd",
|
||||
ehci_to_hcd(ehci)->self.controller,
|
||||
sizeof (struct ehci_qtd),
|
||||
32 /* byte alignment (for hw parts) */,
|
||||
4096 /* can't cross 4K */);
|
||||
if (!ehci->qtd_pool) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* QHs for control/bulk/intr transfers */
|
||||
ehci->qh_pool = dma_pool_create ("ehci_qh",
|
||||
ehci_to_hcd(ehci)->self.controller,
|
||||
sizeof (struct ehci_qh),
|
||||
32 /* byte alignment (for hw parts) */,
|
||||
4096 /* can't cross 4K */);
|
||||
if (!ehci->qh_pool) {
|
||||
goto fail;
|
||||
}
|
||||
ehci->async = ehci_qh_alloc (ehci, flags);
|
||||
if (!ehci->async) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* ITD for high speed ISO transfers */
|
||||
ehci->itd_pool = dma_pool_create ("ehci_itd",
|
||||
ehci_to_hcd(ehci)->self.controller,
|
||||
sizeof (struct ehci_itd),
|
||||
32 /* byte alignment (for hw parts) */,
|
||||
4096 /* can't cross 4K */);
|
||||
if (!ehci->itd_pool) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* SITD for full/low speed split ISO transfers */
|
||||
ehci->sitd_pool = dma_pool_create ("ehci_sitd",
|
||||
ehci_to_hcd(ehci)->self.controller,
|
||||
sizeof (struct ehci_sitd),
|
||||
32 /* byte alignment (for hw parts) */,
|
||||
4096 /* can't cross 4K */);
|
||||
if (!ehci->sitd_pool) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Hardware periodic table */
|
||||
ehci->periodic = (__le32 *)
|
||||
dma_alloc_coherent (ehci_to_hcd(ehci)->self.controller,
|
||||
ehci->periodic_size * sizeof(__le32),
|
||||
&ehci->periodic_dma, 0);
|
||||
if (ehci->periodic == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
for (i = 0; i < ehci->periodic_size; i++)
|
||||
ehci->periodic [i] = EHCI_LIST_END;
|
||||
|
||||
/* software shadow of hardware table */
|
||||
ehci->pshadow = kmalloc (ehci->periodic_size * sizeof (void *), flags);
|
||||
if (ehci->pshadow == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
memset (ehci->pshadow, 0, ehci->periodic_size * sizeof (void *));
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ehci_dbg (ehci, "couldn't init memory\n");
|
||||
ehci_mem_cleanup (ehci);
|
||||
return -ENOMEM;
|
||||
}
|
1090
drivers/usb/host/ehci-q.c
一般檔案
1090
drivers/usb/host/ehci-q.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
1999
drivers/usb/host/ehci-sched.c
一般檔案
1999
drivers/usb/host/ehci-sched.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
637
drivers/usb/host/ehci.h
一般檔案
637
drivers/usb/host/ehci.h
一般檔案
@@ -0,0 +1,637 @@
|
||||
/*
|
||||
* Copyright (c) 2001-2002 by David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_EHCI_HCD_H
|
||||
#define __LINUX_EHCI_HCD_H
|
||||
|
||||
/* definitions used for the EHCI driver */
|
||||
|
||||
/* statistics can be kept for for tuning/monitoring */
|
||||
struct ehci_stats {
|
||||
/* irq usage */
|
||||
unsigned long normal;
|
||||
unsigned long error;
|
||||
unsigned long reclaim;
|
||||
unsigned long lost_iaa;
|
||||
|
||||
/* termination of urbs from core */
|
||||
unsigned long complete;
|
||||
unsigned long unlink;
|
||||
};
|
||||
|
||||
/* ehci_hcd->lock guards shared data against other CPUs:
|
||||
* ehci_hcd: async, reclaim, periodic (and shadow), ...
|
||||
* usb_host_endpoint: hcpriv
|
||||
* ehci_qh: qh_next, qtd_list
|
||||
* ehci_qtd: qtd_list
|
||||
*
|
||||
* Also, hold this lock when talking to HC registers or
|
||||
* when updating hw_* fields in shared qh/qtd/... structures.
|
||||
*/
|
||||
|
||||
#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */
|
||||
|
||||
struct ehci_hcd { /* one per controller */
|
||||
spinlock_t lock;
|
||||
|
||||
/* async schedule support */
|
||||
struct ehci_qh *async;
|
||||
struct ehci_qh *reclaim;
|
||||
unsigned reclaim_ready : 1;
|
||||
unsigned scanning : 1;
|
||||
|
||||
/* periodic schedule support */
|
||||
#define DEFAULT_I_TDPS 1024 /* some HCs can do less */
|
||||
unsigned periodic_size;
|
||||
__le32 *periodic; /* hw periodic table */
|
||||
dma_addr_t periodic_dma;
|
||||
unsigned i_thresh; /* uframes HC might cache */
|
||||
|
||||
union ehci_shadow *pshadow; /* mirror hw periodic table */
|
||||
int next_uframe; /* scan periodic, start here */
|
||||
unsigned periodic_sched; /* periodic activity count */
|
||||
|
||||
/* per root hub port */
|
||||
unsigned long reset_done [EHCI_MAX_ROOT_PORTS];
|
||||
|
||||
/* per-HC memory pools (could be per-bus, but ...) */
|
||||
struct dma_pool *qh_pool; /* qh per active urb */
|
||||
struct dma_pool *qtd_pool; /* one or more per qh */
|
||||
struct dma_pool *itd_pool; /* itd per iso urb */
|
||||
struct dma_pool *sitd_pool; /* sitd per split iso urb */
|
||||
|
||||
struct timer_list watchdog;
|
||||
struct notifier_block reboot_notifier;
|
||||
unsigned long actions;
|
||||
unsigned stamp;
|
||||
unsigned long next_statechange;
|
||||
u32 command;
|
||||
|
||||
unsigned is_tdi_rh_tt:1; /* TDI roothub with TT */
|
||||
|
||||
/* glue to PCI and HCD framework */
|
||||
struct ehci_caps __iomem *caps;
|
||||
struct ehci_regs __iomem *regs;
|
||||
__u32 hcs_params; /* cached register copy */
|
||||
|
||||
/* irq statistics */
|
||||
#ifdef EHCI_STATS
|
||||
struct ehci_stats stats;
|
||||
# define COUNT(x) do { (x)++; } while (0)
|
||||
#else
|
||||
# define COUNT(x) do {} while (0)
|
||||
#endif
|
||||
};
|
||||
|
||||
/* convert between an HCD pointer and the corresponding EHCI_HCD */
|
||||
static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct ehci_hcd *) (hcd->hcd_priv);
|
||||
}
|
||||
static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci)
|
||||
{
|
||||
return container_of ((void *) ehci, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
|
||||
enum ehci_timer_action {
|
||||
TIMER_IO_WATCHDOG,
|
||||
TIMER_IAA_WATCHDOG,
|
||||
TIMER_ASYNC_SHRINK,
|
||||
TIMER_ASYNC_OFF,
|
||||
};
|
||||
|
||||
static inline void
|
||||
timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action)
|
||||
{
|
||||
clear_bit (action, &ehci->actions);
|
||||
}
|
||||
|
||||
static inline void
|
||||
timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
|
||||
{
|
||||
if (!test_and_set_bit (action, &ehci->actions)) {
|
||||
unsigned long t;
|
||||
|
||||
switch (action) {
|
||||
case TIMER_IAA_WATCHDOG:
|
||||
t = EHCI_IAA_JIFFIES;
|
||||
break;
|
||||
case TIMER_IO_WATCHDOG:
|
||||
t = EHCI_IO_JIFFIES;
|
||||
break;
|
||||
case TIMER_ASYNC_OFF:
|
||||
t = EHCI_ASYNC_JIFFIES;
|
||||
break;
|
||||
// case TIMER_ASYNC_SHRINK:
|
||||
default:
|
||||
t = EHCI_SHRINK_JIFFIES;
|
||||
break;
|
||||
}
|
||||
t += jiffies;
|
||||
// all timings except IAA watchdog can be overridden.
|
||||
// async queue SHRINK often precedes IAA. while it's ready
|
||||
// to go OFF neither can matter, and afterwards the IO
|
||||
// watchdog stops unless there's still periodic traffic.
|
||||
if (action != TIMER_IAA_WATCHDOG
|
||||
&& t > ehci->watchdog.expires
|
||||
&& timer_pending (&ehci->watchdog))
|
||||
return;
|
||||
mod_timer (&ehci->watchdog, t);
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */
|
||||
|
||||
/* Section 2.2 Host Controller Capability Registers */
|
||||
struct ehci_caps {
|
||||
/* these fields are specified as 8 and 16 bit registers,
|
||||
* but some hosts can't perform 8 or 16 bit PCI accesses.
|
||||
*/
|
||||
u32 hc_capbase;
|
||||
#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */
|
||||
#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */
|
||||
u32 hcs_params; /* HCSPARAMS - offset 0x4 */
|
||||
#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */
|
||||
#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */
|
||||
#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */
|
||||
#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */
|
||||
#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */
|
||||
#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */
|
||||
#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */
|
||||
|
||||
u32 hcc_params; /* HCCPARAMS - offset 0x8 */
|
||||
#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */
|
||||
#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */
|
||||
#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */
|
||||
#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */
|
||||
#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/
|
||||
#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */
|
||||
u8 portroute [8]; /* nibbles for routing - offset 0xC */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
||||
/* Section 2.3 Host Controller Operational Registers */
|
||||
struct ehci_regs {
|
||||
|
||||
/* USBCMD: offset 0x00 */
|
||||
u32 command;
|
||||
/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */
|
||||
#define CMD_PARK (1<<11) /* enable "park" on async qh */
|
||||
#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */
|
||||
#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */
|
||||
#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */
|
||||
#define CMD_ASE (1<<5) /* async schedule enable */
|
||||
#define CMD_PSE (1<<4) /* periodic schedule enable */
|
||||
/* 3:2 is periodic frame list size */
|
||||
#define CMD_RESET (1<<1) /* reset HC not bus */
|
||||
#define CMD_RUN (1<<0) /* start/stop HC */
|
||||
|
||||
/* USBSTS: offset 0x04 */
|
||||
u32 status;
|
||||
#define STS_ASS (1<<15) /* Async Schedule Status */
|
||||
#define STS_PSS (1<<14) /* Periodic Schedule Status */
|
||||
#define STS_RECL (1<<13) /* Reclamation */
|
||||
#define STS_HALT (1<<12) /* Not running (any reason) */
|
||||
/* some bits reserved */
|
||||
/* these STS_* flags are also intr_enable bits (USBINTR) */
|
||||
#define STS_IAA (1<<5) /* Interrupted on async advance */
|
||||
#define STS_FATAL (1<<4) /* such as some PCI access errors */
|
||||
#define STS_FLR (1<<3) /* frame list rolled over */
|
||||
#define STS_PCD (1<<2) /* port change detect */
|
||||
#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */
|
||||
#define STS_INT (1<<0) /* "normal" completion (short, ...) */
|
||||
|
||||
/* USBINTR: offset 0x08 */
|
||||
u32 intr_enable;
|
||||
|
||||
/* FRINDEX: offset 0x0C */
|
||||
u32 frame_index; /* current microframe number */
|
||||
/* CTRLDSSEGMENT: offset 0x10 */
|
||||
u32 segment; /* address bits 63:32 if needed */
|
||||
/* PERIODICLISTBASE: offset 0x14 */
|
||||
u32 frame_list; /* points to periodic list */
|
||||
/* ASYNCLISTADDR: offset 0x18 */
|
||||
u32 async_next; /* address of next async queue head */
|
||||
|
||||
u32 reserved [9];
|
||||
|
||||
/* CONFIGFLAG: offset 0x40 */
|
||||
u32 configured_flag;
|
||||
#define FLAG_CF (1<<0) /* true: we'll support "high speed" */
|
||||
|
||||
/* PORTSC: offset 0x44 */
|
||||
u32 port_status [0]; /* up to N_PORTS */
|
||||
/* 31:23 reserved */
|
||||
#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */
|
||||
#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */
|
||||
#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */
|
||||
/* 19:16 for port testing */
|
||||
#define PORT_LED_OFF (0<<14)
|
||||
#define PORT_LED_AMBER (1<<14)
|
||||
#define PORT_LED_GREEN (2<<14)
|
||||
#define PORT_LED_MASK (3<<14)
|
||||
#define PORT_OWNER (1<<13) /* true: companion hc owns this port */
|
||||
#define PORT_POWER (1<<12) /* true: has power (see PPC) */
|
||||
#define PORT_USB11(x) (((x)&(3<<10))==(1<<10)) /* USB 1.1 device */
|
||||
/* 11:10 for detecting lowspeed devices (reset vs release ownership) */
|
||||
/* 9 reserved */
|
||||
#define PORT_RESET (1<<8) /* reset port */
|
||||
#define PORT_SUSPEND (1<<7) /* suspend port */
|
||||
#define PORT_RESUME (1<<6) /* resume it */
|
||||
#define PORT_OCC (1<<5) /* over current change */
|
||||
#define PORT_OC (1<<4) /* over current active */
|
||||
#define PORT_PEC (1<<3) /* port enable change */
|
||||
#define PORT_PE (1<<2) /* port enable */
|
||||
#define PORT_CSC (1<<1) /* connect status change */
|
||||
#define PORT_CONNECT (1<<0) /* device connected */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* Appendix C, Debug port ... intended for use with special "debug devices"
|
||||
* that can help if there's no serial console. (nonstandard enumeration.)
|
||||
*/
|
||||
struct ehci_dbg_port {
|
||||
u32 control;
|
||||
#define DBGP_OWNER (1<<30)
|
||||
#define DBGP_ENABLED (1<<28)
|
||||
#define DBGP_DONE (1<<16)
|
||||
#define DBGP_INUSE (1<<10)
|
||||
#define DBGP_ERRCODE(x) (((x)>>7)&0x0f)
|
||||
# define DBGP_ERR_BAD 1
|
||||
# define DBGP_ERR_SIGNAL 2
|
||||
#define DBGP_ERROR (1<<6)
|
||||
#define DBGP_GO (1<<5)
|
||||
#define DBGP_OUT (1<<4)
|
||||
#define DBGP_LEN(x) (((x)>>0)&0x0f)
|
||||
u32 pids;
|
||||
#define DBGP_PID_GET(x) (((x)>>16)&0xff)
|
||||
#define DBGP_PID_SET(data,tok) (((data)<<8)|(tok));
|
||||
u32 data03;
|
||||
u32 data47;
|
||||
u32 address;
|
||||
#define DBGP_EPADDR(dev,ep) (((dev)<<8)|(ep));
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define QTD_NEXT(dma) cpu_to_le32((u32)dma)
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.5
|
||||
* QTD: describe data transfer components (buffer, direction, ...)
|
||||
* See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram".
|
||||
*
|
||||
* These are associated only with "QH" (Queue Head) structures,
|
||||
* used with control, bulk, and interrupt transfers.
|
||||
*/
|
||||
struct ehci_qtd {
|
||||
/* first part defined by EHCI spec */
|
||||
__le32 hw_next; /* see EHCI 3.5.1 */
|
||||
__le32 hw_alt_next; /* see EHCI 3.5.2 */
|
||||
__le32 hw_token; /* see EHCI 3.5.3 */
|
||||
#define QTD_TOGGLE (1 << 31) /* data toggle */
|
||||
#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff)
|
||||
#define QTD_IOC (1 << 15) /* interrupt on complete */
|
||||
#define QTD_CERR(tok) (((tok)>>10) & 0x3)
|
||||
#define QTD_PID(tok) (((tok)>>8) & 0x3)
|
||||
#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */
|
||||
#define QTD_STS_HALT (1 << 6) /* halted on error */
|
||||
#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */
|
||||
#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */
|
||||
#define QTD_STS_XACT (1 << 3) /* device gave illegal response */
|
||||
#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */
|
||||
#define QTD_STS_STS (1 << 1) /* split transaction state */
|
||||
#define QTD_STS_PING (1 << 0) /* issue PING? */
|
||||
__le32 hw_buf [5]; /* see EHCI 3.5.4 */
|
||||
__le32 hw_buf_hi [5]; /* Appendix B */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t qtd_dma; /* qtd address */
|
||||
struct list_head qtd_list; /* sw qtd list */
|
||||
struct urb *urb; /* qtd's urb */
|
||||
size_t length; /* length of buffer */
|
||||
} __attribute__ ((aligned (32)));
|
||||
|
||||
/* mask NakCnt+T in qh->hw_alt_next */
|
||||
#define QTD_MASK __constant_cpu_to_le32 (~0x1f)
|
||||
|
||||
#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* type tag from {qh,itd,sitd,fstn}->hw_next */
|
||||
#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1))
|
||||
|
||||
/* values for that type tag */
|
||||
#define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1)
|
||||
#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1)
|
||||
#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1)
|
||||
#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1)
|
||||
|
||||
/* next async queue entry, or pointer to interrupt/periodic QH */
|
||||
#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH)
|
||||
|
||||
/* for periodic/async schedules and qtd lists, mark end of list */
|
||||
#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */
|
||||
|
||||
/*
|
||||
* Entries in periodic shadow table are pointers to one of four kinds
|
||||
* of data structure. That's dictated by the hardware; a type tag is
|
||||
* encoded in the low bits of the hardware's periodic schedule. Use
|
||||
* Q_NEXT_TYPE to get the tag.
|
||||
*
|
||||
* For entries in the async schedule, the type tag always says "qh".
|
||||
*/
|
||||
union ehci_shadow {
|
||||
struct ehci_qh *qh; /* Q_TYPE_QH */
|
||||
struct ehci_itd *itd; /* Q_TYPE_ITD */
|
||||
struct ehci_sitd *sitd; /* Q_TYPE_SITD */
|
||||
struct ehci_fstn *fstn; /* Q_TYPE_FSTN */
|
||||
u32 *hw_next; /* (all types) */
|
||||
void *ptr;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.6
|
||||
* QH: describes control/bulk/interrupt endpoints
|
||||
* See Fig 3-7 "Queue Head Structure Layout".
|
||||
*
|
||||
* These appear in both the async and (for interrupt) periodic schedules.
|
||||
*/
|
||||
|
||||
struct ehci_qh {
|
||||
/* first part defined by EHCI spec */
|
||||
__le32 hw_next; /* see EHCI 3.6.1 */
|
||||
__le32 hw_info1; /* see EHCI 3.6.2 */
|
||||
#define QH_HEAD 0x00008000
|
||||
__le32 hw_info2; /* see EHCI 3.6.2 */
|
||||
__le32 hw_current; /* qtd list - see EHCI 3.6.4 */
|
||||
|
||||
/* qtd overlay (hardware parts of a struct ehci_qtd) */
|
||||
__le32 hw_qtd_next;
|
||||
__le32 hw_alt_next;
|
||||
__le32 hw_token;
|
||||
__le32 hw_buf [5];
|
||||
__le32 hw_buf_hi [5];
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t qh_dma; /* address of qh */
|
||||
union ehci_shadow qh_next; /* ptr to qh; or periodic */
|
||||
struct list_head qtd_list; /* sw qtd list */
|
||||
struct ehci_qtd *dummy;
|
||||
struct ehci_qh *reclaim; /* next to reclaim */
|
||||
|
||||
struct ehci_hcd *ehci;
|
||||
struct kref kref;
|
||||
unsigned stamp;
|
||||
|
||||
u8 qh_state;
|
||||
#define QH_STATE_LINKED 1 /* HC sees this */
|
||||
#define QH_STATE_UNLINK 2 /* HC may still see this */
|
||||
#define QH_STATE_IDLE 3 /* HC doesn't see this */
|
||||
#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */
|
||||
#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */
|
||||
|
||||
/* periodic schedule info */
|
||||
u8 usecs; /* intr bandwidth */
|
||||
u8 gap_uf; /* uframes split/csplit gap */
|
||||
u8 c_usecs; /* ... split completion bw */
|
||||
unsigned short period; /* polling interval */
|
||||
unsigned short start; /* where polling starts */
|
||||
#define NO_FRAME ((unsigned short)~0) /* pick new start */
|
||||
struct usb_device *dev; /* access to TT */
|
||||
} __attribute__ ((aligned (32)));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* description of one iso transaction (up to 3 KB data if highspeed) */
|
||||
struct ehci_iso_packet {
|
||||
/* These will be copied to iTD when scheduling */
|
||||
u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */
|
||||
__le32 transaction; /* itd->hw_transaction[i] |= */
|
||||
u8 cross; /* buf crosses pages */
|
||||
/* for full speed OUT splits */
|
||||
u32 buf1;
|
||||
};
|
||||
|
||||
/* temporary schedule data for packets from iso urbs (both speeds)
|
||||
* each packet is one logical usb transaction to the device (not TT),
|
||||
* beginning at stream->next_uframe
|
||||
*/
|
||||
struct ehci_iso_sched {
|
||||
struct list_head td_list;
|
||||
unsigned span;
|
||||
struct ehci_iso_packet packet [0];
|
||||
};
|
||||
|
||||
/*
|
||||
* ehci_iso_stream - groups all (s)itds for this endpoint.
|
||||
* acts like a qh would, if EHCI had them for ISO.
|
||||
*/
|
||||
struct ehci_iso_stream {
|
||||
/* first two fields match QH, but info1 == 0 */
|
||||
__le32 hw_next;
|
||||
__le32 hw_info1;
|
||||
|
||||
u32 refcount;
|
||||
u8 bEndpointAddress;
|
||||
u8 highspeed;
|
||||
u16 depth; /* depth in uframes */
|
||||
struct list_head td_list; /* queued itds/sitds */
|
||||
struct list_head free_list; /* list of unused itds/sitds */
|
||||
struct usb_device *udev;
|
||||
struct usb_host_endpoint *ep;
|
||||
|
||||
/* output of (re)scheduling */
|
||||
unsigned long start; /* jiffies */
|
||||
unsigned long rescheduled;
|
||||
int next_uframe;
|
||||
__le32 splits;
|
||||
|
||||
/* the rest is derived from the endpoint descriptor,
|
||||
* trusting urb->interval == f(epdesc->bInterval) and
|
||||
* including the extra info for hw_bufp[0..2]
|
||||
*/
|
||||
u8 interval;
|
||||
u8 usecs, c_usecs;
|
||||
u16 maxp;
|
||||
u16 raw_mask;
|
||||
unsigned bandwidth;
|
||||
|
||||
/* This is used to initialize iTD's hw_bufp fields */
|
||||
__le32 buf0;
|
||||
__le32 buf1;
|
||||
__le32 buf2;
|
||||
|
||||
/* this is used to initialize sITD's tt info */
|
||||
__le32 address;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.3
|
||||
* Fig 3-4 "Isochronous Transaction Descriptor (iTD)"
|
||||
*
|
||||
* Schedule records for high speed iso xfers
|
||||
*/
|
||||
struct ehci_itd {
|
||||
/* first part defined by EHCI spec */
|
||||
__le32 hw_next; /* see EHCI 3.3.1 */
|
||||
__le32 hw_transaction [8]; /* see EHCI 3.3.2 */
|
||||
#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */
|
||||
#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */
|
||||
#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */
|
||||
#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */
|
||||
#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff)
|
||||
#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */
|
||||
|
||||
#define ITD_ACTIVE __constant_cpu_to_le32(EHCI_ISOC_ACTIVE)
|
||||
|
||||
__le32 hw_bufp [7]; /* see EHCI 3.3.3 */
|
||||
__le32 hw_bufp_hi [7]; /* Appendix B */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t itd_dma; /* for this itd */
|
||||
union ehci_shadow itd_next; /* ptr to periodic q entry */
|
||||
|
||||
struct urb *urb;
|
||||
struct ehci_iso_stream *stream; /* endpoint's queue */
|
||||
struct list_head itd_list; /* list of stream's itds */
|
||||
|
||||
/* any/all hw_transactions here may be used by that urb */
|
||||
unsigned frame; /* where scheduled */
|
||||
unsigned pg;
|
||||
unsigned index[8]; /* in urb->iso_frame_desc */
|
||||
u8 usecs[8];
|
||||
} __attribute__ ((aligned (32)));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.4
|
||||
* siTD, aka split-transaction isochronous Transfer Descriptor
|
||||
* ... describe full speed iso xfers through TT in hubs
|
||||
* see Figure 3-5 "Split-transaction Isochronous Transaction Descriptor (siTD)
|
||||
*/
|
||||
struct ehci_sitd {
|
||||
/* first part defined by EHCI spec */
|
||||
__le32 hw_next;
|
||||
/* uses bit field macros above - see EHCI 0.95 Table 3-8 */
|
||||
__le32 hw_fullspeed_ep; /* EHCI table 3-9 */
|
||||
__le32 hw_uframe; /* EHCI table 3-10 */
|
||||
__le32 hw_results; /* EHCI table 3-11 */
|
||||
#define SITD_IOC (1 << 31) /* interrupt on completion */
|
||||
#define SITD_PAGE (1 << 30) /* buffer 0/1 */
|
||||
#define SITD_LENGTH(x) (0x3ff & ((x)>>16))
|
||||
#define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */
|
||||
#define SITD_STS_ERR (1 << 6) /* error from TT */
|
||||
#define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */
|
||||
#define SITD_STS_BABBLE (1 << 4) /* device was babbling */
|
||||
#define SITD_STS_XACT (1 << 3) /* illegal IN response */
|
||||
#define SITD_STS_MMF (1 << 2) /* incomplete split transaction */
|
||||
#define SITD_STS_STS (1 << 1) /* split transaction state */
|
||||
|
||||
#define SITD_ACTIVE __constant_cpu_to_le32(SITD_STS_ACTIVE)
|
||||
|
||||
__le32 hw_buf [2]; /* EHCI table 3-12 */
|
||||
__le32 hw_backpointer; /* EHCI table 3-13 */
|
||||
__le32 hw_buf_hi [2]; /* Appendix B */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t sitd_dma;
|
||||
union ehci_shadow sitd_next; /* ptr to periodic q entry */
|
||||
|
||||
struct urb *urb;
|
||||
struct ehci_iso_stream *stream; /* endpoint's queue */
|
||||
struct list_head sitd_list; /* list of stream's sitds */
|
||||
unsigned frame;
|
||||
unsigned index;
|
||||
} __attribute__ ((aligned (32)));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.96 Section 3.7
|
||||
* Periodic Frame Span Traversal Node (FSTN)
|
||||
*
|
||||
* Manages split interrupt transactions (using TT) that span frame boundaries
|
||||
* into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN
|
||||
* makes the HC jump (back) to a QH to scan for fs/ls QH completions until
|
||||
* it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work.
|
||||
*/
|
||||
struct ehci_fstn {
|
||||
__le32 hw_next; /* any periodic q entry */
|
||||
__le32 hw_prev; /* qh or EHCI_LIST_END */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t fstn_dma;
|
||||
union ehci_shadow fstn_next; /* ptr to periodic q entry */
|
||||
} __attribute__ ((aligned (32)));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_USB_EHCI_ROOT_HUB_TT
|
||||
|
||||
/*
|
||||
* Some EHCI controllers have a Transaction Translator built into the
|
||||
* root hub. This is a non-standard feature. Each controller will need
|
||||
* to add code to the following inline functions, and call them as
|
||||
* needed (mostly in root hub code).
|
||||
*/
|
||||
|
||||
#define ehci_is_TDI(e) ((e)->is_tdi_rh_tt)
|
||||
|
||||
/* Returns the speed of a device attached to a port on the root hub. */
|
||||
static inline unsigned int
|
||||
ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
|
||||
{
|
||||
if (ehci_is_TDI(ehci)) {
|
||||
switch ((portsc>>26)&3) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return (1<<USB_PORT_FEAT_LOWSPEED);
|
||||
case 2:
|
||||
default:
|
||||
return (1<<USB_PORT_FEAT_HIGHSPEED);
|
||||
}
|
||||
}
|
||||
return (1<<USB_PORT_FEAT_HIGHSPEED);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define ehci_is_TDI(e) (0)
|
||||
|
||||
#define ehci_port_speed(ehci, portsc) (1<<USB_PORT_FEAT_HIGHSPEED)
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef DEBUG
|
||||
#define STUB_DEBUG_FILES
|
||||
#endif /* DEBUG */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#endif /* __LINUX_EHCI_HCD_H */
|
4556
drivers/usb/host/hc_crisv10.c
一般檔案
4556
drivers/usb/host/hc_crisv10.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
289
drivers/usb/host/hc_crisv10.h
一般檔案
289
drivers/usb/host/hc_crisv10.h
一般檔案
@@ -0,0 +1,289 @@
|
||||
#ifndef __LINUX_ETRAX_USB_H
|
||||
#define __LINUX_ETRAX_USB_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
typedef struct USB_IN_Desc {
|
||||
volatile __u16 sw_len;
|
||||
volatile __u16 command;
|
||||
volatile unsigned long next;
|
||||
volatile unsigned long buf;
|
||||
volatile __u16 hw_len;
|
||||
volatile __u16 status;
|
||||
} USB_IN_Desc_t;
|
||||
|
||||
typedef struct USB_SB_Desc {
|
||||
volatile __u16 sw_len;
|
||||
volatile __u16 command;
|
||||
volatile unsigned long next;
|
||||
volatile unsigned long buf;
|
||||
__u32 dummy;
|
||||
} USB_SB_Desc_t;
|
||||
|
||||
typedef struct USB_EP_Desc {
|
||||
volatile __u16 hw_len;
|
||||
volatile __u16 command;
|
||||
volatile unsigned long sub;
|
||||
volatile unsigned long next;
|
||||
__u32 dummy;
|
||||
} USB_EP_Desc_t;
|
||||
|
||||
struct virt_root_hub {
|
||||
int devnum;
|
||||
void *urb;
|
||||
void *int_addr;
|
||||
int send;
|
||||
int interval;
|
||||
int numports;
|
||||
struct timer_list rh_int_timer;
|
||||
volatile __u16 wPortChange_1;
|
||||
volatile __u16 wPortChange_2;
|
||||
volatile __u16 prev_wPortStatus_1;
|
||||
volatile __u16 prev_wPortStatus_2;
|
||||
};
|
||||
|
||||
struct etrax_usb_intr_traffic {
|
||||
int sleeping;
|
||||
int error;
|
||||
struct wait_queue *wq;
|
||||
};
|
||||
|
||||
typedef struct etrax_usb_hc {
|
||||
struct usb_bus *bus;
|
||||
struct virt_root_hub rh;
|
||||
struct etrax_usb_intr_traffic intr;
|
||||
} etrax_hc_t;
|
||||
|
||||
typedef enum {
|
||||
STARTED,
|
||||
NOT_STARTED,
|
||||
UNLINK,
|
||||
TRANSFER_DONE,
|
||||
WAITING_FOR_DESCR_INTR
|
||||
} etrax_usb_urb_state_t;
|
||||
|
||||
|
||||
|
||||
typedef struct etrax_usb_urb_priv {
|
||||
/* The first_sb field is used for freeing all SB descriptors belonging
|
||||
to an urb. The corresponding ep descriptor's sub pointer cannot be
|
||||
used for this since the DMA advances the sub pointer as it processes
|
||||
the sb list. */
|
||||
USB_SB_Desc_t *first_sb;
|
||||
/* The last_sb field referes to the last SB descriptor that belongs to
|
||||
this urb. This is important to know so we can free the SB descriptors
|
||||
that ranges between first_sb and last_sb. */
|
||||
USB_SB_Desc_t *last_sb;
|
||||
|
||||
/* The rx_offset field is used in ctrl and bulk traffic to keep track
|
||||
of the offset in the urb's transfer_buffer where incoming data should be
|
||||
copied to. */
|
||||
__u32 rx_offset;
|
||||
|
||||
/* Counter used in isochronous transfers to keep track of the
|
||||
number of packets received/transmitted. */
|
||||
__u32 isoc_packet_counter;
|
||||
|
||||
/* This field is used to pass information about the urb's current state between
|
||||
the various interrupt handlers (thus marked volatile). */
|
||||
volatile etrax_usb_urb_state_t urb_state;
|
||||
|
||||
/* Connection between the submitted urb and ETRAX epid number */
|
||||
__u8 epid;
|
||||
|
||||
/* The rx_data_list field is used for periodic traffic, to hold
|
||||
received data for later processing in the the complete_urb functions,
|
||||
where the data us copied to the urb's transfer_buffer. Basically, we
|
||||
use this intermediate storage because we don't know when it's safe to
|
||||
reuse the transfer_buffer (FIXME?). */
|
||||
struct list_head rx_data_list;
|
||||
} etrax_urb_priv_t;
|
||||
|
||||
/* This struct is for passing data from the top half to the bottom half. */
|
||||
typedef struct usb_interrupt_registers
|
||||
{
|
||||
etrax_hc_t *hc;
|
||||
__u32 r_usb_epid_attn;
|
||||
__u8 r_usb_status;
|
||||
__u16 r_usb_rh_port_status_1;
|
||||
__u16 r_usb_rh_port_status_2;
|
||||
__u32 r_usb_irq_mask_read;
|
||||
__u32 r_usb_fm_number;
|
||||
struct work_struct usb_bh;
|
||||
} usb_interrupt_registers_t;
|
||||
|
||||
/* This struct is for passing data from the isoc top half to the isoc bottom half. */
|
||||
typedef struct usb_isoc_complete_data
|
||||
{
|
||||
struct urb *urb;
|
||||
struct work_struct usb_bh;
|
||||
} usb_isoc_complete_data_t;
|
||||
|
||||
/* This struct holds data we get from the rx descriptors for DMA channel 9
|
||||
for periodic traffic (intr and isoc). */
|
||||
typedef struct rx_data
|
||||
{
|
||||
void *data;
|
||||
int length;
|
||||
struct list_head list;
|
||||
} rx_data_t;
|
||||
|
||||
typedef struct urb_entry
|
||||
{
|
||||
struct urb *urb;
|
||||
struct list_head list;
|
||||
} urb_entry_t;
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Virtual Root HUB
|
||||
------------------------------------------------------------------------- */
|
||||
/* destination of request */
|
||||
#define RH_INTERFACE 0x01
|
||||
#define RH_ENDPOINT 0x02
|
||||
#define RH_OTHER 0x03
|
||||
|
||||
#define RH_CLASS 0x20
|
||||
#define RH_VENDOR 0x40
|
||||
|
||||
/* Requests: bRequest << 8 | bmRequestType */
|
||||
#define RH_GET_STATUS 0x0080
|
||||
#define RH_CLEAR_FEATURE 0x0100
|
||||
#define RH_SET_FEATURE 0x0300
|
||||
#define RH_SET_ADDRESS 0x0500
|
||||
#define RH_GET_DESCRIPTOR 0x0680
|
||||
#define RH_SET_DESCRIPTOR 0x0700
|
||||
#define RH_GET_CONFIGURATION 0x0880
|
||||
#define RH_SET_CONFIGURATION 0x0900
|
||||
#define RH_GET_STATE 0x0280
|
||||
#define RH_GET_INTERFACE 0x0A80
|
||||
#define RH_SET_INTERFACE 0x0B00
|
||||
#define RH_SYNC_FRAME 0x0C80
|
||||
/* Our Vendor Specific Request */
|
||||
#define RH_SET_EP 0x2000
|
||||
|
||||
|
||||
/* Hub port features */
|
||||
#define RH_PORT_CONNECTION 0x00
|
||||
#define RH_PORT_ENABLE 0x01
|
||||
#define RH_PORT_SUSPEND 0x02
|
||||
#define RH_PORT_OVER_CURRENT 0x03
|
||||
#define RH_PORT_RESET 0x04
|
||||
#define RH_PORT_POWER 0x08
|
||||
#define RH_PORT_LOW_SPEED 0x09
|
||||
#define RH_C_PORT_CONNECTION 0x10
|
||||
#define RH_C_PORT_ENABLE 0x11
|
||||
#define RH_C_PORT_SUSPEND 0x12
|
||||
#define RH_C_PORT_OVER_CURRENT 0x13
|
||||
#define RH_C_PORT_RESET 0x14
|
||||
|
||||
/* Hub features */
|
||||
#define RH_C_HUB_LOCAL_POWER 0x00
|
||||
#define RH_C_HUB_OVER_CURRENT 0x01
|
||||
|
||||
#define RH_DEVICE_REMOTE_WAKEUP 0x00
|
||||
#define RH_ENDPOINT_STALL 0x01
|
||||
|
||||
/* Our Vendor Specific feature */
|
||||
#define RH_REMOVE_EP 0x00
|
||||
|
||||
|
||||
#define RH_ACK 0x01
|
||||
#define RH_REQ_ERR -1
|
||||
#define RH_NACK 0x00
|
||||
|
||||
/* Field definitions for */
|
||||
|
||||
#define USB_IN_command__eol__BITNR 0 /* command macros */
|
||||
#define USB_IN_command__eol__WIDTH 1
|
||||
#define USB_IN_command__eol__no 0
|
||||
#define USB_IN_command__eol__yes 1
|
||||
|
||||
#define USB_IN_command__intr__BITNR 3
|
||||
#define USB_IN_command__intr__WIDTH 1
|
||||
#define USB_IN_command__intr__no 0
|
||||
#define USB_IN_command__intr__yes 1
|
||||
|
||||
#define USB_IN_status__eop__BITNR 1 /* status macros. */
|
||||
#define USB_IN_status__eop__WIDTH 1
|
||||
#define USB_IN_status__eop__no 0
|
||||
#define USB_IN_status__eop__yes 1
|
||||
|
||||
#define USB_IN_status__eot__BITNR 5
|
||||
#define USB_IN_status__eot__WIDTH 1
|
||||
#define USB_IN_status__eot__no 0
|
||||
#define USB_IN_status__eot__yes 1
|
||||
|
||||
#define USB_IN_status__error__BITNR 6
|
||||
#define USB_IN_status__error__WIDTH 1
|
||||
#define USB_IN_status__error__no 0
|
||||
#define USB_IN_status__error__yes 1
|
||||
|
||||
#define USB_IN_status__nodata__BITNR 7
|
||||
#define USB_IN_status__nodata__WIDTH 1
|
||||
#define USB_IN_status__nodata__no 0
|
||||
#define USB_IN_status__nodata__yes 1
|
||||
|
||||
#define USB_IN_status__epid__BITNR 8
|
||||
#define USB_IN_status__epid__WIDTH 5
|
||||
|
||||
#define USB_EP_command__eol__BITNR 0
|
||||
#define USB_EP_command__eol__WIDTH 1
|
||||
#define USB_EP_command__eol__no 0
|
||||
#define USB_EP_command__eol__yes 1
|
||||
|
||||
#define USB_EP_command__eof__BITNR 1
|
||||
#define USB_EP_command__eof__WIDTH 1
|
||||
#define USB_EP_command__eof__no 0
|
||||
#define USB_EP_command__eof__yes 1
|
||||
|
||||
#define USB_EP_command__intr__BITNR 3
|
||||
#define USB_EP_command__intr__WIDTH 1
|
||||
#define USB_EP_command__intr__no 0
|
||||
#define USB_EP_command__intr__yes 1
|
||||
|
||||
#define USB_EP_command__enable__BITNR 4
|
||||
#define USB_EP_command__enable__WIDTH 1
|
||||
#define USB_EP_command__enable__no 0
|
||||
#define USB_EP_command__enable__yes 1
|
||||
|
||||
#define USB_EP_command__hw_valid__BITNR 5
|
||||
#define USB_EP_command__hw_valid__WIDTH 1
|
||||
#define USB_EP_command__hw_valid__no 0
|
||||
#define USB_EP_command__hw_valid__yes 1
|
||||
|
||||
#define USB_EP_command__epid__BITNR 8
|
||||
#define USB_EP_command__epid__WIDTH 5
|
||||
|
||||
#define USB_SB_command__eol__BITNR 0 /* command macros. */
|
||||
#define USB_SB_command__eol__WIDTH 1
|
||||
#define USB_SB_command__eol__no 0
|
||||
#define USB_SB_command__eol__yes 1
|
||||
|
||||
#define USB_SB_command__eot__BITNR 1
|
||||
#define USB_SB_command__eot__WIDTH 1
|
||||
#define USB_SB_command__eot__no 0
|
||||
#define USB_SB_command__eot__yes 1
|
||||
|
||||
#define USB_SB_command__intr__BITNR 3
|
||||
#define USB_SB_command__intr__WIDTH 1
|
||||
#define USB_SB_command__intr__no 0
|
||||
#define USB_SB_command__intr__yes 1
|
||||
|
||||
#define USB_SB_command__tt__BITNR 4
|
||||
#define USB_SB_command__tt__WIDTH 2
|
||||
#define USB_SB_command__tt__zout 0
|
||||
#define USB_SB_command__tt__in 1
|
||||
#define USB_SB_command__tt__out 2
|
||||
#define USB_SB_command__tt__setup 3
|
||||
|
||||
|
||||
#define USB_SB_command__rem__BITNR 8
|
||||
#define USB_SB_command__rem__WIDTH 6
|
||||
|
||||
#define USB_SB_command__full__BITNR 6
|
||||
#define USB_SB_command__full__WIDTH 1
|
||||
#define USB_SB_command__full__no 0
|
||||
#define USB_SB_command__full__yes 1
|
||||
|
||||
#endif
|
284
drivers/usb/host/ohci-au1xxx.c
一般檔案
284
drivers/usb/host/ohci-au1xxx.c
一般檔案
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
* (C) Copyright 2002 Hewlett-Packard Company
|
||||
*
|
||||
* Bus Glue for AMD Alchemy Au1xxx
|
||||
*
|
||||
* Written by Christopher Hoover <ch@hpl.hp.com>
|
||||
* Based on fragments of previous driver by Rusell King et al.
|
||||
*
|
||||
* Modified for LH7A404 from ohci-sa1111.c
|
||||
* by Durgesh Pattamatta <pattamattad@sharpsec.com>
|
||||
* Modified for AMD Alchemy Au1xxx
|
||||
* by Matt Porter <mporter@kernel.crashing.org>
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <asm/mach-au1x00/au1000.h>
|
||||
|
||||
#define USBH_ENABLE_BE (1<<0)
|
||||
#define USBH_ENABLE_C (1<<1)
|
||||
#define USBH_ENABLE_E (1<<2)
|
||||
#define USBH_ENABLE_CE (1<<3)
|
||||
#define USBH_ENABLE_RD (1<<4)
|
||||
|
||||
#ifdef __LITTLE_ENDIAN
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C)
|
||||
#elif __BIG_ENDIAN
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C | USBH_ENABLE_BE)
|
||||
#else
|
||||
#error not byte order defined
|
||||
#endif
|
||||
|
||||
extern int usb_disabled(void);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void au1xxx_start_hc(struct platform_device *dev)
|
||||
{
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": starting Au1xxx OHCI USB Controller\n");
|
||||
|
||||
/* enable host controller */
|
||||
au_writel(USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
au_writel(USBH_ENABLE_INIT, USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
|
||||
/* wait for reset complete (read register twice; see au1500 errata) */
|
||||
while (au_readl(USB_HOST_CONFIG),
|
||||
!(au_readl(USB_HOST_CONFIG) & USBH_ENABLE_RD))
|
||||
udelay(1000);
|
||||
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": Clock to USB host has been enabled \n");
|
||||
}
|
||||
|
||||
static void au1xxx_stop_hc(struct platform_device *dev)
|
||||
{
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": stopping Au1xxx OHCI USB Controller\n");
|
||||
|
||||
/* Disable clock */
|
||||
au_writel(readl((void *)USB_HOST_CONFIG) & ~USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
|
||||
/**
|
||||
* usb_hcd_au1xxx_probe - initialize Au1xxx-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
*/
|
||||
int usb_hcd_au1xxx_probe (const struct hc_driver *driver,
|
||||
struct platform_device *dev)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
if(dev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug ("resource[1] is not IORESOURCE_IRQ");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(driver, &dev->dev, "au1xxx");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = dev->resource[0].start;
|
||||
hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed");
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
au1xxx_start_hc(dev);
|
||||
ohci_hcd_init(hcd_to_ohci(hcd));
|
||||
|
||||
retval = usb_add_hcd(hcd, dev->resource[1].start, SA_INTERRUPT);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
au1xxx_stop_hc(dev);
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_au1xxx_remove - shutdown processing for Au1xxx-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_au1xxx_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
void usb_hcd_au1xxx_remove (struct usb_hcd *hcd, struct platform_device *dev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
au1xxx_stop_hc(dev);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __devinit
|
||||
ohci_au1xxx_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
ohci_dbg (ohci, "ohci_au1xxx_start, ohci:%p", ohci);
|
||||
|
||||
if ((ret = ohci_init (ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
err ("can't start %s", hcd->self.bus_name);
|
||||
ohci_stop (hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_au1xxx_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "Au1xxx OHCI",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.start = ohci_au1xxx_start,
|
||||
#ifdef CONFIG_PM
|
||||
/* suspend: ohci_au1xxx_suspend, -- tbd */
|
||||
/* resume: ohci_au1xxx_resume, -- tbd */
|
||||
#endif /*CONFIG_PM*/
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_hcd_au1xxx_drv_probe(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
int ret;
|
||||
|
||||
pr_debug ("In ohci_hcd_au1xxx_drv_probe");
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_hcd_au1xxx_probe(&ohci_au1xxx_hc_driver, pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ohci_hcd_au1xxx_drv_remove(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
usb_hcd_au1xxx_remove(hcd, pdev);
|
||||
return 0;
|
||||
}
|
||||
/*TBD*/
|
||||
/*static int ohci_hcd_au1xxx_drv_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int ohci_hcd_au1xxx_drv_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
static struct device_driver ohci_hcd_au1xxx_driver = {
|
||||
.name = "au1xxx-ohci",
|
||||
.bus = &platform_bus_type,
|
||||
.probe = ohci_hcd_au1xxx_drv_probe,
|
||||
.remove = ohci_hcd_au1xxx_drv_remove,
|
||||
/*.suspend = ohci_hcd_au1xxx_drv_suspend, */
|
||||
/*.resume = ohci_hcd_au1xxx_drv_resume, */
|
||||
};
|
||||
|
||||
static int __init ohci_hcd_au1xxx_init (void)
|
||||
{
|
||||
pr_debug (DRIVER_INFO " (Au1xxx)");
|
||||
pr_debug ("block sizes: ed %d td %d\n",
|
||||
sizeof (struct ed), sizeof (struct td));
|
||||
|
||||
return driver_register(&ohci_hcd_au1xxx_driver);
|
||||
}
|
||||
|
||||
static void __exit ohci_hcd_au1xxx_cleanup (void)
|
||||
{
|
||||
driver_unregister(&ohci_hcd_au1xxx_driver);
|
||||
}
|
||||
|
||||
module_init (ohci_hcd_au1xxx_init);
|
||||
module_exit (ohci_hcd_au1xxx_cleanup);
|
707
drivers/usb/host/ohci-dbg.c
一般檔案
707
drivers/usb/host/ohci-dbg.c
一般檔案
@@ -0,0 +1,707 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#define edstring(ed_type) ({ char *temp; \
|
||||
switch (ed_type) { \
|
||||
case PIPE_CONTROL: temp = "ctrl"; break; \
|
||||
case PIPE_BULK: temp = "bulk"; break; \
|
||||
case PIPE_INTERRUPT: temp = "intr"; break; \
|
||||
default: temp = "isoc"; break; \
|
||||
}; temp;})
|
||||
#define pipestring(pipe) edstring(usb_pipetype(pipe))
|
||||
|
||||
/* debug| print the main components of an URB
|
||||
* small: 0) header + data packets 1) just header
|
||||
*/
|
||||
static void __attribute__((unused))
|
||||
urb_print (struct urb * urb, char * str, int small)
|
||||
{
|
||||
unsigned int pipe= urb->pipe;
|
||||
|
||||
if (!urb->dev || !urb->dev->bus) {
|
||||
dbg("%s URB: no dev", str);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef OHCI_VERBOSE_DEBUG
|
||||
if (urb->status != 0)
|
||||
#endif
|
||||
dbg("%s %p dev=%d ep=%d%s-%s flags=%x len=%d/%d stat=%d",
|
||||
str,
|
||||
urb,
|
||||
usb_pipedevice (pipe),
|
||||
usb_pipeendpoint (pipe),
|
||||
usb_pipeout (pipe)? "out" : "in",
|
||||
pipestring (pipe),
|
||||
urb->transfer_flags,
|
||||
urb->actual_length,
|
||||
urb->transfer_buffer_length,
|
||||
urb->status);
|
||||
|
||||
#ifdef OHCI_VERBOSE_DEBUG
|
||||
if (!small) {
|
||||
int i, len;
|
||||
|
||||
if (usb_pipecontrol (pipe)) {
|
||||
printk (KERN_DEBUG __FILE__ ": setup(8):");
|
||||
for (i = 0; i < 8 ; i++)
|
||||
printk (" %02x", ((__u8 *) urb->setup_packet) [i]);
|
||||
printk ("\n");
|
||||
}
|
||||
if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) {
|
||||
printk (KERN_DEBUG __FILE__ ": data(%d/%d):",
|
||||
urb->actual_length,
|
||||
urb->transfer_buffer_length);
|
||||
len = usb_pipeout (pipe)?
|
||||
urb->transfer_buffer_length: urb->actual_length;
|
||||
for (i = 0; i < 16 && i < len; i++)
|
||||
printk (" %02x", ((__u8 *) urb->transfer_buffer) [i]);
|
||||
printk ("%s stat:%d\n", i < len? "...": "", urb->status);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#define ohci_dbg_sw(ohci, next, size, format, arg...) \
|
||||
do { \
|
||||
if (next) { \
|
||||
unsigned s_len; \
|
||||
s_len = scnprintf (*next, *size, format, ## arg ); \
|
||||
*size -= s_len; *next += s_len; \
|
||||
} else \
|
||||
ohci_dbg(ohci,format, ## arg ); \
|
||||
} while (0);
|
||||
|
||||
|
||||
static void ohci_dump_intr_mask (
|
||||
struct ohci_hcd *ohci,
|
||||
char *label,
|
||||
u32 mask,
|
||||
char **next,
|
||||
unsigned *size)
|
||||
{
|
||||
ohci_dbg_sw (ohci, next, size, "%s 0x%08x%s%s%s%s%s%s%s%s%s\n",
|
||||
label,
|
||||
mask,
|
||||
(mask & OHCI_INTR_MIE) ? " MIE" : "",
|
||||
(mask & OHCI_INTR_OC) ? " OC" : "",
|
||||
(mask & OHCI_INTR_RHSC) ? " RHSC" : "",
|
||||
(mask & OHCI_INTR_FNO) ? " FNO" : "",
|
||||
(mask & OHCI_INTR_UE) ? " UE" : "",
|
||||
(mask & OHCI_INTR_RD) ? " RD" : "",
|
||||
(mask & OHCI_INTR_SF) ? " SF" : "",
|
||||
(mask & OHCI_INTR_WDH) ? " WDH" : "",
|
||||
(mask & OHCI_INTR_SO) ? " SO" : ""
|
||||
);
|
||||
}
|
||||
|
||||
static void maybe_print_eds (
|
||||
struct ohci_hcd *ohci,
|
||||
char *label,
|
||||
u32 value,
|
||||
char **next,
|
||||
unsigned *size)
|
||||
{
|
||||
if (value)
|
||||
ohci_dbg_sw (ohci, next, size, "%s %08x\n", label, value);
|
||||
}
|
||||
|
||||
static char *hcfs2string (int state)
|
||||
{
|
||||
switch (state) {
|
||||
case OHCI_USB_RESET: return "reset";
|
||||
case OHCI_USB_RESUME: return "resume";
|
||||
case OHCI_USB_OPER: return "operational";
|
||||
case OHCI_USB_SUSPEND: return "suspend";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
// dump control and status registers
|
||||
static void
|
||||
ohci_dump_status (struct ohci_hcd *controller, char **next, unsigned *size)
|
||||
{
|
||||
struct ohci_regs __iomem *regs = controller->regs;
|
||||
u32 temp;
|
||||
|
||||
temp = ohci_readl (controller, ®s->revision) & 0xff;
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"OHCI %d.%d, %s legacy support registers\n",
|
||||
0x03 & (temp >> 4), (temp & 0x0f),
|
||||
(temp & 0x0100) ? "with" : "NO");
|
||||
|
||||
temp = ohci_readl (controller, ®s->control);
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"control 0x%03x%s%s%s HCFS=%s%s%s%s%s CBSR=%d\n",
|
||||
temp,
|
||||
(temp & OHCI_CTRL_RWE) ? " RWE" : "",
|
||||
(temp & OHCI_CTRL_RWC) ? " RWC" : "",
|
||||
(temp & OHCI_CTRL_IR) ? " IR" : "",
|
||||
hcfs2string (temp & OHCI_CTRL_HCFS),
|
||||
(temp & OHCI_CTRL_BLE) ? " BLE" : "",
|
||||
(temp & OHCI_CTRL_CLE) ? " CLE" : "",
|
||||
(temp & OHCI_CTRL_IE) ? " IE" : "",
|
||||
(temp & OHCI_CTRL_PLE) ? " PLE" : "",
|
||||
temp & OHCI_CTRL_CBSR
|
||||
);
|
||||
|
||||
temp = ohci_readl (controller, ®s->cmdstatus);
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"cmdstatus 0x%05x SOC=%d%s%s%s%s\n", temp,
|
||||
(temp & OHCI_SOC) >> 16,
|
||||
(temp & OHCI_OCR) ? " OCR" : "",
|
||||
(temp & OHCI_BLF) ? " BLF" : "",
|
||||
(temp & OHCI_CLF) ? " CLF" : "",
|
||||
(temp & OHCI_HCR) ? " HCR" : ""
|
||||
);
|
||||
|
||||
ohci_dump_intr_mask (controller, "intrstatus",
|
||||
ohci_readl (controller, ®s->intrstatus),
|
||||
next, size);
|
||||
ohci_dump_intr_mask (controller, "intrenable",
|
||||
ohci_readl (controller, ®s->intrenable),
|
||||
next, size);
|
||||
// intrdisable always same as intrenable
|
||||
|
||||
maybe_print_eds (controller, "ed_periodcurrent",
|
||||
ohci_readl (controller, ®s->ed_periodcurrent),
|
||||
next, size);
|
||||
|
||||
maybe_print_eds (controller, "ed_controlhead",
|
||||
ohci_readl (controller, ®s->ed_controlhead),
|
||||
next, size);
|
||||
maybe_print_eds (controller, "ed_controlcurrent",
|
||||
ohci_readl (controller, ®s->ed_controlcurrent),
|
||||
next, size);
|
||||
|
||||
maybe_print_eds (controller, "ed_bulkhead",
|
||||
ohci_readl (controller, ®s->ed_bulkhead),
|
||||
next, size);
|
||||
maybe_print_eds (controller, "ed_bulkcurrent",
|
||||
ohci_readl (controller, ®s->ed_bulkcurrent),
|
||||
next, size);
|
||||
|
||||
maybe_print_eds (controller, "donehead",
|
||||
ohci_readl (controller, ®s->donehead), next, size);
|
||||
|
||||
/* broken fminterval means traffic won't flow! */
|
||||
ohci_dbg (controller, "fminterval %08x\n",
|
||||
ohci_readl (controller, ®s->fminterval));
|
||||
}
|
||||
|
||||
#define dbg_port_sw(hc,num,value,next,size) \
|
||||
ohci_dbg_sw (hc, next, size, \
|
||||
"roothub.portstatus [%d] " \
|
||||
"0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \
|
||||
num, temp, \
|
||||
(temp & RH_PS_PRSC) ? " PRSC" : "", \
|
||||
(temp & RH_PS_OCIC) ? " OCIC" : "", \
|
||||
(temp & RH_PS_PSSC) ? " PSSC" : "", \
|
||||
(temp & RH_PS_PESC) ? " PESC" : "", \
|
||||
(temp & RH_PS_CSC) ? " CSC" : "", \
|
||||
\
|
||||
(temp & RH_PS_LSDA) ? " LSDA" : "", \
|
||||
(temp & RH_PS_PPS) ? " PPS" : "", \
|
||||
(temp & RH_PS_PRS) ? " PRS" : "", \
|
||||
(temp & RH_PS_POCI) ? " POCI" : "", \
|
||||
(temp & RH_PS_PSS) ? " PSS" : "", \
|
||||
\
|
||||
(temp & RH_PS_PES) ? " PES" : "", \
|
||||
(temp & RH_PS_CCS) ? " CCS" : "" \
|
||||
);
|
||||
|
||||
|
||||
static void
|
||||
ohci_dump_roothub (
|
||||
struct ohci_hcd *controller,
|
||||
int verbose,
|
||||
char **next,
|
||||
unsigned *size)
|
||||
{
|
||||
u32 temp, ndp, i;
|
||||
|
||||
temp = roothub_a (controller);
|
||||
if (temp == ~(u32)0)
|
||||
return;
|
||||
ndp = (temp & RH_A_NDP);
|
||||
|
||||
if (verbose) {
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"roothub.a %08x POTPGT=%d%s%s%s%s%s NDP=%d\n", temp,
|
||||
((temp & RH_A_POTPGT) >> 24) & 0xff,
|
||||
(temp & RH_A_NOCP) ? " NOCP" : "",
|
||||
(temp & RH_A_OCPM) ? " OCPM" : "",
|
||||
(temp & RH_A_DT) ? " DT" : "",
|
||||
(temp & RH_A_NPS) ? " NPS" : "",
|
||||
(temp & RH_A_PSM) ? " PSM" : "",
|
||||
ndp
|
||||
);
|
||||
temp = roothub_b (controller);
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"roothub.b %08x PPCM=%04x DR=%04x\n",
|
||||
temp,
|
||||
(temp & RH_B_PPCM) >> 16,
|
||||
(temp & RH_B_DR)
|
||||
);
|
||||
temp = roothub_status (controller);
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"roothub.status %08x%s%s%s%s%s%s\n",
|
||||
temp,
|
||||
(temp & RH_HS_CRWE) ? " CRWE" : "",
|
||||
(temp & RH_HS_OCIC) ? " OCIC" : "",
|
||||
(temp & RH_HS_LPSC) ? " LPSC" : "",
|
||||
(temp & RH_HS_DRWE) ? " DRWE" : "",
|
||||
(temp & RH_HS_OCI) ? " OCI" : "",
|
||||
(temp & RH_HS_LPS) ? " LPS" : ""
|
||||
);
|
||||
}
|
||||
|
||||
for (i = 0; i < ndp; i++) {
|
||||
temp = roothub_portstatus (controller, i);
|
||||
dbg_port_sw (controller, i, temp, next, size);
|
||||
}
|
||||
}
|
||||
|
||||
static void ohci_dump (struct ohci_hcd *controller, int verbose)
|
||||
{
|
||||
ohci_dbg (controller, "OHCI controller state\n");
|
||||
|
||||
// dumps some of the state we know about
|
||||
ohci_dump_status (controller, NULL, NULL);
|
||||
if (controller->hcca)
|
||||
ohci_dbg (controller,
|
||||
"hcca frame #%04x\n", ohci_frame_no(controller));
|
||||
ohci_dump_roothub (controller, 1, NULL, NULL);
|
||||
}
|
||||
|
||||
static const char data0 [] = "DATA0";
|
||||
static const char data1 [] = "DATA1";
|
||||
|
||||
static void ohci_dump_td (const struct ohci_hcd *ohci, const char *label,
|
||||
const struct td *td)
|
||||
{
|
||||
u32 tmp = hc32_to_cpup (ohci, &td->hwINFO);
|
||||
|
||||
ohci_dbg (ohci, "%s td %p%s; urb %p index %d; hw next td %08x\n",
|
||||
label, td,
|
||||
(tmp & TD_DONE) ? " (DONE)" : "",
|
||||
td->urb, td->index,
|
||||
hc32_to_cpup (ohci, &td->hwNextTD));
|
||||
if ((tmp & TD_ISO) == 0) {
|
||||
const char *toggle, *pid;
|
||||
u32 cbp, be;
|
||||
|
||||
switch (tmp & TD_T) {
|
||||
case TD_T_DATA0: toggle = data0; break;
|
||||
case TD_T_DATA1: toggle = data1; break;
|
||||
case TD_T_TOGGLE: toggle = "(CARRY)"; break;
|
||||
default: toggle = "(?)"; break;
|
||||
}
|
||||
switch (tmp & TD_DP) {
|
||||
case TD_DP_SETUP: pid = "SETUP"; break;
|
||||
case TD_DP_IN: pid = "IN"; break;
|
||||
case TD_DP_OUT: pid = "OUT"; break;
|
||||
default: pid = "(bad pid)"; break;
|
||||
}
|
||||
ohci_dbg (ohci, " info %08x CC=%x %s DI=%d %s %s\n", tmp,
|
||||
TD_CC_GET(tmp), /* EC, */ toggle,
|
||||
(tmp & TD_DI) >> 21, pid,
|
||||
(tmp & TD_R) ? "R" : "");
|
||||
cbp = hc32_to_cpup (ohci, &td->hwCBP);
|
||||
be = hc32_to_cpup (ohci, &td->hwBE);
|
||||
ohci_dbg (ohci, " cbp %08x be %08x (len %d)\n", cbp, be,
|
||||
cbp ? (be + 1 - cbp) : 0);
|
||||
} else {
|
||||
unsigned i;
|
||||
ohci_dbg (ohci, " info %08x CC=%x FC=%d DI=%d SF=%04x\n", tmp,
|
||||
TD_CC_GET(tmp),
|
||||
(tmp >> 24) & 0x07,
|
||||
(tmp & TD_DI) >> 21,
|
||||
tmp & 0x0000ffff);
|
||||
ohci_dbg (ohci, " bp0 %08x be %08x\n",
|
||||
hc32_to_cpup (ohci, &td->hwCBP) & ~0x0fff,
|
||||
hc32_to_cpup (ohci, &td->hwBE));
|
||||
for (i = 0; i < MAXPSW; i++) {
|
||||
u16 psw = ohci_hwPSW (ohci, td, i);
|
||||
int cc = (psw >> 12) & 0x0f;
|
||||
ohci_dbg (ohci, " psw [%d] = %2x, CC=%x %s=%d\n", i,
|
||||
psw, cc,
|
||||
(cc >= 0x0e) ? "OFFSET" : "SIZE",
|
||||
psw & 0x0fff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* caller MUST own hcd spinlock if verbose is set! */
|
||||
static void __attribute__((unused))
|
||||
ohci_dump_ed (const struct ohci_hcd *ohci, const char *label,
|
||||
const struct ed *ed, int verbose)
|
||||
{
|
||||
u32 tmp = hc32_to_cpu (ohci, ed->hwINFO);
|
||||
char *type = "";
|
||||
|
||||
ohci_dbg (ohci, "%s, ed %p state 0x%x type %s; next ed %08x\n",
|
||||
label,
|
||||
ed, ed->state, edstring (ed->type),
|
||||
hc32_to_cpup (ohci, &ed->hwNextED));
|
||||
switch (tmp & (ED_IN|ED_OUT)) {
|
||||
case ED_OUT: type = "-OUT"; break;
|
||||
case ED_IN: type = "-IN"; break;
|
||||
/* else from TDs ... control */
|
||||
}
|
||||
ohci_dbg (ohci,
|
||||
" info %08x MAX=%d%s%s%s%s EP=%d%s DEV=%d\n", tmp,
|
||||
0x03ff & (tmp >> 16),
|
||||
(tmp & ED_DEQUEUE) ? " DQ" : "",
|
||||
(tmp & ED_ISO) ? " ISO" : "",
|
||||
(tmp & ED_SKIP) ? " SKIP" : "",
|
||||
(tmp & ED_LOWSPEED) ? " LOW" : "",
|
||||
0x000f & (tmp >> 7),
|
||||
type,
|
||||
0x007f & tmp);
|
||||
tmp = hc32_to_cpup (ohci, &ed->hwHeadP);
|
||||
ohci_dbg (ohci, " tds: head %08x %s%s tail %08x%s\n",
|
||||
tmp,
|
||||
(tmp & ED_C) ? data1 : data0,
|
||||
(tmp & ED_H) ? " HALT" : "",
|
||||
hc32_to_cpup (ohci, &ed->hwTailP),
|
||||
verbose ? "" : " (not listing)");
|
||||
if (verbose) {
|
||||
struct list_head *tmp;
|
||||
|
||||
/* use ed->td_list because HC concurrently modifies
|
||||
* hwNextTD as it accumulates ed_donelist.
|
||||
*/
|
||||
list_for_each (tmp, &ed->td_list) {
|
||||
struct td *td;
|
||||
td = list_entry (tmp, struct td, td_list);
|
||||
ohci_dump_td (ohci, " ->", td);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
static inline void ohci_dump (struct ohci_hcd *controller, int verbose) {}
|
||||
|
||||
#undef OHCI_VERBOSE_DEBUG
|
||||
|
||||
#endif /* DEBUG */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef STUB_DEBUG_FILES
|
||||
|
||||
static inline void create_debug_files (struct ohci_hcd *bus) { }
|
||||
static inline void remove_debug_files (struct ohci_hcd *bus) { }
|
||||
|
||||
#else
|
||||
|
||||
static ssize_t
|
||||
show_list (struct ohci_hcd *ohci, char *buf, size_t count, struct ed *ed)
|
||||
{
|
||||
unsigned temp, size = count;
|
||||
|
||||
if (!ed)
|
||||
return 0;
|
||||
|
||||
/* print first --> last */
|
||||
while (ed->ed_prev)
|
||||
ed = ed->ed_prev;
|
||||
|
||||
/* dump a snapshot of the bulk or control schedule */
|
||||
while (ed) {
|
||||
u32 info = hc32_to_cpu (ohci, ed->hwINFO);
|
||||
u32 headp = hc32_to_cpu (ohci, ed->hwHeadP);
|
||||
struct list_head *entry;
|
||||
struct td *td;
|
||||
|
||||
temp = scnprintf (buf, size,
|
||||
"ed/%p %cs dev%d ep%d%s max %d %08x%s%s %s",
|
||||
ed,
|
||||
(info & ED_LOWSPEED) ? 'l' : 'f',
|
||||
info & 0x7f,
|
||||
(info >> 7) & 0xf,
|
||||
(info & ED_IN) ? "in" : "out",
|
||||
0x03ff & (info >> 16),
|
||||
info,
|
||||
(info & ED_SKIP) ? " s" : "",
|
||||
(headp & ED_H) ? " H" : "",
|
||||
(headp & ED_C) ? data1 : data0);
|
||||
size -= temp;
|
||||
buf += temp;
|
||||
|
||||
list_for_each (entry, &ed->td_list) {
|
||||
u32 cbp, be;
|
||||
|
||||
td = list_entry (entry, struct td, td_list);
|
||||
info = hc32_to_cpup (ohci, &td->hwINFO);
|
||||
cbp = hc32_to_cpup (ohci, &td->hwCBP);
|
||||
be = hc32_to_cpup (ohci, &td->hwBE);
|
||||
temp = scnprintf (buf, size,
|
||||
"\n\ttd %p %s %d cc=%x urb %p (%08x)",
|
||||
td,
|
||||
({ char *pid;
|
||||
switch (info & TD_DP) {
|
||||
case TD_DP_SETUP: pid = "setup"; break;
|
||||
case TD_DP_IN: pid = "in"; break;
|
||||
case TD_DP_OUT: pid = "out"; break;
|
||||
default: pid = "(?)"; break;
|
||||
} pid;}),
|
||||
cbp ? (be + 1 - cbp) : 0,
|
||||
TD_CC_GET (info), td->urb, info);
|
||||
size -= temp;
|
||||
buf += temp;
|
||||
}
|
||||
|
||||
temp = scnprintf (buf, size, "\n");
|
||||
size -= temp;
|
||||
buf += temp;
|
||||
|
||||
ed = ed->ed_next;
|
||||
}
|
||||
return count - size;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
show_async (struct class_device *class_dev, char *buf)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
struct usb_hcd *hcd;
|
||||
struct ohci_hcd *ohci;
|
||||
size_t temp;
|
||||
unsigned long flags;
|
||||
|
||||
bus = to_usb_bus(class_dev);
|
||||
hcd = bus->hcpriv;
|
||||
ohci = hcd_to_ohci(hcd);
|
||||
|
||||
/* display control and bulk lists together, for simplicity */
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
temp = show_list (ohci, buf, PAGE_SIZE, ohci->ed_controltail);
|
||||
temp += show_list (ohci, buf + temp, PAGE_SIZE - temp, ohci->ed_bulktail);
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
|
||||
return temp;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR (async, S_IRUGO, show_async, NULL);
|
||||
|
||||
|
||||
#define DBG_SCHED_LIMIT 64
|
||||
|
||||
static ssize_t
|
||||
show_periodic (struct class_device *class_dev, char *buf)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
struct usb_hcd *hcd;
|
||||
struct ohci_hcd *ohci;
|
||||
struct ed **seen, *ed;
|
||||
unsigned long flags;
|
||||
unsigned temp, size, seen_count;
|
||||
char *next;
|
||||
unsigned i;
|
||||
|
||||
if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, SLAB_ATOMIC)))
|
||||
return 0;
|
||||
seen_count = 0;
|
||||
|
||||
bus = to_usb_bus(class_dev);
|
||||
hcd = bus->hcpriv;
|
||||
ohci = hcd_to_ohci(hcd);
|
||||
next = buf;
|
||||
size = PAGE_SIZE;
|
||||
|
||||
temp = scnprintf (next, size, "size = %d\n", NUM_INTS);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
/* dump a snapshot of the periodic schedule (and load) */
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
for (i = 0; i < NUM_INTS; i++) {
|
||||
if (!(ed = ohci->periodic [i]))
|
||||
continue;
|
||||
|
||||
temp = scnprintf (next, size, "%2d [%3d]:", i, ohci->load [i]);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
do {
|
||||
temp = scnprintf (next, size, " ed%d/%p",
|
||||
ed->interval, ed);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
for (temp = 0; temp < seen_count; temp++) {
|
||||
if (seen [temp] == ed)
|
||||
break;
|
||||
}
|
||||
|
||||
/* show more info the first time around */
|
||||
if (temp == seen_count) {
|
||||
u32 info = hc32_to_cpu (ohci, ed->hwINFO);
|
||||
struct list_head *entry;
|
||||
unsigned qlen = 0;
|
||||
|
||||
/* qlen measured here in TDs, not urbs */
|
||||
list_for_each (entry, &ed->td_list)
|
||||
qlen++;
|
||||
|
||||
temp = scnprintf (next, size,
|
||||
" (%cs dev%d ep%d%s-%s qlen %u"
|
||||
" max %d %08x%s%s)",
|
||||
(info & ED_LOWSPEED) ? 'l' : 'f',
|
||||
info & 0x7f,
|
||||
(info >> 7) & 0xf,
|
||||
(info & ED_IN) ? "in" : "out",
|
||||
(info & ED_ISO) ? "iso" : "int",
|
||||
qlen,
|
||||
0x03ff & (info >> 16),
|
||||
info,
|
||||
(info & ED_SKIP) ? " K" : "",
|
||||
(ed->hwHeadP &
|
||||
cpu_to_hc32(ohci, ED_H)) ?
|
||||
" H" : "");
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
if (seen_count < DBG_SCHED_LIMIT)
|
||||
seen [seen_count++] = ed;
|
||||
|
||||
ed = ed->ed_next;
|
||||
|
||||
} else {
|
||||
/* we've seen it and what's after */
|
||||
temp = 0;
|
||||
ed = NULL;
|
||||
}
|
||||
|
||||
} while (ed);
|
||||
|
||||
temp = scnprintf (next, size, "\n");
|
||||
size -= temp;
|
||||
next += temp;
|
||||
}
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
kfree (seen);
|
||||
|
||||
return PAGE_SIZE - size;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR (periodic, S_IRUGO, show_periodic, NULL);
|
||||
|
||||
|
||||
#undef DBG_SCHED_LIMIT
|
||||
|
||||
static ssize_t
|
||||
show_registers (struct class_device *class_dev, char *buf)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
struct usb_hcd *hcd;
|
||||
struct ohci_hcd *ohci;
|
||||
struct ohci_regs __iomem *regs;
|
||||
unsigned long flags;
|
||||
unsigned temp, size;
|
||||
char *next;
|
||||
u32 rdata;
|
||||
|
||||
bus = to_usb_bus(class_dev);
|
||||
hcd = bus->hcpriv;
|
||||
ohci = hcd_to_ohci(hcd);
|
||||
regs = ohci->regs;
|
||||
next = buf;
|
||||
size = PAGE_SIZE;
|
||||
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
|
||||
/* dump driver info, then registers in spec order */
|
||||
|
||||
ohci_dbg_sw (ohci, &next, &size,
|
||||
"bus %s, device %s\n"
|
||||
"%s\n"
|
||||
"%s version " DRIVER_VERSION "\n",
|
||||
hcd->self.controller->bus->name,
|
||||
hcd->self.controller->bus_id,
|
||||
hcd->product_desc,
|
||||
hcd_name);
|
||||
|
||||
if (bus->controller->power.power_state) {
|
||||
size -= scnprintf (next, size,
|
||||
"SUSPENDED (no register access)\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ohci_dump_status(ohci, &next, &size);
|
||||
|
||||
/* hcca */
|
||||
if (ohci->hcca)
|
||||
ohci_dbg_sw (ohci, &next, &size,
|
||||
"hcca frame 0x%04x\n", ohci_frame_no(ohci));
|
||||
|
||||
/* other registers mostly affect frame timings */
|
||||
rdata = ohci_readl (ohci, ®s->fminterval);
|
||||
temp = scnprintf (next, size,
|
||||
"fmintvl 0x%08x %sFSMPS=0x%04x FI=0x%04x\n",
|
||||
rdata, (rdata >> 31) ? "FIT " : "",
|
||||
(rdata >> 16) & 0xefff, rdata & 0xffff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
rdata = ohci_readl (ohci, ®s->fmremaining);
|
||||
temp = scnprintf (next, size, "fmremaining 0x%08x %sFR=0x%04x\n",
|
||||
rdata, (rdata >> 31) ? "FRT " : "",
|
||||
rdata & 0x3fff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
rdata = ohci_readl (ohci, ®s->periodicstart);
|
||||
temp = scnprintf (next, size, "periodicstart 0x%04x\n",
|
||||
rdata & 0x3fff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
rdata = ohci_readl (ohci, ®s->lsthresh);
|
||||
temp = scnprintf (next, size, "lsthresh 0x%04x\n",
|
||||
rdata & 0x3fff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
/* roothub */
|
||||
ohci_dump_roothub (ohci, 1, &next, &size);
|
||||
|
||||
done:
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
return PAGE_SIZE - size;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL);
|
||||
|
||||
|
||||
static inline void create_debug_files (struct ohci_hcd *ohci)
|
||||
{
|
||||
struct class_device *cldev = &ohci_to_hcd(ohci)->self.class_dev;
|
||||
|
||||
class_device_create_file(cldev, &class_device_attr_async);
|
||||
class_device_create_file(cldev, &class_device_attr_periodic);
|
||||
class_device_create_file(cldev, &class_device_attr_registers);
|
||||
ohci_dbg (ohci, "created debug files\n");
|
||||
}
|
||||
|
||||
static inline void remove_debug_files (struct ohci_hcd *ohci)
|
||||
{
|
||||
struct class_device *cldev = &ohci_to_hcd(ohci)->self.class_dev;
|
||||
|
||||
class_device_remove_file(cldev, &class_device_attr_async);
|
||||
class_device_remove_file(cldev, &class_device_attr_periodic);
|
||||
class_device_remove_file(cldev, &class_device_attr_registers);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
925
drivers/usb/host/ohci-hcd.c
一般檔案
925
drivers/usb/host/ohci-hcd.c
一般檔案
@@ -0,0 +1,925 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*
|
||||
* [ Initialisation is based on Linus' ]
|
||||
* [ uhci code and gregs ohci fragments ]
|
||||
* [ (C) Copyright 1999 Linus Torvalds ]
|
||||
* [ (C) Copyright 1999 Gregory P. Smith]
|
||||
*
|
||||
*
|
||||
* OHCI is the main "non-Intel/VIA" standard for USB 1.1 host controller
|
||||
* interfaces (though some non-x86 Intel chips use it). It supports
|
||||
* smarter hardware than UHCI. A download link for the spec available
|
||||
* through the http://www.usb.org website.
|
||||
*
|
||||
* History:
|
||||
*
|
||||
* 2004/03/24 LH7A404 support (Durgesh Pattamatta & Marc Singer)
|
||||
* 2004/02/04 use generic dma_* functions instead of pci_* (dsaxena@plexity.net)
|
||||
* 2003/02/24 show registers in sysfs (Kevin Brosius)
|
||||
*
|
||||
* 2002/09/03 get rid of ed hashtables, rework periodic scheduling and
|
||||
* bandwidth accounting; if debugging, show schedules in driverfs
|
||||
* 2002/07/19 fixes to management of ED and schedule state.
|
||||
* 2002/06/09 SA-1111 support (Christopher Hoover)
|
||||
* 2002/06/01 remember frame when HC won't see EDs any more; use that info
|
||||
* to fix urb unlink races caused by interrupt latency assumptions;
|
||||
* minor ED field and function naming updates
|
||||
* 2002/01/18 package as a patch for 2.5.3; this should match the
|
||||
* 2.4.17 kernel modulo some bugs being fixed.
|
||||
*
|
||||
* 2001/10/18 merge pmac cleanup (Benjamin Herrenschmidt) and bugfixes
|
||||
* from post-2.4.5 patches.
|
||||
* 2001/09/20 URB_ZERO_PACKET support; hcca_dma portability, OPTi warning
|
||||
* 2001/09/07 match PCI PM changes, errnos from Linus' tree
|
||||
* 2001/05/05 fork 2.4.5 version into "hcd" framework, cleanup, simplify;
|
||||
* pbook pci quirks gone (please fix pbook pci sw!) (db)
|
||||
*
|
||||
* 2001/04/08 Identify version on module load (gb)
|
||||
* 2001/03/24 td/ed hashing to remove bus_to_virt (Steve Longerbeam);
|
||||
pci_map_single (db)
|
||||
* 2001/03/21 td and dev/ed allocation uses new pci_pool API (db)
|
||||
* 2001/03/07 hcca allocation uses pci_alloc_consistent (Steve Longerbeam)
|
||||
*
|
||||
* 2000/09/26 fixed races in removing the private portion of the urb
|
||||
* 2000/09/07 disable bulk and control lists when unlinking the last
|
||||
* endpoint descriptor in order to avoid unrecoverable errors on
|
||||
* the Lucent chips. (rwc@sgi)
|
||||
* 2000/08/29 use bandwidth claiming hooks (thanks Randy!), fix some
|
||||
* urb unlink probs, indentation fixes
|
||||
* 2000/08/11 various oops fixes mostly affecting iso and cleanup from
|
||||
* device unplugs.
|
||||
* 2000/06/28 use PCI hotplug framework, for better power management
|
||||
* and for Cardbus support (David Brownell)
|
||||
* 2000/earlier: fixes for NEC/Lucent chips; suspend/resume handling
|
||||
* when the controller loses power; handle UE; cleanup; ...
|
||||
*
|
||||
* v5.2 1999/12/07 URB 3rd preview,
|
||||
* v5.1 1999/11/30 URB 2nd preview, cpia, (usb-scsi)
|
||||
* v5.0 1999/11/22 URB Technical preview, Paul Mackerras powerbook susp/resume
|
||||
* i386: HUB, Keyboard, Mouse, Printer
|
||||
*
|
||||
* v4.3 1999/10/27 multiple HCs, bulk_request
|
||||
* v4.2 1999/09/05 ISO API alpha, new dev alloc, neg Error-codes
|
||||
* v4.1 1999/08/27 Randy Dunlap's - ISO API first impl.
|
||||
* v4.0 1999/08/18
|
||||
* v3.0 1999/06/25
|
||||
* v2.1 1999/05/09 code clean up
|
||||
* v2.0 1999/05/04
|
||||
* v1.0 1999/04/27 initial release
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
# define DEBUG
|
||||
#else
|
||||
# undef DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/interrupt.h> /* for in_interrupt () */
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb_otg.h>
|
||||
#include "../core/hcd.h"
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmapool.h> /* needed by ohci-mem.c when no PCI */
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
|
||||
#define DRIVER_VERSION "2004 Nov 08"
|
||||
#define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell"
|
||||
#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver"
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
// #define OHCI_VERBOSE_DEBUG /* not always helpful */
|
||||
|
||||
/* For initializing controller (mask in an HCFS mode too) */
|
||||
#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR
|
||||
#define OHCI_INTR_INIT \
|
||||
(OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_WDH)
|
||||
|
||||
#ifdef __hppa__
|
||||
/* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */
|
||||
#define IR_DISABLE
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP
|
||||
/* OMAP doesn't support IR (no SMM; not needed) */
|
||||
#define IR_DISABLE
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const char hcd_name [] = "ohci_hcd";
|
||||
|
||||
#include "ohci.h"
|
||||
|
||||
static void ohci_dump (struct ohci_hcd *ohci, int verbose);
|
||||
static int ohci_init (struct ohci_hcd *ohci);
|
||||
static void ohci_stop (struct usb_hcd *hcd);
|
||||
|
||||
#include "ohci-hub.c"
|
||||
#include "ohci-dbg.c"
|
||||
#include "ohci-mem.c"
|
||||
#include "ohci-q.c"
|
||||
|
||||
|
||||
/*
|
||||
* On architectures with edge-triggered interrupts we must never return
|
||||
* IRQ_NONE.
|
||||
*/
|
||||
#if defined(CONFIG_SA1111) /* ... or other edge-triggered systems */
|
||||
#define IRQ_NOTMINE IRQ_HANDLED
|
||||
#else
|
||||
#define IRQ_NOTMINE IRQ_NONE
|
||||
#endif
|
||||
|
||||
|
||||
/* Some boards misreport power switching/overcurrent */
|
||||
static int distrust_firmware = 1;
|
||||
module_param (distrust_firmware, bool, 0);
|
||||
MODULE_PARM_DESC (distrust_firmware,
|
||||
"true to distrust firmware power/overcurrent setup");
|
||||
|
||||
/* Some boards leave IR set wrongly, since they fail BIOS/SMM handshakes */
|
||||
static int no_handshake = 0;
|
||||
module_param (no_handshake, bool, 0);
|
||||
MODULE_PARM_DESC (no_handshake, "true (not default) disables BIOS handshake");
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* queue up an urb for anything except the root hub
|
||||
*/
|
||||
static int ohci_urb_enqueue (
|
||||
struct usb_hcd *hcd,
|
||||
struct usb_host_endpoint *ep,
|
||||
struct urb *urb,
|
||||
int mem_flags
|
||||
) {
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
struct ed *ed;
|
||||
urb_priv_t *urb_priv;
|
||||
unsigned int pipe = urb->pipe;
|
||||
int i, size = 0;
|
||||
unsigned long flags;
|
||||
int retval = 0;
|
||||
|
||||
#ifdef OHCI_VERBOSE_DEBUG
|
||||
urb_print (urb, "SUB", usb_pipein (pipe));
|
||||
#endif
|
||||
|
||||
/* every endpoint has a ed, locate and maybe (re)initialize it */
|
||||
if (! (ed = ed_get (ohci, ep, urb->dev, pipe, urb->interval)))
|
||||
return -ENOMEM;
|
||||
|
||||
/* for the private part of the URB we need the number of TDs (size) */
|
||||
switch (ed->type) {
|
||||
case PIPE_CONTROL:
|
||||
/* td_submit_urb() doesn't yet handle these */
|
||||
if (urb->transfer_buffer_length > 4096)
|
||||
return -EMSGSIZE;
|
||||
|
||||
/* 1 TD for setup, 1 for ACK, plus ... */
|
||||
size = 2;
|
||||
/* FALLTHROUGH */
|
||||
// case PIPE_INTERRUPT:
|
||||
// case PIPE_BULK:
|
||||
default:
|
||||
/* one TD for every 4096 Bytes (can be upto 8K) */
|
||||
size += urb->transfer_buffer_length / 4096;
|
||||
/* ... and for any remaining bytes ... */
|
||||
if ((urb->transfer_buffer_length % 4096) != 0)
|
||||
size++;
|
||||
/* ... and maybe a zero length packet to wrap it up */
|
||||
if (size == 0)
|
||||
size++;
|
||||
else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0
|
||||
&& (urb->transfer_buffer_length
|
||||
% usb_maxpacket (urb->dev, pipe,
|
||||
usb_pipeout (pipe))) == 0)
|
||||
size++;
|
||||
break;
|
||||
case PIPE_ISOCHRONOUS: /* number of packets from URB */
|
||||
size = urb->number_of_packets;
|
||||
break;
|
||||
}
|
||||
|
||||
/* allocate the private part of the URB */
|
||||
urb_priv = kmalloc (sizeof (urb_priv_t) + size * sizeof (struct td *),
|
||||
mem_flags);
|
||||
if (!urb_priv)
|
||||
return -ENOMEM;
|
||||
memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (struct td *));
|
||||
INIT_LIST_HEAD (&urb_priv->pending);
|
||||
urb_priv->length = size;
|
||||
urb_priv->ed = ed;
|
||||
|
||||
/* allocate the TDs (deferring hash chain updates) */
|
||||
for (i = 0; i < size; i++) {
|
||||
urb_priv->td [i] = td_alloc (ohci, mem_flags);
|
||||
if (!urb_priv->td [i]) {
|
||||
urb_priv->length = i;
|
||||
urb_free_priv (ohci, urb_priv);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
|
||||
/* don't submit to a dead HC */
|
||||
if (!HC_IS_RUNNING(hcd->state)) {
|
||||
retval = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* in case of unlink-during-submit */
|
||||
spin_lock (&urb->lock);
|
||||
if (urb->status != -EINPROGRESS) {
|
||||
spin_unlock (&urb->lock);
|
||||
urb->hcpriv = urb_priv;
|
||||
finish_urb (ohci, urb, NULL);
|
||||
retval = 0;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* schedule the ed if needed */
|
||||
if (ed->state == ED_IDLE) {
|
||||
retval = ed_schedule (ohci, ed);
|
||||
if (retval < 0)
|
||||
goto fail0;
|
||||
if (ed->type == PIPE_ISOCHRONOUS) {
|
||||
u16 frame = ohci_frame_no(ohci);
|
||||
|
||||
/* delay a few frames before the first TD */
|
||||
frame += max_t (u16, 8, ed->interval);
|
||||
frame &= ~(ed->interval - 1);
|
||||
frame |= ed->branch;
|
||||
urb->start_frame = frame;
|
||||
|
||||
/* yes, only URB_ISO_ASAP is supported, and
|
||||
* urb->start_frame is never used as input.
|
||||
*/
|
||||
}
|
||||
} else if (ed->type == PIPE_ISOCHRONOUS)
|
||||
urb->start_frame = ed->last_iso + ed->interval;
|
||||
|
||||
/* fill the TDs and link them to the ed; and
|
||||
* enable that part of the schedule, if needed
|
||||
* and update count of queued periodic urbs
|
||||
*/
|
||||
urb->hcpriv = urb_priv;
|
||||
td_submit_urb (ohci, urb);
|
||||
|
||||
fail0:
|
||||
spin_unlock (&urb->lock);
|
||||
fail:
|
||||
if (retval)
|
||||
urb_free_priv (ohci, urb_priv);
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* decouple the URB from the HC queues (TDs, urb_priv); it's
|
||||
* already marked using urb->status. reporting is always done
|
||||
* asynchronously, and we might be dealing with an urb that's
|
||||
* partially transferred, or an ED with other urbs being unlinked.
|
||||
*/
|
||||
static int ohci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
unsigned long flags;
|
||||
|
||||
#ifdef OHCI_VERBOSE_DEBUG
|
||||
urb_print (urb, "UNLINK", 1);
|
||||
#endif
|
||||
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
if (HC_IS_RUNNING(hcd->state)) {
|
||||
urb_priv_t *urb_priv;
|
||||
|
||||
/* Unless an IRQ completed the unlink while it was being
|
||||
* handed to us, flag it for unlink and giveback, and force
|
||||
* some upcoming INTR_SF to call finish_unlinks()
|
||||
*/
|
||||
urb_priv = urb->hcpriv;
|
||||
if (urb_priv) {
|
||||
if (urb_priv->ed->state == ED_OPER)
|
||||
start_ed_unlink (ohci, urb_priv->ed);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* with HC dead, we won't respect hc queue pointers
|
||||
* any more ... just clean up every urb's memory.
|
||||
*/
|
||||
if (urb->hcpriv)
|
||||
finish_urb (ohci, urb, NULL);
|
||||
}
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* frees config/altsetting state for endpoints,
|
||||
* including ED memory, dummy TD, and bulk/intr data toggle
|
||||
*/
|
||||
|
||||
static void
|
||||
ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
unsigned long flags;
|
||||
struct ed *ed = ep->hcpriv;
|
||||
unsigned limit = 1000;
|
||||
|
||||
/* ASSERT: any requests/urbs are being unlinked */
|
||||
/* ASSERT: nobody can be submitting urbs for this any more */
|
||||
|
||||
if (!ed)
|
||||
return;
|
||||
|
||||
rescan:
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
|
||||
if (!HC_IS_RUNNING (hcd->state)) {
|
||||
sanitize:
|
||||
ed->state = ED_IDLE;
|
||||
finish_unlinks (ohci, 0, NULL);
|
||||
}
|
||||
|
||||
switch (ed->state) {
|
||||
case ED_UNLINK: /* wait for hw to finish? */
|
||||
/* major IRQ delivery trouble loses INTR_SF too... */
|
||||
if (limit-- == 0) {
|
||||
ohci_warn (ohci, "IRQ INTR_SF lossage\n");
|
||||
goto sanitize;
|
||||
}
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
set_current_state (TASK_UNINTERRUPTIBLE);
|
||||
schedule_timeout (1);
|
||||
goto rescan;
|
||||
case ED_IDLE: /* fully unlinked */
|
||||
if (list_empty (&ed->td_list)) {
|
||||
td_free (ohci, ed->dummy);
|
||||
ed_free (ohci, ed);
|
||||
break;
|
||||
}
|
||||
/* else FALL THROUGH */
|
||||
default:
|
||||
/* caller was supposed to have unlinked any requests;
|
||||
* that's not our job. can't recover; must leak ed.
|
||||
*/
|
||||
ohci_err (ohci, "leak ed %p (#%02x) state %d%s\n",
|
||||
ed, ep->desc.bEndpointAddress, ed->state,
|
||||
list_empty (&ed->td_list) ? "" : " (has tds)");
|
||||
td_free (ohci, ed->dummy);
|
||||
break;
|
||||
}
|
||||
ep->hcpriv = NULL;
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
static int ohci_get_frame (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
|
||||
return ohci_frame_no(ohci);
|
||||
}
|
||||
|
||||
static void ohci_usb_reset (struct ohci_hcd *ohci)
|
||||
{
|
||||
ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
|
||||
ohci->hc_control &= OHCI_CTRL_RWC;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*
|
||||
* HC functions
|
||||
*-------------------------------------------------------------------------*/
|
||||
|
||||
/* init memory, and kick BIOS/SMM off */
|
||||
|
||||
static int ohci_init (struct ohci_hcd *ohci)
|
||||
{
|
||||
int ret;
|
||||
|
||||
disable (ohci);
|
||||
ohci->regs = ohci_to_hcd(ohci)->regs;
|
||||
ohci->next_statechange = jiffies;
|
||||
|
||||
#ifndef IR_DISABLE
|
||||
/* SMM owns the HC? not for long! */
|
||||
if (!no_handshake && ohci_readl (ohci,
|
||||
&ohci->regs->control) & OHCI_CTRL_IR) {
|
||||
u32 temp;
|
||||
|
||||
ohci_dbg (ohci, "USB HC TakeOver from BIOS/SMM\n");
|
||||
|
||||
/* this timeout is arbitrary. we make it long, so systems
|
||||
* depending on usb keyboards may be usable even if the
|
||||
* BIOS/SMM code seems pretty broken.
|
||||
*/
|
||||
temp = 500; /* arbitrary: five seconds */
|
||||
|
||||
ohci_writel (ohci, OHCI_INTR_OC, &ohci->regs->intrenable);
|
||||
ohci_writel (ohci, OHCI_OCR, &ohci->regs->cmdstatus);
|
||||
while (ohci_readl (ohci, &ohci->regs->control) & OHCI_CTRL_IR) {
|
||||
msleep (10);
|
||||
if (--temp == 0) {
|
||||
ohci_err (ohci, "USB HC takeover failed!"
|
||||
" (BIOS/SMM bug)\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
ohci_usb_reset (ohci);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Disable HC interrupts */
|
||||
ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
|
||||
// flush the writes
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
|
||||
if (ohci->hcca)
|
||||
return 0;
|
||||
|
||||
ohci->hcca = dma_alloc_coherent (ohci_to_hcd(ohci)->self.controller,
|
||||
sizeof *ohci->hcca, &ohci->hcca_dma, 0);
|
||||
if (!ohci->hcca)
|
||||
return -ENOMEM;
|
||||
|
||||
if ((ret = ohci_mem_init (ohci)) < 0)
|
||||
ohci_stop (ohci_to_hcd(ohci));
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Start an OHCI controller, set the BUS operational
|
||||
* resets USB and controller
|
||||
* enable interrupts
|
||||
* connect the virtual root hub
|
||||
*/
|
||||
static int ohci_run (struct ohci_hcd *ohci)
|
||||
{
|
||||
u32 mask, temp;
|
||||
struct usb_device *udev;
|
||||
struct usb_bus *bus;
|
||||
int first = ohci->fminterval == 0;
|
||||
|
||||
disable (ohci);
|
||||
|
||||
/* boot firmware should have set this up (5.1.1.3.1) */
|
||||
if (first) {
|
||||
|
||||
temp = ohci_readl (ohci, &ohci->regs->fminterval);
|
||||
ohci->fminterval = temp & 0x3fff;
|
||||
if (ohci->fminterval != FI)
|
||||
ohci_dbg (ohci, "fminterval delta %d\n",
|
||||
ohci->fminterval - FI);
|
||||
ohci->fminterval |= FSMP (ohci->fminterval) << 16;
|
||||
/* also: power/overcurrent flags in roothub.a */
|
||||
}
|
||||
|
||||
/* Reset USB nearly "by the book". RemoteWakeupConnected
|
||||
* saved if boot firmware (BIOS/SMM/...) told us it's connected
|
||||
* (for OHCI integrated on mainboard, it normally is)
|
||||
*/
|
||||
ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
|
||||
ohci_dbg (ohci, "resetting from state '%s', control = 0x%x\n",
|
||||
hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS),
|
||||
ohci->hc_control);
|
||||
|
||||
if (ohci->hc_control & OHCI_CTRL_RWC
|
||||
&& !(ohci->flags & OHCI_QUIRK_AMD756))
|
||||
ohci_to_hcd(ohci)->can_wakeup = 1;
|
||||
|
||||
switch (ohci->hc_control & OHCI_CTRL_HCFS) {
|
||||
case OHCI_USB_OPER:
|
||||
temp = 0;
|
||||
break;
|
||||
case OHCI_USB_SUSPEND:
|
||||
case OHCI_USB_RESUME:
|
||||
ohci->hc_control &= OHCI_CTRL_RWC;
|
||||
ohci->hc_control |= OHCI_USB_RESUME;
|
||||
temp = 10 /* msec wait */;
|
||||
break;
|
||||
// case OHCI_USB_RESET:
|
||||
default:
|
||||
ohci->hc_control &= OHCI_CTRL_RWC;
|
||||
ohci->hc_control |= OHCI_USB_RESET;
|
||||
temp = 50 /* msec wait */;
|
||||
break;
|
||||
}
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
// flush the writes
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
msleep(temp);
|
||||
temp = roothub_a (ohci);
|
||||
if (!(temp & RH_A_NPS)) {
|
||||
unsigned ports = temp & RH_A_NDP;
|
||||
|
||||
/* power down each port */
|
||||
for (temp = 0; temp < ports; temp++)
|
||||
ohci_writel (ohci, RH_PS_LSDA,
|
||||
&ohci->regs->roothub.portstatus [temp]);
|
||||
}
|
||||
// flush those writes
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
memset (ohci->hcca, 0, sizeof (struct ohci_hcca));
|
||||
|
||||
/* 2msec timelimit here means no irqs/preempt */
|
||||
spin_lock_irq (&ohci->lock);
|
||||
|
||||
retry:
|
||||
/* HC Reset requires max 10 us delay */
|
||||
ohci_writel (ohci, OHCI_HCR, &ohci->regs->cmdstatus);
|
||||
temp = 30; /* ... allow extra time */
|
||||
while ((ohci_readl (ohci, &ohci->regs->cmdstatus) & OHCI_HCR) != 0) {
|
||||
if (--temp == 0) {
|
||||
spin_unlock_irq (&ohci->lock);
|
||||
ohci_err (ohci, "USB HC reset timed out!\n");
|
||||
return -1;
|
||||
}
|
||||
udelay (1);
|
||||
}
|
||||
|
||||
/* now we're in the SUSPEND state ... must go OPERATIONAL
|
||||
* within 2msec else HC enters RESUME
|
||||
*
|
||||
* ... but some hardware won't init fmInterval "by the book"
|
||||
* (SiS, OPTi ...), so reset again instead. SiS doesn't need
|
||||
* this if we write fmInterval after we're OPERATIONAL.
|
||||
* Unclear about ALi, ServerWorks, and others ... this could
|
||||
* easily be a longstanding bug in chip init on Linux.
|
||||
*/
|
||||
if (ohci->flags & OHCI_QUIRK_INITRESET) {
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
// flush those writes
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
}
|
||||
|
||||
/* Tell the controller where the control and bulk lists are
|
||||
* The lists are empty now. */
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_controlhead);
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_bulkhead);
|
||||
|
||||
/* a reset clears this */
|
||||
ohci_writel (ohci, (u32) ohci->hcca_dma, &ohci->regs->hcca);
|
||||
|
||||
periodic_reinit (ohci);
|
||||
|
||||
/* some OHCI implementations are finicky about how they init.
|
||||
* bogus values here mean not even enumeration could work.
|
||||
*/
|
||||
if ((ohci_readl (ohci, &ohci->regs->fminterval) & 0x3fff0000) == 0
|
||||
|| !ohci_readl (ohci, &ohci->regs->periodicstart)) {
|
||||
if (!(ohci->flags & OHCI_QUIRK_INITRESET)) {
|
||||
ohci->flags |= OHCI_QUIRK_INITRESET;
|
||||
ohci_dbg (ohci, "enabling initreset quirk\n");
|
||||
goto retry;
|
||||
}
|
||||
spin_unlock_irq (&ohci->lock);
|
||||
ohci_err (ohci, "init err (%08x %04x)\n",
|
||||
ohci_readl (ohci, &ohci->regs->fminterval),
|
||||
ohci_readl (ohci, &ohci->regs->periodicstart));
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
/* start controller operations */
|
||||
ohci->hc_control &= OHCI_CTRL_RWC;
|
||||
ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
ohci_to_hcd(ohci)->state = HC_STATE_RUNNING;
|
||||
|
||||
/* wake on ConnectStatusChange, matching external hubs */
|
||||
ohci_writel (ohci, RH_HS_DRWE, &ohci->regs->roothub.status);
|
||||
|
||||
/* Choose the interrupts we care about now, others later on demand */
|
||||
mask = OHCI_INTR_INIT;
|
||||
ohci_writel (ohci, mask, &ohci->regs->intrstatus);
|
||||
ohci_writel (ohci, mask, &ohci->regs->intrenable);
|
||||
|
||||
/* handle root hub init quirks ... */
|
||||
temp = roothub_a (ohci);
|
||||
temp &= ~(RH_A_PSM | RH_A_OCPM);
|
||||
if (ohci->flags & OHCI_QUIRK_SUPERIO) {
|
||||
/* NSC 87560 and maybe others */
|
||||
temp |= RH_A_NOCP;
|
||||
temp &= ~(RH_A_POTPGT | RH_A_NPS);
|
||||
ohci_writel (ohci, temp, &ohci->regs->roothub.a);
|
||||
} else if ((ohci->flags & OHCI_QUIRK_AMD756) || distrust_firmware) {
|
||||
/* hub power always on; required for AMD-756 and some
|
||||
* Mac platforms. ganged overcurrent reporting, if any.
|
||||
*/
|
||||
temp |= RH_A_NPS;
|
||||
ohci_writel (ohci, temp, &ohci->regs->roothub.a);
|
||||
}
|
||||
ohci_writel (ohci, RH_HS_LPSC, &ohci->regs->roothub.status);
|
||||
ohci_writel (ohci, (temp & RH_A_NPS) ? 0 : RH_B_PPCM,
|
||||
&ohci->regs->roothub.b);
|
||||
// flush those writes
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
|
||||
spin_unlock_irq (&ohci->lock);
|
||||
|
||||
// POTPGT delay is bits 24-31, in 2 ms units.
|
||||
mdelay ((temp >> 23) & 0x1fe);
|
||||
bus = &ohci_to_hcd(ohci)->self;
|
||||
ohci_to_hcd(ohci)->state = HC_STATE_RUNNING;
|
||||
|
||||
ohci_dump (ohci, 1);
|
||||
|
||||
udev = bus->root_hub;
|
||||
if (udev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* connect the virtual root hub */
|
||||
udev = usb_alloc_dev (NULL, bus, 0);
|
||||
if (!udev) {
|
||||
disable (ohci);
|
||||
ohci->hc_control &= ~OHCI_CTRL_HCFS;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
udev->speed = USB_SPEED_FULL;
|
||||
if (usb_hcd_register_root_hub (udev, ohci_to_hcd(ohci)) != 0) {
|
||||
usb_put_dev (udev);
|
||||
disable (ohci);
|
||||
ohci->hc_control &= ~OHCI_CTRL_HCFS;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (ohci->power_budget)
|
||||
hub_set_power_budget(udev, ohci->power_budget);
|
||||
|
||||
create_debug_files (ohci);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* an interrupt happens */
|
||||
|
||||
static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
struct ohci_regs __iomem *regs = ohci->regs;
|
||||
int ints;
|
||||
|
||||
/* we can eliminate a (slow) ohci_readl()
|
||||
if _only_ WDH caused this irq */
|
||||
if ((ohci->hcca->done_head != 0)
|
||||
&& ! (hc32_to_cpup (ohci, &ohci->hcca->done_head)
|
||||
& 0x01)) {
|
||||
ints = OHCI_INTR_WDH;
|
||||
|
||||
/* cardbus/... hardware gone before remove() */
|
||||
} else if ((ints = ohci_readl (ohci, ®s->intrstatus)) == ~(u32)0) {
|
||||
disable (ohci);
|
||||
ohci_dbg (ohci, "device removed!\n");
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/* interrupt for some other device? */
|
||||
} else if ((ints &= ohci_readl (ohci, ®s->intrenable)) == 0) {
|
||||
return IRQ_NOTMINE;
|
||||
}
|
||||
|
||||
if (ints & OHCI_INTR_UE) {
|
||||
disable (ohci);
|
||||
ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n");
|
||||
// e.g. due to PCI Master/Target Abort
|
||||
|
||||
ohci_dump (ohci, 1);
|
||||
ohci_usb_reset (ohci);
|
||||
}
|
||||
|
||||
if (ints & OHCI_INTR_RD) {
|
||||
ohci_vdbg (ohci, "resume detect\n");
|
||||
if (hcd->state != HC_STATE_QUIESCING)
|
||||
schedule_work(&ohci->rh_resume);
|
||||
}
|
||||
|
||||
if (ints & OHCI_INTR_WDH) {
|
||||
if (HC_IS_RUNNING(hcd->state))
|
||||
ohci_writel (ohci, OHCI_INTR_WDH, ®s->intrdisable);
|
||||
spin_lock (&ohci->lock);
|
||||
dl_done_list (ohci, ptregs);
|
||||
spin_unlock (&ohci->lock);
|
||||
if (HC_IS_RUNNING(hcd->state))
|
||||
ohci_writel (ohci, OHCI_INTR_WDH, ®s->intrenable);
|
||||
}
|
||||
|
||||
/* could track INTR_SO to reduce available PCI/... bandwidth */
|
||||
|
||||
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
|
||||
* when there's still unlinking to be done (next frame).
|
||||
*/
|
||||
spin_lock (&ohci->lock);
|
||||
if (ohci->ed_rm_list)
|
||||
finish_unlinks (ohci, ohci_frame_no(ohci), ptregs);
|
||||
if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
|
||||
&& HC_IS_RUNNING(hcd->state))
|
||||
ohci_writel (ohci, OHCI_INTR_SF, ®s->intrdisable);
|
||||
spin_unlock (&ohci->lock);
|
||||
|
||||
if (HC_IS_RUNNING(hcd->state)) {
|
||||
ohci_writel (ohci, ints, ®s->intrstatus);
|
||||
ohci_writel (ohci, OHCI_INTR_MIE, ®s->intrenable);
|
||||
// flush those writes
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void ohci_stop (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
|
||||
ohci_dbg (ohci, "stop %s controller (state 0x%02x)\n",
|
||||
hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS),
|
||||
hcd->state);
|
||||
ohci_dump (ohci, 1);
|
||||
|
||||
flush_scheduled_work();
|
||||
|
||||
ohci_usb_reset (ohci);
|
||||
ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
|
||||
|
||||
remove_debug_files (ohci);
|
||||
ohci_mem_cleanup (ohci);
|
||||
if (ohci->hcca) {
|
||||
dma_free_coherent (hcd->self.controller,
|
||||
sizeof *ohci->hcca,
|
||||
ohci->hcca, ohci->hcca_dma);
|
||||
ohci->hcca = NULL;
|
||||
ohci->hcca_dma = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* must not be called from interrupt context */
|
||||
|
||||
#if defined(CONFIG_USB_SUSPEND) || defined(CONFIG_PM)
|
||||
|
||||
static int ohci_restart (struct ohci_hcd *ohci)
|
||||
{
|
||||
int temp;
|
||||
int i;
|
||||
struct urb_priv *priv;
|
||||
struct usb_device *root = ohci_to_hcd(ohci)->self.root_hub;
|
||||
|
||||
/* mark any devices gone, so they do nothing till khubd disconnects.
|
||||
* recycle any "live" eds/tds (and urbs) right away.
|
||||
* later, khubd disconnect processing will recycle the other state,
|
||||
* (either as disconnect/reconnect, or maybe someday as a reset).
|
||||
*/
|
||||
spin_lock_irq(&ohci->lock);
|
||||
disable (ohci);
|
||||
for (i = 0; i < root->maxchild; i++) {
|
||||
if (root->children [i])
|
||||
usb_set_device_state (root->children[i],
|
||||
USB_STATE_NOTATTACHED);
|
||||
}
|
||||
if (!list_empty (&ohci->pending))
|
||||
ohci_dbg(ohci, "abort schedule...\n");
|
||||
list_for_each_entry (priv, &ohci->pending, pending) {
|
||||
struct urb *urb = priv->td[0]->urb;
|
||||
struct ed *ed = priv->ed;
|
||||
|
||||
switch (ed->state) {
|
||||
case ED_OPER:
|
||||
ed->state = ED_UNLINK;
|
||||
ed->hwINFO |= cpu_to_hc32(ohci, ED_DEQUEUE);
|
||||
ed_deschedule (ohci, ed);
|
||||
|
||||
ed->ed_next = ohci->ed_rm_list;
|
||||
ed->ed_prev = NULL;
|
||||
ohci->ed_rm_list = ed;
|
||||
/* FALLTHROUGH */
|
||||
case ED_UNLINK:
|
||||
break;
|
||||
default:
|
||||
ohci_dbg(ohci, "bogus ed %p state %d\n",
|
||||
ed, ed->state);
|
||||
}
|
||||
|
||||
spin_lock (&urb->lock);
|
||||
urb->status = -ESHUTDOWN;
|
||||
spin_unlock (&urb->lock);
|
||||
}
|
||||
finish_unlinks (ohci, 0, NULL);
|
||||
spin_unlock_irq(&ohci->lock);
|
||||
|
||||
/* paranoia, in case that didn't work: */
|
||||
|
||||
/* empty the interrupt branches */
|
||||
for (i = 0; i < NUM_INTS; i++) ohci->load [i] = 0;
|
||||
for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0;
|
||||
|
||||
/* no EDs to remove */
|
||||
ohci->ed_rm_list = NULL;
|
||||
|
||||
/* empty control and bulk lists */
|
||||
ohci->ed_controltail = NULL;
|
||||
ohci->ed_bulktail = NULL;
|
||||
|
||||
if ((temp = ohci_run (ohci)) < 0) {
|
||||
ohci_err (ohci, "can't restart, %d\n", temp);
|
||||
return temp;
|
||||
} else {
|
||||
/* here we "know" root ports should always stay powered,
|
||||
* and that if we try to turn them back on the root hub
|
||||
* will respond to CSC processing.
|
||||
*/
|
||||
i = roothub_a (ohci) & RH_A_NDP;
|
||||
while (i--)
|
||||
ohci_writel (ohci, RH_PS_PSS,
|
||||
&ohci->regs->roothub.portstatus [temp]);
|
||||
ohci_dbg (ohci, "restart complete\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
|
||||
|
||||
MODULE_AUTHOR (DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION (DRIVER_INFO);
|
||||
MODULE_LICENSE ("GPL");
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
#include "ohci-pci.c"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SA1111
|
||||
#include "ohci-sa1111.c"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP
|
||||
#include "ohci-omap.c"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ARCH_LH7A404
|
||||
#include "ohci-lh7a404.c"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PXA27x
|
||||
#include "ohci-pxa27x.c"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SOC_AU1X00
|
||||
#include "ohci-au1xxx.c"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_OHCI_HCD_PPC_SOC
|
||||
#include "ohci-ppc-soc.c"
|
||||
#endif
|
||||
|
||||
#if !(defined(CONFIG_PCI) \
|
||||
|| defined(CONFIG_SA1111) \
|
||||
|| defined(CONFIG_ARCH_OMAP) \
|
||||
|| defined (CONFIG_ARCH_LH7A404) \
|
||||
|| defined (CONFIG_PXA27x) \
|
||||
|| defined (CONFIG_SOC_AU1X00) \
|
||||
|| defined (CONFIG_USB_OHCI_HCD_PPC_SOC) \
|
||||
)
|
||||
#error "missing bus glue for ohci-hcd"
|
||||
#endif
|
643
drivers/usb/host/ohci-hub.c
一般檔案
643
drivers/usb/host/ohci-hub.c
一般檔案
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*
|
||||
* This file is licenced under GPL
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* OHCI Root Hub ... the nonsharable stuff
|
||||
*/
|
||||
|
||||
#define dbg_port(hc,label,num,value) \
|
||||
ohci_dbg (hc, \
|
||||
"%s roothub.portstatus [%d] " \
|
||||
"= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \
|
||||
label, num, temp, \
|
||||
(temp & RH_PS_PRSC) ? " PRSC" : "", \
|
||||
(temp & RH_PS_OCIC) ? " OCIC" : "", \
|
||||
(temp & RH_PS_PSSC) ? " PSSC" : "", \
|
||||
(temp & RH_PS_PESC) ? " PESC" : "", \
|
||||
(temp & RH_PS_CSC) ? " CSC" : "", \
|
||||
\
|
||||
(temp & RH_PS_LSDA) ? " LSDA" : "", \
|
||||
(temp & RH_PS_PPS) ? " PPS" : "", \
|
||||
(temp & RH_PS_PRS) ? " PRS" : "", \
|
||||
(temp & RH_PS_POCI) ? " POCI" : "", \
|
||||
(temp & RH_PS_PSS) ? " PSS" : "", \
|
||||
\
|
||||
(temp & RH_PS_PES) ? " PES" : "", \
|
||||
(temp & RH_PS_CCS) ? " CCS" : "" \
|
||||
);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#if defined(CONFIG_USB_SUSPEND) || defined(CONFIG_PM)
|
||||
|
||||
#define OHCI_SCHED_ENABLES \
|
||||
(OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
|
||||
|
||||
static void dl_done_list (struct ohci_hcd *, struct pt_regs *);
|
||||
static void finish_unlinks (struct ohci_hcd *, u16 , struct pt_regs *);
|
||||
static int ohci_restart (struct ohci_hcd *ohci);
|
||||
|
||||
static int ohci_hub_suspend (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int status = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
|
||||
ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
|
||||
switch (ohci->hc_control & OHCI_CTRL_HCFS) {
|
||||
case OHCI_USB_RESUME:
|
||||
ohci_dbg (ohci, "resume/suspend?\n");
|
||||
ohci->hc_control &= ~OHCI_CTRL_HCFS;
|
||||
ohci->hc_control |= OHCI_USB_RESET;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
/* FALL THROUGH */
|
||||
case OHCI_USB_RESET:
|
||||
status = -EBUSY;
|
||||
ohci_dbg (ohci, "needs reinit!\n");
|
||||
goto done;
|
||||
case OHCI_USB_SUSPEND:
|
||||
ohci_dbg (ohci, "already suspended\n");
|
||||
goto done;
|
||||
}
|
||||
ohci_dbg (ohci, "suspend root hub\n");
|
||||
|
||||
/* First stop any processing */
|
||||
hcd->state = HC_STATE_QUIESCING;
|
||||
if (ohci->hc_control & OHCI_SCHED_ENABLES) {
|
||||
int limit;
|
||||
|
||||
ohci->hc_control &= ~OHCI_SCHED_ENABLES;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
|
||||
ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrstatus);
|
||||
|
||||
/* sched disables take effect on the next frame,
|
||||
* then the last WDH could take 6+ msec
|
||||
*/
|
||||
ohci_dbg (ohci, "stopping schedules ...\n");
|
||||
limit = 2000;
|
||||
while (limit > 0) {
|
||||
udelay (250);
|
||||
limit =- 250;
|
||||
if (ohci_readl (ohci, &ohci->regs->intrstatus)
|
||||
& OHCI_INTR_SF)
|
||||
break;
|
||||
}
|
||||
dl_done_list (ohci, NULL);
|
||||
mdelay (7);
|
||||
}
|
||||
dl_done_list (ohci, NULL);
|
||||
finish_unlinks (ohci, ohci_frame_no(ohci), NULL);
|
||||
ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus),
|
||||
&ohci->regs->intrstatus);
|
||||
|
||||
/* maybe resume can wake root hub */
|
||||
if (hcd->remote_wakeup)
|
||||
ohci->hc_control |= OHCI_CTRL_RWE;
|
||||
else
|
||||
ohci->hc_control &= ~OHCI_CTRL_RWE;
|
||||
|
||||
/* Suspend hub */
|
||||
ohci->hc_control &= ~OHCI_CTRL_HCFS;
|
||||
ohci->hc_control |= OHCI_USB_SUSPEND;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
|
||||
/* no resumes until devices finish suspending */
|
||||
ohci->next_statechange = jiffies + msecs_to_jiffies (5);
|
||||
|
||||
done:
|
||||
if (status == 0)
|
||||
hcd->state = HC_STATE_SUSPENDED;
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct ed *find_head (struct ed *ed)
|
||||
{
|
||||
/* for bulk and control lists */
|
||||
while (ed->ed_prev)
|
||||
ed = ed->ed_prev;
|
||||
return ed;
|
||||
}
|
||||
|
||||
/* caller has locked the root hub */
|
||||
static int ohci_hub_resume (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
u32 temp, enables;
|
||||
int status = -EINPROGRESS;
|
||||
|
||||
if (time_before (jiffies, ohci->next_statechange))
|
||||
msleep(5);
|
||||
|
||||
spin_lock_irq (&ohci->lock);
|
||||
ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
|
||||
|
||||
if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) {
|
||||
/* this can happen after suspend-to-disk */
|
||||
if (hcd->state == HC_STATE_RESUMING) {
|
||||
ohci_dbg (ohci, "BIOS/SMM active, control %03x\n",
|
||||
ohci->hc_control);
|
||||
status = -EBUSY;
|
||||
/* this happens when pmcore resumes HC then root */
|
||||
} else {
|
||||
ohci_dbg (ohci, "duplicate resume\n");
|
||||
status = 0;
|
||||
}
|
||||
} else switch (ohci->hc_control & OHCI_CTRL_HCFS) {
|
||||
case OHCI_USB_SUSPEND:
|
||||
ohci->hc_control &= ~(OHCI_CTRL_HCFS|OHCI_SCHED_ENABLES);
|
||||
ohci->hc_control |= OHCI_USB_RESUME;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
ohci_dbg (ohci, "resume root hub\n");
|
||||
break;
|
||||
case OHCI_USB_RESUME:
|
||||
/* HCFS changes sometime after INTR_RD */
|
||||
ohci_info (ohci, "wakeup\n");
|
||||
break;
|
||||
case OHCI_USB_OPER:
|
||||
ohci_dbg (ohci, "already resumed\n");
|
||||
status = 0;
|
||||
break;
|
||||
default: /* RESET, we lost power */
|
||||
ohci_dbg (ohci, "root hub hardware reset\n");
|
||||
status = -EBUSY;
|
||||
}
|
||||
spin_unlock_irq (&ohci->lock);
|
||||
if (status == -EBUSY) {
|
||||
(void) ohci_init (ohci);
|
||||
return ohci_restart (ohci);
|
||||
}
|
||||
if (status != -EINPROGRESS)
|
||||
return status;
|
||||
|
||||
temp = roothub_a (ohci) & RH_A_NDP;
|
||||
enables = 0;
|
||||
while (temp--) {
|
||||
u32 stat = ohci_readl (ohci,
|
||||
&ohci->regs->roothub.portstatus [temp]);
|
||||
|
||||
/* force global, not selective, resume */
|
||||
if (!(stat & RH_PS_PSS))
|
||||
continue;
|
||||
ohci_writel (ohci, RH_PS_POCI,
|
||||
&ohci->regs->roothub.portstatus [temp]);
|
||||
}
|
||||
|
||||
/* Some controllers (lucent erratum) need extra-long delays */
|
||||
hcd->state = HC_STATE_RESUMING;
|
||||
mdelay (20 /* usb 11.5.1.10 */ + 15);
|
||||
|
||||
temp = ohci_readl (ohci, &ohci->regs->control);
|
||||
temp &= OHCI_CTRL_HCFS;
|
||||
if (temp != OHCI_USB_RESUME) {
|
||||
ohci_err (ohci, "controller won't resume\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* disable old schedule state, reinit from scratch */
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_controlhead);
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_controlcurrent);
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_bulkhead);
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_bulkcurrent);
|
||||
ohci_writel (ohci, 0, &ohci->regs->ed_periodcurrent);
|
||||
ohci_writel (ohci, (u32) ohci->hcca_dma, &ohci->regs->hcca);
|
||||
|
||||
/* Sometimes PCI D3 suspend trashes frame timings ... */
|
||||
periodic_reinit (ohci);
|
||||
|
||||
/* interrupts might have been disabled */
|
||||
ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable);
|
||||
if (ohci->ed_rm_list)
|
||||
ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable);
|
||||
ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus),
|
||||
&ohci->regs->intrstatus);
|
||||
|
||||
/* Then re-enable operations */
|
||||
ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control);
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
msleep (3);
|
||||
|
||||
temp = OHCI_CONTROL_INIT | OHCI_USB_OPER;
|
||||
if (hcd->can_wakeup)
|
||||
temp |= OHCI_CTRL_RWC;
|
||||
ohci->hc_control = temp;
|
||||
ohci_writel (ohci, temp, &ohci->regs->control);
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
|
||||
/* TRSMRCY */
|
||||
msleep (10);
|
||||
|
||||
/* keep it alive for ~5x suspend + resume costs */
|
||||
ohci->next_statechange = jiffies + msecs_to_jiffies (250);
|
||||
|
||||
/* maybe turn schedules back on */
|
||||
enables = 0;
|
||||
temp = 0;
|
||||
if (!ohci->ed_rm_list) {
|
||||
if (ohci->ed_controltail) {
|
||||
ohci_writel (ohci,
|
||||
find_head (ohci->ed_controltail)->dma,
|
||||
&ohci->regs->ed_controlhead);
|
||||
enables |= OHCI_CTRL_CLE;
|
||||
temp |= OHCI_CLF;
|
||||
}
|
||||
if (ohci->ed_bulktail) {
|
||||
ohci_writel (ohci, find_head (ohci->ed_bulktail)->dma,
|
||||
&ohci->regs->ed_bulkhead);
|
||||
enables |= OHCI_CTRL_BLE;
|
||||
temp |= OHCI_BLF;
|
||||
}
|
||||
}
|
||||
if (hcd->self.bandwidth_isoc_reqs || hcd->self.bandwidth_int_reqs)
|
||||
enables |= OHCI_CTRL_PLE|OHCI_CTRL_IE;
|
||||
if (enables) {
|
||||
ohci_dbg (ohci, "restarting schedules ... %08x\n", enables);
|
||||
ohci->hc_control |= enables;
|
||||
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
|
||||
if (temp)
|
||||
ohci_writel (ohci, temp, &ohci->regs->cmdstatus);
|
||||
(void) ohci_readl (ohci, &ohci->regs->control);
|
||||
}
|
||||
|
||||
hcd->state = HC_STATE_RUNNING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ohci_rh_resume (void *_hcd)
|
||||
{
|
||||
struct usb_hcd *hcd = _hcd;
|
||||
|
||||
usb_lock_device (hcd->self.root_hub);
|
||||
(void) ohci_hub_resume (hcd);
|
||||
usb_unlock_device (hcd->self.root_hub);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void ohci_rh_resume (void *_hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (_hcd);
|
||||
ohci_dbg(ohci, "rh_resume ??\n");
|
||||
}
|
||||
|
||||
#endif /* CONFIG_USB_SUSPEND || CONFIG_PM */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* build "status change" packet (one or two bytes) from HC registers */
|
||||
|
||||
static int
|
||||
ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ports, i, changed = 0, length = 1;
|
||||
int can_suspend = hcd->can_wakeup;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave (&ohci->lock, flags);
|
||||
|
||||
/* handle autosuspended root: finish resuming before
|
||||
* letting khubd or root hub timer see state changes.
|
||||
*/
|
||||
if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER
|
||||
|| !HC_IS_RUNNING(hcd->state)) {
|
||||
can_suspend = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ports = roothub_a (ohci) & RH_A_NDP;
|
||||
if (ports > MAX_ROOT_PORTS) {
|
||||
ohci_err (ohci, "bogus NDP=%d, rereads as NDP=%d\n", ports,
|
||||
ohci_readl (ohci, &ohci->regs->roothub.a) & RH_A_NDP);
|
||||
/* retry later; "should not happen" */
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* init status */
|
||||
if (roothub_status (ohci) & (RH_HS_LPSC | RH_HS_OCIC))
|
||||
buf [0] = changed = 1;
|
||||
else
|
||||
buf [0] = 0;
|
||||
if (ports > 7) {
|
||||
buf [1] = 0;
|
||||
length++;
|
||||
}
|
||||
|
||||
/* look at each port */
|
||||
for (i = 0; i < ports; i++) {
|
||||
u32 status = roothub_portstatus (ohci, i);
|
||||
|
||||
if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC
|
||||
| RH_PS_OCIC | RH_PS_PRSC)) {
|
||||
changed = 1;
|
||||
if (i < 7)
|
||||
buf [0] |= 1 << (i + 1);
|
||||
else
|
||||
buf [1] |= 1 << (i - 7);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* can suspend if no ports are enabled; or if all all
|
||||
* enabled ports are suspended AND remote wakeup is on.
|
||||
*/
|
||||
if (!(status & RH_PS_CCS))
|
||||
continue;
|
||||
if ((status & RH_PS_PSS) && hcd->remote_wakeup)
|
||||
continue;
|
||||
can_suspend = 0;
|
||||
}
|
||||
done:
|
||||
spin_unlock_irqrestore (&ohci->lock, flags);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/* save power by suspending idle root hubs;
|
||||
* INTR_RD wakes us when there's work
|
||||
* NOTE: if we can do this, we don't need a root hub timer!
|
||||
*/
|
||||
if (can_suspend
|
||||
&& !changed
|
||||
&& !ohci->ed_rm_list
|
||||
&& ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES)
|
||||
& ohci->hc_control)
|
||||
== OHCI_USB_OPER
|
||||
&& time_after (jiffies, ohci->next_statechange)
|
||||
&& usb_trylock_device (hcd->self.root_hub)
|
||||
) {
|
||||
ohci_vdbg (ohci, "autosuspend\n");
|
||||
(void) ohci_hub_suspend (hcd);
|
||||
hcd->state = HC_STATE_RUNNING;
|
||||
usb_unlock_device (hcd->self.root_hub);
|
||||
}
|
||||
#endif
|
||||
|
||||
return changed ? length : 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
ohci_hub_descriptor (
|
||||
struct ohci_hcd *ohci,
|
||||
struct usb_hub_descriptor *desc
|
||||
) {
|
||||
u32 rh = roothub_a (ohci);
|
||||
int ports = rh & RH_A_NDP;
|
||||
u16 temp;
|
||||
|
||||
desc->bDescriptorType = 0x29;
|
||||
desc->bPwrOn2PwrGood = (rh & RH_A_POTPGT) >> 24;
|
||||
desc->bHubContrCurrent = 0;
|
||||
|
||||
desc->bNbrPorts = ports;
|
||||
temp = 1 + (ports / 8);
|
||||
desc->bDescLength = 7 + 2 * temp;
|
||||
|
||||
temp = 0;
|
||||
if (rh & RH_A_NPS) /* no power switching? */
|
||||
temp |= 0x0002;
|
||||
if (rh & RH_A_PSM) /* per-port power switching? */
|
||||
temp |= 0x0001;
|
||||
if (rh & RH_A_NOCP) /* no overcurrent reporting? */
|
||||
temp |= 0x0010;
|
||||
else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */
|
||||
temp |= 0x0008;
|
||||
desc->wHubCharacteristics = (__force __u16)cpu_to_hc16(ohci, temp);
|
||||
|
||||
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
|
||||
rh = roothub_b (ohci);
|
||||
desc->bitmap [0] = rh & RH_B_DR;
|
||||
if (ports > 7) {
|
||||
desc->bitmap [1] = (rh & RH_B_DR) >> 8;
|
||||
desc->bitmap [2] = desc->bitmap [3] = 0xff;
|
||||
} else
|
||||
desc->bitmap [1] = 0xff;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_USB_OTG
|
||||
|
||||
static int ohci_start_port_reset (struct usb_hcd *hcd, unsigned port)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
u32 status;
|
||||
|
||||
if (!port)
|
||||
return -EINVAL;
|
||||
port--;
|
||||
|
||||
/* start port reset before HNP protocol times out */
|
||||
status = ohci_readl(ohci, &ohci->regs->roothub.portstatus [port]);
|
||||
if (!(status & RH_PS_CCS))
|
||||
return -ENODEV;
|
||||
|
||||
/* khubd will finish the reset later */
|
||||
ohci_writel(ohci, RH_PS_PRS, &ohci->regs->roothub.portstatus [port]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void start_hnp(struct ohci_hcd *ohci);
|
||||
|
||||
#else
|
||||
|
||||
#define ohci_start_port_reset NULL
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* See usb 7.1.7.5: root hubs must issue at least 50 msec reset signaling,
|
||||
* not necessarily continuous ... to guard against resume signaling.
|
||||
* The short timeout is safe for non-root hubs, and is backward-compatible
|
||||
* with earlier Linux hosts.
|
||||
*/
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
#define PORT_RESET_MSEC 50
|
||||
#else
|
||||
#define PORT_RESET_MSEC 10
|
||||
#endif
|
||||
|
||||
/* this timer value might be vendor-specific ... */
|
||||
#define PORT_RESET_HW_MSEC 10
|
||||
|
||||
/* wrap-aware logic morphed from <linux/jiffies.h> */
|
||||
#define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0)
|
||||
|
||||
/* called from some task, normally khubd */
|
||||
static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port)
|
||||
{
|
||||
__hc32 __iomem *portstat = &ohci->regs->roothub.portstatus [port];
|
||||
u32 temp;
|
||||
u16 now = ohci_readl(ohci, &ohci->regs->fmnumber);
|
||||
u16 reset_done = now + PORT_RESET_MSEC;
|
||||
|
||||
/* build a "continuous enough" reset signal, with up to
|
||||
* 3msec gap between pulses. scheduler HZ==100 must work;
|
||||
* this might need to be deadline-scheduled.
|
||||
*/
|
||||
do {
|
||||
/* spin until any current reset finishes */
|
||||
for (;;) {
|
||||
temp = ohci_readl (ohci, portstat);
|
||||
if (!(temp & RH_PS_PRS))
|
||||
break;
|
||||
udelay (500);
|
||||
}
|
||||
|
||||
if (!(temp & RH_PS_CCS))
|
||||
break;
|
||||
if (temp & RH_PS_PRSC)
|
||||
ohci_writel (ohci, RH_PS_PRSC, portstat);
|
||||
|
||||
/* start the next reset, sleep till it's probably done */
|
||||
ohci_writel (ohci, RH_PS_PRS, portstat);
|
||||
msleep(PORT_RESET_HW_MSEC);
|
||||
now = ohci_readl(ohci, &ohci->regs->fmnumber);
|
||||
} while (tick_before(now, reset_done));
|
||||
/* caller synchronizes using PRSC */
|
||||
}
|
||||
|
||||
static int ohci_hub_control (
|
||||
struct usb_hcd *hcd,
|
||||
u16 typeReq,
|
||||
u16 wValue,
|
||||
u16 wIndex,
|
||||
char *buf,
|
||||
u16 wLength
|
||||
) {
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ports = hcd_to_bus (hcd)->root_hub->maxchild;
|
||||
u32 temp;
|
||||
int retval = 0;
|
||||
|
||||
switch (typeReq) {
|
||||
case ClearHubFeature:
|
||||
switch (wValue) {
|
||||
case C_HUB_OVER_CURRENT:
|
||||
ohci_writel (ohci, RH_HS_OCIC,
|
||||
&ohci->regs->roothub.status);
|
||||
case C_HUB_LOCAL_POWER:
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
if (!wIndex || wIndex > ports)
|
||||
goto error;
|
||||
wIndex--;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
temp = RH_PS_CCS;
|
||||
break;
|
||||
case USB_PORT_FEAT_C_ENABLE:
|
||||
temp = RH_PS_PESC;
|
||||
break;
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
temp = RH_PS_POCI;
|
||||
if ((ohci->hc_control & OHCI_CTRL_HCFS)
|
||||
!= OHCI_USB_OPER)
|
||||
schedule_work (&ohci->rh_resume);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_SUSPEND:
|
||||
temp = RH_PS_PSSC;
|
||||
break;
|
||||
case USB_PORT_FEAT_POWER:
|
||||
temp = RH_PS_LSDA;
|
||||
break;
|
||||
case USB_PORT_FEAT_C_CONNECTION:
|
||||
temp = RH_PS_CSC;
|
||||
break;
|
||||
case USB_PORT_FEAT_C_OVER_CURRENT:
|
||||
temp = RH_PS_OCIC;
|
||||
break;
|
||||
case USB_PORT_FEAT_C_RESET:
|
||||
temp = RH_PS_PRSC;
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
ohci_writel (ohci, temp,
|
||||
&ohci->regs->roothub.portstatus [wIndex]);
|
||||
// ohci_readl (ohci, &ohci->regs->roothub.portstatus [wIndex]);
|
||||
break;
|
||||
case GetHubDescriptor:
|
||||
ohci_hub_descriptor (ohci, (struct usb_hub_descriptor *) buf);
|
||||
break;
|
||||
case GetHubStatus:
|
||||
temp = roothub_status (ohci) & ~(RH_HS_CRWE | RH_HS_DRWE);
|
||||
*(__le32 *) buf = cpu_to_le32 (temp);
|
||||
break;
|
||||
case GetPortStatus:
|
||||
if (!wIndex || wIndex > ports)
|
||||
goto error;
|
||||
wIndex--;
|
||||
temp = roothub_portstatus (ohci, wIndex);
|
||||
*(__le32 *) buf = cpu_to_le32 (temp);
|
||||
|
||||
#ifndef OHCI_VERBOSE_DEBUG
|
||||
if (*(u16*)(buf+2)) /* only if wPortChange is interesting */
|
||||
#endif
|
||||
dbg_port (ohci, "GetStatus", wIndex, temp);
|
||||
break;
|
||||
case SetHubFeature:
|
||||
switch (wValue) {
|
||||
case C_HUB_OVER_CURRENT:
|
||||
// FIXME: this can be cleared, yes?
|
||||
case C_HUB_LOCAL_POWER:
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case SetPortFeature:
|
||||
if (!wIndex || wIndex > ports)
|
||||
goto error;
|
||||
wIndex--;
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
#ifdef CONFIG_USB_OTG
|
||||
if (hcd->self.otg_port == (wIndex + 1)
|
||||
&& hcd->self.b_hnp_enable)
|
||||
start_hnp(ohci);
|
||||
else
|
||||
#endif
|
||||
ohci_writel (ohci, RH_PS_PSS,
|
||||
&ohci->regs->roothub.portstatus [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_POWER:
|
||||
ohci_writel (ohci, RH_PS_PPS,
|
||||
&ohci->regs->roothub.portstatus [wIndex]);
|
||||
break;
|
||||
case USB_PORT_FEAT_RESET:
|
||||
root_port_reset (ohci, wIndex);
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error:
|
||||
/* "protocol stall" on error */
|
||||
retval = -EPIPE;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
266
drivers/usb/host/ohci-lh7a404.c
一般檔案
266
drivers/usb/host/ohci-lh7a404.c
一般檔案
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
* (C) Copyright 2002 Hewlett-Packard Company
|
||||
*
|
||||
* Bus Glue for Sharp LH7A404
|
||||
*
|
||||
* Written by Christopher Hoover <ch@hpl.hp.com>
|
||||
* Based on fragments of previous driver by Rusell King et al.
|
||||
*
|
||||
* Modified for LH7A404 from ohci-sa1111.c
|
||||
* by Durgesh Pattamatta <pattamattad@sharpsec.com>
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <asm/arch/hardware.h>
|
||||
|
||||
|
||||
extern int usb_disabled(void);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void lh7a404_start_hc(struct platform_device *dev)
|
||||
{
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": starting LH7A404 OHCI USB Controller\n");
|
||||
|
||||
/*
|
||||
* Now, carefully enable the USB clock, and take
|
||||
* the USB host controller out of reset.
|
||||
*/
|
||||
CSC_PWRCNT |= CSC_PWRCNT_USBH_EN; /* Enable clock */
|
||||
udelay(1000);
|
||||
USBH_CMDSTATUS = OHCI_HCR;
|
||||
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": Clock to USB host has been enabled \n");
|
||||
}
|
||||
|
||||
static void lh7a404_stop_hc(struct platform_device *dev)
|
||||
{
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": stopping LH7A404 OHCI USB Controller\n");
|
||||
|
||||
CSC_PWRCNT &= ~CSC_PWRCNT_USBH_EN; /* Disable clock */
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
|
||||
/**
|
||||
* usb_hcd_lh7a404_probe - initialize LH7A404-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
*/
|
||||
int usb_hcd_lh7a404_probe (const struct hc_driver *driver,
|
||||
struct platform_device *dev)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
if (dev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug("resource[1] is not IORESOURCE_IRQ");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(driver, &dev->dev, "lh7a404");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = dev->resource[0].start;
|
||||
hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed");
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
lh7a404_start_hc(dev);
|
||||
ohci_hcd_init(hcd_to_ohci(hcd));
|
||||
|
||||
retval = usb_add_hcd(hcd, dev->resource[1].start, SA_INTERRUPT);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
lh7a404_stop_hc(dev);
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_lh7a404_remove - shutdown processing for LH7A404-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_lh7a404_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
void usb_hcd_lh7a404_remove (struct usb_hcd *hcd, struct platform_device *dev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
lh7a404_stop_hc(dev);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __devinit
|
||||
ohci_lh7a404_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
ohci_dbg (ohci, "ohci_lh7a404_start, ohci:%p", ohci);
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
err ("can't start %s", hcd->self.bus_name);
|
||||
ohci_stop (hcd);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_lh7a404_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "LH7A404 OHCI",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.start = ohci_lh7a404_start,
|
||||
#ifdef CONFIG_PM
|
||||
/* suspend: ohci_lh7a404_suspend, -- tbd */
|
||||
/* resume: ohci_lh7a404_resume, -- tbd */
|
||||
#endif /*CONFIG_PM*/
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_hcd_lh7a404_drv_probe(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
int ret;
|
||||
|
||||
pr_debug ("In ohci_hcd_lh7a404_drv_probe");
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_hcd_lh7a404_probe(&ohci_lh7a404_hc_driver, pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ohci_hcd_lh7a404_drv_remove(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
usb_hcd_lh7a404_remove(hcd, pdev);
|
||||
return 0;
|
||||
}
|
||||
/*TBD*/
|
||||
/*static int ohci_hcd_lh7a404_drv_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int ohci_hcd_lh7a404_drv_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
static struct device_driver ohci_hcd_lh7a404_driver = {
|
||||
.name = "lh7a404-ohci",
|
||||
.bus = &platform_bus_type,
|
||||
.probe = ohci_hcd_lh7a404_drv_probe,
|
||||
.remove = ohci_hcd_lh7a404_drv_remove,
|
||||
/*.suspend = ohci_hcd_lh7a404_drv_suspend, */
|
||||
/*.resume = ohci_hcd_lh7a404_drv_resume, */
|
||||
};
|
||||
|
||||
static int __init ohci_hcd_lh7a404_init (void)
|
||||
{
|
||||
pr_debug (DRIVER_INFO " (LH7A404)");
|
||||
pr_debug ("block sizes: ed %d td %d\n",
|
||||
sizeof (struct ed), sizeof (struct td));
|
||||
|
||||
return driver_register(&ohci_hcd_lh7a404_driver);
|
||||
}
|
||||
|
||||
static void __exit ohci_hcd_lh7a404_cleanup (void)
|
||||
{
|
||||
driver_unregister(&ohci_hcd_lh7a404_driver);
|
||||
}
|
||||
|
||||
module_init (ohci_hcd_lh7a404_init);
|
||||
module_exit (ohci_hcd_lh7a404_cleanup);
|
139
drivers/usb/host/ohci-mem.c
一般檔案
139
drivers/usb/host/ohci-mem.c
一般檔案
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* There's basically three types of memory:
|
||||
* - data used only by the HCD ... kmalloc is fine
|
||||
* - async and periodic schedules, shared by HC and HCD ... these
|
||||
* need to use dma_pool or dma_alloc_coherent
|
||||
* - driver buffers, read/written by HC ... the hcd glue or the
|
||||
* device driver provides us with dma addresses
|
||||
*
|
||||
* There's also PCI "register" data, which is memory mapped.
|
||||
* No memory seen by this driver is pagable.
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void ohci_hcd_init (struct ohci_hcd *ohci)
|
||||
{
|
||||
ohci->next_statechange = jiffies;
|
||||
spin_lock_init (&ohci->lock);
|
||||
INIT_LIST_HEAD (&ohci->pending);
|
||||
INIT_WORK (&ohci->rh_resume, ohci_rh_resume, ohci_to_hcd(ohci));
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_mem_init (struct ohci_hcd *ohci)
|
||||
{
|
||||
ohci->td_cache = dma_pool_create ("ohci_td",
|
||||
ohci_to_hcd(ohci)->self.controller,
|
||||
sizeof (struct td),
|
||||
32 /* byte alignment */,
|
||||
0 /* no page-crossing issues */);
|
||||
if (!ohci->td_cache)
|
||||
return -ENOMEM;
|
||||
ohci->ed_cache = dma_pool_create ("ohci_ed",
|
||||
ohci_to_hcd(ohci)->self.controller,
|
||||
sizeof (struct ed),
|
||||
16 /* byte alignment */,
|
||||
0 /* no page-crossing issues */);
|
||||
if (!ohci->ed_cache) {
|
||||
dma_pool_destroy (ohci->td_cache);
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ohci_mem_cleanup (struct ohci_hcd *ohci)
|
||||
{
|
||||
if (ohci->td_cache) {
|
||||
dma_pool_destroy (ohci->td_cache);
|
||||
ohci->td_cache = NULL;
|
||||
}
|
||||
if (ohci->ed_cache) {
|
||||
dma_pool_destroy (ohci->ed_cache);
|
||||
ohci->ed_cache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ohci "done list" processing needs this mapping */
|
||||
static inline struct td *
|
||||
dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma)
|
||||
{
|
||||
struct td *td;
|
||||
|
||||
td_dma &= TD_MASK;
|
||||
td = hc->td_hash [TD_HASH_FUNC(td_dma)];
|
||||
while (td && td->td_dma != td_dma)
|
||||
td = td->td_hash;
|
||||
return td;
|
||||
}
|
||||
|
||||
/* TDs ... */
|
||||
static struct td *
|
||||
td_alloc (struct ohci_hcd *hc, int mem_flags)
|
||||
{
|
||||
dma_addr_t dma;
|
||||
struct td *td;
|
||||
|
||||
td = dma_pool_alloc (hc->td_cache, mem_flags, &dma);
|
||||
if (td) {
|
||||
/* in case hc fetches it, make it look dead */
|
||||
memset (td, 0, sizeof *td);
|
||||
td->hwNextTD = cpu_to_hc32 (hc, dma);
|
||||
td->td_dma = dma;
|
||||
/* hashed in td_fill */
|
||||
}
|
||||
return td;
|
||||
}
|
||||
|
||||
static void
|
||||
td_free (struct ohci_hcd *hc, struct td *td)
|
||||
{
|
||||
struct td **prev = &hc->td_hash [TD_HASH_FUNC (td->td_dma)];
|
||||
|
||||
while (*prev && *prev != td)
|
||||
prev = &(*prev)->td_hash;
|
||||
if (*prev)
|
||||
*prev = td->td_hash;
|
||||
else if ((td->hwINFO & cpu_to_hc32(hc, TD_DONE)) != 0)
|
||||
ohci_dbg (hc, "no hash for td %p\n", td);
|
||||
dma_pool_free (hc->td_cache, td, td->td_dma);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* EDs ... */
|
||||
static struct ed *
|
||||
ed_alloc (struct ohci_hcd *hc, int mem_flags)
|
||||
{
|
||||
dma_addr_t dma;
|
||||
struct ed *ed;
|
||||
|
||||
ed = dma_pool_alloc (hc->ed_cache, mem_flags, &dma);
|
||||
if (ed) {
|
||||
memset (ed, 0, sizeof (*ed));
|
||||
INIT_LIST_HEAD (&ed->td_list);
|
||||
ed->dma = dma;
|
||||
}
|
||||
return ed;
|
||||
}
|
||||
|
||||
static void
|
||||
ed_free (struct ohci_hcd *hc, struct ed *ed)
|
||||
{
|
||||
dma_pool_free (hc->ed_cache, ed, ed->dma);
|
||||
}
|
||||
|
560
drivers/usb/host/ohci-omap.c
一般檔案
560
drivers/usb/host/ohci-omap.c
一般檔案
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2005 David Brownell
|
||||
* (C) Copyright 2002 Hewlett-Packard Company
|
||||
*
|
||||
* OMAP Bus Glue
|
||||
*
|
||||
* Modified for OMAP by Tony Lindgren <tony@atomide.com>
|
||||
* Based on the 2.4 OMAP OHCI driver originally done by MontaVista Software Inc.
|
||||
* and on ohci-sa1111.c by Christopher Hoover <ch@hpl.hp.com>
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include <asm/arch/hardware.h>
|
||||
#include <asm/arch/mux.h>
|
||||
#include <asm/arch/irqs.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
#include <asm/arch/fpga.h>
|
||||
#include <asm/arch/usb.h>
|
||||
#include <asm/hardware/clock.h>
|
||||
|
||||
|
||||
/* OMAP-1510 OHCI has its own MMU for DMA */
|
||||
#define OMAP1510_LB_MEMSIZE 32 /* Should be same as SDRAM size */
|
||||
#define OMAP1510_LB_CLOCK_DIV 0xfffec10c
|
||||
#define OMAP1510_LB_MMU_CTL 0xfffec208
|
||||
#define OMAP1510_LB_MMU_LCK 0xfffec224
|
||||
#define OMAP1510_LB_MMU_LD_TLB 0xfffec228
|
||||
#define OMAP1510_LB_MMU_CAM_H 0xfffec22c
|
||||
#define OMAP1510_LB_MMU_CAM_L 0xfffec230
|
||||
#define OMAP1510_LB_MMU_RAM_H 0xfffec234
|
||||
#define OMAP1510_LB_MMU_RAM_L 0xfffec238
|
||||
|
||||
|
||||
#ifndef CONFIG_ARCH_OMAP
|
||||
#error "This file is OMAP bus glue. CONFIG_OMAP must be defined."
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TPS65010
|
||||
#include <asm/arch/tps65010.h>
|
||||
#else
|
||||
|
||||
#define LOW 0
|
||||
#define HIGH 1
|
||||
|
||||
#define GPIO1 1
|
||||
|
||||
static inline int tps65010_set_gpio_out_value(unsigned gpio, unsigned value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
extern int usb_disabled(void);
|
||||
extern int ocpi_enable(void);
|
||||
|
||||
static struct clk *usb_host_ck;
|
||||
|
||||
static void omap_ohci_clock_power(int on)
|
||||
{
|
||||
if (on) {
|
||||
clk_enable(usb_host_ck);
|
||||
/* guesstimate for T5 == 1x 32K clock + APLL lock time */
|
||||
udelay(100);
|
||||
} else {
|
||||
clk_disable(usb_host_ck);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Board specific gang-switched transceiver power on/off.
|
||||
* NOTE: OSK supplies power from DC, not battery.
|
||||
*/
|
||||
static int omap_ohci_transceiver_power(int on)
|
||||
{
|
||||
if (on) {
|
||||
if (machine_is_omap_innovator() && cpu_is_omap1510())
|
||||
fpga_write(fpga_read(INNOVATOR_FPGA_CAM_USB_CONTROL)
|
||||
| ((1 << 5/*usb1*/) | (1 << 3/*usb2*/)),
|
||||
INNOVATOR_FPGA_CAM_USB_CONTROL);
|
||||
else if (machine_is_omap_osk())
|
||||
tps65010_set_gpio_out_value(GPIO1, LOW);
|
||||
} else {
|
||||
if (machine_is_omap_innovator() && cpu_is_omap1510())
|
||||
fpga_write(fpga_read(INNOVATOR_FPGA_CAM_USB_CONTROL)
|
||||
& ~((1 << 5/*usb1*/) | (1 << 3/*usb2*/)),
|
||||
INNOVATOR_FPGA_CAM_USB_CONTROL);
|
||||
else if (machine_is_omap_osk())
|
||||
tps65010_set_gpio_out_value(GPIO1, HIGH);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* OMAP-1510 specific Local Bus clock on/off
|
||||
*/
|
||||
static int omap_1510_local_bus_power(int on)
|
||||
{
|
||||
if (on) {
|
||||
omap_writel((1 << 1) | (1 << 0), OMAP1510_LB_MMU_CTL);
|
||||
udelay(200);
|
||||
} else {
|
||||
omap_writel(0, OMAP1510_LB_MMU_CTL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* OMAP-1510 specific Local Bus initialization
|
||||
* NOTE: This assumes 32MB memory size in OMAP1510LB_MEMSIZE.
|
||||
* See also arch/mach-omap/memory.h for __virt_to_dma() and
|
||||
* __dma_to_virt() which need to match with the physical
|
||||
* Local Bus address below.
|
||||
*/
|
||||
static int omap_1510_local_bus_init(void)
|
||||
{
|
||||
unsigned int tlb;
|
||||
unsigned long lbaddr, physaddr;
|
||||
|
||||
omap_writel((omap_readl(OMAP1510_LB_CLOCK_DIV) & 0xfffffff8) | 0x4,
|
||||
OMAP1510_LB_CLOCK_DIV);
|
||||
|
||||
/* Configure the Local Bus MMU table */
|
||||
for (tlb = 0; tlb < OMAP1510_LB_MEMSIZE; tlb++) {
|
||||
lbaddr = tlb * 0x00100000 + OMAP1510_LB_OFFSET;
|
||||
physaddr = tlb * 0x00100000 + PHYS_OFFSET;
|
||||
omap_writel((lbaddr & 0x0fffffff) >> 22, OMAP1510_LB_MMU_CAM_H);
|
||||
omap_writel(((lbaddr & 0x003ffc00) >> 6) | 0xc,
|
||||
OMAP1510_LB_MMU_CAM_L);
|
||||
omap_writel(physaddr >> 16, OMAP1510_LB_MMU_RAM_H);
|
||||
omap_writel((physaddr & 0x0000fc00) | 0x300, OMAP1510_LB_MMU_RAM_L);
|
||||
omap_writel(tlb << 4, OMAP1510_LB_MMU_LCK);
|
||||
omap_writel(0x1, OMAP1510_LB_MMU_LD_TLB);
|
||||
}
|
||||
|
||||
/* Enable the walking table */
|
||||
omap_writel(omap_readl(OMAP1510_LB_MMU_CTL) | (1 << 3), OMAP1510_LB_MMU_CTL);
|
||||
udelay(200);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USB_OTG
|
||||
|
||||
static void start_hnp(struct ohci_hcd *ohci)
|
||||
{
|
||||
const unsigned port = ohci_to_hcd(ohci)->self.otg_port - 1;
|
||||
unsigned long flags;
|
||||
|
||||
otg_start_hnp(ohci->transceiver);
|
||||
|
||||
local_irq_save(flags);
|
||||
ohci->transceiver->state = OTG_STATE_A_SUSPEND;
|
||||
writel (RH_PS_PSS, &ohci->regs->roothub.portstatus [port]);
|
||||
OTG_CTRL_REG &= ~OTG_A_BUSREQ;
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int omap_start_hc(struct ohci_hcd *ohci, struct platform_device *pdev)
|
||||
{
|
||||
struct omap_usb_config *config = pdev->dev.platform_data;
|
||||
int need_transceiver = (config->otg != 0);
|
||||
int ret;
|
||||
|
||||
dev_dbg(&pdev->dev, "starting USB Controller\n");
|
||||
|
||||
if (config->otg) {
|
||||
ohci_to_hcd(ohci)->self.otg_port = config->otg;
|
||||
/* default/minimum OTG power budget: 8 mA */
|
||||
ohci->power_budget = 8;
|
||||
}
|
||||
|
||||
/* boards can use OTG transceivers in non-OTG modes */
|
||||
need_transceiver = need_transceiver
|
||||
|| machine_is_omap_h2() || machine_is_omap_h3();
|
||||
|
||||
if (cpu_is_omap16xx())
|
||||
ocpi_enable();
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP_OTG
|
||||
if (need_transceiver) {
|
||||
ohci->transceiver = otg_get_transceiver();
|
||||
if (ohci->transceiver) {
|
||||
int status = otg_set_host(ohci->transceiver,
|
||||
&ohci_to_hcd(ohci)->self);
|
||||
dev_dbg(&pdev->dev, "init %s transceiver, status %d\n",
|
||||
ohci->transceiver->label, status);
|
||||
if (status) {
|
||||
if (ohci->transceiver)
|
||||
put_device(ohci->transceiver->dev);
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
dev_err(&pdev->dev, "can't find transceiver\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
omap_ohci_clock_power(1);
|
||||
|
||||
if (cpu_is_omap1510()) {
|
||||
omap_1510_local_bus_power(1);
|
||||
omap_1510_local_bus_init();
|
||||
}
|
||||
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
/* board-specific power switching and overcurrent support */
|
||||
if (machine_is_omap_osk() || machine_is_omap_innovator()) {
|
||||
u32 rh = roothub_a (ohci);
|
||||
|
||||
/* power switching (ganged by default) */
|
||||
rh &= ~RH_A_NPS;
|
||||
|
||||
/* TPS2045 switch for internal transceiver (port 1) */
|
||||
if (machine_is_omap_osk()) {
|
||||
ohci->power_budget = 250;
|
||||
|
||||
rh &= ~RH_A_NOCP;
|
||||
|
||||
/* gpio9 for overcurrent detction */
|
||||
omap_cfg_reg(W8_1610_GPIO9);
|
||||
omap_request_gpio(9);
|
||||
omap_set_gpio_direction(9, 1 /* IN */);
|
||||
|
||||
/* for paranoia's sake: disable USB.PUEN */
|
||||
omap_cfg_reg(W4_USB_HIGHZ);
|
||||
}
|
||||
ohci_writel(ohci, rh, &ohci->regs->roothub.a);
|
||||
distrust_firmware = 0;
|
||||
}
|
||||
|
||||
/* FIXME khubd hub requests should manage power switching */
|
||||
omap_ohci_transceiver_power(1);
|
||||
|
||||
/* board init will have already handled HMC and mux setup.
|
||||
* any external transceiver should already be initialized
|
||||
* too, so all configured ports use the right signaling now.
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void omap_stop_hc(struct platform_device *pdev)
|
||||
{
|
||||
dev_dbg(&pdev->dev, "stopping USB Controller\n");
|
||||
omap_ohci_clock_power(0);
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
void usb_hcd_omap_remove (struct usb_hcd *, struct platform_device *);
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
|
||||
/**
|
||||
* usb_hcd_omap_probe - initialize OMAP-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*/
|
||||
int usb_hcd_omap_probe (const struct hc_driver *driver,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd = 0;
|
||||
struct ohci_hcd *ohci;
|
||||
|
||||
if (pdev->num_resources != 2) {
|
||||
printk(KERN_ERR "hcd probe: invalid num_resources: %i\n",
|
||||
pdev->num_resources);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (pdev->resource[0].flags != IORESOURCE_MEM
|
||||
|| pdev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
printk(KERN_ERR "hcd probe: invalid resource type\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
usb_host_ck = clk_get(0, "usb_hhc_ck");
|
||||
if (IS_ERR(usb_host_ck))
|
||||
return PTR_ERR(usb_host_ck);
|
||||
|
||||
hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id);
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto err0;
|
||||
}
|
||||
hcd->rsrc_start = pdev->resource[0].start;
|
||||
hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
dev_dbg(&pdev->dev, "request_mem_region failed\n");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = (void __iomem *) (int) IO_ADDRESS(hcd->rsrc_start);
|
||||
|
||||
ohci = hcd_to_ohci(hcd);
|
||||
ohci_hcd_init(ohci);
|
||||
|
||||
retval = omap_start_hc(ohci, pdev);
|
||||
if (retval < 0)
|
||||
goto err2;
|
||||
|
||||
retval = usb_add_hcd(hcd, platform_get_irq(pdev, 0), SA_INTERRUPT);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
omap_stop_hc(pdev);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
err0:
|
||||
clk_put(usb_host_ck);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_omap_remove - shutdown processing for OMAP-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_omap_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
void usb_hcd_omap_remove (struct usb_hcd *hcd, struct platform_device *pdev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
if (machine_is_omap_osk())
|
||||
omap_free_gpio(9);
|
||||
omap_stop_hc(pdev);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
clk_put(usb_host_ck);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __devinit
|
||||
ohci_omap_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct omap_usb_config *config;
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
config = hcd->self.controller->platform_data;
|
||||
if (config->otg || config->rwc)
|
||||
writel(OHCI_CTRL_RWC, &ohci->regs->control);
|
||||
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
dev_err(hcd->self.controller, "can't start\n");
|
||||
ohci_stop (hcd);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_omap_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "OMAP OHCI",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.start = ohci_omap_start,
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
.hub_suspend = ohci_hub_suspend,
|
||||
.hub_resume = ohci_hub_resume,
|
||||
#endif
|
||||
.start_port_reset = ohci_start_port_reset,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_hcd_omap_drv_probe(struct device *dev)
|
||||
{
|
||||
return usb_hcd_omap_probe(&ohci_omap_hc_driver,
|
||||
to_platform_device(dev));
|
||||
}
|
||||
|
||||
static int ohci_hcd_omap_drv_remove(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
|
||||
usb_hcd_omap_remove(hcd, pdev);
|
||||
if (ohci->transceiver) {
|
||||
(void) otg_set_host(ohci->transceiver, 0);
|
||||
put_device(ohci->transceiver->dev);
|
||||
}
|
||||
dev_set_drvdata(dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* states match PCI usage, always suspending the root hub except that
|
||||
* 4 ~= D3cold (ACPI D3) with clock off (resume sees reset).
|
||||
*/
|
||||
|
||||
static int ohci_omap_suspend(struct device *dev, u32 state, u32 level)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(dev_get_drvdata(dev));
|
||||
int status = -EINVAL;
|
||||
|
||||
if (level != SUSPEND_POWER_DOWN)
|
||||
return 0;
|
||||
if (state <= dev->power.power_state)
|
||||
return 0;
|
||||
|
||||
dev_dbg(dev, "suspend to %d\n", state);
|
||||
down(&ohci_to_hcd(ohci)->self.root_hub->serialize);
|
||||
status = ohci_hub_suspend(ohci_to_hcd(ohci));
|
||||
if (status == 0) {
|
||||
if (state >= 4) {
|
||||
omap_ohci_clock_power(0);
|
||||
ohci_to_hcd(ohci)->self.root_hub->state =
|
||||
USB_STATE_SUSPENDED;
|
||||
state = 4;
|
||||
}
|
||||
ohci_to_hcd(ohci)->state = HC_STATE_SUSPENDED;
|
||||
dev->power.power_state = state;
|
||||
}
|
||||
up(&ohci_to_hcd(ohci)->self.root_hub->serialize);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int ohci_omap_resume(struct device *dev, u32 level)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(dev_get_drvdata(dev));
|
||||
int status = 0;
|
||||
|
||||
if (level != RESUME_POWER_ON)
|
||||
return 0;
|
||||
|
||||
switch (dev->power.power_state) {
|
||||
case 0:
|
||||
break;
|
||||
case 4:
|
||||
if (time_before(jiffies, ohci->next_statechange))
|
||||
msleep(5);
|
||||
ohci->next_statechange = jiffies;
|
||||
omap_ohci_clock_power(1);
|
||||
/* FALLTHROUGH */
|
||||
default:
|
||||
dev_dbg(dev, "resume from %d\n", dev->power.power_state);
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
/* get extra cleanup even if remote wakeup isn't in use */
|
||||
status = usb_resume_device(ohci_to_hcd(ohci)->self.root_hub);
|
||||
#else
|
||||
down(&ohci_to_hcd(ohci)->self.root_hub->serialize);
|
||||
status = ohci_hub_resume(ohci_to_hcd(ohci));
|
||||
up(&ohci_to_hcd(ohci)->self.root_hub->serialize);
|
||||
#endif
|
||||
if (status == 0)
|
||||
dev->power.power_state = 0;
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Driver definition to register with the OMAP bus
|
||||
*/
|
||||
static struct device_driver ohci_hcd_omap_driver = {
|
||||
.name = "ohci",
|
||||
.bus = &platform_bus_type,
|
||||
.probe = ohci_hcd_omap_drv_probe,
|
||||
.remove = ohci_hcd_omap_drv_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = ohci_omap_suspend,
|
||||
.resume = ohci_omap_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int __init ohci_hcd_omap_init (void)
|
||||
{
|
||||
printk (KERN_DEBUG "%s: " DRIVER_INFO " (OMAP)\n", hcd_name);
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
pr_debug("%s: block sizes: ed %Zd td %Zd\n", hcd_name,
|
||||
sizeof (struct ed), sizeof (struct td));
|
||||
|
||||
return driver_register(&ohci_hcd_omap_driver);
|
||||
}
|
||||
|
||||
static void __exit ohci_hcd_omap_cleanup (void)
|
||||
{
|
||||
driver_unregister(&ohci_hcd_omap_driver);
|
||||
}
|
||||
|
||||
module_init (ohci_hcd_omap_init);
|
||||
module_exit (ohci_hcd_omap_cleanup);
|
264
drivers/usb/host/ohci-pci.c
一般檔案
264
drivers/usb/host/ohci-pci.c
一般檔案
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*
|
||||
* [ Initialisation is based on Linus' ]
|
||||
* [ uhci code and gregs ohci fragments ]
|
||||
* [ (C) Copyright 1999 Linus Torvalds ]
|
||||
* [ (C) Copyright 1999 Gregory P. Smith]
|
||||
*
|
||||
* PCI Bus Glue
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PMAC_PBOOK
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/pmac_feature.h>
|
||||
#include <asm/pci-bridge.h>
|
||||
#include <asm/prom.h>
|
||||
#ifndef CONFIG_PM
|
||||
# define CONFIG_PM
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_PCI
|
||||
#error "This file is PCI bus glue. CONFIG_PCI must be defined."
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int
|
||||
ohci_pci_reset (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
|
||||
ohci_hcd_init (ohci);
|
||||
return ohci_init (ohci);
|
||||
}
|
||||
|
||||
static int __devinit
|
||||
ohci_pci_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
if(hcd->self.controller && hcd->self.controller->bus == &pci_bus_type) {
|
||||
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
|
||||
|
||||
/* AMD 756, for most chips (early revs), corrupts register
|
||||
* values on read ... so enable the vendor workaround.
|
||||
*/
|
||||
if (pdev->vendor == PCI_VENDOR_ID_AMD
|
||||
&& pdev->device == 0x740c) {
|
||||
ohci->flags = OHCI_QUIRK_AMD756;
|
||||
ohci_info (ohci, "AMD756 erratum 4 workaround\n");
|
||||
// also somewhat erratum 10 (suspend/resume issues)
|
||||
}
|
||||
|
||||
/* FIXME for some of the early AMD 760 southbridges, OHCI
|
||||
* won't work at all. blacklist them.
|
||||
*/
|
||||
|
||||
/* Apple's OHCI driver has a lot of bizarre workarounds
|
||||
* for this chip. Evidently control and bulk lists
|
||||
* can get confused. (B&W G3 models, and ...)
|
||||
*/
|
||||
else if (pdev->vendor == PCI_VENDOR_ID_OPTI
|
||||
&& pdev->device == 0xc861) {
|
||||
ohci_info (ohci,
|
||||
"WARNING: OPTi workarounds unavailable\n");
|
||||
}
|
||||
|
||||
/* Check for NSC87560. We have to look at the bridge (fn1) to
|
||||
* identify the USB (fn2). This quirk might apply to more or
|
||||
* even all NSC stuff.
|
||||
*/
|
||||
else if (pdev->vendor == PCI_VENDOR_ID_NS) {
|
||||
struct pci_dev *b;
|
||||
|
||||
b = pci_find_slot (pdev->bus->number,
|
||||
PCI_DEVFN (PCI_SLOT (pdev->devfn), 1));
|
||||
if (b && b->device == PCI_DEVICE_ID_NS_87560_LIO
|
||||
&& b->vendor == PCI_VENDOR_ID_NS) {
|
||||
ohci->flags |= OHCI_QUIRK_SUPERIO;
|
||||
ohci_info (ohci, "Using NSC SuperIO setup\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: there may have already been a first reset, to
|
||||
* keep bios/smm irqs from making trouble
|
||||
*/
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
ohci_err (ohci, "can't start\n");
|
||||
ohci_stop (hcd);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int ohci_pci_suspend (struct usb_hcd *hcd, u32 state)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
|
||||
/* suspend root hub, hoping it keeps power during suspend */
|
||||
if (time_before (jiffies, ohci->next_statechange))
|
||||
msleep (100);
|
||||
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
(void) usb_suspend_device (hcd->self.root_hub, state);
|
||||
#else
|
||||
usb_lock_device (hcd->self.root_hub);
|
||||
(void) ohci_hub_suspend (hcd);
|
||||
usb_unlock_device (hcd->self.root_hub);
|
||||
#endif
|
||||
|
||||
/* let things settle down a bit */
|
||||
msleep (100);
|
||||
|
||||
#ifdef CONFIG_PMAC_PBOOK
|
||||
if (_machine == _MACH_Pmac) {
|
||||
struct device_node *of_node;
|
||||
|
||||
/* Disable USB PAD & cell clock */
|
||||
of_node = pci_device_to_OF_node (to_pci_dev(hcd->self.controller));
|
||||
if (of_node)
|
||||
pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0);
|
||||
}
|
||||
#endif /* CONFIG_PMAC_PBOOK */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ohci_pci_resume (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int retval = 0;
|
||||
|
||||
#ifdef CONFIG_PMAC_PBOOK
|
||||
if (_machine == _MACH_Pmac) {
|
||||
struct device_node *of_node;
|
||||
|
||||
/* Re-enable USB PAD & cell clock */
|
||||
of_node = pci_device_to_OF_node (to_pci_dev(hcd->self.controller));
|
||||
if (of_node)
|
||||
pmac_call_feature (PMAC_FTR_USB_ENABLE, of_node, 0, 1);
|
||||
}
|
||||
#endif /* CONFIG_PMAC_PBOOK */
|
||||
|
||||
/* resume root hub */
|
||||
if (time_before (jiffies, ohci->next_statechange))
|
||||
msleep (100);
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
/* get extra cleanup even if remote wakeup isn't in use */
|
||||
retval = usb_resume_device (hcd->self.root_hub);
|
||||
#else
|
||||
usb_lock_device (hcd->self.root_hub);
|
||||
retval = ohci_hub_resume (hcd);
|
||||
usb_unlock_device (hcd->self.root_hub);
|
||||
#endif
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_pci_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "OHCI Host Controller",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_MEMORY | HCD_USB11,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.reset = ohci_pci_reset,
|
||||
.start = ohci_pci_start,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = ohci_pci_suspend,
|
||||
.resume = ohci_pci_resume,
|
||||
#endif
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
.hub_suspend = ohci_hub_suspend,
|
||||
.hub_resume = ohci_hub_resume,
|
||||
#endif
|
||||
.start_port_reset = ohci_start_port_reset,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
static const struct pci_device_id pci_ids [] = { {
|
||||
/* handle any USB OHCI controller */
|
||||
PCI_DEVICE_CLASS((PCI_CLASS_SERIAL_USB << 8) | 0x10, ~0),
|
||||
.driver_data = (unsigned long) &ohci_pci_hc_driver,
|
||||
}, { /* end: all zeroes */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE (pci, pci_ids);
|
||||
|
||||
/* pci driver glue; this is a "new style" PCI driver module */
|
||||
static struct pci_driver ohci_pci_driver = {
|
||||
.name = (char *) hcd_name,
|
||||
.id_table = pci_ids,
|
||||
|
||||
.probe = usb_hcd_pci_probe,
|
||||
.remove = usb_hcd_pci_remove,
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = usb_hcd_pci_suspend,
|
||||
.resume = usb_hcd_pci_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
static int __init ohci_hcd_pci_init (void)
|
||||
{
|
||||
printk (KERN_DEBUG "%s: " DRIVER_INFO " (PCI)\n", hcd_name);
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
pr_debug ("%s: block sizes: ed %Zd td %Zd\n", hcd_name,
|
||||
sizeof (struct ed), sizeof (struct td));
|
||||
return pci_register_driver (&ohci_pci_driver);
|
||||
}
|
||||
module_init (ohci_hcd_pci_init);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void __exit ohci_hcd_pci_cleanup (void)
|
||||
{
|
||||
pci_unregister_driver (&ohci_pci_driver);
|
||||
}
|
||||
module_exit (ohci_hcd_pci_cleanup);
|
234
drivers/usb/host/ohci-ppc-soc.c
一般檔案
234
drivers/usb/host/ohci-ppc-soc.c
一般檔案
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
* (C) Copyright 2002 Hewlett-Packard Company
|
||||
* (C) Copyright 2003-2005 MontaVista Software Inc.
|
||||
*
|
||||
* Bus Glue for PPC On-Chip OHCI driver
|
||||
* Tested on Freescale MPC5200 and IBM STB04xxx
|
||||
*
|
||||
* Modified by Dale Farnsworth <dale@farnsworth.org> from ohci-sa1111.c
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <asm/usb.h>
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
/**
|
||||
* usb_hcd_ppc_soc_probe - initialize On-Chip HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
* Store this function in the HCD's struct pci_driver as probe().
|
||||
*/
|
||||
static int usb_hcd_ppc_soc_probe(const struct hc_driver *driver,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd;
|
||||
struct ohci_hcd *ohci;
|
||||
struct resource *res;
|
||||
int irq;
|
||||
struct usb_hcd_platform_data *pd = pdev->dev.platform_data;
|
||||
|
||||
pr_debug("initializing PPC-SOC USB Controller\n");
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (!res) {
|
||||
pr_debug(__FILE__ ": no irq\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
irq = res->start;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
pr_debug(__FILE__ ": no reg addr\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, "PPC-SOC USB");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = res->start;
|
||||
hcd->rsrc_len = res->end - res->start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug(__FILE__ ": request_mem_region failed\n");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug(__FILE__ ": ioremap failed\n");
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
if (pd->start && (retval = pd->start(pdev)))
|
||||
goto err3;
|
||||
|
||||
ohci = hcd_to_ohci(hcd);
|
||||
ohci->flags |= OHCI_BIG_ENDIAN;
|
||||
ohci_hcd_init(ohci);
|
||||
|
||||
retval = usb_add_hcd(hcd, irq, SA_INTERRUPT);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
pr_debug("Removing PPC-SOC USB Controller\n");
|
||||
if (pd && pd->stop)
|
||||
pd->stop(pdev);
|
||||
err3:
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_ppc_soc_remove - shutdown processing for On-Chip HCDs
|
||||
* @pdev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_ppc_soc_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
static void usb_hcd_ppc_soc_remove(struct usb_hcd *hcd,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct usb_hcd_platform_data *pd = pdev->dev.platform_data;
|
||||
|
||||
usb_remove_hcd(hcd);
|
||||
|
||||
pr_debug("stopping PPC-SOC USB Controller\n");
|
||||
if (pd && pd->stop)
|
||||
pd->stop(pdev);
|
||||
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_hcd_put(hcd);
|
||||
}
|
||||
|
||||
static int __devinit
|
||||
ohci_ppc_soc_start(struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
int ret;
|
||||
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run(ohci)) < 0) {
|
||||
err("can't start %s", ohci_to_hcd(ohci)->self.bus_name);
|
||||
ohci_stop(hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hc_driver ohci_ppc_soc_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.start = ohci_ppc_soc_start,
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
.hub_suspend = ohci_hub_suspend,
|
||||
.hub_resume = ohci_hub_resume,
|
||||
#endif
|
||||
.start_port_reset = ohci_start_port_reset,
|
||||
};
|
||||
|
||||
static int ohci_hcd_ppc_soc_drv_probe(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
int ret;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_hcd_ppc_soc_probe(&ohci_ppc_soc_hc_driver, pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ohci_hcd_ppc_soc_drv_remove(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
usb_hcd_ppc_soc_remove(hcd, pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct device_driver ohci_hcd_ppc_soc_driver = {
|
||||
.name = "ppc-soc-ohci",
|
||||
.bus = &platform_bus_type,
|
||||
.probe = ohci_hcd_ppc_soc_drv_probe,
|
||||
.remove = ohci_hcd_ppc_soc_drv_remove,
|
||||
#if defined(CONFIG_USB_SUSPEND) || defined(CONFIG_PM)
|
||||
/*.suspend = ohci_hcd_ppc_soc_drv_suspend,*/
|
||||
/*.resume = ohci_hcd_ppc_soc_drv_resume,*/
|
||||
#endif
|
||||
};
|
||||
|
||||
static int __init ohci_hcd_ppc_soc_init(void)
|
||||
{
|
||||
pr_debug(DRIVER_INFO " (PPC SOC)\n");
|
||||
pr_debug("block sizes: ed %d td %d\n", sizeof(struct ed),
|
||||
sizeof(struct td));
|
||||
|
||||
return driver_register(&ohci_hcd_ppc_soc_driver);
|
||||
}
|
||||
|
||||
static void __exit ohci_hcd_ppc_soc_cleanup(void)
|
||||
{
|
||||
driver_unregister(&ohci_hcd_ppc_soc_driver);
|
||||
}
|
||||
|
||||
module_init(ohci_hcd_ppc_soc_init);
|
||||
module_exit(ohci_hcd_ppc_soc_cleanup);
|
383
drivers/usb/host/ohci-pxa27x.c
一般檔案
383
drivers/usb/host/ohci-pxa27x.c
一般檔案
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
* (C) Copyright 2002 Hewlett-Packard Company
|
||||
*
|
||||
* Bus Glue for pxa27x
|
||||
*
|
||||
* Written by Christopher Hoover <ch@hpl.hp.com>
|
||||
* Based on fragments of previous driver by Russell King et al.
|
||||
*
|
||||
* Modified for LH7A404 from ohci-sa1111.c
|
||||
* by Durgesh Pattamatta <pattamattad@sharpsec.com>
|
||||
*
|
||||
* Modified for pxa27x from ohci-lh7a404.c
|
||||
* by Nick Bane <nick@cecomputing.co.uk> 26-8-2004
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/arch/pxa-regs.h>
|
||||
|
||||
|
||||
#define PMM_NPS_MODE 1
|
||||
#define PMM_GLOBAL_MODE 2
|
||||
#define PMM_PERPORT_MODE 3
|
||||
|
||||
#define PXA_UHC_MAX_PORTNUM 3
|
||||
|
||||
#define UHCRHPS(x) __REG2( 0x4C000050, (x)<<2 )
|
||||
|
||||
static int pxa27x_ohci_pmm_state;
|
||||
|
||||
/*
|
||||
PMM_NPS_MODE -- PMM Non-power switching mode
|
||||
Ports are powered continuously.
|
||||
|
||||
PMM_GLOBAL_MODE -- PMM global switching mode
|
||||
All ports are powered at the same time.
|
||||
|
||||
PMM_PERPORT_MODE -- PMM per port switching mode
|
||||
Ports are powered individually.
|
||||
*/
|
||||
static int pxa27x_ohci_select_pmm( int mode )
|
||||
{
|
||||
pxa27x_ohci_pmm_state = mode;
|
||||
|
||||
switch ( mode ) {
|
||||
case PMM_NPS_MODE:
|
||||
UHCRHDA |= RH_A_NPS;
|
||||
break;
|
||||
case PMM_GLOBAL_MODE:
|
||||
UHCRHDA &= ~(RH_A_NPS & RH_A_PSM);
|
||||
break;
|
||||
case PMM_PERPORT_MODE:
|
||||
UHCRHDA &= ~(RH_A_NPS);
|
||||
UHCRHDA |= RH_A_PSM;
|
||||
|
||||
/* Set port power control mask bits, only 3 ports. */
|
||||
UHCRHDB |= (0x7<<17);
|
||||
break;
|
||||
default:
|
||||
printk( KERN_ERR
|
||||
"Invalid mode %d, set to non-power switch mode.\n",
|
||||
mode );
|
||||
|
||||
pxa27x_ohci_pmm_state = PMM_NPS_MODE;
|
||||
UHCRHDA |= RH_A_NPS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
If you select PMM_PERPORT_MODE, you should set the port power
|
||||
*/
|
||||
static int pxa27x_ohci_set_port_power( int port )
|
||||
{
|
||||
if ( (pxa27x_ohci_pmm_state==PMM_PERPORT_MODE)
|
||||
&& (port>0) && (port<PXA_UHC_MAX_PORTNUM) ) {
|
||||
UHCRHPS(port) |= 0x100;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
If you select PMM_PERPORT_MODE, you should set the port power
|
||||
*/
|
||||
static int pxa27x_ohci_clear_port_power( int port )
|
||||
{
|
||||
if ( (pxa27x_ohci_pmm_state==PMM_PERPORT_MODE)
|
||||
&& (port>0) && (port<PXA_UHC_MAX_PORTNUM) ) {
|
||||
UHCRHPS(port) |= 0x200;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern int usb_disabled(void);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void pxa27x_start_hc(struct platform_device *dev)
|
||||
{
|
||||
pxa_set_cken(CKEN10_USBHOST, 1);
|
||||
|
||||
UHCHR |= UHCHR_FHR;
|
||||
udelay(11);
|
||||
UHCHR &= ~UHCHR_FHR;
|
||||
|
||||
UHCHR |= UHCHR_FSBIR;
|
||||
while (UHCHR & UHCHR_FSBIR)
|
||||
cpu_relax();
|
||||
|
||||
/* This could be properly abstracted away through the
|
||||
device data the day more machines are supported and
|
||||
their differences can be figured out correctly. */
|
||||
if (machine_is_mainstone()) {
|
||||
/* setup Port1 GPIO pin. */
|
||||
pxa_gpio_mode( 88 | GPIO_ALT_FN_1_IN); /* USBHPWR1 */
|
||||
pxa_gpio_mode( 89 | GPIO_ALT_FN_2_OUT); /* USBHPEN1 */
|
||||
|
||||
/* Set the Power Control Polarity Low and Power Sense
|
||||
Polarity Low to active low. Supply power to USB ports. */
|
||||
UHCHR = (UHCHR | UHCHR_PCPL | UHCHR_PSPL) &
|
||||
~(UHCHR_SSEP1 | UHCHR_SSEP2 | UHCHR_SSEP3 | UHCHR_SSE);
|
||||
}
|
||||
|
||||
UHCHR &= ~UHCHR_SSE;
|
||||
|
||||
UHCHIE = (UHCHIE_UPRIE | UHCHIE_RWIE);
|
||||
}
|
||||
|
||||
static void pxa27x_stop_hc(struct platform_device *dev)
|
||||
{
|
||||
UHCHR |= UHCHR_FHR;
|
||||
udelay(11);
|
||||
UHCHR &= ~UHCHR_FHR;
|
||||
|
||||
UHCCOMS |= 1;
|
||||
udelay(10);
|
||||
|
||||
pxa_set_cken(CKEN10_USBHOST, 0);
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
|
||||
/**
|
||||
* usb_hcd_pxa27x_probe - initialize pxa27x-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
*/
|
||||
int usb_hcd_pxa27x_probe (const struct hc_driver *driver,
|
||||
struct platform_device *dev)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
if (dev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug ("resource[1] is not IORESOURCE_IRQ");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd (driver, &dev->dev, "pxa27x");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = dev->resource[0].start;
|
||||
hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed");
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
pxa27x_start_hc(dev);
|
||||
|
||||
/* Select Power Management Mode */
|
||||
pxa27x_ohci_select_pmm( PMM_PERPORT_MODE );
|
||||
|
||||
/* If choosing PMM_PERPORT_MODE, we should set the port power before we use it. */
|
||||
if (pxa27x_ohci_set_port_power(1) < 0)
|
||||
printk(KERN_ERR "Setting port 1 power failed.\n");
|
||||
|
||||
if (pxa27x_ohci_clear_port_power(2) < 0)
|
||||
printk(KERN_ERR "Setting port 2 power failed.\n");
|
||||
|
||||
if (pxa27x_ohci_clear_port_power(3) < 0)
|
||||
printk(KERN_ERR "Setting port 3 power failed.\n");
|
||||
|
||||
ohci_hcd_init(hcd_to_ohci(hcd));
|
||||
|
||||
retval = usb_add_hcd(hcd, dev->resource[1].start, SA_INTERRUPT);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
pxa27x_stop_hc(dev);
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_pxa27x_remove - shutdown processing for pxa27x-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_pxa27x_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
void usb_hcd_pxa27x_remove (struct usb_hcd *hcd, struct platform_device *dev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
pxa27x_stop_hc(dev);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __devinit
|
||||
ohci_pxa27x_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
ohci_dbg (ohci, "ohci_pxa27x_start, ohci:%p", ohci);
|
||||
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
err ("can't start %s", hcd->self.bus_name);
|
||||
ohci_stop (hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_pxa27x_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "PXA27x OHCI",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.start = ohci_pxa27x_start,
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
.hub_suspend = ohci_hub_suspend,
|
||||
.hub_resume = ohci_hub_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_hcd_pxa27x_drv_probe(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
int ret;
|
||||
|
||||
pr_debug ("In ohci_hcd_pxa27x_drv_probe");
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_hcd_pxa27x_probe(&ohci_pxa27x_hc_driver, pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ohci_hcd_pxa27x_drv_remove(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
|
||||
usb_hcd_pxa27x_remove(hcd, pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ohci_hcd_pxa27x_drv_suspend(struct device *dev, u32 state, u32 level)
|
||||
{
|
||||
// struct platform_device *pdev = to_platform_device(dev);
|
||||
// struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
printk("%s: not implemented yet\n", __FUNCTION__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ohci_hcd_pxa27x_drv_resume(struct device *dev, u32 state)
|
||||
{
|
||||
// struct platform_device *pdev = to_platform_device(dev);
|
||||
// struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
printk("%s: not implemented yet\n", __FUNCTION__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct device_driver ohci_hcd_pxa27x_driver = {
|
||||
.name = "pxa27x-ohci",
|
||||
.bus = &platform_bus_type,
|
||||
.probe = ohci_hcd_pxa27x_drv_probe,
|
||||
.remove = ohci_hcd_pxa27x_drv_remove,
|
||||
.suspend = ohci_hcd_pxa27x_drv_suspend,
|
||||
.resume = ohci_hcd_pxa27x_drv_resume,
|
||||
};
|
||||
|
||||
static int __init ohci_hcd_pxa27x_init (void)
|
||||
{
|
||||
pr_debug (DRIVER_INFO " (pxa27x)");
|
||||
pr_debug ("block sizes: ed %d td %d\n",
|
||||
sizeof (struct ed), sizeof (struct td));
|
||||
|
||||
return driver_register(&ohci_hcd_pxa27x_driver);
|
||||
}
|
||||
|
||||
static void __exit ohci_hcd_pxa27x_cleanup (void)
|
||||
{
|
||||
driver_unregister(&ohci_hcd_pxa27x_driver);
|
||||
}
|
||||
|
||||
module_init (ohci_hcd_pxa27x_init);
|
||||
module_exit (ohci_hcd_pxa27x_cleanup);
|
1107
drivers/usb/host/ohci-q.c
一般檔案
1107
drivers/usb/host/ohci-q.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
289
drivers/usb/host/ohci-sa1111.c
一般檔案
289
drivers/usb/host/ohci-sa1111.c
一般檔案
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
* (C) Copyright 2002 Hewlett-Packard Company
|
||||
*
|
||||
* SA1111 Bus Glue
|
||||
*
|
||||
* Written by Christopher Hoover <ch@hpl.hp.com>
|
||||
* Based on fragments of previous driver by Rusell King et al.
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <asm/arch/assabet.h>
|
||||
#include <asm/arch/badge4.h>
|
||||
#include <asm/hardware/sa1111.h>
|
||||
|
||||
#ifndef CONFIG_SA1111
|
||||
#error "This file is SA-1111 bus glue. CONFIG_SA1111 must be defined."
|
||||
#endif
|
||||
|
||||
extern int usb_disabled(void);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void sa1111_start_hc(struct sa1111_dev *dev)
|
||||
{
|
||||
unsigned int usb_rst = 0;
|
||||
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": starting SA-1111 OHCI USB Controller\n");
|
||||
|
||||
#ifdef CONFIG_SA1100_BADGE4
|
||||
if (machine_is_badge4()) {
|
||||
badge4_set_5V(BADGE4_5V_USB, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (machine_is_xp860() ||
|
||||
machine_has_neponset() ||
|
||||
machine_is_pfs168() ||
|
||||
machine_is_badge4())
|
||||
usb_rst = USB_RESET_PWRSENSELOW | USB_RESET_PWRCTRLLOW;
|
||||
|
||||
/*
|
||||
* Configure the power sense and control lines. Place the USB
|
||||
* host controller in reset.
|
||||
*/
|
||||
sa1111_writel(usb_rst | USB_RESET_FORCEIFRESET | USB_RESET_FORCEHCRESET,
|
||||
dev->mapbase + SA1111_USB_RESET);
|
||||
|
||||
/*
|
||||
* Now, carefully enable the USB clock, and take
|
||||
* the USB host controller out of reset.
|
||||
*/
|
||||
sa1111_enable_device(dev);
|
||||
udelay(11);
|
||||
sa1111_writel(usb_rst, dev->mapbase + SA1111_USB_RESET);
|
||||
}
|
||||
|
||||
static void sa1111_stop_hc(struct sa1111_dev *dev)
|
||||
{
|
||||
unsigned int usb_rst;
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": stopping SA-1111 OHCI USB Controller\n");
|
||||
|
||||
/*
|
||||
* Put the USB host controller into reset.
|
||||
*/
|
||||
usb_rst = sa1111_readl(dev->mapbase + SA1111_USB_RESET);
|
||||
sa1111_writel(usb_rst | USB_RESET_FORCEIFRESET | USB_RESET_FORCEHCRESET,
|
||||
dev->mapbase + SA1111_USB_RESET);
|
||||
|
||||
/*
|
||||
* Stop the USB clock.
|
||||
*/
|
||||
sa1111_disable_device(dev);
|
||||
|
||||
#ifdef CONFIG_SA1100_BADGE4
|
||||
if (machine_is_badge4()) {
|
||||
/* Disable power to the USB bus */
|
||||
badge4_set_5V(BADGE4_5V_USB, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#if 0
|
||||
static void dump_hci_status(struct usb_hcd *hcd, const char *label)
|
||||
{
|
||||
unsigned long status = sa1111_readl(hcd->regs + SA1111_USB_STATUS);
|
||||
|
||||
dbg ("%s USB_STATUS = { %s%s%s%s%s}", label,
|
||||
((status & USB_STATUS_IRQHCIRMTWKUP) ? "IRQHCIRMTWKUP " : ""),
|
||||
((status & USB_STATUS_IRQHCIBUFFACC) ? "IRQHCIBUFFACC " : ""),
|
||||
((status & USB_STATUS_NIRQHCIM) ? "" : "IRQHCIM "),
|
||||
((status & USB_STATUS_NHCIMFCLR) ? "" : "HCIMFCLR "),
|
||||
((status & USB_STATUS_USBPWRSENSE) ? "USBPWRSENSE " : ""));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
|
||||
/**
|
||||
* usb_hcd_sa1111_probe - initialize SA-1111-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
* Store this function in the HCD's struct pci_driver as probe().
|
||||
*/
|
||||
int usb_hcd_sa1111_probe (const struct hc_driver *driver,
|
||||
struct sa1111_dev *dev)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
int retval;
|
||||
|
||||
hcd = usb_create_hcd (driver, &dev->dev, "sa1111");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = dev->res.start;
|
||||
hcd->rsrc_len = dev->res.end - dev->res.start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
dbg("request_mem_region failed");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
hcd->regs = dev->mapbase;
|
||||
|
||||
sa1111_start_hc(dev);
|
||||
ohci_hcd_init(hcd_to_ohci(hcd));
|
||||
|
||||
retval = usb_add_hcd(hcd, dev->irq[1], SA_INTERRUPT);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
sa1111_stop_hc(dev);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_sa1111_remove - shutdown processing for SA-1111-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_sa1111_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
void usb_hcd_sa1111_remove (struct usb_hcd *hcd, struct sa1111_dev *dev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
sa1111_stop_hc(dev);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __devinit
|
||||
ohci_sa1111_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
err ("can't start %s", hcd->self.bus_name);
|
||||
ohci_stop (hcd);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_sa1111_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "SA-1111 OHCI",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.start = ohci_sa1111_start,
|
||||
#ifdef CONFIG_PM
|
||||
/* suspend: ohci_sa1111_suspend, -- tbd */
|
||||
/* resume: ohci_sa1111_resume, -- tbd */
|
||||
#endif
|
||||
.stop = ohci_stop,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
.hub_suspend = ohci_hub_suspend,
|
||||
.hub_resume = ohci_hub_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_hcd_sa1111_drv_probe(struct sa1111_dev *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_hcd_sa1111_probe(&ohci_sa1111_hc_driver, dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ohci_hcd_sa1111_drv_remove(struct sa1111_dev *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = sa1111_get_drvdata(dev);
|
||||
|
||||
usb_hcd_sa1111_remove(hcd, dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sa1111_driver ohci_hcd_sa1111_driver = {
|
||||
.drv = {
|
||||
.name = "sa1111-ohci",
|
||||
},
|
||||
.devid = SA1111_DEVID_USB,
|
||||
.probe = ohci_hcd_sa1111_drv_probe,
|
||||
.remove = ohci_hcd_sa1111_drv_remove,
|
||||
};
|
||||
|
||||
static int __init ohci_hcd_sa1111_init (void)
|
||||
{
|
||||
dbg (DRIVER_INFO " (SA-1111)");
|
||||
dbg ("block sizes: ed %d td %d",
|
||||
sizeof (struct ed), sizeof (struct td));
|
||||
|
||||
return sa1111_driver_register(&ohci_hcd_sa1111_driver);
|
||||
}
|
||||
|
||||
static void __exit ohci_hcd_sa1111_cleanup (void)
|
||||
{
|
||||
sa1111_driver_unregister(&ohci_hcd_sa1111_driver);
|
||||
}
|
||||
|
||||
module_init (ohci_hcd_sa1111_init);
|
||||
module_exit (ohci_hcd_sa1111_cleanup);
|
636
drivers/usb/host/ohci.h
一般檔案
636
drivers/usb/host/ohci.h
一般檔案
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
* OHCI HCD (Host Controller Driver) for USB.
|
||||
*
|
||||
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*
|
||||
* This file is licenced under the GPL.
|
||||
*/
|
||||
|
||||
/*
|
||||
* __hc32 and __hc16 are "Host Controller" types, they may be equivalent to
|
||||
* __leXX (normally) or __beXX (given OHCI_BIG_ENDIAN), depending on the
|
||||
* host controller implementation.
|
||||
*/
|
||||
typedef __u32 __bitwise __hc32;
|
||||
typedef __u16 __bitwise __hc16;
|
||||
|
||||
/*
|
||||
* OHCI Endpoint Descriptor (ED) ... holds TD queue
|
||||
* See OHCI spec, section 4.2
|
||||
*
|
||||
* This is a "Queue Head" for those transfers, which is why
|
||||
* both EHCI and UHCI call similar structures a "QH".
|
||||
*/
|
||||
struct ed {
|
||||
/* first fields are hardware-specified */
|
||||
__hc32 hwINFO; /* endpoint config bitmap */
|
||||
/* info bits defined by hcd */
|
||||
#define ED_DEQUEUE (1 << 27)
|
||||
/* info bits defined by the hardware */
|
||||
#define ED_ISO (1 << 15)
|
||||
#define ED_SKIP (1 << 14)
|
||||
#define ED_LOWSPEED (1 << 13)
|
||||
#define ED_OUT (0x01 << 11)
|
||||
#define ED_IN (0x02 << 11)
|
||||
__hc32 hwTailP; /* tail of TD list */
|
||||
__hc32 hwHeadP; /* head of TD list (hc r/w) */
|
||||
#define ED_C (0x02) /* toggle carry */
|
||||
#define ED_H (0x01) /* halted */
|
||||
__hc32 hwNextED; /* next ED in list */
|
||||
|
||||
/* rest are purely for the driver's use */
|
||||
dma_addr_t dma; /* addr of ED */
|
||||
struct td *dummy; /* next TD to activate */
|
||||
|
||||
/* host's view of schedule */
|
||||
struct ed *ed_next; /* on schedule or rm_list */
|
||||
struct ed *ed_prev; /* for non-interrupt EDs */
|
||||
struct list_head td_list; /* "shadow list" of our TDs */
|
||||
|
||||
/* create --> IDLE --> OPER --> ... --> IDLE --> destroy
|
||||
* usually: OPER --> UNLINK --> (IDLE | OPER) --> ...
|
||||
*/
|
||||
u8 state; /* ED_{IDLE,UNLINK,OPER} */
|
||||
#define ED_IDLE 0x00 /* NOT linked to HC */
|
||||
#define ED_UNLINK 0x01 /* being unlinked from hc */
|
||||
#define ED_OPER 0x02 /* IS linked to hc */
|
||||
|
||||
u8 type; /* PIPE_{BULK,...} */
|
||||
|
||||
/* periodic scheduling params (for intr and iso) */
|
||||
u8 branch;
|
||||
u16 interval;
|
||||
u16 load;
|
||||
u16 last_iso; /* iso only */
|
||||
|
||||
/* HC may see EDs on rm_list until next frame (frame_no == tick) */
|
||||
u16 tick;
|
||||
} __attribute__ ((aligned(16)));
|
||||
|
||||
#define ED_MASK ((u32)~0x0f) /* strip hw status in low addr bits */
|
||||
|
||||
|
||||
/*
|
||||
* OHCI Transfer Descriptor (TD) ... one per transfer segment
|
||||
* See OHCI spec, sections 4.3.1 (general = control/bulk/interrupt)
|
||||
* and 4.3.2 (iso)
|
||||
*/
|
||||
struct td {
|
||||
/* first fields are hardware-specified */
|
||||
__hc32 hwINFO; /* transfer info bitmask */
|
||||
|
||||
/* hwINFO bits for both general and iso tds: */
|
||||
#define TD_CC 0xf0000000 /* condition code */
|
||||
#define TD_CC_GET(td_p) ((td_p >>28) & 0x0f)
|
||||
//#define TD_CC_SET(td_p, cc) (td_p) = ((td_p) & 0x0fffffff) | (((cc) & 0x0f) << 28)
|
||||
#define TD_DI 0x00E00000 /* frames before interrupt */
|
||||
#define TD_DI_SET(X) (((X) & 0x07)<< 21)
|
||||
/* these two bits are available for definition/use by HCDs in both
|
||||
* general and iso tds ... others are available for only one type
|
||||
*/
|
||||
#define TD_DONE 0x00020000 /* retired to donelist */
|
||||
#define TD_ISO 0x00010000 /* copy of ED_ISO */
|
||||
|
||||
/* hwINFO bits for general tds: */
|
||||
#define TD_EC 0x0C000000 /* error count */
|
||||
#define TD_T 0x03000000 /* data toggle state */
|
||||
#define TD_T_DATA0 0x02000000 /* DATA0 */
|
||||
#define TD_T_DATA1 0x03000000 /* DATA1 */
|
||||
#define TD_T_TOGGLE 0x00000000 /* uses ED_C */
|
||||
#define TD_DP 0x00180000 /* direction/pid */
|
||||
#define TD_DP_SETUP 0x00000000 /* SETUP pid */
|
||||
#define TD_DP_IN 0x00100000 /* IN pid */
|
||||
#define TD_DP_OUT 0x00080000 /* OUT pid */
|
||||
/* 0x00180000 rsvd */
|
||||
#define TD_R 0x00040000 /* round: short packets OK? */
|
||||
|
||||
/* (no hwINFO #defines yet for iso tds) */
|
||||
|
||||
__hc32 hwCBP; /* Current Buffer Pointer (or 0) */
|
||||
__hc32 hwNextTD; /* Next TD Pointer */
|
||||
__hc32 hwBE; /* Memory Buffer End Pointer */
|
||||
|
||||
/* PSW is only for ISO. Only 1 PSW entry is used, but on
|
||||
* big-endian PPC hardware that's the second entry.
|
||||
*/
|
||||
#define MAXPSW 2
|
||||
__hc16 hwPSW [MAXPSW];
|
||||
|
||||
/* rest are purely for the driver's use */
|
||||
__u8 index;
|
||||
struct ed *ed;
|
||||
struct td *td_hash; /* dma-->td hashtable */
|
||||
struct td *next_dl_td;
|
||||
struct urb *urb;
|
||||
|
||||
dma_addr_t td_dma; /* addr of this TD */
|
||||
dma_addr_t data_dma; /* addr of data it points to */
|
||||
|
||||
struct list_head td_list; /* "shadow list", TDs on same ED */
|
||||
} __attribute__ ((aligned(32))); /* c/b/i need 16; only iso needs 32 */
|
||||
|
||||
#define TD_MASK ((u32)~0x1f) /* strip hw status in low addr bits */
|
||||
|
||||
/*
|
||||
* Hardware transfer status codes -- CC from td->hwINFO or td->hwPSW
|
||||
*/
|
||||
#define TD_CC_NOERROR 0x00
|
||||
#define TD_CC_CRC 0x01
|
||||
#define TD_CC_BITSTUFFING 0x02
|
||||
#define TD_CC_DATATOGGLEM 0x03
|
||||
#define TD_CC_STALL 0x04
|
||||
#define TD_DEVNOTRESP 0x05
|
||||
#define TD_PIDCHECKFAIL 0x06
|
||||
#define TD_UNEXPECTEDPID 0x07
|
||||
#define TD_DATAOVERRUN 0x08
|
||||
#define TD_DATAUNDERRUN 0x09
|
||||
/* 0x0A, 0x0B reserved for hardware */
|
||||
#define TD_BUFFEROVERRUN 0x0C
|
||||
#define TD_BUFFERUNDERRUN 0x0D
|
||||
/* 0x0E, 0x0F reserved for HCD */
|
||||
#define TD_NOTACCESSED 0x0F
|
||||
|
||||
|
||||
/* map OHCI TD status codes (CC) to errno values */
|
||||
static const int cc_to_error [16] = {
|
||||
/* No Error */ 0,
|
||||
/* CRC Error */ -EILSEQ,
|
||||
/* Bit Stuff */ -EPROTO,
|
||||
/* Data Togg */ -EILSEQ,
|
||||
/* Stall */ -EPIPE,
|
||||
/* DevNotResp */ -ETIMEDOUT,
|
||||
/* PIDCheck */ -EPROTO,
|
||||
/* UnExpPID */ -EPROTO,
|
||||
/* DataOver */ -EOVERFLOW,
|
||||
/* DataUnder */ -EREMOTEIO,
|
||||
/* (for hw) */ -EIO,
|
||||
/* (for hw) */ -EIO,
|
||||
/* BufferOver */ -ECOMM,
|
||||
/* BuffUnder */ -ENOSR,
|
||||
/* (for HCD) */ -EALREADY,
|
||||
/* (for HCD) */ -EALREADY
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* The HCCA (Host Controller Communications Area) is a 256 byte
|
||||
* structure defined section 4.4.1 of the OHCI spec. The HC is
|
||||
* told the base address of it. It must be 256-byte aligned.
|
||||
*/
|
||||
struct ohci_hcca {
|
||||
#define NUM_INTS 32
|
||||
__hc32 int_table [NUM_INTS]; /* periodic schedule */
|
||||
|
||||
/*
|
||||
* OHCI defines u16 frame_no, followed by u16 zero pad.
|
||||
* Since some processors can't do 16 bit bus accesses,
|
||||
* portable access must be a 32 bits wide.
|
||||
*/
|
||||
__hc32 frame_no; /* current frame number */
|
||||
__hc32 done_head; /* info returned for an interrupt */
|
||||
u8 reserved_for_hc [116];
|
||||
u8 what [4]; /* spec only identifies 252 bytes :) */
|
||||
} __attribute__ ((aligned(256)));
|
||||
|
||||
/*
|
||||
* This is the structure of the OHCI controller's memory mapped I/O region.
|
||||
* You must use readl() and writel() (in <asm/io.h>) to access these fields!!
|
||||
* Layout is in section 7 (and appendix B) of the spec.
|
||||
*/
|
||||
struct ohci_regs {
|
||||
/* control and status registers (section 7.1) */
|
||||
__hc32 revision;
|
||||
__hc32 control;
|
||||
__hc32 cmdstatus;
|
||||
__hc32 intrstatus;
|
||||
__hc32 intrenable;
|
||||
__hc32 intrdisable;
|
||||
|
||||
/* memory pointers (section 7.2) */
|
||||
__hc32 hcca;
|
||||
__hc32 ed_periodcurrent;
|
||||
__hc32 ed_controlhead;
|
||||
__hc32 ed_controlcurrent;
|
||||
__hc32 ed_bulkhead;
|
||||
__hc32 ed_bulkcurrent;
|
||||
__hc32 donehead;
|
||||
|
||||
/* frame counters (section 7.3) */
|
||||
__hc32 fminterval;
|
||||
__hc32 fmremaining;
|
||||
__hc32 fmnumber;
|
||||
__hc32 periodicstart;
|
||||
__hc32 lsthresh;
|
||||
|
||||
/* Root hub ports (section 7.4) */
|
||||
struct ohci_roothub_regs {
|
||||
__hc32 a;
|
||||
__hc32 b;
|
||||
__hc32 status;
|
||||
#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports (RH_A_NDP) */
|
||||
__hc32 portstatus [MAX_ROOT_PORTS];
|
||||
} roothub;
|
||||
|
||||
/* and optional "legacy support" registers (appendix B) at 0x0100 */
|
||||
|
||||
} __attribute__ ((aligned(32)));
|
||||
|
||||
|
||||
/* OHCI CONTROL AND STATUS REGISTER MASKS */
|
||||
|
||||
/*
|
||||
* HcControl (control) register masks
|
||||
*/
|
||||
#define OHCI_CTRL_CBSR (3 << 0) /* control/bulk service ratio */
|
||||
#define OHCI_CTRL_PLE (1 << 2) /* periodic list enable */
|
||||
#define OHCI_CTRL_IE (1 << 3) /* isochronous enable */
|
||||
#define OHCI_CTRL_CLE (1 << 4) /* control list enable */
|
||||
#define OHCI_CTRL_BLE (1 << 5) /* bulk list enable */
|
||||
#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */
|
||||
#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */
|
||||
#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */
|
||||
#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */
|
||||
|
||||
/* pre-shifted values for HCFS */
|
||||
# define OHCI_USB_RESET (0 << 6)
|
||||
# define OHCI_USB_RESUME (1 << 6)
|
||||
# define OHCI_USB_OPER (2 << 6)
|
||||
# define OHCI_USB_SUSPEND (3 << 6)
|
||||
|
||||
/*
|
||||
* HcCommandStatus (cmdstatus) register masks
|
||||
*/
|
||||
#define OHCI_HCR (1 << 0) /* host controller reset */
|
||||
#define OHCI_CLF (1 << 1) /* control list filled */
|
||||
#define OHCI_BLF (1 << 2) /* bulk list filled */
|
||||
#define OHCI_OCR (1 << 3) /* ownership change request */
|
||||
#define OHCI_SOC (3 << 16) /* scheduling overrun count */
|
||||
|
||||
/*
|
||||
* masks used with interrupt registers:
|
||||
* HcInterruptStatus (intrstatus)
|
||||
* HcInterruptEnable (intrenable)
|
||||
* HcInterruptDisable (intrdisable)
|
||||
*/
|
||||
#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */
|
||||
#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */
|
||||
#define OHCI_INTR_SF (1 << 2) /* start frame */
|
||||
#define OHCI_INTR_RD (1 << 3) /* resume detect */
|
||||
#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */
|
||||
#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */
|
||||
#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */
|
||||
#define OHCI_INTR_OC (1 << 30) /* ownership change */
|
||||
#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */
|
||||
|
||||
|
||||
/* OHCI ROOT HUB REGISTER MASKS */
|
||||
|
||||
/* roothub.portstatus [i] bits */
|
||||
#define RH_PS_CCS 0x00000001 /* current connect status */
|
||||
#define RH_PS_PES 0x00000002 /* port enable status*/
|
||||
#define RH_PS_PSS 0x00000004 /* port suspend status */
|
||||
#define RH_PS_POCI 0x00000008 /* port over current indicator */
|
||||
#define RH_PS_PRS 0x00000010 /* port reset status */
|
||||
#define RH_PS_PPS 0x00000100 /* port power status */
|
||||
#define RH_PS_LSDA 0x00000200 /* low speed device attached */
|
||||
#define RH_PS_CSC 0x00010000 /* connect status change */
|
||||
#define RH_PS_PESC 0x00020000 /* port enable status change */
|
||||
#define RH_PS_PSSC 0x00040000 /* port suspend status change */
|
||||
#define RH_PS_OCIC 0x00080000 /* over current indicator change */
|
||||
#define RH_PS_PRSC 0x00100000 /* port reset status change */
|
||||
|
||||
/* roothub.status bits */
|
||||
#define RH_HS_LPS 0x00000001 /* local power status */
|
||||
#define RH_HS_OCI 0x00000002 /* over current indicator */
|
||||
#define RH_HS_DRWE 0x00008000 /* device remote wakeup enable */
|
||||
#define RH_HS_LPSC 0x00010000 /* local power status change */
|
||||
#define RH_HS_OCIC 0x00020000 /* over current indicator change */
|
||||
#define RH_HS_CRWE 0x80000000 /* clear remote wakeup enable */
|
||||
|
||||
/* roothub.b masks */
|
||||
#define RH_B_DR 0x0000ffff /* device removable flags */
|
||||
#define RH_B_PPCM 0xffff0000 /* port power control mask */
|
||||
|
||||
/* roothub.a masks */
|
||||
#define RH_A_NDP (0xff << 0) /* number of downstream ports */
|
||||
#define RH_A_PSM (1 << 8) /* power switching mode */
|
||||
#define RH_A_NPS (1 << 9) /* no power switching */
|
||||
#define RH_A_DT (1 << 10) /* device type (mbz) */
|
||||
#define RH_A_OCPM (1 << 11) /* over current protection mode */
|
||||
#define RH_A_NOCP (1 << 12) /* no over current protection */
|
||||
#define RH_A_POTPGT (0xff << 24) /* power on to power good time */
|
||||
|
||||
|
||||
/* hcd-private per-urb state */
|
||||
typedef struct urb_priv {
|
||||
struct ed *ed;
|
||||
u16 length; // # tds in this request
|
||||
u16 td_cnt; // tds already serviced
|
||||
struct list_head pending;
|
||||
struct td *td [0]; // all TDs in this request
|
||||
|
||||
} urb_priv_t;
|
||||
|
||||
#define TD_HASH_SIZE 64 /* power'o'two */
|
||||
// sizeof (struct td) ~= 64 == 2^6 ...
|
||||
#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE)
|
||||
|
||||
|
||||
/*
|
||||
* This is the full ohci controller description
|
||||
*
|
||||
* Note how the "proper" USB information is just
|
||||
* a subset of what the full implementation needs. (Linus)
|
||||
*/
|
||||
|
||||
struct ohci_hcd {
|
||||
spinlock_t lock;
|
||||
|
||||
/*
|
||||
* I/O memory used to communicate with the HC (dma-consistent)
|
||||
*/
|
||||
struct ohci_regs __iomem *regs;
|
||||
|
||||
/*
|
||||
* main memory used to communicate with the HC (dma-consistent).
|
||||
* hcd adds to schedule for a live hc any time, but removals finish
|
||||
* only at the start of the next frame.
|
||||
*/
|
||||
struct ohci_hcca *hcca;
|
||||
dma_addr_t hcca_dma;
|
||||
|
||||
struct ed *ed_rm_list; /* to be removed */
|
||||
|
||||
struct ed *ed_bulktail; /* last in bulk list */
|
||||
struct ed *ed_controltail; /* last in ctrl list */
|
||||
struct ed *periodic [NUM_INTS]; /* shadow int_table */
|
||||
|
||||
/*
|
||||
* OTG controllers and transceivers need software interaction;
|
||||
* other external transceivers should be software-transparent
|
||||
*/
|
||||
struct otg_transceiver *transceiver;
|
||||
unsigned power_budget;
|
||||
|
||||
/*
|
||||
* memory management for queue data structures
|
||||
*/
|
||||
struct dma_pool *td_cache;
|
||||
struct dma_pool *ed_cache;
|
||||
struct td *td_hash [TD_HASH_SIZE];
|
||||
struct list_head pending;
|
||||
|
||||
/*
|
||||
* driver state
|
||||
*/
|
||||
int load [NUM_INTS];
|
||||
u32 hc_control; /* copy of hc control reg */
|
||||
unsigned long next_statechange; /* suspend/resume */
|
||||
u32 fminterval; /* saved register */
|
||||
|
||||
struct work_struct rh_resume;
|
||||
|
||||
unsigned long flags; /* for HC bugs */
|
||||
#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */
|
||||
#define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */
|
||||
#define OHCI_QUIRK_INITRESET 0x04 /* SiS, OPTi, ... */
|
||||
#define OHCI_BIG_ENDIAN 0x08 /* big endian HC */
|
||||
// there are also chip quirks/bugs in init logic
|
||||
|
||||
};
|
||||
|
||||
/* convert between an hcd pointer and the corresponding ohci_hcd */
|
||||
static inline struct ohci_hcd *hcd_to_ohci (struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct ohci_hcd *) (hcd->hcd_priv);
|
||||
}
|
||||
static inline struct usb_hcd *ohci_to_hcd (const struct ohci_hcd *ohci)
|
||||
{
|
||||
return container_of ((void *) ohci, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef DEBUG
|
||||
#define STUB_DEBUG_FILES
|
||||
#endif /* DEBUG */
|
||||
|
||||
#define ohci_dbg(ohci, fmt, args...) \
|
||||
dev_dbg (ohci_to_hcd(ohci)->self.controller , fmt , ## args )
|
||||
#define ohci_err(ohci, fmt, args...) \
|
||||
dev_err (ohci_to_hcd(ohci)->self.controller , fmt , ## args )
|
||||
#define ohci_info(ohci, fmt, args...) \
|
||||
dev_info (ohci_to_hcd(ohci)->self.controller , fmt , ## args )
|
||||
#define ohci_warn(ohci, fmt, args...) \
|
||||
dev_warn (ohci_to_hcd(ohci)->self.controller , fmt , ## args )
|
||||
|
||||
#ifdef OHCI_VERBOSE_DEBUG
|
||||
# define ohci_vdbg ohci_dbg
|
||||
#else
|
||||
# define ohci_vdbg(ohci, fmt, args...) do { } while (0)
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* While most USB host controllers implement their registers and
|
||||
* in-memory communication descriptors in little-endian format,
|
||||
* a minority (notably the IBM STB04XXX and the Motorola MPC5200
|
||||
* processors) implement them in big endian format.
|
||||
*
|
||||
* This attempts to support either format at compile time without a
|
||||
* runtime penalty, or both formats with the additional overhead
|
||||
* of checking a flag bit.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_USB_OHCI_BIG_ENDIAN
|
||||
|
||||
#ifdef CONFIG_USB_OHCI_LITTLE_ENDIAN
|
||||
#define big_endian(ohci) (ohci->flags & OHCI_BIG_ENDIAN) /* either */
|
||||
#else
|
||||
#define big_endian(ohci) 1 /* only big endian */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Big-endian read/write functions are arch-specific.
|
||||
* Other arches can be added if/when they're needed.
|
||||
*/
|
||||
#if defined(CONFIG_PPC)
|
||||
#define readl_be(addr) in_be32((__force unsigned *)addr)
|
||||
#define writel_be(val, addr) out_be32((__force unsigned *)addr, val)
|
||||
#endif
|
||||
|
||||
static inline unsigned int ohci_readl (const struct ohci_hcd *ohci,
|
||||
__hc32 __iomem * regs)
|
||||
{
|
||||
return big_endian(ohci) ? readl_be (regs) : readl ((__force u32 *)regs);
|
||||
}
|
||||
|
||||
static inline void ohci_writel (const struct ohci_hcd *ohci,
|
||||
const unsigned int val, __hc32 __iomem *regs)
|
||||
{
|
||||
big_endian(ohci) ? writel_be (val, regs) :
|
||||
writel (val, (__force u32 *)regs);
|
||||
}
|
||||
|
||||
#else /* !CONFIG_USB_OHCI_BIG_ENDIAN */
|
||||
|
||||
#define big_endian(ohci) 0 /* only little endian */
|
||||
|
||||
#ifdef CONFIG_ARCH_LH7A404
|
||||
/* Marc Singer: at the time this code was written, the LH7A404
|
||||
* had a problem reading the USB host registers. This
|
||||
* implementation of the ohci_readl function performs the read
|
||||
* twice as a work-around.
|
||||
*/
|
||||
static inline unsigned int
|
||||
ohci_readl (const struct ohci_hcd *ohci, const __hc32 *regs)
|
||||
{
|
||||
*(volatile __force unsigned int*) regs;
|
||||
return *(volatile __force unsigned int*) regs;
|
||||
}
|
||||
#else
|
||||
/* Standard version of ohci_readl uses standard, platform
|
||||
* specific implementation. */
|
||||
static inline unsigned int
|
||||
ohci_readl (const struct ohci_hcd *ohci, __hc32 __iomem * regs)
|
||||
{
|
||||
return readl(regs);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void ohci_writel (const struct ohci_hcd *ohci,
|
||||
const unsigned int val, __hc32 __iomem *regs)
|
||||
{
|
||||
writel (val, regs);
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_USB_OHCI_BIG_ENDIAN */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* cpu to ohci */
|
||||
static inline __hc16 cpu_to_hc16 (const struct ohci_hcd *ohci, const u16 x)
|
||||
{
|
||||
return big_endian(ohci) ? (__force __hc16)cpu_to_be16(x) : (__force __hc16)cpu_to_le16(x);
|
||||
}
|
||||
|
||||
static inline __hc16 cpu_to_hc16p (const struct ohci_hcd *ohci, const u16 *x)
|
||||
{
|
||||
return big_endian(ohci) ? cpu_to_be16p(x) : cpu_to_le16p(x);
|
||||
}
|
||||
|
||||
static inline __hc32 cpu_to_hc32 (const struct ohci_hcd *ohci, const u32 x)
|
||||
{
|
||||
return big_endian(ohci) ? (__force __hc32)cpu_to_be32(x) : (__force __hc32)cpu_to_le32(x);
|
||||
}
|
||||
|
||||
static inline __hc32 cpu_to_hc32p (const struct ohci_hcd *ohci, const u32 *x)
|
||||
{
|
||||
return big_endian(ohci) ? cpu_to_be32p(x) : cpu_to_le32p(x);
|
||||
}
|
||||
|
||||
/* ohci to cpu */
|
||||
static inline u16 hc16_to_cpu (const struct ohci_hcd *ohci, const __hc16 x)
|
||||
{
|
||||
return big_endian(ohci) ? be16_to_cpu((__force __be16)x) : le16_to_cpu((__force __le16)x);
|
||||
}
|
||||
|
||||
static inline u16 hc16_to_cpup (const struct ohci_hcd *ohci, const __hc16 *x)
|
||||
{
|
||||
return big_endian(ohci) ? be16_to_cpup((__force __be16 *)x) : le16_to_cpup((__force __le16 *)x);
|
||||
}
|
||||
|
||||
static inline u32 hc32_to_cpu (const struct ohci_hcd *ohci, const __hc32 x)
|
||||
{
|
||||
return big_endian(ohci) ? be32_to_cpu((__force __be32)x) : le32_to_cpu((__force __le32)x);
|
||||
}
|
||||
|
||||
static inline u32 hc32_to_cpup (const struct ohci_hcd *ohci, const __hc32 *x)
|
||||
{
|
||||
return big_endian(ohci) ? be32_to_cpup((__force __be32 *)x) : le32_to_cpup((__force __le32 *)x);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* HCCA frame number is 16 bits, but is accessed as 32 bits since not all
|
||||
* hardware handles 16 bit reads. That creates a different confusion on
|
||||
* some big-endian SOC implementations. Same thing happens with PSW access.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_STB03xxx
|
||||
#define OHCI_BE_FRAME_NO_SHIFT 16
|
||||
#else
|
||||
#define OHCI_BE_FRAME_NO_SHIFT 0
|
||||
#endif
|
||||
|
||||
static inline u16 ohci_frame_no(const struct ohci_hcd *ohci)
|
||||
{
|
||||
u32 tmp;
|
||||
if (big_endian(ohci)) {
|
||||
tmp = be32_to_cpup((__force __be32 *)&ohci->hcca->frame_no);
|
||||
tmp >>= OHCI_BE_FRAME_NO_SHIFT;
|
||||
} else
|
||||
tmp = le32_to_cpup((__force __le32 *)&ohci->hcca->frame_no);
|
||||
|
||||
return (u16)tmp;
|
||||
}
|
||||
|
||||
static inline __hc16 *ohci_hwPSWp(const struct ohci_hcd *ohci,
|
||||
const struct td *td, int index)
|
||||
{
|
||||
return (__hc16 *)(big_endian(ohci) ?
|
||||
&td->hwPSW[index ^ 1] : &td->hwPSW[index]);
|
||||
}
|
||||
|
||||
static inline u16 ohci_hwPSW(const struct ohci_hcd *ohci,
|
||||
const struct td *td, int index)
|
||||
{
|
||||
return hc16_to_cpup(ohci, ohci_hwPSWp(ohci, td, index));
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline void disable (struct ohci_hcd *ohci)
|
||||
{
|
||||
ohci_to_hcd(ohci)->state = HC_STATE_HALT;
|
||||
}
|
||||
|
||||
#define FI 0x2edf /* 12000 bits per frame (-1) */
|
||||
#define FSMP(fi) (0x7fff & ((6 * ((fi) - 210)) / 7))
|
||||
#define FIT (1 << 31)
|
||||
#define LSTHRESH 0x628 /* lowspeed bit threshold */
|
||||
|
||||
static void periodic_reinit (struct ohci_hcd *ohci)
|
||||
{
|
||||
u32 fi = ohci->fminterval & 0x03fff;
|
||||
u32 fit = ohci_readl(ohci, &ohci->regs->fminterval) & FIT;
|
||||
|
||||
ohci_writel (ohci, (fit ^ FIT) | ohci->fminterval,
|
||||
&ohci->regs->fminterval);
|
||||
ohci_writel (ohci, ((9 * fi) / 10) & 0x3fff,
|
||||
&ohci->regs->periodicstart);
|
||||
}
|
||||
|
||||
/* AMD-756 (D2 rev) reports corrupt register contents in some cases.
|
||||
* The erratum (#4) description is incorrect. AMD's workaround waits
|
||||
* till some bits (mostly reserved) are clear; ok for all revs.
|
||||
*/
|
||||
#define read_roothub(hc, register, mask) ({ \
|
||||
u32 temp = ohci_readl (hc, &hc->regs->roothub.register); \
|
||||
if (temp == -1) \
|
||||
disable (hc); \
|
||||
else if (hc->flags & OHCI_QUIRK_AMD756) \
|
||||
while (temp & mask) \
|
||||
temp = ohci_readl (hc, &hc->regs->roothub.register); \
|
||||
temp; })
|
||||
|
||||
static u32 roothub_a (struct ohci_hcd *hc)
|
||||
{ return read_roothub (hc, a, 0xfc0fe000); }
|
||||
static inline u32 roothub_b (struct ohci_hcd *hc)
|
||||
{ return ohci_readl (hc, &hc->regs->roothub.b); }
|
||||
static inline u32 roothub_status (struct ohci_hcd *hc)
|
||||
{ return ohci_readl (hc, &hc->regs->roothub.status); }
|
||||
static u32 roothub_portstatus (struct ohci_hcd *hc, int i)
|
||||
{ return read_roothub (hc, portstatus [i], 0xffe0fce0); }
|
1851
drivers/usb/host/sl811-hcd.c
一般檔案
1851
drivers/usb/host/sl811-hcd.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
266
drivers/usb/host/sl811.h
一般檔案
266
drivers/usb/host/sl811.h
一般檔案
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* SL811HS register declarations and HCD data structures
|
||||
*
|
||||
* Copyright (C) 2004 Psion Teklogix
|
||||
* Copyright (C) 2004 David Brownell
|
||||
* Copyright (C) 2001 Cypress Semiconductor Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
* SL811HS has transfer registers, and control registers. In host/master
|
||||
* mode one set of registers is used; in peripheral/slave mode, another.
|
||||
* - SL11H only has some "A" transfer registers from 0x00-0x04
|
||||
* - SL811HS also has "B" registers from 0x08-0x0c
|
||||
* - SL811S (or HS in slave mode) has four A+B sets, at 00, 10, 20, 30
|
||||
*/
|
||||
|
||||
#define SL811_EP_A(base) ((base) + 0)
|
||||
#define SL811_EP_B(base) ((base) + 8)
|
||||
|
||||
#define SL811_HOST_BUF 0x00
|
||||
#define SL811_PERIPH_EP0 0x00
|
||||
#define SL811_PERIPH_EP1 0x10
|
||||
#define SL811_PERIPH_EP2 0x20
|
||||
#define SL811_PERIPH_EP3 0x30
|
||||
|
||||
|
||||
/* TRANSFER REGISTERS: host and peripheral sides are similar
|
||||
* except for the control models (master vs slave).
|
||||
*/
|
||||
#define SL11H_HOSTCTLREG 0
|
||||
# define SL11H_HCTLMASK_ARM 0x01
|
||||
# define SL11H_HCTLMASK_ENABLE 0x02
|
||||
# define SL11H_HCTLMASK_IN 0x00
|
||||
# define SL11H_HCTLMASK_OUT 0x04
|
||||
# define SL11H_HCTLMASK_ISOCH 0x10
|
||||
# define SL11H_HCTLMASK_AFTERSOF 0x20
|
||||
# define SL11H_HCTLMASK_TOGGLE 0x40
|
||||
# define SL11H_HCTLMASK_PREAMBLE 0x80
|
||||
#define SL11H_BUFADDRREG 1
|
||||
#define SL11H_BUFLNTHREG 2
|
||||
#define SL11H_PKTSTATREG 3 /* read */
|
||||
# define SL11H_STATMASK_ACK 0x01
|
||||
# define SL11H_STATMASK_ERROR 0x02
|
||||
# define SL11H_STATMASK_TMOUT 0x04
|
||||
# define SL11H_STATMASK_SEQ 0x08
|
||||
# define SL11H_STATMASK_SETUP 0x10
|
||||
# define SL11H_STATMASK_OVF 0x20
|
||||
# define SL11H_STATMASK_NAK 0x40
|
||||
# define SL11H_STATMASK_STALL 0x80
|
||||
#define SL11H_PIDEPREG 3 /* write */
|
||||
# define SL_SETUP 0xd0
|
||||
# define SL_IN 0x90
|
||||
# define SL_OUT 0x10
|
||||
# define SL_SOF 0x50
|
||||
# define SL_PREAMBLE 0xc0
|
||||
# define SL_NAK 0xa0
|
||||
# define SL_STALL 0xe0
|
||||
# define SL_DATA0 0x30
|
||||
# define SL_DATA1 0xb0
|
||||
#define SL11H_XFERCNTREG 4 /* read */
|
||||
#define SL11H_DEVADDRREG 4 /* write */
|
||||
|
||||
|
||||
/* CONTROL REGISTERS: host and peripheral are very different.
|
||||
*/
|
||||
#define SL11H_CTLREG1 5
|
||||
# define SL11H_CTL1MASK_SOF_ENA 0x01
|
||||
# define SL11H_CTL1MASK_FORCE 0x18
|
||||
# define SL11H_CTL1MASK_NORMAL 0x00
|
||||
# define SL11H_CTL1MASK_SE0 0x08 /* reset */
|
||||
# define SL11H_CTL1MASK_J 0x10
|
||||
# define SL11H_CTL1MASK_K 0x18 /* resume */
|
||||
# define SL11H_CTL1MASK_LSPD 0x20
|
||||
# define SL11H_CTL1MASK_SUSPEND 0x40
|
||||
#define SL11H_IRQ_ENABLE 6
|
||||
# define SL11H_INTMASK_DONE_A 0x01
|
||||
# define SL11H_INTMASK_DONE_B 0x02
|
||||
# define SL11H_INTMASK_SOFINTR 0x10
|
||||
# define SL11H_INTMASK_INSRMV 0x20 /* to/from SE0 */
|
||||
# define SL11H_INTMASK_RD 0x40
|
||||
# define SL11H_INTMASK_DP 0x80 /* only in INTSTATREG */
|
||||
#define SL11S_ADDRESS 7
|
||||
|
||||
/* 0x08-0x0c are for the B buffer (not in SL11) */
|
||||
|
||||
#define SL11H_IRQ_STATUS 0x0D /* write to ack */
|
||||
#define SL11H_HWREVREG 0x0E /* read */
|
||||
# define SL11H_HWRMASK_HWREV 0xF0
|
||||
#define SL11H_SOFLOWREG 0x0E /* write */
|
||||
#define SL11H_SOFTMRREG 0x0F /* read */
|
||||
|
||||
/* a write to this register enables SL811HS features.
|
||||
* HOST flag presumably overrides the chip input signal?
|
||||
*/
|
||||
#define SL811HS_CTLREG2 0x0F
|
||||
# define SL811HS_CTL2MASK_SOF_MASK 0x3F
|
||||
# define SL811HS_CTL2MASK_DSWAP 0x40
|
||||
# define SL811HS_CTL2MASK_HOST 0x80
|
||||
|
||||
#define SL811HS_CTL2_INIT (SL811HS_CTL2MASK_HOST | 0x2e)
|
||||
|
||||
|
||||
/* DATA BUFFERS: registers from 0x10..0xff are for data buffers;
|
||||
* that's 240 bytes, which we'll split evenly between A and B sides.
|
||||
* Only ISO can use more than 64 bytes per packet.
|
||||
* (The SL11S has 0x40..0xff for buffers.)
|
||||
*/
|
||||
#define H_MAXPACKET 120 /* bytes in A or B fifos */
|
||||
|
||||
#define SL11H_DATA_START 0x10
|
||||
#define SL811HS_PACKET_BUF(is_a) ((is_a) \
|
||||
? SL11H_DATA_START \
|
||||
: (SL11H_DATA_START + H_MAXPACKET))
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */
|
||||
#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE)
|
||||
|
||||
struct sl811 {
|
||||
spinlock_t lock;
|
||||
void __iomem *addr_reg;
|
||||
void __iomem *data_reg;
|
||||
struct sl811_platform_data *board;
|
||||
struct proc_dir_entry *pde;
|
||||
|
||||
unsigned long stat_insrmv;
|
||||
unsigned long stat_wake;
|
||||
unsigned long stat_sof;
|
||||
unsigned long stat_a;
|
||||
unsigned long stat_b;
|
||||
unsigned long stat_lost;
|
||||
unsigned long stat_overrun;
|
||||
|
||||
/* sw model */
|
||||
struct timer_list timer;
|
||||
struct sl811h_ep *next_periodic;
|
||||
struct sl811h_ep *next_async;
|
||||
|
||||
struct sl811h_ep *active_a;
|
||||
unsigned long jiffies_a;
|
||||
struct sl811h_ep *active_b;
|
||||
unsigned long jiffies_b;
|
||||
|
||||
u32 port1;
|
||||
u8 ctrl1, ctrl2, irq_enable;
|
||||
u16 frame;
|
||||
|
||||
/* async schedule: control, bulk */
|
||||
struct list_head async;
|
||||
|
||||
/* periodic schedule: interrupt, iso */
|
||||
u16 load[PERIODIC_SIZE];
|
||||
struct sl811h_ep *periodic[PERIODIC_SIZE];
|
||||
unsigned periodic_count;
|
||||
};
|
||||
|
||||
static inline struct sl811 *hcd_to_sl811(struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct sl811 *) (hcd->hcd_priv);
|
||||
}
|
||||
|
||||
static inline struct usb_hcd *sl811_to_hcd(struct sl811 *sl811)
|
||||
{
|
||||
return container_of((void *) sl811, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
struct sl811h_ep {
|
||||
struct usb_host_endpoint *hep;
|
||||
struct usb_device *udev;
|
||||
|
||||
u8 defctrl;
|
||||
u8 maxpacket;
|
||||
u8 epnum;
|
||||
u8 nextpid;
|
||||
|
||||
u16 error_count;
|
||||
u16 nak_count;
|
||||
u16 length; /* of current packet */
|
||||
|
||||
/* periodic schedule */
|
||||
u16 period;
|
||||
u16 branch;
|
||||
u16 load;
|
||||
struct sl811h_ep *next;
|
||||
|
||||
/* async schedule */
|
||||
struct list_head schedule;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* These register utilities should work for the SL811S register API too
|
||||
* NOTE: caller must hold sl811->lock.
|
||||
*/
|
||||
|
||||
static inline u8 sl811_read(struct sl811 *sl811, int reg)
|
||||
{
|
||||
writeb(reg, sl811->addr_reg);
|
||||
return readb(sl811->data_reg);
|
||||
}
|
||||
|
||||
static inline void sl811_write(struct sl811 *sl811, int reg, u8 val)
|
||||
{
|
||||
writeb(reg, sl811->addr_reg);
|
||||
writeb(val, sl811->data_reg);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sl811_write_buf(struct sl811 *sl811, int addr, const void *buf, size_t count)
|
||||
{
|
||||
const u8 *data;
|
||||
void __iomem *data_reg;
|
||||
|
||||
if (!count)
|
||||
return;
|
||||
writeb(addr, sl811->addr_reg);
|
||||
|
||||
data = buf;
|
||||
data_reg = sl811->data_reg;
|
||||
do {
|
||||
writeb(*data++, data_reg);
|
||||
} while (--count);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sl811_read_buf(struct sl811 *sl811, int addr, void *buf, size_t count)
|
||||
{
|
||||
u8 *data;
|
||||
void __iomem *data_reg;
|
||||
|
||||
if (!count)
|
||||
return;
|
||||
writeb(addr, sl811->addr_reg);
|
||||
|
||||
data = buf;
|
||||
data_reg = sl811->data_reg;
|
||||
do {
|
||||
*data++ = readb(data_reg);
|
||||
} while (--count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(stuff...) printk(KERN_DEBUG "sl811: " stuff)
|
||||
#else
|
||||
#define DBG(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#ifdef VERBOSE
|
||||
# define VDBG DBG
|
||||
#else
|
||||
# define VDBG(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#ifdef PACKET_TRACE
|
||||
# define PACKET VDBG
|
||||
#else
|
||||
# define PACKET(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#define ERR(stuff...) printk(KERN_ERR "sl811: " stuff)
|
||||
#define WARN(stuff...) printk(KERN_WARNING "sl811: " stuff)
|
||||
#define INFO(stuff...) printk(KERN_INFO "sl811: " stuff)
|
||||
|
587
drivers/usb/host/uhci-debug.c
一般檔案
587
drivers/usb/host/uhci-debug.c
一般檔案
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* UHCI-specific debugging code. Invaluable when something
|
||||
* goes wrong, but don't get in my face.
|
||||
*
|
||||
* Kernel visible pointers are surrounded in []'s and bus
|
||||
* visible pointers are surrounded in ()'s
|
||||
*
|
||||
* (C) Copyright 1999 Linus Torvalds
|
||||
* (C) Copyright 1999-2001 Johannes Erdfelt
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include "uhci-hcd.h"
|
||||
|
||||
static struct dentry *uhci_debugfs_root = NULL;
|
||||
|
||||
/* Handle REALLY large printk's so we don't overflow buffers */
|
||||
static inline void lprintk(char *buf)
|
||||
{
|
||||
char *p;
|
||||
|
||||
/* Just write one line at a time */
|
||||
while (buf) {
|
||||
p = strchr(buf, '\n');
|
||||
if (p)
|
||||
*p = 0;
|
||||
printk(KERN_DEBUG "%s\n", buf);
|
||||
buf = p;
|
||||
if (buf)
|
||||
buf++;
|
||||
}
|
||||
}
|
||||
|
||||
static int uhci_show_td(struct uhci_td *td, char *buf, int len, int space)
|
||||
{
|
||||
char *out = buf;
|
||||
char *spid;
|
||||
u32 status, token;
|
||||
|
||||
/* Try to make sure there's enough memory */
|
||||
if (len < 160)
|
||||
return 0;
|
||||
|
||||
status = td_status(td);
|
||||
out += sprintf(out, "%*s[%p] link (%08x) ", space, "", td, le32_to_cpu(td->link));
|
||||
out += sprintf(out, "e%d %s%s%s%s%s%s%s%s%s%sLength=%x ",
|
||||
((status >> 27) & 3),
|
||||
(status & TD_CTRL_SPD) ? "SPD " : "",
|
||||
(status & TD_CTRL_LS) ? "LS " : "",
|
||||
(status & TD_CTRL_IOC) ? "IOC " : "",
|
||||
(status & TD_CTRL_ACTIVE) ? "Active " : "",
|
||||
(status & TD_CTRL_STALLED) ? "Stalled " : "",
|
||||
(status & TD_CTRL_DBUFERR) ? "DataBufErr " : "",
|
||||
(status & TD_CTRL_BABBLE) ? "Babble " : "",
|
||||
(status & TD_CTRL_NAK) ? "NAK " : "",
|
||||
(status & TD_CTRL_CRCTIMEO) ? "CRC/Timeo " : "",
|
||||
(status & TD_CTRL_BITSTUFF) ? "BitStuff " : "",
|
||||
status & 0x7ff);
|
||||
|
||||
token = td_token(td);
|
||||
switch (uhci_packetid(token)) {
|
||||
case USB_PID_SETUP:
|
||||
spid = "SETUP";
|
||||
break;
|
||||
case USB_PID_OUT:
|
||||
spid = "OUT";
|
||||
break;
|
||||
case USB_PID_IN:
|
||||
spid = "IN";
|
||||
break;
|
||||
default:
|
||||
spid = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
out += sprintf(out, "MaxLen=%x DT%d EndPt=%x Dev=%x, PID=%x(%s) ",
|
||||
token >> 21,
|
||||
((token >> 19) & 1),
|
||||
(token >> 15) & 15,
|
||||
(token >> 8) & 127,
|
||||
(token & 0xff),
|
||||
spid);
|
||||
out += sprintf(out, "(buf=%08x)\n", le32_to_cpu(td->buffer));
|
||||
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space)
|
||||
{
|
||||
char *out = buf;
|
||||
struct urb_priv *urbp;
|
||||
struct list_head *head, *tmp;
|
||||
struct uhci_td *td;
|
||||
int i = 0, checked = 0, prevactive = 0;
|
||||
__le32 element = qh_element(qh);
|
||||
|
||||
/* Try to make sure there's enough memory */
|
||||
if (len < 80 * 6)
|
||||
return 0;
|
||||
|
||||
out += sprintf(out, "%*s[%p] link (%08x) element (%08x)\n", space, "",
|
||||
qh, le32_to_cpu(qh->link), le32_to_cpu(element));
|
||||
|
||||
if (element & UHCI_PTR_QH)
|
||||
out += sprintf(out, "%*s Element points to QH (bug?)\n", space, "");
|
||||
|
||||
if (element & UHCI_PTR_DEPTH)
|
||||
out += sprintf(out, "%*s Depth traverse\n", space, "");
|
||||
|
||||
if (element & cpu_to_le32(8))
|
||||
out += sprintf(out, "%*s Bit 3 set (bug?)\n", space, "");
|
||||
|
||||
if (!(element & ~(UHCI_PTR_QH | UHCI_PTR_DEPTH)))
|
||||
out += sprintf(out, "%*s Element is NULL (bug?)\n", space, "");
|
||||
|
||||
if (!qh->urbp) {
|
||||
out += sprintf(out, "%*s urbp == NULL\n", space, "");
|
||||
goto out;
|
||||
}
|
||||
|
||||
urbp = qh->urbp;
|
||||
|
||||
head = &urbp->td_list;
|
||||
tmp = head->next;
|
||||
|
||||
td = list_entry(tmp, struct uhci_td, list);
|
||||
|
||||
if (cpu_to_le32(td->dma_handle) != (element & ~UHCI_PTR_BITS))
|
||||
out += sprintf(out, "%*s Element != First TD\n", space, "");
|
||||
|
||||
while (tmp != head) {
|
||||
struct uhci_td *td = list_entry(tmp, struct uhci_td, list);
|
||||
|
||||
tmp = tmp->next;
|
||||
|
||||
out += sprintf(out, "%*s%d: ", space + 2, "", i++);
|
||||
out += uhci_show_td(td, out, len - (out - buf), 0);
|
||||
|
||||
if (i > 10 && !checked && prevactive && tmp != head &&
|
||||
debug <= 2) {
|
||||
struct list_head *ntmp = tmp;
|
||||
struct uhci_td *ntd = td;
|
||||
int active = 1, ni = i;
|
||||
|
||||
checked = 1;
|
||||
|
||||
while (ntmp != head && ntmp->next != head && active) {
|
||||
ntd = list_entry(ntmp, struct uhci_td, list);
|
||||
|
||||
ntmp = ntmp->next;
|
||||
|
||||
active = td_status(ntd) & TD_CTRL_ACTIVE;
|
||||
|
||||
ni++;
|
||||
}
|
||||
|
||||
if (active && ni > i) {
|
||||
out += sprintf(out, "%*s[skipped %d active TD's]\n", space, "", ni - i);
|
||||
tmp = ntmp;
|
||||
td = ntd;
|
||||
i = ni;
|
||||
}
|
||||
}
|
||||
|
||||
prevactive = td_status(td) & TD_CTRL_ACTIVE;
|
||||
}
|
||||
|
||||
if (list_empty(&urbp->queue_list) || urbp->queued)
|
||||
goto out;
|
||||
|
||||
out += sprintf(out, "%*sQueued QH's:\n", -space, "--");
|
||||
|
||||
head = &urbp->queue_list;
|
||||
tmp = head->next;
|
||||
|
||||
while (tmp != head) {
|
||||
struct urb_priv *nurbp = list_entry(tmp, struct urb_priv,
|
||||
queue_list);
|
||||
tmp = tmp->next;
|
||||
|
||||
out += uhci_show_qh(nurbp->qh, out, len - (out - buf), space);
|
||||
}
|
||||
|
||||
out:
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
#define show_frame_num() \
|
||||
if (!shown) { \
|
||||
shown = 1; \
|
||||
out += sprintf(out, "- Frame %d\n", i); \
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static const char *qh_names[] = {
|
||||
"skel_int128_qh", "skel_int64_qh",
|
||||
"skel_int32_qh", "skel_int16_qh",
|
||||
"skel_int8_qh", "skel_int4_qh",
|
||||
"skel_int2_qh", "skel_int1_qh",
|
||||
"skel_ls_control_qh", "skel_fs_control_qh",
|
||||
"skel_bulk_qh", "skel_term_qh"
|
||||
};
|
||||
|
||||
#define show_qh_name() \
|
||||
if (!shown) { \
|
||||
shown = 1; \
|
||||
out += sprintf(out, "- %s\n", qh_names[i]); \
|
||||
}
|
||||
|
||||
static int uhci_show_sc(int port, unsigned short status, char *buf, int len)
|
||||
{
|
||||
char *out = buf;
|
||||
|
||||
/* Try to make sure there's enough memory */
|
||||
if (len < 160)
|
||||
return 0;
|
||||
|
||||
out += sprintf(out, " stat%d = %04x %s%s%s%s%s%s%s%s%s%s\n",
|
||||
port,
|
||||
status,
|
||||
(status & USBPORTSC_SUSP) ? " Suspend" : "",
|
||||
(status & USBPORTSC_OCC) ? " OverCurrentChange" : "",
|
||||
(status & USBPORTSC_OC) ? " OverCurrent" : "",
|
||||
(status & USBPORTSC_PR) ? " Reset" : "",
|
||||
(status & USBPORTSC_LSDA) ? " LowSpeed" : "",
|
||||
(status & USBPORTSC_RD) ? " ResumeDetect" : "",
|
||||
(status & USBPORTSC_PEC) ? " EnableChange" : "",
|
||||
(status & USBPORTSC_PE) ? " Enabled" : "",
|
||||
(status & USBPORTSC_CSC) ? " ConnectChange" : "",
|
||||
(status & USBPORTSC_CCS) ? " Connected" : "");
|
||||
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
static int uhci_show_status(struct uhci_hcd *uhci, char *buf, int len)
|
||||
{
|
||||
char *out = buf;
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
unsigned short usbcmd, usbstat, usbint, usbfrnum;
|
||||
unsigned int flbaseadd;
|
||||
unsigned char sof;
|
||||
unsigned short portsc1, portsc2;
|
||||
|
||||
/* Try to make sure there's enough memory */
|
||||
if (len < 80 * 6)
|
||||
return 0;
|
||||
|
||||
usbcmd = inw(io_addr + 0);
|
||||
usbstat = inw(io_addr + 2);
|
||||
usbint = inw(io_addr + 4);
|
||||
usbfrnum = inw(io_addr + 6);
|
||||
flbaseadd = inl(io_addr + 8);
|
||||
sof = inb(io_addr + 12);
|
||||
portsc1 = inw(io_addr + 16);
|
||||
portsc2 = inw(io_addr + 18);
|
||||
|
||||
out += sprintf(out, " usbcmd = %04x %s%s%s%s%s%s%s%s\n",
|
||||
usbcmd,
|
||||
(usbcmd & USBCMD_MAXP) ? "Maxp64 " : "Maxp32 ",
|
||||
(usbcmd & USBCMD_CF) ? "CF " : "",
|
||||
(usbcmd & USBCMD_SWDBG) ? "SWDBG " : "",
|
||||
(usbcmd & USBCMD_FGR) ? "FGR " : "",
|
||||
(usbcmd & USBCMD_EGSM) ? "EGSM " : "",
|
||||
(usbcmd & USBCMD_GRESET) ? "GRESET " : "",
|
||||
(usbcmd & USBCMD_HCRESET) ? "HCRESET " : "",
|
||||
(usbcmd & USBCMD_RS) ? "RS " : "");
|
||||
|
||||
out += sprintf(out, " usbstat = %04x %s%s%s%s%s%s\n",
|
||||
usbstat,
|
||||
(usbstat & USBSTS_HCH) ? "HCHalted " : "",
|
||||
(usbstat & USBSTS_HCPE) ? "HostControllerProcessError " : "",
|
||||
(usbstat & USBSTS_HSE) ? "HostSystemError " : "",
|
||||
(usbstat & USBSTS_RD) ? "ResumeDetect " : "",
|
||||
(usbstat & USBSTS_ERROR) ? "USBError " : "",
|
||||
(usbstat & USBSTS_USBINT) ? "USBINT " : "");
|
||||
|
||||
out += sprintf(out, " usbint = %04x\n", usbint);
|
||||
out += sprintf(out, " usbfrnum = (%d)%03x\n", (usbfrnum >> 10) & 1,
|
||||
0xfff & (4*(unsigned int)usbfrnum));
|
||||
out += sprintf(out, " flbaseadd = %08x\n", flbaseadd);
|
||||
out += sprintf(out, " sof = %02x\n", sof);
|
||||
out += uhci_show_sc(1, portsc1, out, len - (out - buf));
|
||||
out += uhci_show_sc(2, portsc2, out, len - (out - buf));
|
||||
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
static int uhci_show_urbp(struct uhci_hcd *uhci, struct urb_priv *urbp, char *buf, int len)
|
||||
{
|
||||
struct list_head *tmp;
|
||||
char *out = buf;
|
||||
int count = 0;
|
||||
|
||||
if (len < 200)
|
||||
return 0;
|
||||
|
||||
out += sprintf(out, "urb_priv [%p] ", urbp);
|
||||
out += sprintf(out, "urb [%p] ", urbp->urb);
|
||||
out += sprintf(out, "qh [%p] ", urbp->qh);
|
||||
out += sprintf(out, "Dev=%d ", usb_pipedevice(urbp->urb->pipe));
|
||||
out += sprintf(out, "EP=%x(%s) ", usb_pipeendpoint(urbp->urb->pipe), (usb_pipein(urbp->urb->pipe) ? "IN" : "OUT"));
|
||||
|
||||
switch (usb_pipetype(urbp->urb->pipe)) {
|
||||
case PIPE_ISOCHRONOUS: out += sprintf(out, "ISO "); break;
|
||||
case PIPE_INTERRUPT: out += sprintf(out, "INT "); break;
|
||||
case PIPE_BULK: out += sprintf(out, "BLK "); break;
|
||||
case PIPE_CONTROL: out += sprintf(out, "CTL "); break;
|
||||
}
|
||||
|
||||
out += sprintf(out, "%s", (urbp->fsbr ? "FSBR " : ""));
|
||||
out += sprintf(out, "%s", (urbp->fsbr_timeout ? "FSBR_TO " : ""));
|
||||
|
||||
if (urbp->urb->status != -EINPROGRESS)
|
||||
out += sprintf(out, "Status=%d ", urbp->urb->status);
|
||||
//out += sprintf(out, "Inserttime=%lx ",urbp->inserttime);
|
||||
//out += sprintf(out, "FSBRtime=%lx ",urbp->fsbrtime);
|
||||
|
||||
count = 0;
|
||||
list_for_each(tmp, &urbp->td_list)
|
||||
count++;
|
||||
out += sprintf(out, "TDs=%d ",count);
|
||||
|
||||
if (urbp->queued)
|
||||
out += sprintf(out, "queued\n");
|
||||
else {
|
||||
count = 0;
|
||||
list_for_each(tmp, &urbp->queue_list)
|
||||
count++;
|
||||
out += sprintf(out, "queued URBs=%d\n", count);
|
||||
}
|
||||
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
static int uhci_show_lists(struct uhci_hcd *uhci, char *buf, int len)
|
||||
{
|
||||
char *out = buf;
|
||||
struct list_head *head, *tmp;
|
||||
int count;
|
||||
|
||||
out += sprintf(out, "Main list URBs:");
|
||||
if (list_empty(&uhci->urb_list))
|
||||
out += sprintf(out, " Empty\n");
|
||||
else {
|
||||
out += sprintf(out, "\n");
|
||||
count = 0;
|
||||
head = &uhci->urb_list;
|
||||
tmp = head->next;
|
||||
while (tmp != head) {
|
||||
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
|
||||
|
||||
out += sprintf(out, " %d: ", ++count);
|
||||
out += uhci_show_urbp(uhci, urbp, out, len - (out - buf));
|
||||
tmp = tmp->next;
|
||||
}
|
||||
}
|
||||
|
||||
out += sprintf(out, "Remove list URBs:");
|
||||
if (list_empty(&uhci->urb_remove_list))
|
||||
out += sprintf(out, " Empty\n");
|
||||
else {
|
||||
out += sprintf(out, "\n");
|
||||
count = 0;
|
||||
head = &uhci->urb_remove_list;
|
||||
tmp = head->next;
|
||||
while (tmp != head) {
|
||||
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
|
||||
|
||||
out += sprintf(out, " %d: ", ++count);
|
||||
out += uhci_show_urbp(uhci, urbp, out, len - (out - buf));
|
||||
tmp = tmp->next;
|
||||
}
|
||||
}
|
||||
|
||||
out += sprintf(out, "Complete list URBs:");
|
||||
if (list_empty(&uhci->complete_list))
|
||||
out += sprintf(out, " Empty\n");
|
||||
else {
|
||||
out += sprintf(out, "\n");
|
||||
count = 0;
|
||||
head = &uhci->complete_list;
|
||||
tmp = head->next;
|
||||
while (tmp != head) {
|
||||
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
|
||||
|
||||
out += sprintf(out, " %d: ", ++count);
|
||||
out += uhci_show_urbp(uhci, urbp, out, len - (out - buf));
|
||||
tmp = tmp->next;
|
||||
}
|
||||
}
|
||||
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
|
||||
{
|
||||
unsigned long flags;
|
||||
char *out = buf;
|
||||
int i, j;
|
||||
struct uhci_qh *qh;
|
||||
struct uhci_td *td;
|
||||
struct list_head *tmp, *head;
|
||||
|
||||
spin_lock_irqsave(&uhci->lock, flags);
|
||||
|
||||
out += sprintf(out, "HC status\n");
|
||||
out += uhci_show_status(uhci, out, len - (out - buf));
|
||||
|
||||
out += sprintf(out, "Frame List\n");
|
||||
for (i = 0; i < UHCI_NUMFRAMES; ++i) {
|
||||
int shown = 0;
|
||||
td = uhci->fl->frame_cpu[i];
|
||||
if (!td)
|
||||
continue;
|
||||
|
||||
if (td->dma_handle != (dma_addr_t)uhci->fl->frame[i]) {
|
||||
show_frame_num();
|
||||
out += sprintf(out, " frame list does not match td->dma_handle!\n");
|
||||
}
|
||||
show_frame_num();
|
||||
|
||||
head = &td->fl_list;
|
||||
tmp = head;
|
||||
do {
|
||||
td = list_entry(tmp, struct uhci_td, fl_list);
|
||||
tmp = tmp->next;
|
||||
out += uhci_show_td(td, out, len - (out - buf), 4);
|
||||
} while (tmp != head);
|
||||
}
|
||||
|
||||
out += sprintf(out, "Skeleton QH's\n");
|
||||
|
||||
for (i = 0; i < UHCI_NUM_SKELQH; ++i) {
|
||||
int shown = 0;
|
||||
|
||||
qh = uhci->skelqh[i];
|
||||
|
||||
if (debug > 1) {
|
||||
show_qh_name();
|
||||
out += uhci_show_qh(qh, out, len - (out - buf), 4);
|
||||
}
|
||||
|
||||
/* Last QH is the Terminating QH, it's different */
|
||||
if (i == UHCI_NUM_SKELQH - 1) {
|
||||
if (qh->link != UHCI_PTR_TERM)
|
||||
out += sprintf(out, " bandwidth reclamation on!\n");
|
||||
|
||||
if (qh_element(qh) != cpu_to_le32(uhci->term_td->dma_handle))
|
||||
out += sprintf(out, " skel_term_qh element is not set to term_td!\n");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
j = (i < 7) ? 7 : i+1; /* Next skeleton */
|
||||
if (list_empty(&qh->list)) {
|
||||
if (i < UHCI_NUM_SKELQH - 1) {
|
||||
if (qh->link !=
|
||||
(cpu_to_le32(uhci->skelqh[j]->dma_handle) | UHCI_PTR_QH)) {
|
||||
show_qh_name();
|
||||
out += sprintf(out, " skeleton QH not linked to next skeleton QH!\n");
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
show_qh_name();
|
||||
|
||||
head = &qh->list;
|
||||
tmp = head->next;
|
||||
|
||||
while (tmp != head) {
|
||||
qh = list_entry(tmp, struct uhci_qh, list);
|
||||
|
||||
tmp = tmp->next;
|
||||
|
||||
out += uhci_show_qh(qh, out, len - (out - buf), 4);
|
||||
}
|
||||
|
||||
if (i < UHCI_NUM_SKELQH - 1) {
|
||||
if (qh->link !=
|
||||
(cpu_to_le32(uhci->skelqh[j]->dma_handle) | UHCI_PTR_QH))
|
||||
out += sprintf(out, " last QH not linked to next skeleton!\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (debug > 2)
|
||||
out += uhci_show_lists(uhci, out, len - (out - buf));
|
||||
|
||||
spin_unlock_irqrestore(&uhci->lock, flags);
|
||||
|
||||
return out - buf;
|
||||
}
|
||||
|
||||
#define MAX_OUTPUT (64 * 1024)
|
||||
|
||||
struct uhci_debug {
|
||||
int size;
|
||||
char *data;
|
||||
struct uhci_hcd *uhci;
|
||||
};
|
||||
|
||||
static int uhci_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct uhci_hcd *uhci = inode->u.generic_ip;
|
||||
struct uhci_debug *up;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
lock_kernel();
|
||||
up = kmalloc(sizeof(*up), GFP_KERNEL);
|
||||
if (!up)
|
||||
goto out;
|
||||
|
||||
up->data = kmalloc(MAX_OUTPUT, GFP_KERNEL);
|
||||
if (!up->data) {
|
||||
kfree(up);
|
||||
goto out;
|
||||
}
|
||||
|
||||
up->size = uhci_sprint_schedule(uhci, up->data, MAX_OUTPUT);
|
||||
|
||||
file->private_data = up;
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
unlock_kernel();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static loff_t uhci_debug_lseek(struct file *file, loff_t off, int whence)
|
||||
{
|
||||
struct uhci_debug *up;
|
||||
loff_t new = -1;
|
||||
|
||||
lock_kernel();
|
||||
up = file->private_data;
|
||||
|
||||
switch (whence) {
|
||||
case 0:
|
||||
new = off;
|
||||
break;
|
||||
case 1:
|
||||
new = file->f_pos + off;
|
||||
break;
|
||||
}
|
||||
if (new < 0 || new > up->size) {
|
||||
unlock_kernel();
|
||||
return -EINVAL;
|
||||
}
|
||||
unlock_kernel();
|
||||
return (file->f_pos = new);
|
||||
}
|
||||
|
||||
static ssize_t uhci_debug_read(struct file *file, char __user *buf,
|
||||
size_t nbytes, loff_t *ppos)
|
||||
{
|
||||
struct uhci_debug *up = file->private_data;
|
||||
return simple_read_from_buffer(buf, nbytes, ppos, up->data, up->size);
|
||||
}
|
||||
|
||||
static int uhci_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct uhci_debug *up = file->private_data;
|
||||
|
||||
kfree(up->data);
|
||||
kfree(up);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct file_operations uhci_debug_operations = {
|
||||
.open = uhci_debug_open,
|
||||
.llseek = uhci_debug_lseek,
|
||||
.read = uhci_debug_read,
|
||||
.release = uhci_debug_release,
|
||||
};
|
||||
|
||||
#else /* CONFIG_DEBUG_FS */
|
||||
|
||||
#define uhci_debug_operations (* (struct file_operations *) NULL)
|
||||
|
||||
#endif
|
919
drivers/usb/host/uhci-hcd.c
一般檔案
919
drivers/usb/host/uhci-hcd.c
一般檔案
@@ -0,0 +1,919 @@
|
||||
/*
|
||||
* Universal Host Controller Interface driver for USB.
|
||||
*
|
||||
* Maintainer: Alan Stern <stern@rowland.harvard.edu>
|
||||
*
|
||||
* (C) Copyright 1999 Linus Torvalds
|
||||
* (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com
|
||||
* (C) Copyright 1999 Randy Dunlap
|
||||
* (C) Copyright 1999 Georg Acher, acher@in.tum.de
|
||||
* (C) Copyright 1999 Deti Fliegl, deti@fliegl.de
|
||||
* (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch
|
||||
* (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at
|
||||
* (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface
|
||||
* support from usb-ohci.c by Adam Richter, adam@yggdrasil.com).
|
||||
* (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c)
|
||||
* (C) Copyright 2004 Alan Stern, stern@rowland.harvard.edu
|
||||
*
|
||||
* Intel documents this fairly well, and as far as I know there
|
||||
* are no royalties or anything like that, but even so there are
|
||||
* people who decided that they want to do the same thing in a
|
||||
* completely different way.
|
||||
*
|
||||
* WARNING! The USB documentation is downright evil. Most of it
|
||||
* is just crap, written by a committee. You're better off ignoring
|
||||
* most of it, the important stuff is:
|
||||
* - the low-level protocol (fairly simple but lots of small details)
|
||||
* - working around the horridness of the rest
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#else
|
||||
#undef DEBUG
|
||||
#endif
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/system.h>
|
||||
|
||||
#include "../core/hcd.h"
|
||||
#include "uhci-hcd.h"
|
||||
|
||||
/*
|
||||
* Version Information
|
||||
*/
|
||||
#define DRIVER_VERSION "v2.2"
|
||||
#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, \
|
||||
Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, \
|
||||
Alan Stern"
|
||||
#define DRIVER_DESC "USB Universal Host Controller Interface driver"
|
||||
|
||||
/*
|
||||
* debug = 0, no debugging messages
|
||||
* debug = 1, dump failed URB's except for stalls
|
||||
* debug = 2, dump all failed URB's (including stalls)
|
||||
* show all queues in /debug/uhci/[pci_addr]
|
||||
* debug = 3, show all TD's in URB's when dumping
|
||||
*/
|
||||
#ifdef DEBUG
|
||||
static int debug = 1;
|
||||
#else
|
||||
static int debug = 0;
|
||||
#endif
|
||||
module_param(debug, int, S_IRUGO | S_IWUSR);
|
||||
MODULE_PARM_DESC(debug, "Debug level");
|
||||
static char *errbuf;
|
||||
#define ERRBUF_LEN (32 * 1024)
|
||||
|
||||
static kmem_cache_t *uhci_up_cachep; /* urb_priv */
|
||||
|
||||
static void uhci_get_current_frame_number(struct uhci_hcd *uhci);
|
||||
static void hc_state_transitions(struct uhci_hcd *uhci);
|
||||
|
||||
/* If a transfer is still active after this much time, turn off FSBR */
|
||||
#define IDLE_TIMEOUT msecs_to_jiffies(50)
|
||||
#define FSBR_DELAY msecs_to_jiffies(50)
|
||||
|
||||
/* When we timeout an idle transfer for FSBR, we'll switch it over to */
|
||||
/* depth first traversal. We'll do it in groups of this number of TD's */
|
||||
/* to make sure it doesn't hog all of the bandwidth */
|
||||
#define DEPTH_INTERVAL 5
|
||||
|
||||
#include "uhci-hub.c"
|
||||
#include "uhci-debug.c"
|
||||
#include "uhci-q.c"
|
||||
|
||||
static int init_stall_timer(struct usb_hcd *hcd);
|
||||
|
||||
static void stall_callback(unsigned long ptr)
|
||||
{
|
||||
struct usb_hcd *hcd = (struct usb_hcd *)ptr;
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
struct urb_priv *up;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&uhci->lock, flags);
|
||||
uhci_scan_schedule(uhci, NULL);
|
||||
|
||||
list_for_each_entry(up, &uhci->urb_list, urb_list) {
|
||||
struct urb *u = up->urb;
|
||||
|
||||
spin_lock(&u->lock);
|
||||
|
||||
/* Check if the FSBR timed out */
|
||||
if (up->fsbr && !up->fsbr_timeout && time_after_eq(jiffies, up->fsbrtime + IDLE_TIMEOUT))
|
||||
uhci_fsbr_timeout(uhci, u);
|
||||
|
||||
spin_unlock(&u->lock);
|
||||
}
|
||||
|
||||
/* Really disable FSBR */
|
||||
if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) {
|
||||
uhci->fsbrtimeout = 0;
|
||||
uhci->skel_term_qh->link = UHCI_PTR_TERM;
|
||||
}
|
||||
|
||||
/* Poll for and perform state transitions */
|
||||
hc_state_transitions(uhci);
|
||||
if (unlikely(uhci->suspended_ports && uhci->state != UHCI_SUSPENDED))
|
||||
uhci_check_ports(uhci);
|
||||
|
||||
init_stall_timer(hcd);
|
||||
spin_unlock_irqrestore(&uhci->lock, flags);
|
||||
}
|
||||
|
||||
static int init_stall_timer(struct usb_hcd *hcd)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
|
||||
init_timer(&uhci->stall_timer);
|
||||
uhci->stall_timer.function = stall_callback;
|
||||
uhci->stall_timer.data = (unsigned long)hcd;
|
||||
uhci->stall_timer.expires = jiffies + msecs_to_jiffies(100);
|
||||
add_timer(&uhci->stall_timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
unsigned short status;
|
||||
|
||||
/*
|
||||
* Read the interrupt status, and write it back to clear the
|
||||
* interrupt cause. Contrary to the UHCI specification, the
|
||||
* "HC Halted" status bit is persistent: it is RO, not R/WC.
|
||||
*/
|
||||
status = inw(io_addr + USBSTS);
|
||||
if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */
|
||||
return IRQ_NONE;
|
||||
outw(status, io_addr + USBSTS); /* Clear it */
|
||||
|
||||
if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) {
|
||||
if (status & USBSTS_HSE)
|
||||
dev_err(uhci_dev(uhci), "host system error, "
|
||||
"PCI problems?\n");
|
||||
if (status & USBSTS_HCPE)
|
||||
dev_err(uhci_dev(uhci), "host controller process "
|
||||
"error, something bad happened!\n");
|
||||
if ((status & USBSTS_HCH) && uhci->state > 0) {
|
||||
dev_err(uhci_dev(uhci), "host controller halted, "
|
||||
"very bad!\n");
|
||||
/* FIXME: Reset the controller, fix the offending TD */
|
||||
}
|
||||
}
|
||||
|
||||
if (status & USBSTS_RD)
|
||||
uhci->resume_detect = 1;
|
||||
|
||||
spin_lock(&uhci->lock);
|
||||
uhci_scan_schedule(uhci, regs);
|
||||
spin_unlock(&uhci->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void reset_hc(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
|
||||
/* Turn off PIRQ, SMI, and all interrupts. This also turns off
|
||||
* the BIOS's USB Legacy Support.
|
||||
*/
|
||||
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, 0);
|
||||
outw(0, uhci->io_addr + USBINTR);
|
||||
|
||||
/* Global reset for 50ms */
|
||||
uhci->state = UHCI_RESET;
|
||||
outw(USBCMD_GRESET, io_addr + USBCMD);
|
||||
msleep(50);
|
||||
outw(0, io_addr + USBCMD);
|
||||
|
||||
/* Another 10ms delay */
|
||||
msleep(10);
|
||||
uhci->resume_detect = 0;
|
||||
uhci->is_stopped = UHCI_IS_STOPPED;
|
||||
}
|
||||
|
||||
static void suspend_hc(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
|
||||
dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__);
|
||||
uhci->state = UHCI_SUSPENDED;
|
||||
uhci->resume_detect = 0;
|
||||
outw(USBCMD_EGSM, io_addr + USBCMD);
|
||||
|
||||
/* FIXME: Wait for the controller to actually stop */
|
||||
uhci_get_current_frame_number(uhci);
|
||||
uhci->is_stopped = UHCI_IS_STOPPED;
|
||||
|
||||
uhci_scan_schedule(uhci, NULL);
|
||||
}
|
||||
|
||||
static void wakeup_hc(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
|
||||
switch (uhci->state) {
|
||||
case UHCI_SUSPENDED: /* Start the resume */
|
||||
dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__);
|
||||
|
||||
/* Global resume for >= 20ms */
|
||||
outw(USBCMD_FGR | USBCMD_EGSM, io_addr + USBCMD);
|
||||
uhci->state = UHCI_RESUMING_1;
|
||||
uhci->state_end = jiffies + msecs_to_jiffies(20);
|
||||
uhci->is_stopped = 0;
|
||||
break;
|
||||
|
||||
case UHCI_RESUMING_1: /* End global resume */
|
||||
uhci->state = UHCI_RESUMING_2;
|
||||
outw(0, io_addr + USBCMD);
|
||||
/* Falls through */
|
||||
|
||||
case UHCI_RESUMING_2: /* Wait for EOP to be sent */
|
||||
if (inw(io_addr + USBCMD) & USBCMD_FGR)
|
||||
break;
|
||||
|
||||
/* Run for at least 1 second, and
|
||||
* mark it configured with a 64-byte max packet */
|
||||
uhci->state = UHCI_RUNNING_GRACE;
|
||||
uhci->state_end = jiffies + HZ;
|
||||
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP,
|
||||
io_addr + USBCMD);
|
||||
break;
|
||||
|
||||
case UHCI_RUNNING_GRACE: /* Now allowed to suspend */
|
||||
uhci->state = UHCI_RUNNING;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int ports_active(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
int connection = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < uhci->rh_numports; i++)
|
||||
connection |= (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_CCS);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
static int suspend_allowed(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
int i;
|
||||
|
||||
if (to_pci_dev(uhci_dev(uhci))->vendor != PCI_VENDOR_ID_INTEL)
|
||||
return 1;
|
||||
|
||||
/* Some of Intel's USB controllers have a bug that causes false
|
||||
* resume indications if any port has an over current condition.
|
||||
* To prevent problems, we will not allow a global suspend if
|
||||
* any ports are OC.
|
||||
*
|
||||
* Some motherboards using Intel's chipsets (but not using all
|
||||
* the USB ports) appear to hardwire the over current inputs active
|
||||
* to disable the USB ports.
|
||||
*/
|
||||
|
||||
/* check for over current condition on any port */
|
||||
for (i = 0; i < uhci->rh_numports; i++) {
|
||||
if (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_OC)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void hc_state_transitions(struct uhci_hcd *uhci)
|
||||
{
|
||||
switch (uhci->state) {
|
||||
case UHCI_RUNNING:
|
||||
|
||||
/* global suspend if nothing connected for 1 second */
|
||||
if (!ports_active(uhci) && suspend_allowed(uhci)) {
|
||||
uhci->state = UHCI_SUSPENDING_GRACE;
|
||||
uhci->state_end = jiffies + HZ;
|
||||
}
|
||||
break;
|
||||
|
||||
case UHCI_SUSPENDING_GRACE:
|
||||
if (ports_active(uhci))
|
||||
uhci->state = UHCI_RUNNING;
|
||||
else if (time_after_eq(jiffies, uhci->state_end))
|
||||
suspend_hc(uhci);
|
||||
break;
|
||||
|
||||
case UHCI_SUSPENDED:
|
||||
|
||||
/* wakeup if requested by a device */
|
||||
if (uhci->resume_detect)
|
||||
wakeup_hc(uhci);
|
||||
break;
|
||||
|
||||
case UHCI_RESUMING_1:
|
||||
case UHCI_RESUMING_2:
|
||||
case UHCI_RUNNING_GRACE:
|
||||
if (time_after_eq(jiffies, uhci->state_end))
|
||||
wakeup_hc(uhci);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the current frame number in uhci->frame_number if the controller
|
||||
* is runnning
|
||||
*/
|
||||
static void uhci_get_current_frame_number(struct uhci_hcd *uhci)
|
||||
{
|
||||
if (!uhci->is_stopped)
|
||||
uhci->frame_number = inw(uhci->io_addr + USBFRNUM);
|
||||
}
|
||||
|
||||
static int start_hc(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned long io_addr = uhci->io_addr;
|
||||
int timeout = 10;
|
||||
|
||||
/*
|
||||
* Reset the HC - this will force us to get a
|
||||
* new notification of any already connected
|
||||
* ports due to the virtual disconnect that it
|
||||
* implies.
|
||||
*/
|
||||
outw(USBCMD_HCRESET, io_addr + USBCMD);
|
||||
while (inw(io_addr + USBCMD) & USBCMD_HCRESET) {
|
||||
if (--timeout < 0) {
|
||||
dev_err(uhci_dev(uhci), "USBCMD_HCRESET timed out!\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
/* Mark controller as running before we enable interrupts */
|
||||
uhci_to_hcd(uhci)->state = HC_STATE_RUNNING;
|
||||
|
||||
/* Turn on PIRQ and all interrupts */
|
||||
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,
|
||||
USBLEGSUP_DEFAULT);
|
||||
outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,
|
||||
io_addr + USBINTR);
|
||||
|
||||
/* Start at frame 0 */
|
||||
outw(0, io_addr + USBFRNUM);
|
||||
outl(uhci->fl->dma_handle, io_addr + USBFLBASEADD);
|
||||
|
||||
/* Run and mark it configured with a 64-byte max packet */
|
||||
uhci->state = UHCI_RUNNING_GRACE;
|
||||
uhci->state_end = jiffies + HZ;
|
||||
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD);
|
||||
uhci->is_stopped = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* De-allocate all resources
|
||||
*/
|
||||
static void release_uhci(struct uhci_hcd *uhci)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < UHCI_NUM_SKELQH; i++)
|
||||
if (uhci->skelqh[i]) {
|
||||
uhci_free_qh(uhci, uhci->skelqh[i]);
|
||||
uhci->skelqh[i] = NULL;
|
||||
}
|
||||
|
||||
if (uhci->term_td) {
|
||||
uhci_free_td(uhci, uhci->term_td);
|
||||
uhci->term_td = NULL;
|
||||
}
|
||||
|
||||
if (uhci->qh_pool) {
|
||||
dma_pool_destroy(uhci->qh_pool);
|
||||
uhci->qh_pool = NULL;
|
||||
}
|
||||
|
||||
if (uhci->td_pool) {
|
||||
dma_pool_destroy(uhci->td_pool);
|
||||
uhci->td_pool = NULL;
|
||||
}
|
||||
|
||||
if (uhci->fl) {
|
||||
dma_free_coherent(uhci_dev(uhci), sizeof(*uhci->fl),
|
||||
uhci->fl, uhci->fl->dma_handle);
|
||||
uhci->fl = NULL;
|
||||
}
|
||||
|
||||
if (uhci->dentry) {
|
||||
debugfs_remove(uhci->dentry);
|
||||
uhci->dentry = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int uhci_reset(struct usb_hcd *hcd)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
|
||||
uhci->io_addr = (unsigned long) hcd->rsrc_start;
|
||||
|
||||
/* Kick BIOS off this hardware and reset, so we won't get
|
||||
* interrupts from any previous setup.
|
||||
*/
|
||||
reset_hc(uhci);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a frame list, and then setup the skeleton
|
||||
*
|
||||
* The hardware doesn't really know any difference
|
||||
* in the queues, but the order does matter for the
|
||||
* protocols higher up. The order is:
|
||||
*
|
||||
* - any isochronous events handled before any
|
||||
* of the queues. We don't do that here, because
|
||||
* we'll create the actual TD entries on demand.
|
||||
* - The first queue is the interrupt queue.
|
||||
* - The second queue is the control queue, split into low- and full-speed
|
||||
* - The third queue is bulk queue.
|
||||
* - The fourth queue is the bandwidth reclamation queue, which loops back
|
||||
* to the full-speed control queue.
|
||||
*/
|
||||
static int uhci_start(struct usb_hcd *hcd)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
int retval = -EBUSY;
|
||||
int i, port;
|
||||
unsigned io_size;
|
||||
dma_addr_t dma_handle;
|
||||
struct usb_device *udev;
|
||||
struct dentry *dentry;
|
||||
|
||||
io_size = (unsigned) hcd->rsrc_len;
|
||||
|
||||
dentry = debugfs_create_file(hcd->self.bus_name, S_IFREG|S_IRUGO|S_IWUSR, uhci_debugfs_root, uhci, &uhci_debug_operations);
|
||||
if (!dentry) {
|
||||
dev_err(uhci_dev(uhci), "couldn't create uhci debugfs entry\n");
|
||||
retval = -ENOMEM;
|
||||
goto err_create_debug_entry;
|
||||
}
|
||||
uhci->dentry = dentry;
|
||||
|
||||
uhci->fsbr = 0;
|
||||
uhci->fsbrtimeout = 0;
|
||||
|
||||
spin_lock_init(&uhci->lock);
|
||||
INIT_LIST_HEAD(&uhci->qh_remove_list);
|
||||
|
||||
INIT_LIST_HEAD(&uhci->td_remove_list);
|
||||
|
||||
INIT_LIST_HEAD(&uhci->urb_remove_list);
|
||||
|
||||
INIT_LIST_HEAD(&uhci->urb_list);
|
||||
|
||||
INIT_LIST_HEAD(&uhci->complete_list);
|
||||
|
||||
init_waitqueue_head(&uhci->waitqh);
|
||||
|
||||
uhci->fl = dma_alloc_coherent(uhci_dev(uhci), sizeof(*uhci->fl),
|
||||
&dma_handle, 0);
|
||||
if (!uhci->fl) {
|
||||
dev_err(uhci_dev(uhci), "unable to allocate "
|
||||
"consistent memory for frame list\n");
|
||||
goto err_alloc_fl;
|
||||
}
|
||||
|
||||
memset((void *)uhci->fl, 0, sizeof(*uhci->fl));
|
||||
|
||||
uhci->fl->dma_handle = dma_handle;
|
||||
|
||||
uhci->td_pool = dma_pool_create("uhci_td", uhci_dev(uhci),
|
||||
sizeof(struct uhci_td), 16, 0);
|
||||
if (!uhci->td_pool) {
|
||||
dev_err(uhci_dev(uhci), "unable to create td dma_pool\n");
|
||||
goto err_create_td_pool;
|
||||
}
|
||||
|
||||
uhci->qh_pool = dma_pool_create("uhci_qh", uhci_dev(uhci),
|
||||
sizeof(struct uhci_qh), 16, 0);
|
||||
if (!uhci->qh_pool) {
|
||||
dev_err(uhci_dev(uhci), "unable to create qh dma_pool\n");
|
||||
goto err_create_qh_pool;
|
||||
}
|
||||
|
||||
/* Initialize the root hub */
|
||||
|
||||
/* UHCI specs says devices must have 2 ports, but goes on to say */
|
||||
/* they may have more but give no way to determine how many they */
|
||||
/* have. However, according to the UHCI spec, Bit 7 is always set */
|
||||
/* to 1. So we try to use this to our advantage */
|
||||
for (port = 0; port < (io_size - 0x10) / 2; port++) {
|
||||
unsigned int portstatus;
|
||||
|
||||
portstatus = inw(uhci->io_addr + 0x10 + (port * 2));
|
||||
if (!(portstatus & 0x0080))
|
||||
break;
|
||||
}
|
||||
if (debug)
|
||||
dev_info(uhci_dev(uhci), "detected %d ports\n", port);
|
||||
|
||||
/* This is experimental so anything less than 2 or greater than 8 is */
|
||||
/* something weird and we'll ignore it */
|
||||
if (port < 2 || port > UHCI_RH_MAXCHILD) {
|
||||
dev_info(uhci_dev(uhci), "port count misdetected? "
|
||||
"forcing to 2 ports\n");
|
||||
port = 2;
|
||||
}
|
||||
|
||||
uhci->rh_numports = port;
|
||||
|
||||
udev = usb_alloc_dev(NULL, &hcd->self, 0);
|
||||
if (!udev) {
|
||||
dev_err(uhci_dev(uhci), "unable to allocate root hub\n");
|
||||
goto err_alloc_root_hub;
|
||||
}
|
||||
|
||||
uhci->term_td = uhci_alloc_td(uhci, udev);
|
||||
if (!uhci->term_td) {
|
||||
dev_err(uhci_dev(uhci), "unable to allocate terminating TD\n");
|
||||
goto err_alloc_term_td;
|
||||
}
|
||||
|
||||
for (i = 0; i < UHCI_NUM_SKELQH; i++) {
|
||||
uhci->skelqh[i] = uhci_alloc_qh(uhci, udev);
|
||||
if (!uhci->skelqh[i]) {
|
||||
dev_err(uhci_dev(uhci), "unable to allocate QH\n");
|
||||
goto err_alloc_skelqh;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 8 Interrupt queues; link all higher int queues to int1,
|
||||
* then link int1 to control and control to bulk
|
||||
*/
|
||||
uhci->skel_int128_qh->link =
|
||||
uhci->skel_int64_qh->link =
|
||||
uhci->skel_int32_qh->link =
|
||||
uhci->skel_int16_qh->link =
|
||||
uhci->skel_int8_qh->link =
|
||||
uhci->skel_int4_qh->link =
|
||||
uhci->skel_int2_qh->link =
|
||||
cpu_to_le32(uhci->skel_int1_qh->dma_handle) | UHCI_PTR_QH;
|
||||
uhci->skel_int1_qh->link = cpu_to_le32(uhci->skel_ls_control_qh->dma_handle) | UHCI_PTR_QH;
|
||||
|
||||
uhci->skel_ls_control_qh->link = cpu_to_le32(uhci->skel_fs_control_qh->dma_handle) | UHCI_PTR_QH;
|
||||
uhci->skel_fs_control_qh->link = cpu_to_le32(uhci->skel_bulk_qh->dma_handle) | UHCI_PTR_QH;
|
||||
uhci->skel_bulk_qh->link = cpu_to_le32(uhci->skel_term_qh->dma_handle) | UHCI_PTR_QH;
|
||||
|
||||
/* This dummy TD is to work around a bug in Intel PIIX controllers */
|
||||
uhci_fill_td(uhci->term_td, 0, (UHCI_NULL_DATA_SIZE << 21) |
|
||||
(0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0);
|
||||
uhci->term_td->link = cpu_to_le32(uhci->term_td->dma_handle);
|
||||
|
||||
uhci->skel_term_qh->link = UHCI_PTR_TERM;
|
||||
uhci->skel_term_qh->element = cpu_to_le32(uhci->term_td->dma_handle);
|
||||
|
||||
/*
|
||||
* Fill the frame list: make all entries point to the proper
|
||||
* interrupt queue.
|
||||
*
|
||||
* The interrupt queues will be interleaved as evenly as possible.
|
||||
* There's not much to be done about period-1 interrupts; they have
|
||||
* to occur in every frame. But we can schedule period-2 interrupts
|
||||
* in odd-numbered frames, period-4 interrupts in frames congruent
|
||||
* to 2 (mod 4), and so on. This way each frame only has two
|
||||
* interrupt QHs, which will help spread out bandwidth utilization.
|
||||
*/
|
||||
for (i = 0; i < UHCI_NUMFRAMES; i++) {
|
||||
int irq;
|
||||
|
||||
/*
|
||||
* ffs (Find First bit Set) does exactly what we need:
|
||||
* 1,3,5,... => ffs = 0 => use skel_int2_qh = skelqh[6],
|
||||
* 2,6,10,... => ffs = 1 => use skel_int4_qh = skelqh[5], etc.
|
||||
* ffs > 6 => not on any high-period queue, so use
|
||||
* skel_int1_qh = skelqh[7].
|
||||
* Add UHCI_NUMFRAMES to insure at least one bit is set.
|
||||
*/
|
||||
irq = 6 - (int) __ffs(i + UHCI_NUMFRAMES);
|
||||
if (irq < 0)
|
||||
irq = 7;
|
||||
|
||||
/* Only place we don't use the frame list routines */
|
||||
uhci->fl->frame[i] = UHCI_PTR_QH |
|
||||
cpu_to_le32(uhci->skelqh[irq]->dma_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some architectures require a full mb() to enforce completion of
|
||||
* the memory writes above before the I/O transfers in start_hc().
|
||||
*/
|
||||
mb();
|
||||
if ((retval = start_hc(uhci)) != 0)
|
||||
goto err_alloc_skelqh;
|
||||
|
||||
init_stall_timer(hcd);
|
||||
|
||||
udev->speed = USB_SPEED_FULL;
|
||||
|
||||
if (usb_hcd_register_root_hub(udev, hcd) != 0) {
|
||||
dev_err(uhci_dev(uhci), "unable to start root hub\n");
|
||||
retval = -ENOMEM;
|
||||
goto err_start_root_hub;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* error exits:
|
||||
*/
|
||||
err_start_root_hub:
|
||||
reset_hc(uhci);
|
||||
|
||||
del_timer_sync(&uhci->stall_timer);
|
||||
|
||||
err_alloc_skelqh:
|
||||
for (i = 0; i < UHCI_NUM_SKELQH; i++)
|
||||
if (uhci->skelqh[i]) {
|
||||
uhci_free_qh(uhci, uhci->skelqh[i]);
|
||||
uhci->skelqh[i] = NULL;
|
||||
}
|
||||
|
||||
uhci_free_td(uhci, uhci->term_td);
|
||||
uhci->term_td = NULL;
|
||||
|
||||
err_alloc_term_td:
|
||||
usb_put_dev(udev);
|
||||
|
||||
err_alloc_root_hub:
|
||||
dma_pool_destroy(uhci->qh_pool);
|
||||
uhci->qh_pool = NULL;
|
||||
|
||||
err_create_qh_pool:
|
||||
dma_pool_destroy(uhci->td_pool);
|
||||
uhci->td_pool = NULL;
|
||||
|
||||
err_create_td_pool:
|
||||
dma_free_coherent(uhci_dev(uhci), sizeof(*uhci->fl),
|
||||
uhci->fl, uhci->fl->dma_handle);
|
||||
uhci->fl = NULL;
|
||||
|
||||
err_alloc_fl:
|
||||
debugfs_remove(uhci->dentry);
|
||||
uhci->dentry = NULL;
|
||||
|
||||
err_create_debug_entry:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void uhci_stop(struct usb_hcd *hcd)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
|
||||
del_timer_sync(&uhci->stall_timer);
|
||||
reset_hc(uhci);
|
||||
|
||||
spin_lock_irq(&uhci->lock);
|
||||
uhci_scan_schedule(uhci, NULL);
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
|
||||
release_uhci(uhci);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int uhci_suspend(struct usb_hcd *hcd, u32 state)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
|
||||
spin_lock_irq(&uhci->lock);
|
||||
|
||||
/* Don't try to suspend broken motherboards, reset instead */
|
||||
if (suspend_allowed(uhci))
|
||||
suspend_hc(uhci);
|
||||
else {
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
reset_hc(uhci);
|
||||
spin_lock_irq(&uhci->lock);
|
||||
uhci_scan_schedule(uhci, NULL);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhci_resume(struct usb_hcd *hcd)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
int rc;
|
||||
|
||||
pci_set_master(to_pci_dev(uhci_dev(uhci)));
|
||||
|
||||
spin_lock_irq(&uhci->lock);
|
||||
|
||||
if (uhci->state == UHCI_SUSPENDED) {
|
||||
|
||||
/*
|
||||
* Some systems don't maintain the UHCI register values
|
||||
* during a PM suspend/resume cycle, so reinitialize
|
||||
* the Frame Number, Framelist Base Address, Interrupt
|
||||
* Enable, and Legacy Support registers.
|
||||
*/
|
||||
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,
|
||||
0);
|
||||
outw(uhci->frame_number, uhci->io_addr + USBFRNUM);
|
||||
outl(uhci->fl->dma_handle, uhci->io_addr + USBFLBASEADD);
|
||||
outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC |
|
||||
USBINTR_SP, uhci->io_addr + USBINTR);
|
||||
uhci->resume_detect = 1;
|
||||
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,
|
||||
USBLEGSUP_DEFAULT);
|
||||
} else {
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
reset_hc(uhci);
|
||||
if ((rc = start_hc(uhci)) != 0)
|
||||
return rc;
|
||||
spin_lock_irq(&uhci->lock);
|
||||
}
|
||||
hcd->state = HC_STATE_RUNNING;
|
||||
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Wait until all the URBs for a particular device/endpoint are gone */
|
||||
static void uhci_hcd_endpoint_disable(struct usb_hcd *hcd,
|
||||
struct usb_host_endpoint *ep)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
|
||||
wait_event_interruptible(uhci->waitqh, list_empty(&ep->urb_list));
|
||||
}
|
||||
|
||||
static int uhci_hcd_get_frame_number(struct usb_hcd *hcd)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
int frame_number;
|
||||
unsigned long flags;
|
||||
|
||||
/* Minimize latency by avoiding the spinlock */
|
||||
local_irq_save(flags);
|
||||
rmb();
|
||||
frame_number = (uhci->is_stopped ? uhci->frame_number :
|
||||
inw(uhci->io_addr + USBFRNUM));
|
||||
local_irq_restore(flags);
|
||||
return frame_number;
|
||||
}
|
||||
|
||||
static const char hcd_name[] = "uhci_hcd";
|
||||
|
||||
static const struct hc_driver uhci_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "UHCI Host Controller",
|
||||
.hcd_priv_size = sizeof(struct uhci_hcd),
|
||||
|
||||
/* Generic hardware linkage */
|
||||
.irq = uhci_irq,
|
||||
.flags = HCD_USB11,
|
||||
|
||||
/* Basic lifecycle operations */
|
||||
.reset = uhci_reset,
|
||||
.start = uhci_start,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = uhci_suspend,
|
||||
.resume = uhci_resume,
|
||||
#endif
|
||||
.stop = uhci_stop,
|
||||
|
||||
.urb_enqueue = uhci_urb_enqueue,
|
||||
.urb_dequeue = uhci_urb_dequeue,
|
||||
|
||||
.endpoint_disable = uhci_hcd_endpoint_disable,
|
||||
.get_frame_number = uhci_hcd_get_frame_number,
|
||||
|
||||
.hub_status_data = uhci_hub_status_data,
|
||||
.hub_control = uhci_hub_control,
|
||||
};
|
||||
|
||||
static const struct pci_device_id uhci_pci_ids[] = { {
|
||||
/* handle any USB UHCI controller */
|
||||
PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x00), ~0),
|
||||
.driver_data = (unsigned long) &uhci_driver,
|
||||
}, { /* end: all zeroes */ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, uhci_pci_ids);
|
||||
|
||||
static struct pci_driver uhci_pci_driver = {
|
||||
.name = (char *)hcd_name,
|
||||
.id_table = uhci_pci_ids,
|
||||
|
||||
.probe = usb_hcd_pci_probe,
|
||||
.remove = usb_hcd_pci_remove,
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = usb_hcd_pci_suspend,
|
||||
.resume = usb_hcd_pci_resume,
|
||||
#endif /* PM */
|
||||
};
|
||||
|
||||
static int __init uhci_hcd_init(void)
|
||||
{
|
||||
int retval = -ENOMEM;
|
||||
|
||||
printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "\n");
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
if (debug) {
|
||||
errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL);
|
||||
if (!errbuf)
|
||||
goto errbuf_failed;
|
||||
}
|
||||
|
||||
uhci_debugfs_root = debugfs_create_dir("uhci", NULL);
|
||||
if (!uhci_debugfs_root)
|
||||
goto debug_failed;
|
||||
|
||||
uhci_up_cachep = kmem_cache_create("uhci_urb_priv",
|
||||
sizeof(struct urb_priv), 0, 0, NULL, NULL);
|
||||
if (!uhci_up_cachep)
|
||||
goto up_failed;
|
||||
|
||||
retval = pci_register_driver(&uhci_pci_driver);
|
||||
if (retval)
|
||||
goto init_failed;
|
||||
|
||||
return 0;
|
||||
|
||||
init_failed:
|
||||
if (kmem_cache_destroy(uhci_up_cachep))
|
||||
warn("not all urb_priv's were freed!");
|
||||
|
||||
up_failed:
|
||||
debugfs_remove(uhci_debugfs_root);
|
||||
|
||||
debug_failed:
|
||||
if (errbuf)
|
||||
kfree(errbuf);
|
||||
|
||||
errbuf_failed:
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void __exit uhci_hcd_cleanup(void)
|
||||
{
|
||||
pci_unregister_driver(&uhci_pci_driver);
|
||||
|
||||
if (kmem_cache_destroy(uhci_up_cachep))
|
||||
warn("not all urb_priv's were freed!");
|
||||
|
||||
debugfs_remove(uhci_debugfs_root);
|
||||
|
||||
if (errbuf)
|
||||
kfree(errbuf);
|
||||
}
|
||||
|
||||
module_init(uhci_hcd_init);
|
||||
module_exit(uhci_hcd_cleanup);
|
||||
|
||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
454
drivers/usb/host/uhci-hcd.h
一般檔案
454
drivers/usb/host/uhci-hcd.h
一般檔案
@@ -0,0 +1,454 @@
|
||||
#ifndef __LINUX_UHCI_HCD_H
|
||||
#define __LINUX_UHCI_HCD_H
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#define usb_packetid(pipe) (usb_pipein(pipe) ? USB_PID_IN : USB_PID_OUT)
|
||||
#define PIPE_DEVEP_MASK 0x0007ff00
|
||||
|
||||
/*
|
||||
* Universal Host Controller Interface data structures and defines
|
||||
*/
|
||||
|
||||
/* Command register */
|
||||
#define USBCMD 0
|
||||
#define USBCMD_RS 0x0001 /* Run/Stop */
|
||||
#define USBCMD_HCRESET 0x0002 /* Host reset */
|
||||
#define USBCMD_GRESET 0x0004 /* Global reset */
|
||||
#define USBCMD_EGSM 0x0008 /* Global Suspend Mode */
|
||||
#define USBCMD_FGR 0x0010 /* Force Global Resume */
|
||||
#define USBCMD_SWDBG 0x0020 /* SW Debug mode */
|
||||
#define USBCMD_CF 0x0040 /* Config Flag (sw only) */
|
||||
#define USBCMD_MAXP 0x0080 /* Max Packet (0 = 32, 1 = 64) */
|
||||
|
||||
/* Status register */
|
||||
#define USBSTS 2
|
||||
#define USBSTS_USBINT 0x0001 /* Interrupt due to IOC */
|
||||
#define USBSTS_ERROR 0x0002 /* Interrupt due to error */
|
||||
#define USBSTS_RD 0x0004 /* Resume Detect */
|
||||
#define USBSTS_HSE 0x0008 /* Host System Error - basically PCI problems */
|
||||
#define USBSTS_HCPE 0x0010 /* Host Controller Process Error - the scripts were buggy */
|
||||
#define USBSTS_HCH 0x0020 /* HC Halted */
|
||||
|
||||
/* Interrupt enable register */
|
||||
#define USBINTR 4
|
||||
#define USBINTR_TIMEOUT 0x0001 /* Timeout/CRC error enable */
|
||||
#define USBINTR_RESUME 0x0002 /* Resume interrupt enable */
|
||||
#define USBINTR_IOC 0x0004 /* Interrupt On Complete enable */
|
||||
#define USBINTR_SP 0x0008 /* Short packet interrupt enable */
|
||||
|
||||
#define USBFRNUM 6
|
||||
#define USBFLBASEADD 8
|
||||
#define USBSOF 12
|
||||
|
||||
/* USB port status and control registers */
|
||||
#define USBPORTSC1 16
|
||||
#define USBPORTSC2 18
|
||||
#define USBPORTSC_CCS 0x0001 /* Current Connect Status ("device present") */
|
||||
#define USBPORTSC_CSC 0x0002 /* Connect Status Change */
|
||||
#define USBPORTSC_PE 0x0004 /* Port Enable */
|
||||
#define USBPORTSC_PEC 0x0008 /* Port Enable Change */
|
||||
#define USBPORTSC_DPLUS 0x0010 /* D+ high (line status) */
|
||||
#define USBPORTSC_DMINUS 0x0020 /* D- high (line status) */
|
||||
#define USBPORTSC_RD 0x0040 /* Resume Detect */
|
||||
#define USBPORTSC_RES1 0x0080 /* reserved, always 1 */
|
||||
#define USBPORTSC_LSDA 0x0100 /* Low Speed Device Attached */
|
||||
#define USBPORTSC_PR 0x0200 /* Port Reset */
|
||||
/* OC and OCC from Intel 430TX and later (not UHCI 1.1d spec) */
|
||||
#define USBPORTSC_OC 0x0400 /* Over Current condition */
|
||||
#define USBPORTSC_OCC 0x0800 /* Over Current Change R/WC */
|
||||
#define USBPORTSC_SUSP 0x1000 /* Suspend */
|
||||
#define USBPORTSC_RES2 0x2000 /* reserved, write zeroes */
|
||||
#define USBPORTSC_RES3 0x4000 /* reserved, write zeroes */
|
||||
#define USBPORTSC_RES4 0x8000 /* reserved, write zeroes */
|
||||
|
||||
/* Legacy support register */
|
||||
#define USBLEGSUP 0xc0
|
||||
#define USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */
|
||||
|
||||
#define UHCI_NULL_DATA_SIZE 0x7FF /* for UHCI controller TD */
|
||||
|
||||
#define UHCI_PTR_BITS cpu_to_le32(0x000F)
|
||||
#define UHCI_PTR_TERM cpu_to_le32(0x0001)
|
||||
#define UHCI_PTR_QH cpu_to_le32(0x0002)
|
||||
#define UHCI_PTR_DEPTH cpu_to_le32(0x0004)
|
||||
#define UHCI_PTR_BREADTH cpu_to_le32(0x0000)
|
||||
|
||||
#define UHCI_NUMFRAMES 1024 /* in the frame list [array] */
|
||||
#define UHCI_MAX_SOF_NUMBER 2047 /* in an SOF packet */
|
||||
#define CAN_SCHEDULE_FRAMES 1000 /* how far future frames can be scheduled */
|
||||
|
||||
struct uhci_frame_list {
|
||||
__le32 frame[UHCI_NUMFRAMES];
|
||||
|
||||
void *frame_cpu[UHCI_NUMFRAMES];
|
||||
|
||||
dma_addr_t dma_handle;
|
||||
};
|
||||
|
||||
struct urb_priv;
|
||||
|
||||
/*
|
||||
* One role of a QH is to hold a queue of TDs for some endpoint. Each QH is
|
||||
* used with one URB, and qh->element (updated by the HC) is either:
|
||||
* - the next unprocessed TD for the URB, or
|
||||
* - UHCI_PTR_TERM (when there's no more traffic for this endpoint), or
|
||||
* - the QH for the next URB queued to the same endpoint.
|
||||
*
|
||||
* The other role of a QH is to serve as a "skeleton" framelist entry, so we
|
||||
* can easily splice a QH for some endpoint into the schedule at the right
|
||||
* place. Then qh->element is UHCI_PTR_TERM.
|
||||
*
|
||||
* In the frame list, qh->link maintains a list of QHs seen by the HC:
|
||||
* skel1 --> ep1-qh --> ep2-qh --> ... --> skel2 --> ...
|
||||
*/
|
||||
struct uhci_qh {
|
||||
/* Hardware fields */
|
||||
__le32 link; /* Next queue */
|
||||
__le32 element; /* Queue element pointer */
|
||||
|
||||
/* Software fields */
|
||||
dma_addr_t dma_handle;
|
||||
|
||||
struct usb_device *dev;
|
||||
struct urb_priv *urbp;
|
||||
|
||||
struct list_head list; /* P: uhci->frame_list_lock */
|
||||
struct list_head remove_list; /* P: uhci->remove_list_lock */
|
||||
} __attribute__((aligned(16)));
|
||||
|
||||
/*
|
||||
* We need a special accessor for the element pointer because it is
|
||||
* subject to asynchronous updates by the controller
|
||||
*/
|
||||
static __le32 inline qh_element(struct uhci_qh *qh) {
|
||||
__le32 element = qh->element;
|
||||
|
||||
barrier();
|
||||
return element;
|
||||
}
|
||||
|
||||
/*
|
||||
* for TD <status>:
|
||||
*/
|
||||
#define TD_CTRL_SPD (1 << 29) /* Short Packet Detect */
|
||||
#define TD_CTRL_C_ERR_MASK (3 << 27) /* Error Counter bits */
|
||||
#define TD_CTRL_C_ERR_SHIFT 27
|
||||
#define TD_CTRL_LS (1 << 26) /* Low Speed Device */
|
||||
#define TD_CTRL_IOS (1 << 25) /* Isochronous Select */
|
||||
#define TD_CTRL_IOC (1 << 24) /* Interrupt on Complete */
|
||||
#define TD_CTRL_ACTIVE (1 << 23) /* TD Active */
|
||||
#define TD_CTRL_STALLED (1 << 22) /* TD Stalled */
|
||||
#define TD_CTRL_DBUFERR (1 << 21) /* Data Buffer Error */
|
||||
#define TD_CTRL_BABBLE (1 << 20) /* Babble Detected */
|
||||
#define TD_CTRL_NAK (1 << 19) /* NAK Received */
|
||||
#define TD_CTRL_CRCTIMEO (1 << 18) /* CRC/Time Out Error */
|
||||
#define TD_CTRL_BITSTUFF (1 << 17) /* Bit Stuff Error */
|
||||
#define TD_CTRL_ACTLEN_MASK 0x7FF /* actual length, encoded as n - 1 */
|
||||
|
||||
#define TD_CTRL_ANY_ERROR (TD_CTRL_STALLED | TD_CTRL_DBUFERR | \
|
||||
TD_CTRL_BABBLE | TD_CTRL_CRCTIME | TD_CTRL_BITSTUFF)
|
||||
|
||||
#define uhci_maxerr(err) ((err) << TD_CTRL_C_ERR_SHIFT)
|
||||
#define uhci_status_bits(ctrl_sts) ((ctrl_sts) & 0xF60000)
|
||||
#define uhci_actual_length(ctrl_sts) (((ctrl_sts) + 1) & TD_CTRL_ACTLEN_MASK) /* 1-based */
|
||||
|
||||
/*
|
||||
* for TD <info>: (a.k.a. Token)
|
||||
*/
|
||||
#define td_token(td) le32_to_cpu((td)->token)
|
||||
#define TD_TOKEN_DEVADDR_SHIFT 8
|
||||
#define TD_TOKEN_TOGGLE_SHIFT 19
|
||||
#define TD_TOKEN_TOGGLE (1 << 19)
|
||||
#define TD_TOKEN_EXPLEN_SHIFT 21
|
||||
#define TD_TOKEN_EXPLEN_MASK 0x7FF /* expected length, encoded as n - 1 */
|
||||
#define TD_TOKEN_PID_MASK 0xFF
|
||||
|
||||
#define uhci_explen(len) ((len) << TD_TOKEN_EXPLEN_SHIFT)
|
||||
|
||||
#define uhci_expected_length(token) ((((token) >> 21) + 1) & TD_TOKEN_EXPLEN_MASK)
|
||||
#define uhci_toggle(token) (((token) >> TD_TOKEN_TOGGLE_SHIFT) & 1)
|
||||
#define uhci_endpoint(token) (((token) >> 15) & 0xf)
|
||||
#define uhci_devaddr(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7f)
|
||||
#define uhci_devep(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7ff)
|
||||
#define uhci_packetid(token) ((token) & TD_TOKEN_PID_MASK)
|
||||
#define uhci_packetout(token) (uhci_packetid(token) != USB_PID_IN)
|
||||
#define uhci_packetin(token) (uhci_packetid(token) == USB_PID_IN)
|
||||
|
||||
/*
|
||||
* The documentation says "4 words for hardware, 4 words for software".
|
||||
*
|
||||
* That's silly, the hardware doesn't care. The hardware only cares that
|
||||
* the hardware words are 16-byte aligned, and we can have any amount of
|
||||
* sw space after the TD entry as far as I can tell.
|
||||
*
|
||||
* But let's just go with the documentation, at least for 32-bit machines.
|
||||
* On 64-bit machines we probably want to take advantage of the fact that
|
||||
* hw doesn't really care about the size of the sw-only area.
|
||||
*
|
||||
* Alas, not anymore, we have more than 4 words for software, woops.
|
||||
* Everything still works tho, surprise! -jerdfelt
|
||||
*
|
||||
* td->link points to either another TD (not necessarily for the same urb or
|
||||
* even the same endpoint), or nothing (PTR_TERM), or a QH (for queued urbs)
|
||||
*/
|
||||
struct uhci_td {
|
||||
/* Hardware fields */
|
||||
__le32 link;
|
||||
__le32 status;
|
||||
__le32 token;
|
||||
__le32 buffer;
|
||||
|
||||
/* Software fields */
|
||||
dma_addr_t dma_handle;
|
||||
|
||||
struct usb_device *dev;
|
||||
struct urb *urb;
|
||||
|
||||
struct list_head list; /* P: urb->lock */
|
||||
struct list_head remove_list; /* P: uhci->td_remove_list_lock */
|
||||
|
||||
int frame; /* for iso: what frame? */
|
||||
struct list_head fl_list; /* P: uhci->frame_list_lock */
|
||||
} __attribute__((aligned(16)));
|
||||
|
||||
/*
|
||||
* We need a special accessor for the control/status word because it is
|
||||
* subject to asynchronous updates by the controller
|
||||
*/
|
||||
static u32 inline td_status(struct uhci_td *td) {
|
||||
__le32 status = td->status;
|
||||
|
||||
barrier();
|
||||
return le32_to_cpu(status);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The UHCI driver places Interrupt, Control and Bulk into QH's both
|
||||
* to group together TD's for one transfer, and also to faciliate queuing
|
||||
* of URB's. To make it easy to insert entries into the schedule, we have
|
||||
* a skeleton of QH's for each predefined Interrupt latency, low-speed
|
||||
* control, full-speed control and terminating QH (see explanation for
|
||||
* the terminating QH below).
|
||||
*
|
||||
* When we want to add a new QH, we add it to the end of the list for the
|
||||
* skeleton QH.
|
||||
*
|
||||
* For instance, the queue can look like this:
|
||||
*
|
||||
* skel int128 QH
|
||||
* dev 1 interrupt QH
|
||||
* dev 5 interrupt QH
|
||||
* skel int64 QH
|
||||
* skel int32 QH
|
||||
* ...
|
||||
* skel int1 QH
|
||||
* skel low-speed control QH
|
||||
* dev 5 control QH
|
||||
* skel full-speed control QH
|
||||
* skel bulk QH
|
||||
* dev 1 bulk QH
|
||||
* dev 2 bulk QH
|
||||
* skel terminating QH
|
||||
*
|
||||
* The terminating QH is used for 2 reasons:
|
||||
* - To place a terminating TD which is used to workaround a PIIX bug
|
||||
* (see Intel errata for explanation)
|
||||
* - To loop back to the full-speed control queue for full-speed bandwidth
|
||||
* reclamation
|
||||
*
|
||||
* Isochronous transfers are stored before the start of the skeleton
|
||||
* schedule and don't use QH's. While the UHCI spec doesn't forbid the
|
||||
* use of QH's for Isochronous, it doesn't use them either. Since we don't
|
||||
* need to use them either, we follow the spec diagrams in hope that it'll
|
||||
* be more compatible with future UHCI implementations.
|
||||
*/
|
||||
|
||||
#define UHCI_NUM_SKELQH 12
|
||||
#define skel_int128_qh skelqh[0]
|
||||
#define skel_int64_qh skelqh[1]
|
||||
#define skel_int32_qh skelqh[2]
|
||||
#define skel_int16_qh skelqh[3]
|
||||
#define skel_int8_qh skelqh[4]
|
||||
#define skel_int4_qh skelqh[5]
|
||||
#define skel_int2_qh skelqh[6]
|
||||
#define skel_int1_qh skelqh[7]
|
||||
#define skel_ls_control_qh skelqh[8]
|
||||
#define skel_fs_control_qh skelqh[9]
|
||||
#define skel_bulk_qh skelqh[10]
|
||||
#define skel_term_qh skelqh[11]
|
||||
|
||||
/*
|
||||
* Search tree for determining where <interval> fits in the skelqh[]
|
||||
* skeleton.
|
||||
*
|
||||
* An interrupt request should be placed into the slowest skelqh[]
|
||||
* which meets the interval/period/frequency requirement.
|
||||
* An interrupt request is allowed to be faster than <interval> but not slower.
|
||||
*
|
||||
* For a given <interval>, this function returns the appropriate/matching
|
||||
* skelqh[] index value.
|
||||
*/
|
||||
static inline int __interval_to_skel(int interval)
|
||||
{
|
||||
if (interval < 16) {
|
||||
if (interval < 4) {
|
||||
if (interval < 2)
|
||||
return 7; /* int1 for 0-1 ms */
|
||||
return 6; /* int2 for 2-3 ms */
|
||||
}
|
||||
if (interval < 8)
|
||||
return 5; /* int4 for 4-7 ms */
|
||||
return 4; /* int8 for 8-15 ms */
|
||||
}
|
||||
if (interval < 64) {
|
||||
if (interval < 32)
|
||||
return 3; /* int16 for 16-31 ms */
|
||||
return 2; /* int32 for 32-63 ms */
|
||||
}
|
||||
if (interval < 128)
|
||||
return 1; /* int64 for 64-127 ms */
|
||||
return 0; /* int128 for 128-255 ms (Max.) */
|
||||
}
|
||||
|
||||
/*
|
||||
* Device states for the host controller.
|
||||
*
|
||||
* To prevent "bouncing" in the presence of electrical noise,
|
||||
* we insist on a 1-second "grace" period, before switching to
|
||||
* the RUNNING or SUSPENDED states, during which the state is
|
||||
* not allowed to change.
|
||||
*
|
||||
* The resume process is divided into substates in order to avoid
|
||||
* potentially length delays during the timer handler.
|
||||
*
|
||||
* States in which the host controller is halted must have values <= 0.
|
||||
*/
|
||||
enum uhci_state {
|
||||
UHCI_RESET,
|
||||
UHCI_RUNNING_GRACE, /* Before RUNNING */
|
||||
UHCI_RUNNING, /* The normal state */
|
||||
UHCI_SUSPENDING_GRACE, /* Before SUSPENDED */
|
||||
UHCI_SUSPENDED = -10, /* When no devices are attached */
|
||||
UHCI_RESUMING_1,
|
||||
UHCI_RESUMING_2
|
||||
};
|
||||
|
||||
/*
|
||||
* This describes the full uhci information.
|
||||
*
|
||||
* Note how the "proper" USB information is just
|
||||
* a subset of what the full implementation needs.
|
||||
*/
|
||||
struct uhci_hcd {
|
||||
|
||||
/* debugfs */
|
||||
struct dentry *dentry;
|
||||
|
||||
/* Grabbed from PCI */
|
||||
unsigned long io_addr;
|
||||
|
||||
struct dma_pool *qh_pool;
|
||||
struct dma_pool *td_pool;
|
||||
|
||||
struct usb_bus *bus;
|
||||
|
||||
struct uhci_td *term_td; /* Terminating TD, see UHCI bug */
|
||||
struct uhci_qh *skelqh[UHCI_NUM_SKELQH]; /* Skeleton QH's */
|
||||
|
||||
spinlock_t lock;
|
||||
struct uhci_frame_list *fl; /* P: uhci->lock */
|
||||
int fsbr; /* Full-speed bandwidth reclamation */
|
||||
unsigned long fsbrtimeout; /* FSBR delay */
|
||||
|
||||
enum uhci_state state; /* FIXME: needs a spinlock */
|
||||
unsigned long state_end; /* Time of next transition */
|
||||
unsigned int frame_number; /* As of last check */
|
||||
unsigned int is_stopped;
|
||||
#define UHCI_IS_STOPPED 9999 /* Larger than a frame # */
|
||||
|
||||
unsigned int scan_in_progress:1; /* Schedule scan is running */
|
||||
unsigned int need_rescan:1; /* Redo the schedule scan */
|
||||
unsigned int resume_detect:1; /* Need a Global Resume */
|
||||
|
||||
/* Support for port suspend/resume/reset */
|
||||
unsigned long port_c_suspend; /* Bit-arrays of ports */
|
||||
unsigned long suspended_ports;
|
||||
unsigned long resuming_ports;
|
||||
unsigned long ports_timeout; /* Time to stop signalling */
|
||||
|
||||
/* Main list of URB's currently controlled by this HC */
|
||||
struct list_head urb_list; /* P: uhci->lock */
|
||||
|
||||
/* List of QH's that are done, but waiting to be unlinked (race) */
|
||||
struct list_head qh_remove_list; /* P: uhci->lock */
|
||||
unsigned int qh_remove_age; /* Age in frames */
|
||||
|
||||
/* List of TD's that are done, but waiting to be freed (race) */
|
||||
struct list_head td_remove_list; /* P: uhci->lock */
|
||||
unsigned int td_remove_age; /* Age in frames */
|
||||
|
||||
/* List of asynchronously unlinked URB's */
|
||||
struct list_head urb_remove_list; /* P: uhci->lock */
|
||||
unsigned int urb_remove_age; /* Age in frames */
|
||||
|
||||
/* List of URB's awaiting completion callback */
|
||||
struct list_head complete_list; /* P: uhci->lock */
|
||||
|
||||
int rh_numports;
|
||||
|
||||
struct timer_list stall_timer;
|
||||
|
||||
wait_queue_head_t waitqh; /* endpoint_disable waiters */
|
||||
};
|
||||
|
||||
/* Convert between a usb_hcd pointer and the corresponding uhci_hcd */
|
||||
static inline struct uhci_hcd *hcd_to_uhci(struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct uhci_hcd *) (hcd->hcd_priv);
|
||||
}
|
||||
static inline struct usb_hcd *uhci_to_hcd(struct uhci_hcd *uhci)
|
||||
{
|
||||
return container_of((void *) uhci, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
#define uhci_dev(u) (uhci_to_hcd(u)->self.controller)
|
||||
|
||||
struct urb_priv {
|
||||
struct list_head urb_list;
|
||||
|
||||
struct urb *urb;
|
||||
|
||||
struct uhci_qh *qh; /* QH for this URB */
|
||||
struct list_head td_list; /* P: urb->lock */
|
||||
|
||||
unsigned fsbr : 1; /* URB turned on FSBR */
|
||||
unsigned fsbr_timeout : 1; /* URB timed out on FSBR */
|
||||
unsigned queued : 1; /* QH was queued (not linked in) */
|
||||
unsigned short_control_packet : 1; /* If we get a short packet during */
|
||||
/* a control transfer, retrigger */
|
||||
/* the status phase */
|
||||
|
||||
unsigned long inserttime; /* In jiffies */
|
||||
unsigned long fsbrtime; /* In jiffies */
|
||||
|
||||
struct list_head queue_list; /* P: uhci->frame_list_lock */
|
||||
};
|
||||
|
||||
/*
|
||||
* Locking in uhci.c
|
||||
*
|
||||
* Almost everything relating to the hardware schedule and processing
|
||||
* of URBs is protected by uhci->lock. urb->status is protected by
|
||||
* urb->lock; that's the one exception.
|
||||
*
|
||||
* To prevent deadlocks, never lock uhci->lock while holding urb->lock.
|
||||
* The safe order of locking is:
|
||||
*
|
||||
* #1 uhci->lock
|
||||
* #2 urb->lock
|
||||
*/
|
||||
|
||||
#endif
|
299
drivers/usb/host/uhci-hub.c
一般檔案
299
drivers/usb/host/uhci-hub.c
一般檔案
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Universal Host Controller Interface driver for USB.
|
||||
*
|
||||
* Maintainer: Alan Stern <stern@rowland.harvard.edu>
|
||||
*
|
||||
* (C) Copyright 1999 Linus Torvalds
|
||||
* (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com
|
||||
* (C) Copyright 1999 Randy Dunlap
|
||||
* (C) Copyright 1999 Georg Acher, acher@in.tum.de
|
||||
* (C) Copyright 1999 Deti Fliegl, deti@fliegl.de
|
||||
* (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch
|
||||
* (C) Copyright 2004 Alan Stern, stern@rowland.harvard.edu
|
||||
*/
|
||||
|
||||
static __u8 root_hub_hub_des[] =
|
||||
{
|
||||
0x09, /* __u8 bLength; */
|
||||
0x29, /* __u8 bDescriptorType; Hub-descriptor */
|
||||
0x02, /* __u8 bNbrPorts; */
|
||||
0x0a, /* __u16 wHubCharacteristics; */
|
||||
0x00, /* (per-port OC, no power switching) */
|
||||
0x01, /* __u8 bPwrOn2pwrGood; 2ms */
|
||||
0x00, /* __u8 bHubContrCurrent; 0 mA */
|
||||
0x00, /* __u8 DeviceRemovable; *** 7 Ports max *** */
|
||||
0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */
|
||||
};
|
||||
|
||||
#define UHCI_RH_MAXCHILD 7
|
||||
|
||||
/* must write as zeroes */
|
||||
#define WZ_BITS (USBPORTSC_RES2 | USBPORTSC_RES3 | USBPORTSC_RES4)
|
||||
|
||||
/* status change bits: nonzero writes will clear */
|
||||
#define RWC_BITS (USBPORTSC_OCC | USBPORTSC_PEC | USBPORTSC_CSC)
|
||||
|
||||
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
int port;
|
||||
|
||||
*buf = 0;
|
||||
for (port = 0; port < uhci->rh_numports; ++port) {
|
||||
if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & RWC_BITS) ||
|
||||
test_bit(port, &uhci->port_c_suspend))
|
||||
*buf |= (1 << (port + 1));
|
||||
}
|
||||
if (*buf && uhci->state == UHCI_SUSPENDED)
|
||||
uhci->resume_detect = 1;
|
||||
return !!*buf;
|
||||
}
|
||||
|
||||
#define OK(x) len = (x); break
|
||||
|
||||
#define CLR_RH_PORTSTAT(x) \
|
||||
status = inw(port_addr); \
|
||||
status &= ~(RWC_BITS|WZ_BITS); \
|
||||
status &= ~(x); \
|
||||
status |= RWC_BITS & (x); \
|
||||
outw(status, port_addr)
|
||||
|
||||
#define SET_RH_PORTSTAT(x) \
|
||||
status = inw(port_addr); \
|
||||
status |= (x); \
|
||||
status &= ~(RWC_BITS|WZ_BITS); \
|
||||
outw(status, port_addr)
|
||||
|
||||
/* UHCI controllers don't automatically stop resume signalling after 20 msec,
|
||||
* so we have to poll and check timeouts in order to take care of it.
|
||||
*/
|
||||
static void uhci_finish_suspend(struct uhci_hcd *uhci, int port,
|
||||
unsigned long port_addr)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (test_bit(port, &uhci->suspended_ports)) {
|
||||
CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD);
|
||||
clear_bit(port, &uhci->suspended_ports);
|
||||
clear_bit(port, &uhci->resuming_ports);
|
||||
set_bit(port, &uhci->port_c_suspend);
|
||||
|
||||
/* The controller won't actually turn off the RD bit until
|
||||
* it has had a chance to send a low-speed EOP sequence,
|
||||
* which takes 3 bit times (= 2 microseconds). We'll delay
|
||||
* slightly longer for good luck. */
|
||||
udelay(4);
|
||||
}
|
||||
}
|
||||
|
||||
static void uhci_check_ports(struct uhci_hcd *uhci)
|
||||
{
|
||||
unsigned int port;
|
||||
unsigned long port_addr;
|
||||
int status;
|
||||
|
||||
for (port = 0; port < uhci->rh_numports; ++port) {
|
||||
port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
|
||||
status = inw(port_addr);
|
||||
if (unlikely(status & USBPORTSC_PR)) {
|
||||
if (time_after_eq(jiffies, uhci->ports_timeout)) {
|
||||
CLR_RH_PORTSTAT(USBPORTSC_PR);
|
||||
udelay(10);
|
||||
|
||||
/* If the port was enabled before, turning
|
||||
* reset on caused a port enable change.
|
||||
* Turning reset off causes a port connect
|
||||
* status change. Clear these changes. */
|
||||
CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);
|
||||
SET_RH_PORTSTAT(USBPORTSC_PE);
|
||||
}
|
||||
}
|
||||
if (unlikely(status & USBPORTSC_RD)) {
|
||||
if (!test_bit(port, &uhci->resuming_ports)) {
|
||||
|
||||
/* Port received a wakeup request */
|
||||
set_bit(port, &uhci->resuming_ports);
|
||||
uhci->ports_timeout = jiffies +
|
||||
msecs_to_jiffies(20);
|
||||
} else if (time_after_eq(jiffies,
|
||||
uhci->ports_timeout)) {
|
||||
uhci_finish_suspend(uhci, port, port_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* size of returned buffer is part of USB spec */
|
||||
static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
||||
u16 wIndex, char *buf, u16 wLength)
|
||||
{
|
||||
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
||||
int status, lstatus, retval = 0, len = 0;
|
||||
unsigned int port = wIndex - 1;
|
||||
unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
|
||||
u16 wPortChange, wPortStatus;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&uhci->lock, flags);
|
||||
switch (typeReq) {
|
||||
|
||||
case GetHubStatus:
|
||||
*(__le32 *)buf = cpu_to_le32(0);
|
||||
OK(4); /* hub power */
|
||||
case GetPortStatus:
|
||||
if (port >= uhci->rh_numports)
|
||||
goto err;
|
||||
|
||||
uhci_check_ports(uhci);
|
||||
status = inw(port_addr);
|
||||
|
||||
/* Intel controllers report the OverCurrent bit active on.
|
||||
* VIA controllers report it active off, so we'll adjust the
|
||||
* bit value. (It's not standardized in the UHCI spec.)
|
||||
*/
|
||||
if (to_pci_dev(hcd->self.controller)->vendor ==
|
||||
PCI_VENDOR_ID_VIA)
|
||||
status ^= USBPORTSC_OC;
|
||||
|
||||
/* UHCI doesn't support C_RESET (always false) */
|
||||
wPortChange = lstatus = 0;
|
||||
if (status & USBPORTSC_CSC)
|
||||
wPortChange |= USB_PORT_STAT_C_CONNECTION;
|
||||
if (status & USBPORTSC_PEC)
|
||||
wPortChange |= USB_PORT_STAT_C_ENABLE;
|
||||
if (status & USBPORTSC_OCC)
|
||||
wPortChange |= USB_PORT_STAT_C_OVERCURRENT;
|
||||
|
||||
if (test_bit(port, &uhci->port_c_suspend)) {
|
||||
wPortChange |= USB_PORT_STAT_C_SUSPEND;
|
||||
lstatus |= 1;
|
||||
}
|
||||
if (test_bit(port, &uhci->suspended_ports))
|
||||
lstatus |= 2;
|
||||
if (test_bit(port, &uhci->resuming_ports))
|
||||
lstatus |= 4;
|
||||
|
||||
/* UHCI has no power switching (always on) */
|
||||
wPortStatus = USB_PORT_STAT_POWER;
|
||||
if (status & USBPORTSC_CCS)
|
||||
wPortStatus |= USB_PORT_STAT_CONNECTION;
|
||||
if (status & USBPORTSC_PE) {
|
||||
wPortStatus |= USB_PORT_STAT_ENABLE;
|
||||
if (status & (USBPORTSC_SUSP | USBPORTSC_RD))
|
||||
wPortStatus |= USB_PORT_STAT_SUSPEND;
|
||||
}
|
||||
if (status & USBPORTSC_OC)
|
||||
wPortStatus |= USB_PORT_STAT_OVERCURRENT;
|
||||
if (status & USBPORTSC_PR)
|
||||
wPortStatus |= USB_PORT_STAT_RESET;
|
||||
if (status & USBPORTSC_LSDA)
|
||||
wPortStatus |= USB_PORT_STAT_LOW_SPEED;
|
||||
|
||||
if (wPortChange)
|
||||
dev_dbg(uhci_dev(uhci), "port %d portsc %04x,%02x\n",
|
||||
wIndex, status, lstatus);
|
||||
|
||||
*(__le16 *)buf = cpu_to_le16(wPortStatus);
|
||||
*(__le16 *)(buf + 2) = cpu_to_le16(wPortChange);
|
||||
OK(4);
|
||||
case SetHubFeature: /* We don't implement these */
|
||||
case ClearHubFeature:
|
||||
switch (wValue) {
|
||||
case C_HUB_OVER_CURRENT:
|
||||
case C_HUB_LOCAL_POWER:
|
||||
OK(0);
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case SetPortFeature:
|
||||
if (port >= uhci->rh_numports)
|
||||
goto err;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
set_bit(port, &uhci->suspended_ports);
|
||||
SET_RH_PORTSTAT(USBPORTSC_SUSP);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_RESET:
|
||||
SET_RH_PORTSTAT(USBPORTSC_PR);
|
||||
|
||||
/* Reset terminates Resume signalling */
|
||||
uhci_finish_suspend(uhci, port, port_addr);
|
||||
|
||||
/* USB v2.0 7.1.7.5 */
|
||||
uhci->ports_timeout = jiffies + msecs_to_jiffies(50);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_POWER:
|
||||
/* UHCI has no power switching */
|
||||
OK(0);
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
if (port >= uhci->rh_numports)
|
||||
goto err;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
CLR_RH_PORTSTAT(USBPORTSC_PE);
|
||||
|
||||
/* Disable terminates Resume signalling */
|
||||
uhci_finish_suspend(uhci, port, port_addr);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_C_ENABLE:
|
||||
CLR_RH_PORTSTAT(USBPORTSC_PEC);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
if (test_bit(port, &uhci->suspended_ports) &&
|
||||
!test_and_set_bit(port,
|
||||
&uhci->resuming_ports)) {
|
||||
SET_RH_PORTSTAT(USBPORTSC_RD);
|
||||
|
||||
/* The controller won't allow RD to be set
|
||||
* if the port is disabled. When this happens
|
||||
* just skip the Resume signalling.
|
||||
*/
|
||||
if (!(inw(port_addr) & USBPORTSC_RD))
|
||||
uhci_finish_suspend(uhci, port,
|
||||
port_addr);
|
||||
else
|
||||
/* USB v2.0 7.1.7.7 */
|
||||
uhci->ports_timeout = jiffies +
|
||||
msecs_to_jiffies(20);
|
||||
}
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_C_SUSPEND:
|
||||
clear_bit(port, &uhci->port_c_suspend);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_POWER:
|
||||
/* UHCI has no power switching */
|
||||
goto err;
|
||||
case USB_PORT_FEAT_C_CONNECTION:
|
||||
CLR_RH_PORTSTAT(USBPORTSC_CSC);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_C_OVER_CURRENT:
|
||||
CLR_RH_PORTSTAT(USBPORTSC_OCC);
|
||||
OK(0);
|
||||
case USB_PORT_FEAT_C_RESET:
|
||||
/* this driver won't report these */
|
||||
OK(0);
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case GetHubDescriptor:
|
||||
len = min_t(unsigned int, sizeof(root_hub_hub_des), wLength);
|
||||
memcpy(buf, root_hub_hub_des, len);
|
||||
if (len > 2)
|
||||
buf[2] = uhci->rh_numports;
|
||||
OK(len);
|
||||
default:
|
||||
err:
|
||||
retval = -EPIPE;
|
||||
}
|
||||
spin_unlock_irqrestore(&uhci->lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
1539
drivers/usb/host/uhci-q.c
一般檔案
1539
drivers/usb/host/uhci-q.c
一般檔案
檔案差異因為檔案過大而無法顯示
載入差異
新增問題並參考
封鎖使用者