mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-28 02:32:09 -04:00
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!
This commit is contained in:
commit
1da177e4c3
17291 changed files with 6718755 additions and 0 deletions
127
drivers/usb/Kconfig
Normal file
127
drivers/usb/Kconfig
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#
|
||||
# USB device configuration
|
||||
#
|
||||
|
||||
menu "USB support"
|
||||
|
||||
# Host-side USB depends on having a host controller
|
||||
# NOTE: dummy_hcd is always an option, but it's ignored here ...
|
||||
# NOTE: SL-811 option should be board-specific ...
|
||||
config USB_ARCH_HAS_HCD
|
||||
boolean
|
||||
default y if USB_ARCH_HAS_OHCI
|
||||
default y if ARM # SL-811
|
||||
default PCI
|
||||
|
||||
# many non-PCI SOC chips embed OHCI
|
||||
config USB_ARCH_HAS_OHCI
|
||||
boolean
|
||||
# ARM:
|
||||
default y if SA1111
|
||||
default y if ARCH_OMAP
|
||||
default y if ARCH_LH7A404
|
||||
default y if PXA27x
|
||||
# PPC:
|
||||
default y if STB03xxx
|
||||
default y if PPC_MPC52xx
|
||||
# MIPS:
|
||||
default y if SOC_AU1X00
|
||||
# more:
|
||||
default PCI
|
||||
|
||||
# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface.
|
||||
config USB
|
||||
tristate "Support for Host-side USB"
|
||||
depends on USB_ARCH_HAS_HCD
|
||||
---help---
|
||||
Universal Serial Bus (USB) is a specification for a serial bus
|
||||
subsystem which offers higher speeds and more features than the
|
||||
traditional PC serial port. The bus supplies power to peripherals
|
||||
and allows for hot swapping. Up to 127 USB peripherals can be
|
||||
connected to a single USB host in a tree structure.
|
||||
|
||||
The USB host is the root of the tree, the peripherals are the
|
||||
leaves and the inner nodes are special USB devices called hubs.
|
||||
Most PCs now have USB host ports, used to connect peripherals
|
||||
such as scanners, keyboards, mice, modems, cameras, disks,
|
||||
flash memory, network links, and printers to the PC.
|
||||
|
||||
Say Y here if your computer has a host-side USB port and you want
|
||||
to use USB devices. You then need to say Y to at least one of the
|
||||
Host Controller Driver (HCD) options below. Choose a USB 1.1
|
||||
controller, such as "UHCI HCD support" or "OHCI HCD support",
|
||||
and "EHCI HCD (USB 2.0) support" except for older systems that
|
||||
do not have USB 2.0 support. It doesn't normally hurt to select
|
||||
them all if you are not certain.
|
||||
|
||||
If your system has a device-side USB port, used in the peripheral
|
||||
side of the USB protocol, see the "USB Gadget" framework instead.
|
||||
|
||||
After choosing your HCD, then select drivers for the USB peripherals
|
||||
you'll be using. You may want to check out the information provided
|
||||
in <file:Documentation/usb/> and especially the links given in
|
||||
<file:Documentation/usb/usb-help.txt>.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usbcore.
|
||||
|
||||
source "drivers/usb/core/Kconfig"
|
||||
|
||||
source "drivers/usb/host/Kconfig"
|
||||
|
||||
source "drivers/usb/class/Kconfig"
|
||||
|
||||
source "drivers/usb/storage/Kconfig"
|
||||
|
||||
source "drivers/usb/input/Kconfig"
|
||||
|
||||
source "drivers/usb/image/Kconfig"
|
||||
|
||||
source "drivers/usb/media/Kconfig"
|
||||
|
||||
source "drivers/usb/net/Kconfig"
|
||||
|
||||
source "drivers/usb/mon/Kconfig"
|
||||
|
||||
comment "USB port drivers"
|
||||
depends on USB
|
||||
|
||||
config USB_USS720
|
||||
tristate "USS720 parport driver"
|
||||
depends on USB && PARPORT
|
||||
select PARPORT_NOT_PC
|
||||
---help---
|
||||
This driver is for USB parallel port adapters that use the Lucent
|
||||
Technologies USS-720 chip. These cables are plugged into your USB
|
||||
port and provide USB compatibility to peripherals designed with
|
||||
parallel port interfaces.
|
||||
|
||||
The chip has two modes: automatic mode and manual mode. In automatic
|
||||
mode, it looks to the computer like a standard USB printer. Only
|
||||
printers may be connected to the USS-720 in this mode. The generic
|
||||
USB printer driver ("USB Printer support", above) may be used in
|
||||
that mode, and you can say N here if you want to use the chip only
|
||||
in this mode.
|
||||
|
||||
Manual mode is not limited to printers, any parallel port
|
||||
device should work. This driver utilizes manual mode.
|
||||
Note however that some operations are three orders of magnitude
|
||||
slower than on a PCI/ISA Parallel Port, so timing critical
|
||||
applications might not work.
|
||||
|
||||
Say Y here if you own an USS-720 USB->Parport cable and intend to
|
||||
connect anything other than a printer to it.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called uss720.
|
||||
|
||||
source "drivers/usb/serial/Kconfig"
|
||||
|
||||
source "drivers/usb/misc/Kconfig"
|
||||
|
||||
source "drivers/usb/atm/Kconfig"
|
||||
|
||||
source "drivers/usb/gadget/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
74
drivers/usb/Makefile
Normal file
74
drivers/usb/Makefile
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#
|
||||
# Makefile for the kernel USB device drivers.
|
||||
#
|
||||
|
||||
# Object files in subdirectories
|
||||
|
||||
obj-$(CONFIG_USB) += core/
|
||||
|
||||
obj-$(CONFIG_USB_MON) += mon/
|
||||
|
||||
obj-$(CONFIG_USB_EHCI_HCD) += host/
|
||||
obj-$(CONFIG_USB_OHCI_HCD) += host/
|
||||
obj-$(CONFIG_USB_UHCI_HCD) += host/
|
||||
obj-$(CONFIG_USB_SL811_HCD) += host/
|
||||
obj-$(CONFIG_ETRAX_USB_HOST) += host/
|
||||
|
||||
obj-$(CONFIG_USB_ACM) += class/
|
||||
obj-$(CONFIG_USB_AUDIO) += class/
|
||||
obj-$(CONFIG_USB_BLUETOOTH_TTY) += class/
|
||||
obj-$(CONFIG_USB_MIDI) += class/
|
||||
obj-$(CONFIG_USB_PRINTER) += class/
|
||||
|
||||
obj-$(CONFIG_USB_STORAGE) += storage/
|
||||
|
||||
obj-$(CONFIG_USB_AIPTEK) += input/
|
||||
obj-$(CONFIG_USB_ATI_REMOTE) += input/
|
||||
obj-$(CONFIG_USB_HID) += input/
|
||||
obj-$(CONFIG_USB_KBD) += input/
|
||||
obj-$(CONFIG_USB_KBTAB) += input/
|
||||
obj-$(CONFIG_USB_MOUSE) += input/
|
||||
obj-$(CONFIG_USB_MTOUCH) += input/
|
||||
obj-$(CONFIG_USB_POWERMATE) += input/
|
||||
obj-$(CONFIG_USB_WACOM) += input/
|
||||
obj-$(CONFIG_USB_XPAD) += input/
|
||||
|
||||
obj-$(CONFIG_USB_DABUSB) += media/
|
||||
obj-$(CONFIG_USB_DSBR) += media/
|
||||
obj-$(CONFIG_USB_IBMCAM) += media/
|
||||
obj-$(CONFIG_USB_KONICAWC) += media/
|
||||
obj-$(CONFIG_USB_OV511) += media/
|
||||
obj-$(CONFIG_USB_PWC) += media/
|
||||
obj-$(CONFIG_USB_SE401) += media/
|
||||
obj-$(CONFIG_USB_SN9C102) += media/
|
||||
obj-$(CONFIG_USB_STV680) += media/
|
||||
obj-$(CONFIG_USB_VICAM) += media/
|
||||
obj-$(CONFIG_USB_W9968CF) += media/
|
||||
|
||||
obj-$(CONFIG_USB_CATC) += net/
|
||||
obj-$(CONFIG_USB_KAWETH) += net/
|
||||
obj-$(CONFIG_USB_PEGASUS) += net/
|
||||
obj-$(CONFIG_USB_RTL8150) += net/
|
||||
obj-$(CONFIG_USB_USBNET) += net/
|
||||
obj-$(CONFIG_USB_ZD1201) += net/
|
||||
|
||||
obj-$(CONFIG_USB_MDC800) += image/
|
||||
obj-$(CONFIG_USB_MICROTEK) += image/
|
||||
|
||||
obj-$(CONFIG_USB_SERIAL) += serial/
|
||||
|
||||
obj-$(CONFIG_USB_AUERSWALD) += misc/
|
||||
obj-$(CONFIG_USB_CYTHERM) += misc/
|
||||
obj-$(CONFIG_USB_EMI26) += misc/
|
||||
obj-$(CONFIG_USB_EMI62) += misc/
|
||||
obj-$(CONFIG_USB_IDMOUSE) += misc/
|
||||
obj-$(CONFIG_USB_LCD) += misc/
|
||||
obj-$(CONFIG_USB_LED) += misc/
|
||||
obj-$(CONFIG_USB_LEGOTOWER) += misc/
|
||||
obj-$(CONFIG_USB_RIO500) += misc/
|
||||
obj-$(CONFIG_USB_TEST) += misc/
|
||||
obj-$(CONFIG_USB_USS720) += misc/
|
||||
obj-$(CONFIG_USB_PHIDGETSERVO) += misc/
|
||||
|
||||
obj-$(CONFIG_USB_ATM) += atm/
|
||||
obj-$(CONFIG_USB_SPEEDTOUCH) += atm/
|
||||
54
drivers/usb/README
Normal file
54
drivers/usb/README
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
To understand all the Linux-USB framework, you'll use these resources:
|
||||
|
||||
* This source code. This is necessarily an evolving work, and
|
||||
includes kerneldoc that should help you get a current overview.
|
||||
("make pdfdocs", and then look at "usb.pdf" for host side and
|
||||
"gadget.pdf" for peripheral side.) Also, Documentation/usb has
|
||||
more information.
|
||||
|
||||
* The USB 2.0 specification (from www.usb.org), with supplements
|
||||
such as those for USB OTG and the various device classes.
|
||||
The USB specification has a good overview chapter, and USB
|
||||
peripherals conform to the widely known "Chapter 9".
|
||||
|
||||
* Chip specifications for USB controllers. Examples include
|
||||
host controllers (on PCs, servers, and more); peripheral
|
||||
controllers (in devices with Linux firmware, like printers or
|
||||
cell phones); and hard-wired peripherals like Ethernet adapters.
|
||||
|
||||
* Specifications for other protocols implemented by USB peripheral
|
||||
functions. Some are vendor-specific; others are vendor-neutral
|
||||
but just standardized outside of the www.usb.org team.
|
||||
|
||||
Here is a list of what each subdirectory here is, and what is contained in
|
||||
them.
|
||||
|
||||
core/ - This is for the core USB host code, including the
|
||||
usbfs files and the hub class driver ("khubd").
|
||||
|
||||
host/ - This is for USB host controller drivers. This
|
||||
includes UHCI, OHCI, EHCI, and others that might
|
||||
be used with more specialized "embedded" systems.
|
||||
|
||||
gadget/ - This is for USB peripheral controller drivers and
|
||||
the various gadget drivers which talk to them.
|
||||
|
||||
|
||||
Individual USB driver directories. A new driver should be added to the
|
||||
first subdirectory in the list below that it fits into.
|
||||
|
||||
image/ - This is for still image drivers, like scanners or
|
||||
digital cameras.
|
||||
input/ - This is for any driver that uses the input subsystem,
|
||||
like keyboard, mice, touchscreens, tablets, etc.
|
||||
media/ - This is for multimedia drivers, like video cameras,
|
||||
radios, and any other drivers that talk to the v4l
|
||||
subsystem.
|
||||
net/ - This is for network drivers.
|
||||
serial/ - This is for USB to serial drivers.
|
||||
storage/ - This is for USB mass-storage drivers.
|
||||
class/ - This is for all USB device drivers that do not fit
|
||||
into any of the above categories, and work for a range
|
||||
of USB Class specified devices.
|
||||
misc/ - This is for all USB device drivers that do not fit
|
||||
into any of the above categories.
|
||||
30
drivers/usb/atm/Kconfig
Normal file
30
drivers/usb/atm/Kconfig
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# USB ATM driver configuration
|
||||
#
|
||||
comment "USB ATM/DSL drivers"
|
||||
depends on USB
|
||||
|
||||
config USB_ATM
|
||||
tristate "Generic USB ATM/DSL core I/O support"
|
||||
depends on USB && ATM
|
||||
select CRC32
|
||||
default n
|
||||
help
|
||||
This provides a library which is used for packet I/O by USB DSL
|
||||
modems, such as the SpeedTouch driver below.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usb_atm.
|
||||
|
||||
config USB_SPEEDTOUCH
|
||||
tristate "Alcatel Speedtouch USB support"
|
||||
depends on USB && ATM
|
||||
select USB_ATM
|
||||
help
|
||||
Say Y here if you have an Alcatel SpeedTouch USB or SpeedTouch 330
|
||||
modem. In order to use your modem you will need to install the
|
||||
two parts of the firmware, extracted by the user space tools; see
|
||||
<http://www.linux-usb.org/SpeedTouch/> for details.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called speedtch.
|
||||
7
drivers/usb/atm/Makefile
Normal file
7
drivers/usb/atm/Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Makefile for the rest of the USB drivers
|
||||
# (the ones that don't fit into any other categories)
|
||||
#
|
||||
|
||||
obj-$(CONFIG_USB_ATM) += usb_atm.o
|
||||
obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o
|
||||
847
drivers/usb/atm/speedtch.c
Normal file
847
drivers/usb/atm/speedtch.c
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
/******************************************************************************
|
||||
* speedtch.c - Alcatel SpeedTouch USB xDSL modem driver
|
||||
*
|
||||
* Copyright (C) 2001, Alcatel
|
||||
* Copyright (C) 2003, Duncan Sands
|
||||
* Copyright (C) 2004, David Woodhouse
|
||||
*
|
||||
* 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., 59
|
||||
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/list.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/firmware.h>
|
||||
|
||||
#include "usb_atm.h"
|
||||
|
||||
#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
|
||||
# define USE_FW_LOADER
|
||||
#endif
|
||||
|
||||
#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands <[email protected]>"
|
||||
#define DRIVER_VERSION "1.8"
|
||||
#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION
|
||||
|
||||
static const char speedtch_driver_name[] = "speedtch";
|
||||
|
||||
#define SPEEDTOUCH_VENDORID 0x06b9
|
||||
#define SPEEDTOUCH_PRODUCTID 0x4061
|
||||
|
||||
/* Timeout in jiffies */
|
||||
#define CTRL_TIMEOUT 2000
|
||||
#define DATA_TIMEOUT 2000
|
||||
|
||||
#define OFFSET_7 0 /* size 1 */
|
||||
#define OFFSET_b 1 /* size 8 */
|
||||
#define OFFSET_d 9 /* size 4 */
|
||||
#define OFFSET_e 13 /* size 1 */
|
||||
#define OFFSET_f 14 /* size 1 */
|
||||
#define TOTAL 15
|
||||
|
||||
#define SIZE_7 1
|
||||
#define SIZE_b 8
|
||||
#define SIZE_d 4
|
||||
#define SIZE_e 1
|
||||
#define SIZE_f 1
|
||||
|
||||
static int dl_512_first = 0;
|
||||
static int sw_buffering = 0;
|
||||
|
||||
module_param(dl_512_first, bool, 0444);
|
||||
MODULE_PARM_DESC(dl_512_first, "Read 512 bytes before sending firmware");
|
||||
|
||||
module_param(sw_buffering, uint, 0444);
|
||||
MODULE_PARM_DESC(sw_buffering, "Enable software buffering");
|
||||
|
||||
#define UDSL_IOCTL_LINE_UP 1
|
||||
#define UDSL_IOCTL_LINE_DOWN 2
|
||||
|
||||
#define SPEEDTCH_ENDPOINT_INT 0x81
|
||||
#define SPEEDTCH_ENDPOINT_DATA 0x07
|
||||
#define SPEEDTCH_ENDPOINT_FIRMWARE 0x05
|
||||
|
||||
#define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) )
|
||||
|
||||
static struct usb_device_id speedtch_usb_ids[] = {
|
||||
{USB_DEVICE(SPEEDTOUCH_VENDORID, SPEEDTOUCH_PRODUCTID)},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(usb, speedtch_usb_ids);
|
||||
|
||||
struct speedtch_instance_data {
|
||||
struct udsl_instance_data u;
|
||||
|
||||
/* Status */
|
||||
struct urb *int_urb;
|
||||
unsigned char int_data[16];
|
||||
struct work_struct poll_work;
|
||||
struct timer_list poll_timer;
|
||||
};
|
||||
/* USB */
|
||||
|
||||
static int speedtch_usb_probe(struct usb_interface *intf,
|
||||
const struct usb_device_id *id);
|
||||
static void speedtch_usb_disconnect(struct usb_interface *intf);
|
||||
static int speedtch_usb_ioctl(struct usb_interface *intf, unsigned int code,
|
||||
void *user_data);
|
||||
static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs);
|
||||
static void speedtch_poll_status(struct speedtch_instance_data *instance);
|
||||
|
||||
static struct usb_driver speedtch_usb_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = speedtch_driver_name,
|
||||
.probe = speedtch_usb_probe,
|
||||
.disconnect = speedtch_usb_disconnect,
|
||||
.ioctl = speedtch_usb_ioctl,
|
||||
.id_table = speedtch_usb_ids,
|
||||
};
|
||||
|
||||
/***************
|
||||
** firmware **
|
||||
***************/
|
||||
|
||||
static void speedtch_got_firmware(struct speedtch_instance_data *instance,
|
||||
int got_it)
|
||||
{
|
||||
int err;
|
||||
struct usb_interface *intf;
|
||||
|
||||
down(&instance->u.serialize); /* vs self, speedtch_firmware_start */
|
||||
if (instance->u.status == UDSL_LOADED_FIRMWARE)
|
||||
goto out;
|
||||
if (!got_it) {
|
||||
instance->u.status = UDSL_NO_FIRMWARE;
|
||||
goto out;
|
||||
}
|
||||
if ((err = usb_set_interface(instance->u.usb_dev, 1, 1)) < 0) {
|
||||
dbg("speedtch_got_firmware: usb_set_interface returned %d!", err);
|
||||
instance->u.status = UDSL_NO_FIRMWARE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Set up interrupt endpoint */
|
||||
intf = usb_ifnum_to_if(instance->u.usb_dev, 0);
|
||||
if (intf && !usb_driver_claim_interface(&speedtch_usb_driver, intf, NULL)) {
|
||||
|
||||
instance->int_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (instance->int_urb) {
|
||||
|
||||
usb_fill_int_urb(instance->int_urb, instance->u.usb_dev,
|
||||
usb_rcvintpipe(instance->u.usb_dev, SPEEDTCH_ENDPOINT_INT),
|
||||
instance->int_data,
|
||||
sizeof(instance->int_data),
|
||||
speedtch_handle_int, instance, 50);
|
||||
err = usb_submit_urb(instance->int_urb, GFP_KERNEL);
|
||||
if (err) {
|
||||
/* Doesn't matter; we'll poll anyway */
|
||||
dbg("speedtch_got_firmware: Submission of interrupt URB failed %d", err);
|
||||
usb_free_urb(instance->int_urb);
|
||||
instance->int_urb = NULL;
|
||||
usb_driver_release_interface(&speedtch_usb_driver, intf);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Start status polling */
|
||||
mod_timer(&instance->poll_timer, jiffies + (1 * HZ));
|
||||
|
||||
instance->u.status = UDSL_LOADED_FIRMWARE;
|
||||
tasklet_schedule(&instance->u.receive_tasklet);
|
||||
out:
|
||||
up(&instance->u.serialize);
|
||||
wake_up_interruptible(&instance->u.firmware_waiters);
|
||||
}
|
||||
|
||||
static int speedtch_set_swbuff(struct speedtch_instance_data *instance,
|
||||
int state)
|
||||
{
|
||||
struct usb_device *dev = instance->u.usb_dev;
|
||||
int ret;
|
||||
|
||||
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
|
||||
0x32, 0x40, state ? 0x01 : 0x00,
|
||||
0x00, NULL, 0, 100);
|
||||
if (ret < 0) {
|
||||
printk("Warning: %sabling SW buffering: usb_control_msg returned %d\n",
|
||||
state ? "En" : "Dis", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dbg("speedtch_set_swbuff: %sbled SW buffering", state ? "En" : "Dis");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void speedtch_test_sequence(struct speedtch_instance_data *instance)
|
||||
{
|
||||
struct usb_device *dev = instance->u.usb_dev;
|
||||
unsigned char buf[10];
|
||||
int ret;
|
||||
|
||||
/* URB 147 */
|
||||
buf[0] = 0x1c;
|
||||
buf[1] = 0x50;
|
||||
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
|
||||
0x01, 0x40, 0x0b, 0x00, buf, 2, 100);
|
||||
if (ret < 0)
|
||||
printk(KERN_WARNING "%s failed on URB147: %d\n", __func__, ret);
|
||||
|
||||
/* URB 148 */
|
||||
buf[0] = 0x32;
|
||||
buf[1] = 0x00;
|
||||
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
|
||||
0x01, 0x40, 0x02, 0x00, buf, 2, 100);
|
||||
if (ret < 0)
|
||||
printk(KERN_WARNING "%s failed on URB148: %d\n", __func__, ret);
|
||||
|
||||
/* URB 149 */
|
||||
buf[0] = 0x01;
|
||||
buf[1] = 0x00;
|
||||
buf[2] = 0x01;
|
||||
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
|
||||
0x01, 0x40, 0x03, 0x00, buf, 3, 100);
|
||||
if (ret < 0)
|
||||
printk(KERN_WARNING "%s failed on URB149: %d\n", __func__, ret);
|
||||
|
||||
/* URB 150 */
|
||||
buf[0] = 0x01;
|
||||
buf[1] = 0x00;
|
||||
buf[2] = 0x01;
|
||||
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
|
||||
0x01, 0x40, 0x04, 0x00, buf, 3, 100);
|
||||
if (ret < 0)
|
||||
printk(KERN_WARNING "%s failed on URB150: %d\n", __func__, ret);
|
||||
}
|
||||
|
||||
static int speedtch_start_synchro(struct speedtch_instance_data *instance)
|
||||
{
|
||||
struct usb_device *dev = instance->u.usb_dev;
|
||||
unsigned char buf[2];
|
||||
int ret;
|
||||
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x12, 0xc0, 0x04, 0x00,
|
||||
buf, sizeof(buf), CTRL_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
printk(KERN_WARNING "SpeedTouch: Failed to start ADSL synchronisation: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dbg("speedtch_start_synchro: modem prodded. %d Bytes returned: %02x %02x", ret, buf[0], buf[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs)
|
||||
{
|
||||
struct speedtch_instance_data *instance = urb->context;
|
||||
unsigned int count = urb->actual_length;
|
||||
int ret;
|
||||
|
||||
/* The magic interrupt for "up state" */
|
||||
const static unsigned char up_int[6] = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 };
|
||||
/* The magic interrupt for "down state" */
|
||||
const static unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
/* success */
|
||||
break;
|
||||
case -ECONNRESET:
|
||||
case -ENOENT:
|
||||
case -ESHUTDOWN:
|
||||
/* this urb is terminated; clean up */
|
||||
dbg("%s - urb shutting down with status: %d", __func__, urb->status);
|
||||
return;
|
||||
default:
|
||||
dbg("%s - nonzero urb status received: %d", __func__, urb->status);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (count < 6) {
|
||||
dbg("%s - int packet too short", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!memcmp(up_int, instance->int_data, 6)) {
|
||||
del_timer(&instance->poll_timer);
|
||||
printk(KERN_NOTICE "DSL line goes up\n");
|
||||
} else if (!memcmp(down_int, instance->int_data, 6)) {
|
||||
printk(KERN_NOTICE "DSL line goes down\n");
|
||||
} else {
|
||||
int i;
|
||||
|
||||
printk(KERN_DEBUG "Unknown interrupt packet of %d bytes:", count);
|
||||
for (i = 0; i < count; i++)
|
||||
printk(" %02x", instance->int_data[i]);
|
||||
printk("\n");
|
||||
}
|
||||
schedule_work(&instance->poll_work);
|
||||
|
||||
exit:
|
||||
rmb();
|
||||
if (!instance->int_urb)
|
||||
return;
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret)
|
||||
err("%s - usb_submit_urb failed with result %d", __func__, ret);
|
||||
}
|
||||
|
||||
static int speedtch_get_status(struct speedtch_instance_data *instance,
|
||||
unsigned char *buf)
|
||||
{
|
||||
struct usb_device *dev = instance->u.usb_dev;
|
||||
int ret;
|
||||
|
||||
memset(buf, 0, TOTAL);
|
||||
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7,
|
||||
CTRL_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
dbg("MSG 7 failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b,
|
||||
CTRL_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
dbg("MSG B failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d,
|
||||
CTRL_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
dbg("MSG D failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e,
|
||||
CTRL_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
dbg("MSG E failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f,
|
||||
CTRL_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
dbg("MSG F failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void speedtch_poll_status(struct speedtch_instance_data *instance)
|
||||
{
|
||||
unsigned char buf[TOTAL];
|
||||
int ret;
|
||||
|
||||
ret = speedtch_get_status(instance, buf);
|
||||
if (ret) {
|
||||
printk(KERN_WARNING
|
||||
"SpeedTouch: Error %d fetching device status\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
dbg("Line state %02x", buf[OFFSET_7]);
|
||||
|
||||
switch (buf[OFFSET_7]) {
|
||||
case 0:
|
||||
if (instance->u.atm_dev->signal != ATM_PHY_SIG_LOST) {
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
|
||||
printk(KERN_NOTICE "ADSL line is down\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
if (instance->u.atm_dev->signal != ATM_PHY_SIG_UNKNOWN) {
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
|
||||
printk(KERN_NOTICE "ADSL line is blocked?\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
if (instance->u.atm_dev->signal != ATM_PHY_SIG_LOST) {
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
|
||||
printk(KERN_NOTICE "ADSL line is synchronising\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
if (instance->u.atm_dev->signal != ATM_PHY_SIG_FOUND) {
|
||||
int down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8)
|
||||
| (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24);
|
||||
int up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8)
|
||||
| (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24);
|
||||
|
||||
if (!(down_speed & 0x0000ffff) &&
|
||||
!(up_speed & 0x0000ffff)) {
|
||||
down_speed >>= 16;
|
||||
up_speed >>= 16;
|
||||
}
|
||||
instance->u.atm_dev->link_rate = down_speed * 1000 / 424;
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;
|
||||
|
||||
printk(KERN_NOTICE
|
||||
"ADSL line is up (%d Kib/s down | %d Kib/s up)\n",
|
||||
down_speed, up_speed);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (instance->u.atm_dev->signal != ATM_PHY_SIG_UNKNOWN) {
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
|
||||
printk(KERN_NOTICE "Unknown line state %02x\n", buf[OFFSET_7]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void speedtch_timer_poll(unsigned long data)
|
||||
{
|
||||
struct speedtch_instance_data *instance = (void *)data;
|
||||
|
||||
schedule_work(&instance->poll_work);
|
||||
mod_timer(&instance->poll_timer, jiffies + (5 * HZ));
|
||||
}
|
||||
|
||||
#ifdef USE_FW_LOADER
|
||||
static void speedtch_upload_firmware(struct speedtch_instance_data *instance,
|
||||
const struct firmware *fw1,
|
||||
const struct firmware *fw2)
|
||||
{
|
||||
unsigned char *buffer;
|
||||
struct usb_device *usb_dev = instance->u.usb_dev;
|
||||
struct usb_interface *intf;
|
||||
int actual_length, ret;
|
||||
int offset;
|
||||
|
||||
dbg("speedtch_upload_firmware");
|
||||
|
||||
if (!(intf = usb_ifnum_to_if(usb_dev, 2))) {
|
||||
dbg("speedtch_upload_firmware: interface not found!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) {
|
||||
dbg("speedtch_upload_firmware: no memory for buffer!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* A user-space firmware loader may already have claimed interface #2 */
|
||||
if ((ret =
|
||||
usb_driver_claim_interface(&speedtch_usb_driver, intf, NULL)) < 0) {
|
||||
dbg("speedtch_upload_firmware: interface in use (%d)!", ret);
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
/* URB 7 */
|
||||
if (dl_512_first) { /* some modems need a read before writing the firmware */
|
||||
ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
|
||||
buffer, 0x200, &actual_length, 2000);
|
||||
|
||||
if (ret < 0 && ret != -ETIMEDOUT)
|
||||
dbg("speedtch_upload_firmware: read BLOCK0 from modem failed (%d)!", ret);
|
||||
else
|
||||
dbg("speedtch_upload_firmware: BLOCK0 downloaded (%d bytes)", ret);
|
||||
}
|
||||
|
||||
/* URB 8 : both leds are static green */
|
||||
for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) {
|
||||
int thislen = min_t(int, PAGE_SIZE, fw1->size - offset);
|
||||
memcpy(buffer, fw1->data + offset, thislen);
|
||||
|
||||
ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
|
||||
buffer, thislen, &actual_length, DATA_TIMEOUT);
|
||||
|
||||
if (ret < 0) {
|
||||
dbg("speedtch_upload_firmware: write BLOCK1 to modem failed (%d)!", ret);
|
||||
goto fail_release;
|
||||
}
|
||||
dbg("speedtch_upload_firmware: BLOCK1 uploaded (%zu bytes)", fw1->size);
|
||||
}
|
||||
|
||||
/* USB led blinking green, ADSL led off */
|
||||
|
||||
/* URB 11 */
|
||||
ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
|
||||
buffer, 0x200, &actual_length, DATA_TIMEOUT);
|
||||
|
||||
if (ret < 0) {
|
||||
dbg("speedtch_upload_firmware: read BLOCK2 from modem failed (%d)!", ret);
|
||||
goto fail_release;
|
||||
}
|
||||
dbg("speedtch_upload_firmware: BLOCK2 downloaded (%d bytes)", actual_length);
|
||||
|
||||
/* URBs 12 to 139 - USB led blinking green, ADSL led off */
|
||||
for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) {
|
||||
int thislen = min_t(int, PAGE_SIZE, fw2->size - offset);
|
||||
memcpy(buffer, fw2->data + offset, thislen);
|
||||
|
||||
ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
|
||||
buffer, thislen, &actual_length, DATA_TIMEOUT);
|
||||
|
||||
if (ret < 0) {
|
||||
dbg("speedtch_upload_firmware: write BLOCK3 to modem failed (%d)!", ret);
|
||||
goto fail_release;
|
||||
}
|
||||
}
|
||||
dbg("speedtch_upload_firmware: BLOCK3 uploaded (%zu bytes)", fw2->size);
|
||||
|
||||
/* USB led static green, ADSL led static red */
|
||||
|
||||
/* URB 142 */
|
||||
ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
|
||||
buffer, 0x200, &actual_length, DATA_TIMEOUT);
|
||||
|
||||
if (ret < 0) {
|
||||
dbg("speedtch_upload_firmware: read BLOCK4 from modem failed (%d)!", ret);
|
||||
goto fail_release;
|
||||
}
|
||||
|
||||
/* success */
|
||||
dbg("speedtch_upload_firmware: BLOCK4 downloaded (%d bytes)", actual_length);
|
||||
|
||||
/* Delay to allow firmware to start up. We can do this here
|
||||
because we're in our own kernel thread anyway. */
|
||||
msleep(1000);
|
||||
|
||||
/* Enable software buffering, if requested */
|
||||
if (sw_buffering)
|
||||
speedtch_set_swbuff(instance, 1);
|
||||
|
||||
/* Magic spell; don't ask us what this does */
|
||||
speedtch_test_sequence(instance);
|
||||
|
||||
/* Start modem synchronisation */
|
||||
if (speedtch_start_synchro(instance))
|
||||
dbg("speedtch_start_synchro: failed");
|
||||
|
||||
speedtch_got_firmware(instance, 1);
|
||||
|
||||
free_page((unsigned long)buffer);
|
||||
return;
|
||||
|
||||
fail_release:
|
||||
/* Only release interface #2 if uploading failed; we don't release it
|
||||
we succeeded. This prevents the userspace tools from trying to load
|
||||
the firmware themselves */
|
||||
usb_driver_release_interface(&speedtch_usb_driver, intf);
|
||||
fail_free:
|
||||
free_page((unsigned long)buffer);
|
||||
fail:
|
||||
speedtch_got_firmware(instance, 0);
|
||||
}
|
||||
|
||||
static int speedtch_find_firmware(struct speedtch_instance_data
|
||||
*instance, int phase,
|
||||
const struct firmware **fw_p)
|
||||
{
|
||||
char buf[24];
|
||||
const u16 bcdDevice = le16_to_cpu(instance->u.usb_dev->descriptor.bcdDevice);
|
||||
const u8 major_revision = bcdDevice >> 8;
|
||||
const u8 minor_revision = bcdDevice & 0xff;
|
||||
|
||||
sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, major_revision, minor_revision);
|
||||
dbg("speedtch_find_firmware: looking for %s", buf);
|
||||
|
||||
if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
|
||||
sprintf(buf, "speedtch-%d.bin.%x", phase, major_revision);
|
||||
dbg("speedtch_find_firmware: looking for %s", buf);
|
||||
|
||||
if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
|
||||
sprintf(buf, "speedtch-%d.bin", phase);
|
||||
dbg("speedtch_find_firmware: looking for %s", buf);
|
||||
|
||||
if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
|
||||
dev_warn(&instance->u.usb_dev->dev, "no stage %d firmware found!", phase);
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dev_info(&instance->u.usb_dev->dev, "found stage %d firmware %s\n", phase, buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int speedtch_load_firmware(void *arg)
|
||||
{
|
||||
const struct firmware *fw1, *fw2;
|
||||
struct speedtch_instance_data *instance = arg;
|
||||
|
||||
BUG_ON(!instance);
|
||||
|
||||
daemonize("firmware/speedtch");
|
||||
|
||||
if (!speedtch_find_firmware(instance, 1, &fw1)) {
|
||||
if (!speedtch_find_firmware(instance, 2, &fw2)) {
|
||||
speedtch_upload_firmware(instance, fw1, fw2);
|
||||
release_firmware(fw2);
|
||||
}
|
||||
release_firmware(fw1);
|
||||
}
|
||||
|
||||
/* In case we failed, set state back to NO_FIRMWARE so that
|
||||
another later attempt may work. Otherwise, we never actually
|
||||
manage to recover if, for example, the firmware is on /usr and
|
||||
we look for it too early. */
|
||||
speedtch_got_firmware(instance, 0);
|
||||
|
||||
module_put(THIS_MODULE);
|
||||
udsl_put_instance(&instance->u);
|
||||
return 0;
|
||||
}
|
||||
#endif /* USE_FW_LOADER */
|
||||
|
||||
static void speedtch_firmware_start(struct speedtch_instance_data *instance)
|
||||
{
|
||||
#ifdef USE_FW_LOADER
|
||||
int ret;
|
||||
#endif
|
||||
|
||||
dbg("speedtch_firmware_start");
|
||||
|
||||
down(&instance->u.serialize); /* vs self, speedtch_got_firmware */
|
||||
|
||||
if (instance->u.status >= UDSL_LOADING_FIRMWARE) {
|
||||
up(&instance->u.serialize);
|
||||
return;
|
||||
}
|
||||
|
||||
instance->u.status = UDSL_LOADING_FIRMWARE;
|
||||
up(&instance->u.serialize);
|
||||
|
||||
#ifdef USE_FW_LOADER
|
||||
udsl_get_instance(&instance->u);
|
||||
try_module_get(THIS_MODULE);
|
||||
|
||||
ret = kernel_thread(speedtch_load_firmware, instance,
|
||||
CLONE_FS | CLONE_FILES);
|
||||
|
||||
if (ret >= 0)
|
||||
return; /* OK */
|
||||
|
||||
dbg("speedtch_firmware_start: kernel_thread failed (%d)!", ret);
|
||||
|
||||
module_put(THIS_MODULE);
|
||||
udsl_put_instance(&instance->u);
|
||||
/* Just pretend it never happened... hope modem_run happens */
|
||||
#endif /* USE_FW_LOADER */
|
||||
|
||||
speedtch_got_firmware(instance, 0);
|
||||
}
|
||||
|
||||
static int speedtch_firmware_wait(struct udsl_instance_data *instance)
|
||||
{
|
||||
speedtch_firmware_start((void *)instance);
|
||||
|
||||
if (wait_event_interruptible(instance->firmware_waiters, instance->status != UDSL_LOADING_FIRMWARE) < 0)
|
||||
return -ERESTARTSYS;
|
||||
|
||||
return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EAGAIN;
|
||||
}
|
||||
|
||||
/**********
|
||||
** USB **
|
||||
**********/
|
||||
|
||||
static int speedtch_usb_ioctl(struct usb_interface *intf, unsigned int code,
|
||||
void *user_data)
|
||||
{
|
||||
struct speedtch_instance_data *instance = usb_get_intfdata(intf);
|
||||
|
||||
dbg("speedtch_usb_ioctl entered");
|
||||
|
||||
if (!instance) {
|
||||
dbg("speedtch_usb_ioctl: NULL instance!");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case UDSL_IOCTL_LINE_UP:
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;
|
||||
speedtch_got_firmware(instance, 1);
|
||||
return (instance->u.status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO;
|
||||
case UDSL_IOCTL_LINE_DOWN:
|
||||
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
|
||||
return 0;
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static int speedtch_usb_probe(struct usb_interface *intf,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct usb_device *dev = interface_to_usbdev(intf);
|
||||
int ifnum = intf->altsetting->desc.bInterfaceNumber;
|
||||
struct speedtch_instance_data *instance;
|
||||
unsigned char mac_str[13];
|
||||
int ret, i;
|
||||
char buf7[SIZE_7];
|
||||
|
||||
dbg("speedtch_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d",
|
||||
le16_to_cpu(dev->descriptor.idVendor),
|
||||
le16_to_cpu(dev->descriptor.idProduct), ifnum);
|
||||
|
||||
if ((dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) ||
|
||||
(ifnum != 1))
|
||||
return -ENODEV;
|
||||
|
||||
dbg("speedtch_usb_probe: device accepted");
|
||||
|
||||
/* instance init */
|
||||
instance = kmalloc(sizeof(*instance), GFP_KERNEL);
|
||||
if (!instance) {
|
||||
dbg("speedtch_usb_probe: no memory for instance data!");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memset(instance, 0, sizeof(struct speedtch_instance_data));
|
||||
|
||||
if ((ret = usb_set_interface(dev, 0, 0)) < 0)
|
||||
goto fail;
|
||||
|
||||
if ((ret = usb_set_interface(dev, 2, 0)) < 0)
|
||||
goto fail;
|
||||
|
||||
instance->u.data_endpoint = SPEEDTCH_ENDPOINT_DATA;
|
||||
instance->u.firmware_wait = speedtch_firmware_wait;
|
||||
instance->u.driver_name = speedtch_driver_name;
|
||||
|
||||
ret = udsl_instance_setup(dev, &instance->u);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
init_timer(&instance->poll_timer);
|
||||
instance->poll_timer.function = speedtch_timer_poll;
|
||||
instance->poll_timer.data = (unsigned long)instance;
|
||||
|
||||
INIT_WORK(&instance->poll_work, (void *)speedtch_poll_status, instance);
|
||||
|
||||
/* set MAC address, it is stored in the serial number */
|
||||
memset(instance->u.atm_dev->esi, 0, sizeof(instance->u.atm_dev->esi));
|
||||
if (usb_string(dev, dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) {
|
||||
for (i = 0; i < 6; i++)
|
||||
instance->u.atm_dev->esi[i] =
|
||||
(hex2int(mac_str[i * 2]) * 16) + (hex2int(mac_str[i * 2 + 1]));
|
||||
}
|
||||
|
||||
/* First check whether the modem already seems to be alive */
|
||||
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
|
||||
0x12, 0xc0, 0x07, 0x00, buf7, SIZE_7, 500);
|
||||
|
||||
if (ret == SIZE_7) {
|
||||
dbg("firmware appears to be already loaded");
|
||||
speedtch_got_firmware(instance, 1);
|
||||
speedtch_poll_status(instance);
|
||||
} else {
|
||||
speedtch_firmware_start(instance);
|
||||
}
|
||||
|
||||
usb_set_intfdata(intf, instance);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kfree(instance);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void speedtch_usb_disconnect(struct usb_interface *intf)
|
||||
{
|
||||
struct speedtch_instance_data *instance = usb_get_intfdata(intf);
|
||||
|
||||
dbg("speedtch_usb_disconnect entered");
|
||||
|
||||
if (!instance) {
|
||||
dbg("speedtch_usb_disconnect: NULL instance!");
|
||||
return;
|
||||
}
|
||||
|
||||
/*QQ need to handle disconnects on interface #2 while uploading firmware */
|
||||
/*QQ and what about interface #1? */
|
||||
|
||||
if (instance->int_urb) {
|
||||
struct urb *int_urb = instance->int_urb;
|
||||
instance->int_urb = NULL;
|
||||
wmb();
|
||||
usb_unlink_urb(int_urb);
|
||||
usb_free_urb(int_urb);
|
||||
}
|
||||
|
||||
instance->int_data[0] = 1;
|
||||
del_timer_sync(&instance->poll_timer);
|
||||
wmb();
|
||||
flush_scheduled_work();
|
||||
|
||||
udsl_instance_disconnect(&instance->u);
|
||||
|
||||
/* clean up */
|
||||
usb_set_intfdata(intf, NULL);
|
||||
udsl_put_instance(&instance->u);
|
||||
}
|
||||
|
||||
/***********
|
||||
** init **
|
||||
***********/
|
||||
|
||||
static int __init speedtch_usb_init(void)
|
||||
{
|
||||
dbg("speedtch_usb_init: driver version " DRIVER_VERSION);
|
||||
|
||||
return usb_register(&speedtch_usb_driver);
|
||||
}
|
||||
|
||||
static void __exit speedtch_usb_cleanup(void)
|
||||
{
|
||||
dbg("speedtch_usb_cleanup entered");
|
||||
|
||||
usb_deregister(&speedtch_usb_driver);
|
||||
}
|
||||
|
||||
module_init(speedtch_usb_init);
|
||||
module_exit(speedtch_usb_cleanup);
|
||||
|
||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
1188
drivers/usb/atm/usb_atm.c
Normal file
1188
drivers/usb/atm/usb_atm.c
Normal file
File diff suppressed because it is too large
Load diff
176
drivers/usb/atm/usb_atm.h
Normal file
176
drivers/usb/atm/usb_atm.h
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/******************************************************************************
|
||||
* usb_atm.h - Generic USB xDSL driver core
|
||||
*
|
||||
* Copyright (C) 2001, Alcatel
|
||||
* Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
|
||||
* Copyright (C) 2004, David Woodhouse
|
||||
*
|
||||
* 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., 59
|
||||
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
/*
|
||||
#define DEBUG
|
||||
#define VERBOSE_DEBUG
|
||||
*/
|
||||
|
||||
#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG)
|
||||
# define DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/usb.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define UDSL_ASSERT(x) BUG_ON(!(x))
|
||||
#else
|
||||
#define UDSL_ASSERT(x) do { if (!(x)) warn("failed assertion '" #x "' at line %d", __LINE__); } while(0)
|
||||
#endif
|
||||
|
||||
#define UDSL_MAX_RCV_URBS 4
|
||||
#define UDSL_MAX_SND_URBS 4
|
||||
#define UDSL_MAX_RCV_BUFS 8
|
||||
#define UDSL_MAX_SND_BUFS 8
|
||||
#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */
|
||||
#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */
|
||||
#define UDSL_DEFAULT_RCV_URBS 2
|
||||
#define UDSL_DEFAULT_SND_URBS 2
|
||||
#define UDSL_DEFAULT_RCV_BUFS 4
|
||||
#define UDSL_DEFAULT_SND_BUFS 4
|
||||
#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */
|
||||
#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */
|
||||
|
||||
#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD)
|
||||
#define UDSL_NUM_CELLS(x) (((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD)
|
||||
|
||||
/* receive */
|
||||
|
||||
struct udsl_receive_buffer {
|
||||
struct list_head list;
|
||||
unsigned char *base;
|
||||
unsigned int filled_cells;
|
||||
};
|
||||
|
||||
struct udsl_receiver {
|
||||
struct list_head list;
|
||||
struct udsl_receive_buffer *buffer;
|
||||
struct urb *urb;
|
||||
struct udsl_instance_data *instance;
|
||||
};
|
||||
|
||||
struct udsl_vcc_data {
|
||||
/* vpi/vci lookup */
|
||||
struct list_head list;
|
||||
short vpi;
|
||||
int vci;
|
||||
struct atm_vcc *vcc;
|
||||
|
||||
/* raw cell reassembly */
|
||||
struct sk_buff *sarb;
|
||||
};
|
||||
|
||||
/* send */
|
||||
|
||||
struct udsl_send_buffer {
|
||||
struct list_head list;
|
||||
unsigned char *base;
|
||||
unsigned char *free_start;
|
||||
unsigned int free_cells;
|
||||
};
|
||||
|
||||
struct udsl_sender {
|
||||
struct list_head list;
|
||||
struct udsl_send_buffer *buffer;
|
||||
struct urb *urb;
|
||||
struct udsl_instance_data *instance;
|
||||
};
|
||||
|
||||
struct udsl_control {
|
||||
struct atm_skb_data atm_data;
|
||||
unsigned int num_cells;
|
||||
unsigned int num_entire;
|
||||
unsigned int pdu_padding;
|
||||
unsigned char aal5_trailer[ATM_AAL5_TRAILER];
|
||||
};
|
||||
|
||||
#define UDSL_SKB(x) ((struct udsl_control *)(x)->cb)
|
||||
|
||||
/* main driver data */
|
||||
|
||||
enum udsl_status {
|
||||
UDSL_NO_FIRMWARE,
|
||||
UDSL_LOADING_FIRMWARE,
|
||||
UDSL_LOADED_FIRMWARE
|
||||
};
|
||||
|
||||
struct udsl_instance_data {
|
||||
struct kref refcount;
|
||||
struct semaphore serialize;
|
||||
|
||||
/* USB device part */
|
||||
struct usb_device *usb_dev;
|
||||
char description[64];
|
||||
int data_endpoint;
|
||||
int snd_padding;
|
||||
int rcv_padding;
|
||||
const char *driver_name;
|
||||
|
||||
/* ATM device part */
|
||||
struct atm_dev *atm_dev;
|
||||
struct list_head vcc_list;
|
||||
|
||||
/* firmware */
|
||||
int (*firmware_wait) (struct udsl_instance_data *);
|
||||
enum udsl_status status;
|
||||
wait_queue_head_t firmware_waiters;
|
||||
|
||||
/* receive */
|
||||
struct udsl_receiver receivers[UDSL_MAX_RCV_URBS];
|
||||
struct udsl_receive_buffer receive_buffers[UDSL_MAX_RCV_BUFS];
|
||||
|
||||
spinlock_t receive_lock;
|
||||
struct list_head spare_receivers;
|
||||
struct list_head filled_receive_buffers;
|
||||
|
||||
struct tasklet_struct receive_tasklet;
|
||||
struct list_head spare_receive_buffers;
|
||||
|
||||
/* send */
|
||||
struct udsl_sender senders[UDSL_MAX_SND_URBS];
|
||||
struct udsl_send_buffer send_buffers[UDSL_MAX_SND_BUFS];
|
||||
|
||||
struct sk_buff_head sndqueue;
|
||||
|
||||
spinlock_t send_lock;
|
||||
struct list_head spare_senders;
|
||||
struct list_head spare_send_buffers;
|
||||
|
||||
struct tasklet_struct send_tasklet;
|
||||
struct sk_buff *current_skb; /* being emptied */
|
||||
struct udsl_send_buffer *current_buffer; /* being filled */
|
||||
struct list_head filled_send_buffers;
|
||||
};
|
||||
|
||||
extern int udsl_instance_setup(struct usb_device *dev,
|
||||
struct udsl_instance_data *instance);
|
||||
extern void udsl_instance_disconnect(struct udsl_instance_data *instance);
|
||||
extern void udsl_get_instance(struct udsl_instance_data *instance);
|
||||
extern void udsl_put_instance(struct udsl_instance_data *instance);
|
||||
86
drivers/usb/class/Kconfig
Normal file
86
drivers/usb/class/Kconfig
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#
|
||||
# USB Class driver configuration
|
||||
#
|
||||
comment "USB Device Class drivers"
|
||||
depends on USB
|
||||
|
||||
config USB_AUDIO
|
||||
tristate "USB Audio support"
|
||||
depends on USB && SOUND
|
||||
help
|
||||
Say Y here if you want to connect USB audio equipment such as
|
||||
speakers to your computer's USB port. You only need this if you use
|
||||
the OSS sound driver; ALSA has its own option for usb audio support.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called audio.
|
||||
|
||||
comment "USB Bluetooth TTY can only be used with disabled Bluetooth subsystem"
|
||||
depends on USB && BT
|
||||
|
||||
config USB_BLUETOOTH_TTY
|
||||
tristate "USB Bluetooth TTY support"
|
||||
depends on USB && BT=n
|
||||
---help---
|
||||
This driver implements a nonstandard tty interface to a Bluetooth
|
||||
device that can be used only by specialized Bluetooth HCI software.
|
||||
|
||||
Say Y here if you want to use OpenBT Bluetooth stack (available
|
||||
at <http://developer.axis.com/software>), or other TTY based
|
||||
Bluetooth stacks, and want to connect a USB Bluetooth device
|
||||
to your computer's USB port.
|
||||
|
||||
Do *not* enable this driver if you want to use generic Linux
|
||||
Bluetooth support.
|
||||
|
||||
If in doubt, say N here.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called bluetty.
|
||||
|
||||
config USB_MIDI
|
||||
tristate "USB MIDI support"
|
||||
depends on USB && SOUND
|
||||
---help---
|
||||
Say Y here if you want to connect a USB MIDI device to your
|
||||
computer's USB port. This driver is for devices that comply with
|
||||
'Universal Serial Bus Device Class Definition for MIDI Device'.
|
||||
|
||||
The following devices are known to work:
|
||||
* Steinberg USB2MIDI
|
||||
* Roland MPU64
|
||||
* Roland PC-300
|
||||
* Roland SC8850
|
||||
* Roland UM-1
|
||||
* Roland UM-2
|
||||
* Roland UA-100
|
||||
* Yamaha MU1000
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usb-midi.
|
||||
|
||||
config USB_ACM
|
||||
tristate "USB Modem (CDC ACM) support"
|
||||
depends on USB
|
||||
---help---
|
||||
This driver supports USB modems and ISDN adapters which support the
|
||||
Communication Device Class Abstract Control Model interface.
|
||||
Please read <file:Documentation/usb/acm.txt> for details.
|
||||
|
||||
If your modem only reports "Cls=ff(vend.)" in the descriptors in
|
||||
/proc/bus/usb/devices, then your modem will not work with this
|
||||
driver.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cdc-acm.
|
||||
|
||||
config USB_PRINTER
|
||||
tristate "USB Printer support"
|
||||
depends on USB
|
||||
help
|
||||
Say Y here if you want to connect a USB printer to your computer's
|
||||
USB port.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usblp.
|
||||
|
||||
10
drivers/usb/class/Makefile
Normal file
10
drivers/usb/class/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for USB Class drivers
|
||||
# (one step up from the misc category)
|
||||
#
|
||||
|
||||
obj-$(CONFIG_USB_ACM) += cdc-acm.o
|
||||
obj-$(CONFIG_USB_AUDIO) += audio.o
|
||||
obj-$(CONFIG_USB_BLUETOOTH_TTY) += bluetty.o
|
||||
obj-$(CONFIG_USB_MIDI) += usb-midi.o
|
||||
obj-$(CONFIG_USB_PRINTER) += usblp.o
|
||||
3882
drivers/usb/class/audio.c
Normal file
3882
drivers/usb/class/audio.c
Normal file
File diff suppressed because it is too large
Load diff
110
drivers/usb/class/audio.h
Normal file
110
drivers/usb/class/audio.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#define CS_AUDIO_UNDEFINED 0x20
|
||||
#define CS_AUDIO_DEVICE 0x21
|
||||
#define CS_AUDIO_CONFIGURATION 0x22
|
||||
#define CS_AUDIO_STRING 0x23
|
||||
#define CS_AUDIO_INTERFACE 0x24
|
||||
#define CS_AUDIO_ENDPOINT 0x25
|
||||
|
||||
#define HEADER 0x01
|
||||
#define INPUT_TERMINAL 0x02
|
||||
#define OUTPUT_TERMINAL 0x03
|
||||
#define MIXER_UNIT 0x04
|
||||
#define SELECTOR_UNIT 0x05
|
||||
#define FEATURE_UNIT 0x06
|
||||
#define PROCESSING_UNIT 0x07
|
||||
#define EXTENSION_UNIT 0x08
|
||||
|
||||
#define AS_GENERAL 0x01
|
||||
#define FORMAT_TYPE 0x02
|
||||
#define FORMAT_SPECIFIC 0x03
|
||||
|
||||
#define EP_GENERAL 0x01
|
||||
|
||||
#define MAX_CHAN 9
|
||||
#define MAX_FREQ 16
|
||||
#define MAX_IFACE 8
|
||||
#define MAX_FORMAT 8
|
||||
#define MAX_ALT 32 /* Sorry, we need quite a few for the Philips webcams */
|
||||
|
||||
struct usb_audio_terminal
|
||||
{
|
||||
u8 flags;
|
||||
u8 assoc;
|
||||
u16 type; /* Mic etc */
|
||||
u8 channels;
|
||||
u8 source;
|
||||
u16 chancfg;
|
||||
};
|
||||
|
||||
struct usb_audio_format
|
||||
{
|
||||
u8 type;
|
||||
u8 channels;
|
||||
u8 num_freq;
|
||||
u8 sfz;
|
||||
u8 bits;
|
||||
u16 freq[MAX_FREQ];
|
||||
};
|
||||
|
||||
struct usb_audio_interface
|
||||
{
|
||||
u8 terminal;
|
||||
u8 delay;
|
||||
u16 num_formats;
|
||||
u16 format_type;
|
||||
u8 flags;
|
||||
u8 idleconf; /* Idle config */
|
||||
#define AU_IFACE_FOUND 1
|
||||
struct usb_audio_format format[MAX_FORMAT];
|
||||
};
|
||||
|
||||
struct usb_audio_device
|
||||
{
|
||||
struct list_head list;
|
||||
u8 mixer;
|
||||
u8 selector;
|
||||
void *irq_handle;
|
||||
u8 num_channels;
|
||||
u8 num_dsp_iface;
|
||||
u8 channel_map[MAX_CHAN];
|
||||
struct usb_audio_terminal terminal[MAX_CHAN];
|
||||
struct usb_audio_interface interface[MAX_IFACE][MAX_ALT];
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* Audio Class specific Request Codes */
|
||||
|
||||
#define SET_CUR 0x01
|
||||
#define GET_CUR 0x81
|
||||
#define SET_MIN 0x02
|
||||
#define GET_MIN 0x82
|
||||
#define SET_MAX 0x03
|
||||
#define GET_MAX 0x83
|
||||
#define SET_RES 0x04
|
||||
#define GET_RES 0x84
|
||||
#define SET_MEM 0x05
|
||||
#define GET_MEM 0x85
|
||||
#define GET_STAT 0xff
|
||||
|
||||
/* Terminal Control Selectors */
|
||||
|
||||
#define COPY_PROTECT_CONTROL 0x01
|
||||
|
||||
/* Feature Unit Control Selectors */
|
||||
|
||||
#define MUTE_CONTROL 0x01
|
||||
#define VOLUME_CONTROL 0x02
|
||||
#define BASS_CONTROL 0x03
|
||||
#define MID_CONTROL 0x04
|
||||
#define TREBLE_CONTROL 0x05
|
||||
#define GRAPHIC_EQUALIZER_CONTROL 0x06
|
||||
#define AUTOMATIC_GAIN_CONTROL 0x07
|
||||
#define DELAY_CONTROL 0x08
|
||||
#define BASS_BOOST_CONTROL 0x09
|
||||
#define LOUDNESS_CONTROL 0x0a
|
||||
|
||||
/* Endpoint Control Selectors */
|
||||
|
||||
#define SAMPLING_FREQ_CONTROL 0x01
|
||||
#define PITCH_CONTROL 0x02
|
||||
1279
drivers/usb/class/bluetty.c
Normal file
1279
drivers/usb/class/bluetty.c
Normal file
File diff suppressed because it is too large
Load diff
942
drivers/usb/class/cdc-acm.c
Normal file
942
drivers/usb/class/cdc-acm.c
Normal file
|
|
@ -0,0 +1,942 @@
|
|||
/*
|
||||
* cdc-acm.c
|
||||
*
|
||||
* Copyright (c) 1999 Armin Fuerst <fuerst@in.tum.de>
|
||||
* Copyright (c) 1999 Pavel Machek <pavel@suse.cz>
|
||||
* Copyright (c) 1999 Johannes Erdfelt <johannes@erdfelt.com>
|
||||
* Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz>
|
||||
* Copyright (c) 2004 Oliver Neukum <oliver@neukum.name>
|
||||
*
|
||||
* USB Abstract Control Model driver for USB modems and ISDN adapters
|
||||
*
|
||||
* Sponsored by SuSE
|
||||
*
|
||||
* ChangeLog:
|
||||
* v0.9 - thorough cleaning, URBification, almost a rewrite
|
||||
* v0.10 - some more cleanups
|
||||
* v0.11 - fixed flow control, read error doesn't stop reads
|
||||
* v0.12 - added TIOCM ioctls, added break handling, made struct acm kmalloced
|
||||
* v0.13 - added termios, added hangup
|
||||
* v0.14 - sized down struct acm
|
||||
* v0.15 - fixed flow control again - characters could be lost
|
||||
* v0.16 - added code for modems with swapped data and control interfaces
|
||||
* v0.17 - added new style probing
|
||||
* v0.18 - fixed new style probing for devices with more configurations
|
||||
* v0.19 - fixed CLOCAL handling (thanks to Richard Shih-Ping Chan)
|
||||
* v0.20 - switched to probing on interface (rather than device) class
|
||||
* v0.21 - revert to probing on device for devices with multiple configs
|
||||
* v0.22 - probe only the control interface. if usbcore doesn't choose the
|
||||
* config we want, sysadmin changes bConfigurationValue in sysfs.
|
||||
* v0.23 - use softirq for rx processing, as needed by tty layer
|
||||
* v0.24 - change probe method to evaluate CDC union descriptor
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_driver.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb_cdc.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include "cdc-acm.h"
|
||||
|
||||
/*
|
||||
* Version Information
|
||||
*/
|
||||
#define DRIVER_VERSION "v0.23"
|
||||
#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik"
|
||||
#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
|
||||
|
||||
static struct usb_driver acm_driver;
|
||||
static struct tty_driver *acm_tty_driver;
|
||||
static struct acm *acm_table[ACM_TTY_MINORS];
|
||||
|
||||
static DECLARE_MUTEX(open_sem);
|
||||
|
||||
#define ACM_READY(acm) (acm && acm->dev && acm->used)
|
||||
|
||||
/*
|
||||
* Functions for ACM control messages.
|
||||
*/
|
||||
|
||||
static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int len)
|
||||
{
|
||||
int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0),
|
||||
request, USB_RT_ACM, value,
|
||||
acm->control->altsetting[0].desc.bInterfaceNumber,
|
||||
buf, len, 5000);
|
||||
dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval);
|
||||
return retval < 0 ? retval : 0;
|
||||
}
|
||||
|
||||
/* devices aren't required to support these requests.
|
||||
* the cdc acm descriptor tells whether they do...
|
||||
*/
|
||||
#define acm_set_control(acm, control) \
|
||||
acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0)
|
||||
#define acm_set_line(acm, line) \
|
||||
acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line))
|
||||
#define acm_send_break(acm, ms) \
|
||||
acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0)
|
||||
|
||||
/*
|
||||
* Interrupt handlers for various ACM device responses
|
||||
*/
|
||||
|
||||
/* control interface reports status changes with "interrupt" transfers */
|
||||
static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
|
||||
{
|
||||
struct acm *acm = urb->context;
|
||||
struct usb_cdc_notification *dr = urb->transfer_buffer;
|
||||
unsigned char *data;
|
||||
int newctrl;
|
||||
int status;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
/* success */
|
||||
break;
|
||||
case -ECONNRESET:
|
||||
case -ENOENT:
|
||||
case -ESHUTDOWN:
|
||||
/* this urb is terminated, clean up */
|
||||
dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
|
||||
return;
|
||||
default:
|
||||
dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
goto exit;
|
||||
|
||||
data = (unsigned char *)(dr + 1);
|
||||
switch (dr->bNotificationType) {
|
||||
|
||||
case USB_CDC_NOTIFY_NETWORK_CONNECTION:
|
||||
|
||||
dbg("%s network", dr->wValue ? "connected to" : "disconnected from");
|
||||
break;
|
||||
|
||||
case USB_CDC_NOTIFY_SERIAL_STATE:
|
||||
|
||||
newctrl = le16_to_cpu(get_unaligned((__le16 *) data));
|
||||
|
||||
if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
|
||||
dbg("calling hangup");
|
||||
tty_hangup(acm->tty);
|
||||
}
|
||||
|
||||
acm->ctrlin = newctrl;
|
||||
|
||||
dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c",
|
||||
acm->ctrlin & ACM_CTRL_DCD ? '+' : '-', acm->ctrlin & ACM_CTRL_DSR ? '+' : '-',
|
||||
acm->ctrlin & ACM_CTRL_BRK ? '+' : '-', acm->ctrlin & ACM_CTRL_RI ? '+' : '-',
|
||||
acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-', acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-',
|
||||
acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-');
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
dbg("unknown notification %d received: index %d len %d data0 %d data1 %d",
|
||||
dr->bNotificationType, dr->wIndex,
|
||||
dr->wLength, data[0], data[1]);
|
||||
break;
|
||||
}
|
||||
exit:
|
||||
status = usb_submit_urb (urb, GFP_ATOMIC);
|
||||
if (status)
|
||||
err ("%s - usb_submit_urb failed with result %d",
|
||||
__FUNCTION__, status);
|
||||
}
|
||||
|
||||
/* data interface returns incoming bytes, or we got unthrottled */
|
||||
static void acm_read_bulk(struct urb *urb, struct pt_regs *regs)
|
||||
{
|
||||
struct acm *acm = urb->context;
|
||||
dbg("Entering acm_read_bulk with status %d\n", urb->status);
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return;
|
||||
|
||||
if (urb->status)
|
||||
dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status);
|
||||
|
||||
/* calling tty_flip_buffer_push() in_irq() isn't allowed */
|
||||
tasklet_schedule(&acm->bh);
|
||||
}
|
||||
|
||||
static void acm_rx_tasklet(unsigned long _acm)
|
||||
{
|
||||
struct acm *acm = (void *)_acm;
|
||||
struct urb *urb = acm->readurb;
|
||||
struct tty_struct *tty = acm->tty;
|
||||
unsigned char *data = urb->transfer_buffer;
|
||||
int i = 0;
|
||||
dbg("Entering acm_rx_tasklet");
|
||||
|
||||
if (urb->actual_length > 0 && !acm->throttle) {
|
||||
for (i = 0; i < urb->actual_length && !acm->throttle; i++) {
|
||||
/* if we insert more than TTY_FLIPBUF_SIZE characters,
|
||||
* we drop them. */
|
||||
if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
|
||||
tty_flip_buffer_push(tty);
|
||||
}
|
||||
tty_insert_flip_char(tty, data[i], 0);
|
||||
}
|
||||
dbg("Handed %d bytes to tty layer", i+1);
|
||||
tty_flip_buffer_push(tty);
|
||||
}
|
||||
|
||||
spin_lock(&acm->throttle_lock);
|
||||
if (acm->throttle) {
|
||||
dbg("Throtteling noticed");
|
||||
memmove(data, data + i, urb->actual_length - i);
|
||||
urb->actual_length -= i;
|
||||
acm->resubmit_to_unthrottle = 1;
|
||||
spin_unlock(&acm->throttle_lock);
|
||||
return;
|
||||
}
|
||||
spin_unlock(&acm->throttle_lock);
|
||||
|
||||
urb->actual_length = 0;
|
||||
urb->dev = acm->dev;
|
||||
|
||||
i = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (i)
|
||||
dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i);
|
||||
}
|
||||
|
||||
/* data interface wrote those outgoing bytes */
|
||||
static void acm_write_bulk(struct urb *urb, struct pt_regs *regs)
|
||||
{
|
||||
struct acm *acm = (struct acm *)urb->context;
|
||||
dbg("Entering acm_write_bulk with status %d\n", urb->status);
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
goto out;
|
||||
|
||||
if (urb->status)
|
||||
dbg("nonzero write bulk status received: %d", urb->status);
|
||||
|
||||
schedule_work(&acm->work);
|
||||
out:
|
||||
acm->ready_for_write = 1;
|
||||
}
|
||||
|
||||
static void acm_softint(void *private)
|
||||
{
|
||||
struct acm *acm = private;
|
||||
dbg("Entering acm_softint.\n");
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return;
|
||||
tty_wakeup(acm->tty);
|
||||
}
|
||||
|
||||
/*
|
||||
* TTY handlers
|
||||
*/
|
||||
|
||||
static int acm_tty_open(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
struct acm *acm;
|
||||
int rv = -EINVAL;
|
||||
dbg("Entering acm_tty_open.\n");
|
||||
|
||||
down(&open_sem);
|
||||
|
||||
acm = acm_table[tty->index];
|
||||
if (!acm || !acm->dev)
|
||||
goto err_out;
|
||||
else
|
||||
rv = 0;
|
||||
|
||||
tty->driver_data = acm;
|
||||
acm->tty = tty;
|
||||
|
||||
|
||||
|
||||
if (acm->used++) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
acm->ctrlurb->dev = acm->dev;
|
||||
if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
|
||||
dbg("usb_submit_urb(ctrl irq) failed");
|
||||
goto bail_out;
|
||||
}
|
||||
|
||||
acm->readurb->dev = acm->dev;
|
||||
if (usb_submit_urb(acm->readurb, GFP_KERNEL)) {
|
||||
dbg("usb_submit_urb(read bulk) failed");
|
||||
goto bail_out_and_unlink;
|
||||
}
|
||||
|
||||
if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS))
|
||||
goto full_bailout;
|
||||
|
||||
/* force low_latency on so that our tty_push actually forces the data through,
|
||||
otherwise it is scheduled, and with high data rates data can get lost. */
|
||||
tty->low_latency = 1;
|
||||
|
||||
done:
|
||||
err_out:
|
||||
up(&open_sem);
|
||||
return rv;
|
||||
|
||||
full_bailout:
|
||||
usb_kill_urb(acm->readurb);
|
||||
bail_out_and_unlink:
|
||||
usb_kill_urb(acm->ctrlurb);
|
||||
bail_out:
|
||||
acm->used--;
|
||||
up(&open_sem);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static void acm_tty_close(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
|
||||
if (!acm || !acm->used)
|
||||
return;
|
||||
|
||||
down(&open_sem);
|
||||
if (!--acm->used) {
|
||||
if (acm->dev) {
|
||||
acm_set_control(acm, acm->ctrlout = 0);
|
||||
usb_kill_urb(acm->ctrlurb);
|
||||
usb_kill_urb(acm->writeurb);
|
||||
usb_kill_urb(acm->readurb);
|
||||
} else {
|
||||
tty_unregister_device(acm_tty_driver, acm->minor);
|
||||
acm_table[acm->minor] = NULL;
|
||||
usb_free_urb(acm->ctrlurb);
|
||||
usb_free_urb(acm->readurb);
|
||||
usb_free_urb(acm->writeurb);
|
||||
kfree(acm);
|
||||
}
|
||||
}
|
||||
up(&open_sem);
|
||||
}
|
||||
|
||||
static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
int stat;
|
||||
dbg("Entering acm_tty_write to write %d bytes,\n", count);
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return -EINVAL;
|
||||
if (!acm->ready_for_write)
|
||||
return 0;
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
count = (count > acm->writesize) ? acm->writesize : count;
|
||||
|
||||
dbg("Get %d bytes...", count);
|
||||
memcpy(acm->write_buffer, buf, count);
|
||||
dbg(" Successfully copied.\n");
|
||||
|
||||
acm->writeurb->transfer_buffer_length = count;
|
||||
acm->writeurb->dev = acm->dev;
|
||||
|
||||
acm->ready_for_write = 0;
|
||||
stat = usb_submit_urb(acm->writeurb, GFP_ATOMIC);
|
||||
if (stat < 0) {
|
||||
dbg("usb_submit_urb(write bulk) failed");
|
||||
acm->ready_for_write = 1;
|
||||
return stat;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int acm_tty_write_room(struct tty_struct *tty)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
if (!ACM_READY(acm))
|
||||
return -EINVAL;
|
||||
return !acm->ready_for_write ? 0 : acm->writesize;
|
||||
}
|
||||
|
||||
static int acm_tty_chars_in_buffer(struct tty_struct *tty)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
if (!ACM_READY(acm))
|
||||
return -EINVAL;
|
||||
return !acm->ready_for_write ? acm->writeurb->transfer_buffer_length : 0;
|
||||
}
|
||||
|
||||
static void acm_tty_throttle(struct tty_struct *tty)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
if (!ACM_READY(acm))
|
||||
return;
|
||||
spin_lock_bh(&acm->throttle_lock);
|
||||
acm->throttle = 1;
|
||||
spin_unlock_bh(&acm->throttle_lock);
|
||||
}
|
||||
|
||||
static void acm_tty_unthrottle(struct tty_struct *tty)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
if (!ACM_READY(acm))
|
||||
return;
|
||||
spin_lock_bh(&acm->throttle_lock);
|
||||
acm->throttle = 0;
|
||||
spin_unlock_bh(&acm->throttle_lock);
|
||||
if (acm->resubmit_to_unthrottle) {
|
||||
acm->resubmit_to_unthrottle = 0;
|
||||
acm_read_bulk(acm->readurb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void acm_tty_break_ctl(struct tty_struct *tty, int state)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
if (!ACM_READY(acm))
|
||||
return;
|
||||
if (acm_send_break(acm, state ? 0xffff : 0))
|
||||
dbg("send break failed");
|
||||
}
|
||||
|
||||
static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return -EINVAL;
|
||||
|
||||
return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) |
|
||||
(acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) |
|
||||
(acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) |
|
||||
(acm->ctrlin & ACM_CTRL_RI ? TIOCM_RI : 0) |
|
||||
(acm->ctrlin & ACM_CTRL_DCD ? TIOCM_CD : 0) |
|
||||
TIOCM_CTS;
|
||||
}
|
||||
|
||||
static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file,
|
||||
unsigned int set, unsigned int clear)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
unsigned int newctrl;
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return -EINVAL;
|
||||
|
||||
newctrl = acm->ctrlout;
|
||||
set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (set & TIOCM_RTS ? ACM_CTRL_RTS : 0);
|
||||
clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0);
|
||||
|
||||
newctrl = (newctrl & ~clear) | set;
|
||||
|
||||
if (acm->ctrlout == newctrl)
|
||||
return 0;
|
||||
return acm_set_control(acm, acm->ctrlout = newctrl);
|
||||
}
|
||||
|
||||
static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return -EINVAL;
|
||||
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static __u32 acm_tty_speed[] = {
|
||||
0, 50, 75, 110, 134, 150, 200, 300, 600,
|
||||
1200, 1800, 2400, 4800, 9600, 19200, 38400,
|
||||
57600, 115200, 230400, 460800, 500000, 576000,
|
||||
921600, 1000000, 1152000, 1500000, 2000000,
|
||||
2500000, 3000000, 3500000, 4000000
|
||||
};
|
||||
|
||||
static __u8 acm_tty_size[] = {
|
||||
5, 6, 7, 8
|
||||
};
|
||||
|
||||
static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_old)
|
||||
{
|
||||
struct acm *acm = tty->driver_data;
|
||||
struct termios *termios = tty->termios;
|
||||
struct usb_cdc_line_coding newline;
|
||||
int newctrl = acm->ctrlout;
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
return;
|
||||
|
||||
newline.dwDTERate = cpu_to_le32p(acm_tty_speed +
|
||||
(termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0));
|
||||
newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0;
|
||||
newline.bParityType = termios->c_cflag & PARENB ?
|
||||
(termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0;
|
||||
newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
|
||||
|
||||
acm->clocal = ((termios->c_cflag & CLOCAL) != 0);
|
||||
|
||||
if (!newline.dwDTERate) {
|
||||
newline.dwDTERate = acm->line.dwDTERate;
|
||||
newctrl &= ~ACM_CTRL_DTR;
|
||||
} else newctrl |= ACM_CTRL_DTR;
|
||||
|
||||
if (newctrl != acm->ctrlout)
|
||||
acm_set_control(acm, acm->ctrlout = newctrl);
|
||||
|
||||
if (memcmp(&acm->line, &newline, sizeof newline)) {
|
||||
memcpy(&acm->line, &newline, sizeof newline);
|
||||
dbg("set line: %d %d %d %d", le32_to_cpu(newline.dwDTERate),
|
||||
newline.bCharFormat, newline.bParityType,
|
||||
newline.bDataBits);
|
||||
acm_set_line(acm, &acm->line);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* USB probe and disconnect routines.
|
||||
*/
|
||||
|
||||
static int acm_probe (struct usb_interface *intf,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct usb_cdc_union_desc *union_header = NULL;
|
||||
char *buffer = intf->altsetting->extra;
|
||||
int buflen = intf->altsetting->extralen;
|
||||
struct usb_interface *control_interface;
|
||||
struct usb_interface *data_interface;
|
||||
struct usb_endpoint_descriptor *epctrl;
|
||||
struct usb_endpoint_descriptor *epread;
|
||||
struct usb_endpoint_descriptor *epwrite;
|
||||
struct usb_device *usb_dev = interface_to_usbdev(intf);
|
||||
struct acm *acm;
|
||||
int minor;
|
||||
int ctrlsize,readsize;
|
||||
u8 *buf;
|
||||
u8 ac_management_function = 0;
|
||||
u8 call_management_function = 0;
|
||||
int call_interface_num = -1;
|
||||
int data_interface_num;
|
||||
unsigned long quirks;
|
||||
|
||||
/* handle quirks deadly to normal probing*/
|
||||
quirks = (unsigned long)id->driver_info;
|
||||
if (quirks == NO_UNION_NORMAL) {
|
||||
data_interface = usb_ifnum_to_if(usb_dev, 1);
|
||||
control_interface = usb_ifnum_to_if(usb_dev, 0);
|
||||
goto skip_normal_probe;
|
||||
}
|
||||
|
||||
/* normal probing*/
|
||||
if (!buffer) {
|
||||
err("Wierd descriptor references\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!buflen) {
|
||||
if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) {
|
||||
dev_dbg(&intf->dev,"Seeking extra descriptors on endpoint\n");
|
||||
buflen = intf->cur_altsetting->endpoint->extralen;
|
||||
buffer = intf->cur_altsetting->endpoint->extra;
|
||||
} else {
|
||||
err("Zero length descriptor references\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
while (buflen > 0) {
|
||||
if (buffer [1] != USB_DT_CS_INTERFACE) {
|
||||
err("skipping garbage\n");
|
||||
goto next_desc;
|
||||
}
|
||||
|
||||
switch (buffer [2]) {
|
||||
case USB_CDC_UNION_TYPE: /* we've found it */
|
||||
if (union_header) {
|
||||
err("More than one union descriptor, skipping ...");
|
||||
goto next_desc;
|
||||
}
|
||||
union_header = (struct usb_cdc_union_desc *)
|
||||
buffer;
|
||||
break;
|
||||
case USB_CDC_COUNTRY_TYPE: /* maybe somehow export */
|
||||
break; /* for now we ignore it */
|
||||
case USB_CDC_HEADER_TYPE: /* maybe check version */
|
||||
break; /* for now we ignore it */
|
||||
case USB_CDC_ACM_TYPE:
|
||||
ac_management_function = buffer[3];
|
||||
break;
|
||||
case USB_CDC_CALL_MANAGEMENT_TYPE:
|
||||
call_management_function = buffer[3];
|
||||
call_interface_num = buffer[4];
|
||||
if ((call_management_function & 3) != 3)
|
||||
err("This device cannot do calls on its own. It is no modem.");
|
||||
break;
|
||||
|
||||
default:
|
||||
err("Ignoring extra header, type %d, length %d", buffer[2], buffer[0]);
|
||||
break;
|
||||
}
|
||||
next_desc:
|
||||
buflen -= buffer[0];
|
||||
buffer += buffer[0];
|
||||
}
|
||||
|
||||
if (!union_header) {
|
||||
if (call_interface_num > 0) {
|
||||
dev_dbg(&intf->dev,"No union descriptor, using call management descriptor\n");
|
||||
data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num));
|
||||
control_interface = intf;
|
||||
} else {
|
||||
dev_dbg(&intf->dev,"No union descriptor, giving up\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
} else {
|
||||
control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);
|
||||
data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0));
|
||||
if (!control_interface || !data_interface) {
|
||||
dev_dbg(&intf->dev,"no interfaces\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (data_interface_num != call_interface_num)
|
||||
dev_dbg(&intf->dev,"Seperate call control interface. That is not fully supported.\n");
|
||||
|
||||
skip_normal_probe:
|
||||
|
||||
/*workaround for switched interfaces */
|
||||
if (data_interface->cur_altsetting->desc.bInterfaceClass != CDC_DATA_INTERFACE_TYPE) {
|
||||
if (control_interface->cur_altsetting->desc.bInterfaceClass == CDC_DATA_INTERFACE_TYPE) {
|
||||
struct usb_interface *t;
|
||||
dev_dbg(&intf->dev,"Your device has switched interfaces.\n");
|
||||
|
||||
t = control_interface;
|
||||
control_interface = data_interface;
|
||||
data_interface = t;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (usb_interface_claimed(data_interface)) { /* valid in this context */
|
||||
dev_dbg(&intf->dev,"The data interface isn't available\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
||||
if (data_interface->cur_altsetting->desc.bNumEndpoints < 2)
|
||||
return -EINVAL;
|
||||
|
||||
epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
|
||||
epread = &data_interface->cur_altsetting->endpoint[0].desc;
|
||||
epwrite = &data_interface->cur_altsetting->endpoint[1].desc;
|
||||
|
||||
|
||||
/* workaround for switched endpoints */
|
||||
if ((epread->bEndpointAddress & USB_DIR_IN) != USB_DIR_IN) {
|
||||
/* descriptors are swapped */
|
||||
struct usb_endpoint_descriptor *t;
|
||||
dev_dbg(&intf->dev,"The data interface has switched endpoints\n");
|
||||
|
||||
t = epread;
|
||||
epread = epwrite;
|
||||
epwrite = t;
|
||||
}
|
||||
dbg("interfaces are valid");
|
||||
for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++);
|
||||
|
||||
if (minor == ACM_TTY_MINORS) {
|
||||
err("no more free acm devices");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) {
|
||||
dev_dbg(&intf->dev, "out of memory (acm kmalloc)\n");
|
||||
goto alloc_fail;
|
||||
}
|
||||
memset(acm, 0, sizeof(struct acm));
|
||||
|
||||
ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize);
|
||||
readsize = le16_to_cpu(epread->wMaxPacketSize);
|
||||
acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize);
|
||||
acm->control = control_interface;
|
||||
acm->data = data_interface;
|
||||
acm->minor = minor;
|
||||
acm->dev = usb_dev;
|
||||
acm->ctrl_caps = ac_management_function;
|
||||
acm->ctrlsize = ctrlsize;
|
||||
acm->readsize = readsize;
|
||||
acm->bh.func = acm_rx_tasklet;
|
||||
acm->bh.data = (unsigned long) acm;
|
||||
INIT_WORK(&acm->work, acm_softint, acm);
|
||||
spin_lock_init(&acm->throttle_lock);
|
||||
acm->ready_for_write = 1;
|
||||
|
||||
buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
|
||||
if (!buf) {
|
||||
dev_dbg(&intf->dev, "out of memory (ctrl buffer alloc)\n");
|
||||
goto alloc_fail2;
|
||||
}
|
||||
acm->ctrl_buffer = buf;
|
||||
|
||||
buf = usb_buffer_alloc(usb_dev, readsize, GFP_KERNEL, &acm->read_dma);
|
||||
if (!buf) {
|
||||
dev_dbg(&intf->dev, "out of memory (read buffer alloc)\n");
|
||||
goto alloc_fail3;
|
||||
}
|
||||
acm->read_buffer = buf;
|
||||
|
||||
buf = usb_buffer_alloc(usb_dev, acm->writesize, GFP_KERNEL, &acm->write_dma);
|
||||
if (!buf) {
|
||||
dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n");
|
||||
goto alloc_fail4;
|
||||
}
|
||||
acm->write_buffer = buf;
|
||||
|
||||
acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!acm->ctrlurb) {
|
||||
dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
|
||||
goto alloc_fail5;
|
||||
}
|
||||
acm->readurb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!acm->readurb) {
|
||||
dev_dbg(&intf->dev, "out of memory (readurb kmalloc)\n");
|
||||
goto alloc_fail6;
|
||||
}
|
||||
acm->writeurb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!acm->writeurb) {
|
||||
dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)\n");
|
||||
goto alloc_fail7;
|
||||
}
|
||||
|
||||
usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
|
||||
acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval);
|
||||
acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||
acm->ctrlurb->transfer_dma = acm->ctrl_dma;
|
||||
|
||||
usb_fill_bulk_urb(acm->readurb, usb_dev, usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress),
|
||||
acm->read_buffer, readsize, acm_read_bulk, acm);
|
||||
acm->readurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
|
||||
acm->readurb->transfer_dma = acm->read_dma;
|
||||
|
||||
usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
|
||||
acm->write_buffer, acm->writesize, acm_write_bulk, acm);
|
||||
acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
|
||||
acm->writeurb->transfer_dma = acm->write_dma;
|
||||
|
||||
dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
|
||||
|
||||
acm_set_control(acm, acm->ctrlout);
|
||||
|
||||
acm->line.dwDTERate = cpu_to_le32(9600);
|
||||
acm->line.bDataBits = 8;
|
||||
acm_set_line(acm, &acm->line);
|
||||
|
||||
usb_driver_claim_interface(&acm_driver, data_interface, acm);
|
||||
|
||||
tty_register_device(acm_tty_driver, minor, &intf->dev);
|
||||
|
||||
acm_table[minor] = acm;
|
||||
usb_set_intfdata (intf, acm);
|
||||
return 0;
|
||||
|
||||
alloc_fail7:
|
||||
usb_free_urb(acm->readurb);
|
||||
alloc_fail6:
|
||||
usb_free_urb(acm->ctrlurb);
|
||||
alloc_fail5:
|
||||
usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma);
|
||||
alloc_fail4:
|
||||
usb_buffer_free(usb_dev, readsize, acm->read_buffer, acm->read_dma);
|
||||
alloc_fail3:
|
||||
usb_buffer_free(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
|
||||
alloc_fail2:
|
||||
kfree(acm);
|
||||
alloc_fail:
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void acm_disconnect(struct usb_interface *intf)
|
||||
{
|
||||
struct acm *acm = usb_get_intfdata (intf);
|
||||
struct usb_device *usb_dev = interface_to_usbdev(intf);
|
||||
|
||||
if (!acm || !acm->dev) {
|
||||
dbg("disconnect on nonexisting interface");
|
||||
return;
|
||||
}
|
||||
|
||||
down(&open_sem);
|
||||
acm->dev = NULL;
|
||||
usb_set_intfdata (intf, NULL);
|
||||
|
||||
usb_kill_urb(acm->ctrlurb);
|
||||
usb_kill_urb(acm->readurb);
|
||||
usb_kill_urb(acm->writeurb);
|
||||
|
||||
flush_scheduled_work(); /* wait for acm_softint */
|
||||
|
||||
usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma);
|
||||
usb_buffer_free(usb_dev, acm->readsize, acm->read_buffer, acm->read_dma);
|
||||
usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
|
||||
|
||||
usb_driver_release_interface(&acm_driver, acm->data);
|
||||
|
||||
if (!acm->used) {
|
||||
tty_unregister_device(acm_tty_driver, acm->minor);
|
||||
acm_table[acm->minor] = NULL;
|
||||
usb_free_urb(acm->ctrlurb);
|
||||
usb_free_urb(acm->readurb);
|
||||
usb_free_urb(acm->writeurb);
|
||||
kfree(acm);
|
||||
up(&open_sem);
|
||||
return;
|
||||
}
|
||||
|
||||
up(&open_sem);
|
||||
|
||||
if (acm->tty)
|
||||
tty_hangup(acm->tty);
|
||||
}
|
||||
|
||||
/*
|
||||
* USB driver structure.
|
||||
*/
|
||||
|
||||
static struct usb_device_id acm_ids[] = {
|
||||
/* quirky and broken devices */
|
||||
{ USB_DEVICE(0x0870, 0x0001), /* Metricom GS Modem */
|
||||
.driver_info = NO_UNION_NORMAL, /* has no union descriptor */
|
||||
},
|
||||
/* control interfaces with various AT-command sets */
|
||||
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
|
||||
USB_CDC_ACM_PROTO_AT_V25TER) },
|
||||
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
|
||||
USB_CDC_ACM_PROTO_AT_PCCA101) },
|
||||
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
|
||||
USB_CDC_ACM_PROTO_AT_PCCA101_WAKE) },
|
||||
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
|
||||
USB_CDC_ACM_PROTO_AT_GSM) },
|
||||
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
|
||||
USB_CDC_ACM_PROTO_AT_3G ) },
|
||||
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
|
||||
USB_CDC_ACM_PROTO_AT_CDMA) },
|
||||
|
||||
/* NOTE: COMM/ACM/0xff is likely MSFT RNDIS ... NOT a modem!! */
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE (usb, acm_ids);
|
||||
|
||||
static struct usb_driver acm_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "cdc_acm",
|
||||
.probe = acm_probe,
|
||||
.disconnect = acm_disconnect,
|
||||
.id_table = acm_ids,
|
||||
};
|
||||
|
||||
/*
|
||||
* TTY driver structures.
|
||||
*/
|
||||
|
||||
static struct tty_operations acm_ops = {
|
||||
.open = acm_tty_open,
|
||||
.close = acm_tty_close,
|
||||
.write = acm_tty_write,
|
||||
.write_room = acm_tty_write_room,
|
||||
.ioctl = acm_tty_ioctl,
|
||||
.throttle = acm_tty_throttle,
|
||||
.unthrottle = acm_tty_unthrottle,
|
||||
.chars_in_buffer = acm_tty_chars_in_buffer,
|
||||
.break_ctl = acm_tty_break_ctl,
|
||||
.set_termios = acm_tty_set_termios,
|
||||
.tiocmget = acm_tty_tiocmget,
|
||||
.tiocmset = acm_tty_tiocmset,
|
||||
};
|
||||
|
||||
/*
|
||||
* Init / exit.
|
||||
*/
|
||||
|
||||
static int __init acm_init(void)
|
||||
{
|
||||
int retval;
|
||||
acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS);
|
||||
if (!acm_tty_driver)
|
||||
return -ENOMEM;
|
||||
acm_tty_driver->owner = THIS_MODULE,
|
||||
acm_tty_driver->driver_name = "acm",
|
||||
acm_tty_driver->name = "ttyACM",
|
||||
acm_tty_driver->devfs_name = "usb/acm/",
|
||||
acm_tty_driver->major = ACM_TTY_MAJOR,
|
||||
acm_tty_driver->minor_start = 0,
|
||||
acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
|
||||
acm_tty_driver->subtype = SERIAL_TYPE_NORMAL,
|
||||
acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
|
||||
acm_tty_driver->init_termios = tty_std_termios;
|
||||
acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
|
||||
tty_set_operations(acm_tty_driver, &acm_ops);
|
||||
|
||||
retval = tty_register_driver(acm_tty_driver);
|
||||
if (retval) {
|
||||
put_tty_driver(acm_tty_driver);
|
||||
return retval;
|
||||
}
|
||||
|
||||
retval = usb_register(&acm_driver);
|
||||
if (retval) {
|
||||
tty_unregister_driver(acm_tty_driver);
|
||||
put_tty_driver(acm_tty_driver);
|
||||
return retval;
|
||||
}
|
||||
|
||||
info(DRIVER_VERSION ":" DRIVER_DESC);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit acm_exit(void)
|
||||
{
|
||||
usb_deregister(&acm_driver);
|
||||
tty_unregister_driver(acm_tty_driver);
|
||||
put_tty_driver(acm_tty_driver);
|
||||
}
|
||||
|
||||
module_init(acm_init);
|
||||
module_exit(acm_exit);
|
||||
|
||||
MODULE_AUTHOR( DRIVER_AUTHOR );
|
||||
MODULE_DESCRIPTION( DRIVER_DESC );
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
82
drivers/usb/class/cdc-acm.h
Normal file
82
drivers/usb/class/cdc-acm.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
*
|
||||
* Includes for cdc-acm.c
|
||||
*
|
||||
* Mainly take from usbnet's cdc-ether part
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* CMSPAR, some architectures can't have space and mark parity.
|
||||
*/
|
||||
|
||||
#ifndef CMSPAR
|
||||
#define CMSPAR 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Major and minor numbers.
|
||||
*/
|
||||
|
||||
#define ACM_TTY_MAJOR 166
|
||||
#define ACM_TTY_MINORS 32
|
||||
|
||||
/*
|
||||
* Requests.
|
||||
*/
|
||||
|
||||
#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE)
|
||||
|
||||
/*
|
||||
* Output control lines.
|
||||
*/
|
||||
|
||||
#define ACM_CTRL_DTR 0x01
|
||||
#define ACM_CTRL_RTS 0x02
|
||||
|
||||
/*
|
||||
* Input control lines and line errors.
|
||||
*/
|
||||
|
||||
#define ACM_CTRL_DCD 0x01
|
||||
#define ACM_CTRL_DSR 0x02
|
||||
#define ACM_CTRL_BRK 0x04
|
||||
#define ACM_CTRL_RI 0x08
|
||||
|
||||
#define ACM_CTRL_FRAMING 0x10
|
||||
#define ACM_CTRL_PARITY 0x20
|
||||
#define ACM_CTRL_OVERRUN 0x40
|
||||
|
||||
/*
|
||||
* Internal driver structures.
|
||||
*/
|
||||
|
||||
struct acm {
|
||||
struct usb_device *dev; /* the corresponding usb device */
|
||||
struct usb_interface *control; /* control interface */
|
||||
struct usb_interface *data; /* data interface */
|
||||
struct tty_struct *tty; /* the corresponding tty */
|
||||
struct urb *ctrlurb, *readurb, *writeurb; /* urbs */
|
||||
u8 *ctrl_buffer, *read_buffer, *write_buffer; /* buffers of urbs */
|
||||
dma_addr_t ctrl_dma, read_dma, write_dma; /* dma handles of buffers */
|
||||
struct usb_cdc_line_coding line; /* bits, stop, parity */
|
||||
struct work_struct work; /* work queue entry for line discipline waking up */
|
||||
struct tasklet_struct bh; /* rx processing */
|
||||
spinlock_t throttle_lock; /* synchronize throtteling and read callback */
|
||||
unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
|
||||
unsigned int ctrlout; /* output control lines (DTR, RTS) */
|
||||
unsigned int writesize; /* max packet size for the output bulk endpoint */
|
||||
unsigned int readsize,ctrlsize; /* buffer sizes for freeing */
|
||||
unsigned int used; /* someone has this acm's device open */
|
||||
unsigned int minor; /* acm minor number */
|
||||
unsigned char throttle; /* throttled by tty layer */
|
||||
unsigned char clocal; /* termios CLOCAL */
|
||||
unsigned char ready_for_write; /* write urb can be used */
|
||||
unsigned char resubmit_to_unthrottle; /* throtteling has disabled the read urb */
|
||||
unsigned int ctrl_caps; /* control capabilities from the class specific header */
|
||||
};
|
||||
|
||||
#define CDC_DATA_INTERFACE_TYPE 0x0a
|
||||
|
||||
/* constants describing various quirks and errors */
|
||||
#define NO_UNION_NORMAL 1
|
||||
2154
drivers/usb/class/usb-midi.c
Normal file
2154
drivers/usb/class/usb-midi.c
Normal file
File diff suppressed because it is too large
Load diff
164
drivers/usb/class/usb-midi.h
Normal file
164
drivers/usb/class/usb-midi.h
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
usb-midi.h -- USB-MIDI driver
|
||||
|
||||
Copyright (C) 2001
|
||||
NAGANO Daisuke <breeze.nagano@nifty.ne.jp>
|
||||
|
||||
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, 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 _USB_MIDI_H_
|
||||
#define _USB_MIDI_H_
|
||||
|
||||
#ifndef USB_SUBCLASS_MIDISTREAMING
|
||||
#define USB_SUBCLASS_MIDISTREAMING 3
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Roland MIDI Devices */
|
||||
|
||||
#define USB_VENDOR_ID_ROLAND 0x0582
|
||||
#define USBMIDI_ROLAND_UA100G 0x0000
|
||||
#define USBMIDI_ROLAND_MPU64 0x0002
|
||||
#define USBMIDI_ROLAND_SC8850 0x0003
|
||||
#define USBMIDI_ROLAND_SC8820 0x0007
|
||||
#define USBMIDI_ROLAND_UM2 0x0005
|
||||
#define USBMIDI_ROLAND_UM1 0x0009
|
||||
#define USBMIDI_ROLAND_PC300 0x0008
|
||||
|
||||
/* YAMAHA MIDI Devices */
|
||||
#define USB_VENDOR_ID_YAMAHA 0x0499
|
||||
#define USBMIDI_YAMAHA_MU1000 0x1001
|
||||
|
||||
/* Steinberg MIDI Devices */
|
||||
#define USB_VENDOR_ID_STEINBERG 0x0763
|
||||
#define USBMIDI_STEINBERG_USB2MIDI 0x1001
|
||||
|
||||
/* Mark of the Unicorn MIDI Devices */
|
||||
#define USB_VENDOR_ID_MOTU 0x07fd
|
||||
#define USBMIDI_MOTU_FASTLANE 0x0001
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Supported devices */
|
||||
|
||||
struct usb_midi_endpoint {
|
||||
int endpoint;
|
||||
int cableId; /* if bit-n == 1 then cableId-n is enabled (n: 0 - 15) */
|
||||
};
|
||||
|
||||
struct usb_midi_device {
|
||||
char *deviceName;
|
||||
|
||||
u16 idVendor;
|
||||
u16 idProduct;
|
||||
int interface;
|
||||
int altSetting; /* -1: auto detect */
|
||||
|
||||
struct usb_midi_endpoint in[15];
|
||||
struct usb_midi_endpoint out[15];
|
||||
};
|
||||
|
||||
static struct usb_midi_device usb_midi_devices[] = {
|
||||
{ /* Roland UM-1 */
|
||||
"Roland UM-1",
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM1, 2, -1,
|
||||
{ { 0x81, 1 }, {-1, -1} },
|
||||
{ { 0x01, 1,}, {-1, -1} },
|
||||
},
|
||||
|
||||
{ /* Roland UM-2 */
|
||||
"Roland UM-2" ,
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM2, 2, -1,
|
||||
{ { 0x81, 3 }, {-1, -1} },
|
||||
{ { 0x01, 3,}, {-1, -1} },
|
||||
},
|
||||
|
||||
/** Next entry courtesy research by Michael Minn <[email protected]> **/
|
||||
{ /* Roland UA-100 */
|
||||
"Roland UA-100",
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UA100G, 2, -1,
|
||||
{ { 0x82, 7 }, {-1, -1} }, /** cables 0,1 and 2 for SYSEX **/
|
||||
{ { 0x02, 7 }, {-1, -1} },
|
||||
},
|
||||
|
||||
/** Next entry courtesy research by Michael Minn <[email protected]> **/
|
||||
{ /* Roland SC8850 */
|
||||
"Roland SC8850",
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8850, 2, -1,
|
||||
{ { 0x81, 0x3f }, {-1, -1} },
|
||||
{ { 0x01, 0x3f }, {-1, -1} },
|
||||
},
|
||||
|
||||
{ /* Roland SC8820 */
|
||||
"Roland SC8820",
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8820, 2, -1,
|
||||
{ { 0x81, 0x13 }, {-1, -1} },
|
||||
{ { 0x01, 0x13 }, {-1, -1} },
|
||||
},
|
||||
|
||||
{ /* Roland SC8820 */
|
||||
"Roland SC8820",
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8820, 2, -1,
|
||||
{ { 0x81, 17 }, {-1, -1} },
|
||||
{ { 0x01, 17 }, {-1, -1} },
|
||||
},
|
||||
|
||||
{ /* YAMAHA MU1000 */
|
||||
"YAMAHA MU1000",
|
||||
USB_VENDOR_ID_YAMAHA, USBMIDI_YAMAHA_MU1000, 0, -1,
|
||||
{ { 0x81, 1 }, {-1, -1} },
|
||||
{ { 0x01, 15 }, {-1, -1} },
|
||||
},
|
||||
{ /* Roland PC-300 */
|
||||
"Roland PC-300",
|
||||
USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_PC300, 2, -1,
|
||||
{ { 0x81, 1 }, {-1, -1} },
|
||||
{ { 0x01, 1 }, {-1, -1} },
|
||||
},
|
||||
{ /* MOTU Fastlane USB */
|
||||
"MOTU Fastlane USB",
|
||||
USB_VENDOR_ID_MOTU, USBMIDI_MOTU_FASTLANE, 1, 0,
|
||||
{ { 0x82, 3 }, {-1, -1} },
|
||||
{ { 0x02, 3 }, {-1, -1} },
|
||||
}
|
||||
};
|
||||
|
||||
#define VENDOR_SPECIFIC_USB_MIDI_DEVICES (sizeof(usb_midi_devices)/sizeof(struct usb_midi_device))
|
||||
|
||||
/* for Hot-Plugging */
|
||||
|
||||
static struct usb_device_id usb_midi_ids [] = {
|
||||
{ .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
|
||||
.bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING},
|
||||
{ USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM1 ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM2 ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UA100G ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_PC300 ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8850 ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8820 ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_YAMAHA, USBMIDI_YAMAHA_MU1000 ) },
|
||||
{ USB_DEVICE( USB_VENDOR_ID_MOTU, USBMIDI_MOTU_FASTLANE ) },
|
||||
/* { USB_DEVICE( USB_VENDOR_ID_STEINBERG, USBMIDI_STEINBERG_USB2MIDI ) },*/
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE (usb, usb_midi_ids);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
#endif /* _USB_MIDI_H_ */
|
||||
|
||||
|
||||
1214
drivers/usb/class/usblp.c
Normal file
1214
drivers/usb/class/usblp.c
Normal file
File diff suppressed because it is too large
Load diff
99
drivers/usb/core/Kconfig
Normal file
99
drivers/usb/core/Kconfig
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#
|
||||
# USB Core configuration
|
||||
#
|
||||
config USB_DEBUG
|
||||
bool "USB verbose debug messages"
|
||||
depends on USB
|
||||
help
|
||||
Say Y here if you want the USB core & hub drivers to produce a bunch
|
||||
of debug messages to the system log. Select this if you are having a
|
||||
problem with USB support and want to see more of what is going on.
|
||||
|
||||
comment "Miscellaneous USB options"
|
||||
depends on USB
|
||||
|
||||
config USB_DEVICEFS
|
||||
bool "USB device filesystem"
|
||||
depends on USB
|
||||
---help---
|
||||
If you say Y here (and to "/proc file system support" in the "File
|
||||
systems" section, above), you will get a file /proc/bus/usb/devices
|
||||
which lists the devices currently connected to your USB bus or
|
||||
busses, and for every connected device a file named
|
||||
"/proc/bus/usb/xxx/yyy", where xxx is the bus number and yyy the
|
||||
device number; the latter files can be used by user space programs
|
||||
to talk directly to the device. These files are "virtual", meaning
|
||||
they are generated on the fly and not stored on the hard drive.
|
||||
|
||||
You may need to mount the usbfs file system to see the files, use
|
||||
mount -t usbfs none /proc/bus/usb
|
||||
|
||||
For the format of the various /proc/bus/usb/ files, please read
|
||||
<file:Documentation/usb/proc_usb_info.txt>.
|
||||
|
||||
Please note that this code is completely unrelated to devfs, the
|
||||
"/dev file system support".
|
||||
|
||||
Most users want to say Y here.
|
||||
|
||||
config USB_BANDWIDTH
|
||||
bool "Enforce USB bandwidth allocation (EXPERIMENTAL)"
|
||||
depends on USB && EXPERIMENTAL
|
||||
help
|
||||
If you say Y here, the USB subsystem enforces USB bandwidth
|
||||
allocation and will prevent some device opens from succeeding
|
||||
if they would cause USB bandwidth usage to go above 90% of
|
||||
the bus bandwidth.
|
||||
|
||||
If you say N here, these conditions will cause warning messages
|
||||
about USB bandwidth usage to be logged and some devices or
|
||||
drivers may not work correctly.
|
||||
|
||||
config USB_DYNAMIC_MINORS
|
||||
bool "Dynamic USB minor allocation (EXPERIMENTAL)"
|
||||
depends on USB && EXPERIMENTAL
|
||||
help
|
||||
If you say Y here, the USB subsystem will use dynamic minor
|
||||
allocation for any device that uses the USB major number.
|
||||
This means that you can have more than 16 of a single type
|
||||
of device (like USB printers).
|
||||
|
||||
If you are unsure about this, say N here.
|
||||
|
||||
config USB_SUSPEND
|
||||
bool "USB suspend/resume (EXPERIMENTAL)"
|
||||
depends on USB && PM && EXPERIMENTAL
|
||||
help
|
||||
If you say Y here, you can use driver calls or the sysfs
|
||||
"power/state" file to suspend or resume individual USB
|
||||
peripherals. There are many related features, such as
|
||||
remote wakeup and driver-specific suspend processing, that
|
||||
may not yet work as expected.
|
||||
|
||||
If you are unsure about this, say N here.
|
||||
|
||||
|
||||
config USB_OTG
|
||||
bool
|
||||
depends on USB && EXPERIMENTAL
|
||||
select USB_SUSPEND
|
||||
default n
|
||||
|
||||
|
||||
config USB_OTG_WHITELIST
|
||||
bool "Rely on OTG Targeted Peripherals List"
|
||||
depends on USB_OTG
|
||||
default y
|
||||
help
|
||||
If you say Y here, the "otg_whitelist.h" file will be used as a
|
||||
product whitelist, so USB peripherals not listed there will be
|
||||
rejected during enumeration. This behavior is required by the
|
||||
USB OTG specification for all devices not on your product's
|
||||
"Targeted Peripherals List".
|
||||
|
||||
Otherwise, peripherals not listed there will only generate a
|
||||
warning and enumeration will continue. That's more like what
|
||||
normal Linux-USB hosts do (other than the warning), and is
|
||||
convenient for many stages of product development.
|
||||
|
||||
|
||||
16
drivers/usb/core/Makefile
Normal file
16
drivers/usb/core/Makefile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# Makefile for USB Core files and filesystem
|
||||
#
|
||||
|
||||
usbcore-objs := usb.o hub.o hcd.o urb.o message.o \
|
||||
config.o file.o buffer.o sysfs.o
|
||||
|
||||
ifeq ($(CONFIG_PCI),y)
|
||||
usbcore-objs += hcd-pci.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_USB_DEVICEFS),y)
|
||||
usbcore-objs += devio.o inode.o devices.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_USB) += usbcore.o
|
||||
154
drivers/usb/core/buffer.c
Normal file
154
drivers/usb/core/buffer.c
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* DMA memory management for framework level HCD code (hc_driver)
|
||||
*
|
||||
* This implementation plugs in through generic "usb_bus" level methods,
|
||||
* and should work with all USB controllers, regardles of bus type.
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mm.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/scatterlist.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmapool.h>
|
||||
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#else
|
||||
#undef DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/usb.h>
|
||||
#include "hcd.h"
|
||||
|
||||
|
||||
/*
|
||||
* DMA-Coherent Buffers
|
||||
*/
|
||||
|
||||
/* FIXME tune these based on pool statistics ... */
|
||||
static const size_t pool_max [HCD_BUFFER_POOLS] = {
|
||||
/* platforms without dma-friendly caches might need to
|
||||
* prevent cacheline sharing...
|
||||
*/
|
||||
32,
|
||||
128,
|
||||
512,
|
||||
PAGE_SIZE / 2
|
||||
/* bigger --> allocate pages */
|
||||
};
|
||||
|
||||
|
||||
/* SETUP primitives */
|
||||
|
||||
/**
|
||||
* hcd_buffer_create - initialize buffer pools
|
||||
* @hcd: the bus whose buffer pools are to be initialized
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Call this as part of initializing a host controller that uses the dma
|
||||
* memory allocators. It initializes some pools of dma-coherent memory that
|
||||
* will be shared by all drivers using that controller, or returns a negative
|
||||
* errno value on error.
|
||||
*
|
||||
* Call hcd_buffer_destroy() to clean up after using those pools.
|
||||
*/
|
||||
int hcd_buffer_create (struct usb_hcd *hcd)
|
||||
{
|
||||
char name [16];
|
||||
int i, size;
|
||||
|
||||
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
||||
if (!(size = pool_max [i]))
|
||||
continue;
|
||||
snprintf (name, sizeof name, "buffer-%d", size);
|
||||
hcd->pool [i] = dma_pool_create (name, hcd->self.controller,
|
||||
size, size, 0);
|
||||
if (!hcd->pool [i]) {
|
||||
hcd_buffer_destroy (hcd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* hcd_buffer_destroy - deallocate buffer pools
|
||||
* @hcd: the bus whose buffer pools are to be destroyed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* This frees the buffer pools created by hcd_buffer_create().
|
||||
*/
|
||||
void hcd_buffer_destroy (struct usb_hcd *hcd)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
||||
struct dma_pool *pool = hcd->pool [i];
|
||||
if (pool) {
|
||||
dma_pool_destroy (pool);
|
||||
hcd->pool[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* sometimes alloc/free could use kmalloc with SLAB_DMA, for
|
||||
* better sharing and to leverage mm/slab.c intelligence.
|
||||
*/
|
||||
|
||||
void *hcd_buffer_alloc (
|
||||
struct usb_bus *bus,
|
||||
size_t size,
|
||||
int mem_flags,
|
||||
dma_addr_t *dma
|
||||
)
|
||||
{
|
||||
struct usb_hcd *hcd = bus->hcpriv;
|
||||
int i;
|
||||
|
||||
/* some USB hosts just use PIO */
|
||||
if (!bus->controller->dma_mask) {
|
||||
*dma = ~(dma_addr_t) 0;
|
||||
return kmalloc (size, mem_flags);
|
||||
}
|
||||
|
||||
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
||||
if (size <= pool_max [i])
|
||||
return dma_pool_alloc (hcd->pool [i], mem_flags, dma);
|
||||
}
|
||||
return dma_alloc_coherent (hcd->self.controller, size, dma, 0);
|
||||
}
|
||||
|
||||
void hcd_buffer_free (
|
||||
struct usb_bus *bus,
|
||||
size_t size,
|
||||
void *addr,
|
||||
dma_addr_t dma
|
||||
)
|
||||
{
|
||||
struct usb_hcd *hcd = bus->hcpriv;
|
||||
int i;
|
||||
|
||||
if (!addr)
|
||||
return;
|
||||
|
||||
if (!bus->controller->dma_mask) {
|
||||
kfree (addr);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
||||
if (size <= pool_max [i]) {
|
||||
dma_pool_free (hcd->pool [i], addr, dma);
|
||||
return;
|
||||
}
|
||||
}
|
||||
dma_free_coherent (hcd->self.controller, size, addr, dma);
|
||||
}
|
||||
534
drivers/usb/core/config.c
Normal file
534
drivers/usb/core/config.c
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
#include <linux/config.h>
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/usb.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
|
||||
#define USB_MAXALTSETTING 128 /* Hard limit */
|
||||
#define USB_MAXENDPOINTS 30 /* Hard limit */
|
||||
|
||||
#define USB_MAXCONFIG 8 /* Arbitrary limit */
|
||||
|
||||
|
||||
static inline const char *plural(int n)
|
||||
{
|
||||
return (n == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
static int find_next_descriptor(unsigned char *buffer, int size,
|
||||
int dt1, int dt2, int *num_skipped)
|
||||
{
|
||||
struct usb_descriptor_header *h;
|
||||
int n = 0;
|
||||
unsigned char *buffer0 = buffer;
|
||||
|
||||
/* Find the next descriptor of type dt1 or dt2 */
|
||||
while (size > 0) {
|
||||
h = (struct usb_descriptor_header *) buffer;
|
||||
if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2)
|
||||
break;
|
||||
buffer += h->bLength;
|
||||
size -= h->bLength;
|
||||
++n;
|
||||
}
|
||||
|
||||
/* Store the number of descriptors skipped and return the
|
||||
* number of bytes skipped */
|
||||
if (num_skipped)
|
||||
*num_skipped = n;
|
||||
return buffer - buffer0;
|
||||
}
|
||||
|
||||
static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
|
||||
int asnum, struct usb_host_interface *ifp, int num_ep,
|
||||
unsigned char *buffer, int size)
|
||||
{
|
||||
unsigned char *buffer0 = buffer;
|
||||
struct usb_endpoint_descriptor *d;
|
||||
struct usb_host_endpoint *endpoint;
|
||||
int n, i;
|
||||
|
||||
d = (struct usb_endpoint_descriptor *) buffer;
|
||||
buffer += d->bLength;
|
||||
size -= d->bLength;
|
||||
|
||||
if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE)
|
||||
n = USB_DT_ENDPOINT_AUDIO_SIZE;
|
||||
else if (d->bLength >= USB_DT_ENDPOINT_SIZE)
|
||||
n = USB_DT_ENDPOINT_SIZE;
|
||||
else {
|
||||
dev_warn(ddev, "config %d interface %d altsetting %d has an "
|
||||
"invalid endpoint descriptor of length %d, skipping\n",
|
||||
cfgno, inum, asnum, d->bLength);
|
||||
goto skip_to_next_endpoint_or_interface_descriptor;
|
||||
}
|
||||
|
||||
i = d->bEndpointAddress & ~USB_ENDPOINT_DIR_MASK;
|
||||
if (i >= 16 || i == 0) {
|
||||
dev_warn(ddev, "config %d interface %d altsetting %d has an "
|
||||
"invalid endpoint with address 0x%X, skipping\n",
|
||||
cfgno, inum, asnum, d->bEndpointAddress);
|
||||
goto skip_to_next_endpoint_or_interface_descriptor;
|
||||
}
|
||||
|
||||
/* Only store as many endpoints as we have room for */
|
||||
if (ifp->desc.bNumEndpoints >= num_ep)
|
||||
goto skip_to_next_endpoint_or_interface_descriptor;
|
||||
|
||||
endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints];
|
||||
++ifp->desc.bNumEndpoints;
|
||||
|
||||
memcpy(&endpoint->desc, d, n);
|
||||
INIT_LIST_HEAD(&endpoint->urb_list);
|
||||
|
||||
/* Skip over any Class Specific or Vendor Specific descriptors;
|
||||
* find the next endpoint or interface descriptor */
|
||||
endpoint->extra = buffer;
|
||||
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
|
||||
USB_DT_INTERFACE, &n);
|
||||
endpoint->extralen = i;
|
||||
if (n > 0)
|
||||
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
|
||||
n, plural(n), "endpoint");
|
||||
return buffer - buffer0 + i;
|
||||
|
||||
skip_to_next_endpoint_or_interface_descriptor:
|
||||
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
|
||||
USB_DT_INTERFACE, NULL);
|
||||
return buffer - buffer0 + i;
|
||||
}
|
||||
|
||||
void usb_release_interface_cache(struct kref *ref)
|
||||
{
|
||||
struct usb_interface_cache *intfc = ref_to_usb_interface_cache(ref);
|
||||
int j;
|
||||
|
||||
for (j = 0; j < intfc->num_altsetting; j++)
|
||||
kfree(intfc->altsetting[j].endpoint);
|
||||
kfree(intfc);
|
||||
}
|
||||
|
||||
static int usb_parse_interface(struct device *ddev, int cfgno,
|
||||
struct usb_host_config *config, unsigned char *buffer, int size,
|
||||
u8 inums[], u8 nalts[])
|
||||
{
|
||||
unsigned char *buffer0 = buffer;
|
||||
struct usb_interface_descriptor *d;
|
||||
int inum, asnum;
|
||||
struct usb_interface_cache *intfc;
|
||||
struct usb_host_interface *alt;
|
||||
int i, n;
|
||||
int len, retval;
|
||||
int num_ep, num_ep_orig;
|
||||
|
||||
d = (struct usb_interface_descriptor *) buffer;
|
||||
buffer += d->bLength;
|
||||
size -= d->bLength;
|
||||
|
||||
if (d->bLength < USB_DT_INTERFACE_SIZE)
|
||||
goto skip_to_next_interface_descriptor;
|
||||
|
||||
/* Which interface entry is this? */
|
||||
intfc = NULL;
|
||||
inum = d->bInterfaceNumber;
|
||||
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
|
||||
if (inums[i] == inum) {
|
||||
intfc = config->intf_cache[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!intfc || intfc->num_altsetting >= nalts[i])
|
||||
goto skip_to_next_interface_descriptor;
|
||||
|
||||
/* Check for duplicate altsetting entries */
|
||||
asnum = d->bAlternateSetting;
|
||||
for ((i = 0, alt = &intfc->altsetting[0]);
|
||||
i < intfc->num_altsetting;
|
||||
(++i, ++alt)) {
|
||||
if (alt->desc.bAlternateSetting == asnum) {
|
||||
dev_warn(ddev, "Duplicate descriptor for config %d "
|
||||
"interface %d altsetting %d, skipping\n",
|
||||
cfgno, inum, asnum);
|
||||
goto skip_to_next_interface_descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
++intfc->num_altsetting;
|
||||
memcpy(&alt->desc, d, USB_DT_INTERFACE_SIZE);
|
||||
|
||||
/* Skip over any Class Specific or Vendor Specific descriptors;
|
||||
* find the first endpoint or interface descriptor */
|
||||
alt->extra = buffer;
|
||||
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
|
||||
USB_DT_INTERFACE, &n);
|
||||
alt->extralen = i;
|
||||
if (n > 0)
|
||||
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
|
||||
n, plural(n), "interface");
|
||||
buffer += i;
|
||||
size -= i;
|
||||
|
||||
/* Allocate space for the right(?) number of endpoints */
|
||||
num_ep = num_ep_orig = alt->desc.bNumEndpoints;
|
||||
alt->desc.bNumEndpoints = 0; // Use as a counter
|
||||
if (num_ep > USB_MAXENDPOINTS) {
|
||||
dev_warn(ddev, "too many endpoints for config %d interface %d "
|
||||
"altsetting %d: %d, using maximum allowed: %d\n",
|
||||
cfgno, inum, asnum, num_ep, USB_MAXENDPOINTS);
|
||||
num_ep = USB_MAXENDPOINTS;
|
||||
}
|
||||
|
||||
len = sizeof(struct usb_host_endpoint) * num_ep;
|
||||
alt->endpoint = kmalloc(len, GFP_KERNEL);
|
||||
if (!alt->endpoint)
|
||||
return -ENOMEM;
|
||||
memset(alt->endpoint, 0, len);
|
||||
|
||||
/* Parse all the endpoint descriptors */
|
||||
n = 0;
|
||||
while (size > 0) {
|
||||
if (((struct usb_descriptor_header *) buffer)->bDescriptorType
|
||||
== USB_DT_INTERFACE)
|
||||
break;
|
||||
retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt,
|
||||
num_ep, buffer, size);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
++n;
|
||||
|
||||
buffer += retval;
|
||||
size -= retval;
|
||||
}
|
||||
|
||||
if (n != num_ep_orig)
|
||||
dev_warn(ddev, "config %d interface %d altsetting %d has %d "
|
||||
"endpoint descriptor%s, different from the interface "
|
||||
"descriptor's value: %d\n",
|
||||
cfgno, inum, asnum, n, plural(n), num_ep_orig);
|
||||
return buffer - buffer0;
|
||||
|
||||
skip_to_next_interface_descriptor:
|
||||
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
|
||||
USB_DT_INTERFACE, NULL);
|
||||
return buffer - buffer0 + i;
|
||||
}
|
||||
|
||||
static int usb_parse_configuration(struct device *ddev, int cfgidx,
|
||||
struct usb_host_config *config, unsigned char *buffer, int size)
|
||||
{
|
||||
unsigned char *buffer0 = buffer;
|
||||
int cfgno;
|
||||
int nintf, nintf_orig;
|
||||
int i, j, n;
|
||||
struct usb_interface_cache *intfc;
|
||||
unsigned char *buffer2;
|
||||
int size2;
|
||||
struct usb_descriptor_header *header;
|
||||
int len, retval;
|
||||
u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];
|
||||
|
||||
memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
|
||||
if (config->desc.bDescriptorType != USB_DT_CONFIG ||
|
||||
config->desc.bLength < USB_DT_CONFIG_SIZE) {
|
||||
dev_err(ddev, "invalid descriptor for config index %d: "
|
||||
"type = 0x%X, length = %d\n", cfgidx,
|
||||
config->desc.bDescriptorType, config->desc.bLength);
|
||||
return -EINVAL;
|
||||
}
|
||||
cfgno = config->desc.bConfigurationValue;
|
||||
|
||||
buffer += config->desc.bLength;
|
||||
size -= config->desc.bLength;
|
||||
|
||||
nintf = nintf_orig = config->desc.bNumInterfaces;
|
||||
if (nintf > USB_MAXINTERFACES) {
|
||||
dev_warn(ddev, "config %d has too many interfaces: %d, "
|
||||
"using maximum allowed: %d\n",
|
||||
cfgno, nintf, USB_MAXINTERFACES);
|
||||
nintf = USB_MAXINTERFACES;
|
||||
}
|
||||
|
||||
/* Go through the descriptors, checking their length and counting the
|
||||
* number of altsettings for each interface */
|
||||
n = 0;
|
||||
for ((buffer2 = buffer, size2 = size);
|
||||
size2 > 0;
|
||||
(buffer2 += header->bLength, size2 -= header->bLength)) {
|
||||
|
||||
if (size2 < sizeof(struct usb_descriptor_header)) {
|
||||
dev_warn(ddev, "config %d descriptor has %d excess "
|
||||
"byte%s, ignoring\n",
|
||||
cfgno, size2, plural(size2));
|
||||
break;
|
||||
}
|
||||
|
||||
header = (struct usb_descriptor_header *) buffer2;
|
||||
if ((header->bLength > size2) || (header->bLength < 2)) {
|
||||
dev_warn(ddev, "config %d has an invalid descriptor "
|
||||
"of length %d, skipping remainder of the config\n",
|
||||
cfgno, header->bLength);
|
||||
break;
|
||||
}
|
||||
|
||||
if (header->bDescriptorType == USB_DT_INTERFACE) {
|
||||
struct usb_interface_descriptor *d;
|
||||
int inum;
|
||||
|
||||
d = (struct usb_interface_descriptor *) header;
|
||||
if (d->bLength < USB_DT_INTERFACE_SIZE) {
|
||||
dev_warn(ddev, "config %d has an invalid "
|
||||
"interface descriptor of length %d, "
|
||||
"skipping\n", cfgno, d->bLength);
|
||||
continue;
|
||||
}
|
||||
|
||||
inum = d->bInterfaceNumber;
|
||||
if (inum >= nintf_orig)
|
||||
dev_warn(ddev, "config %d has an invalid "
|
||||
"interface number: %d but max is %d\n",
|
||||
cfgno, inum, nintf_orig - 1);
|
||||
|
||||
/* Have we already encountered this interface?
|
||||
* Count its altsettings */
|
||||
for (i = 0; i < n; ++i) {
|
||||
if (inums[i] == inum)
|
||||
break;
|
||||
}
|
||||
if (i < n) {
|
||||
if (nalts[i] < 255)
|
||||
++nalts[i];
|
||||
} else if (n < USB_MAXINTERFACES) {
|
||||
inums[n] = inum;
|
||||
nalts[n] = 1;
|
||||
++n;
|
||||
}
|
||||
|
||||
} else if (header->bDescriptorType == USB_DT_DEVICE ||
|
||||
header->bDescriptorType == USB_DT_CONFIG)
|
||||
dev_warn(ddev, "config %d contains an unexpected "
|
||||
"descriptor of type 0x%X, skipping\n",
|
||||
cfgno, header->bDescriptorType);
|
||||
|
||||
} /* for ((buffer2 = buffer, size2 = size); ...) */
|
||||
size = buffer2 - buffer;
|
||||
config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0);
|
||||
|
||||
if (n != nintf)
|
||||
dev_warn(ddev, "config %d has %d interface%s, different from "
|
||||
"the descriptor's value: %d\n",
|
||||
cfgno, n, plural(n), nintf_orig);
|
||||
else if (n == 0)
|
||||
dev_warn(ddev, "config %d has no interfaces?\n", cfgno);
|
||||
config->desc.bNumInterfaces = nintf = n;
|
||||
|
||||
/* Check for missing interface numbers */
|
||||
for (i = 0; i < nintf; ++i) {
|
||||
for (j = 0; j < nintf; ++j) {
|
||||
if (inums[j] == i)
|
||||
break;
|
||||
}
|
||||
if (j >= nintf)
|
||||
dev_warn(ddev, "config %d has no interface number "
|
||||
"%d\n", cfgno, i);
|
||||
}
|
||||
|
||||
/* Allocate the usb_interface_caches and altsetting arrays */
|
||||
for (i = 0; i < nintf; ++i) {
|
||||
j = nalts[i];
|
||||
if (j > USB_MAXALTSETTING) {
|
||||
dev_warn(ddev, "too many alternate settings for "
|
||||
"config %d interface %d: %d, "
|
||||
"using maximum allowed: %d\n",
|
||||
cfgno, inums[i], j, USB_MAXALTSETTING);
|
||||
nalts[i] = j = USB_MAXALTSETTING;
|
||||
}
|
||||
|
||||
len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j;
|
||||
config->intf_cache[i] = intfc = kmalloc(len, GFP_KERNEL);
|
||||
if (!intfc)
|
||||
return -ENOMEM;
|
||||
memset(intfc, 0, len);
|
||||
kref_init(&intfc->ref);
|
||||
}
|
||||
|
||||
/* Skip over any Class Specific or Vendor Specific descriptors;
|
||||
* find the first interface descriptor */
|
||||
config->extra = buffer;
|
||||
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
|
||||
USB_DT_INTERFACE, &n);
|
||||
config->extralen = i;
|
||||
if (n > 0)
|
||||
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
|
||||
n, plural(n), "configuration");
|
||||
buffer += i;
|
||||
size -= i;
|
||||
|
||||
/* Parse all the interface/altsetting descriptors */
|
||||
while (size > 0) {
|
||||
retval = usb_parse_interface(ddev, cfgno, config,
|
||||
buffer, size, inums, nalts);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
buffer += retval;
|
||||
size -= retval;
|
||||
}
|
||||
|
||||
/* Check for missing altsettings */
|
||||
for (i = 0; i < nintf; ++i) {
|
||||
intfc = config->intf_cache[i];
|
||||
for (j = 0; j < intfc->num_altsetting; ++j) {
|
||||
for (n = 0; n < intfc->num_altsetting; ++n) {
|
||||
if (intfc->altsetting[n].desc.
|
||||
bAlternateSetting == j)
|
||||
break;
|
||||
}
|
||||
if (n >= intfc->num_altsetting)
|
||||
dev_warn(ddev, "config %d interface %d has no "
|
||||
"altsetting %d\n", cfgno, inums[i], j);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// hub-only!! ... and only exported for reset/reinit path.
|
||||
// otherwise used internally on disconnect/destroy path
|
||||
void usb_destroy_configuration(struct usb_device *dev)
|
||||
{
|
||||
int c, i;
|
||||
|
||||
if (!dev->config)
|
||||
return;
|
||||
|
||||
if (dev->rawdescriptors) {
|
||||
for (i = 0; i < dev->descriptor.bNumConfigurations; i++)
|
||||
kfree(dev->rawdescriptors[i]);
|
||||
|
||||
kfree(dev->rawdescriptors);
|
||||
dev->rawdescriptors = NULL;
|
||||
}
|
||||
|
||||
for (c = 0; c < dev->descriptor.bNumConfigurations; c++) {
|
||||
struct usb_host_config *cf = &dev->config[c];
|
||||
|
||||
kfree(cf->string);
|
||||
cf->string = NULL;
|
||||
|
||||
for (i = 0; i < cf->desc.bNumInterfaces; i++) {
|
||||
if (cf->intf_cache[i])
|
||||
kref_put(&cf->intf_cache[i]->ref,
|
||||
usb_release_interface_cache);
|
||||
}
|
||||
}
|
||||
kfree(dev->config);
|
||||
dev->config = NULL;
|
||||
}
|
||||
|
||||
|
||||
// hub-only!! ... and only in reset path, or usb_new_device()
|
||||
// (used by real hubs and virtual root hubs)
|
||||
int usb_get_configuration(struct usb_device *dev)
|
||||
{
|
||||
struct device *ddev = &dev->dev;
|
||||
int ncfg = dev->descriptor.bNumConfigurations;
|
||||
int result = -ENOMEM;
|
||||
unsigned int cfgno, length;
|
||||
unsigned char *buffer;
|
||||
unsigned char *bigbuffer;
|
||||
struct usb_config_descriptor *desc;
|
||||
|
||||
if (ncfg > USB_MAXCONFIG) {
|
||||
dev_warn(ddev, "too many configurations: %d, "
|
||||
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
|
||||
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
|
||||
}
|
||||
|
||||
if (ncfg < 1) {
|
||||
dev_err(ddev, "no configurations\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
length = ncfg * sizeof(struct usb_host_config);
|
||||
dev->config = kmalloc(length, GFP_KERNEL);
|
||||
if (!dev->config)
|
||||
goto err2;
|
||||
memset(dev->config, 0, length);
|
||||
|
||||
length = ncfg * sizeof(char *);
|
||||
dev->rawdescriptors = kmalloc(length, GFP_KERNEL);
|
||||
if (!dev->rawdescriptors)
|
||||
goto err2;
|
||||
memset(dev->rawdescriptors, 0, length);
|
||||
|
||||
buffer = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
goto err2;
|
||||
desc = (struct usb_config_descriptor *)buffer;
|
||||
|
||||
for (cfgno = 0; cfgno < ncfg; cfgno++) {
|
||||
/* We grab just the first descriptor so we know how long
|
||||
* the whole configuration is */
|
||||
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
|
||||
buffer, USB_DT_CONFIG_SIZE);
|
||||
if (result < 0) {
|
||||
dev_err(ddev, "unable to read config index %d "
|
||||
"descriptor/%s\n", cfgno, "start");
|
||||
goto err;
|
||||
} else if (result < 4) {
|
||||
dev_err(ddev, "config index %d descriptor too short "
|
||||
"(expected %i, got %i)\n", cfgno,
|
||||
USB_DT_CONFIG_SIZE, result);
|
||||
result = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
length = max((int) le16_to_cpu(desc->wTotalLength),
|
||||
USB_DT_CONFIG_SIZE);
|
||||
|
||||
/* Now that we know the length, get the whole thing */
|
||||
bigbuffer = kmalloc(length, GFP_KERNEL);
|
||||
if (!bigbuffer) {
|
||||
result = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
|
||||
bigbuffer, length);
|
||||
if (result < 0) {
|
||||
dev_err(ddev, "unable to read config index %d "
|
||||
"descriptor/%s\n", cfgno, "all");
|
||||
kfree(bigbuffer);
|
||||
goto err;
|
||||
}
|
||||
if (result < length) {
|
||||
dev_warn(ddev, "config index %d descriptor too short "
|
||||
"(expected %i, got %i)\n", cfgno, length, result);
|
||||
length = result;
|
||||
}
|
||||
|
||||
dev->rawdescriptors[cfgno] = bigbuffer;
|
||||
|
||||
result = usb_parse_configuration(&dev->dev, cfgno,
|
||||
&dev->config[cfgno], bigbuffer, length);
|
||||
if (result < 0) {
|
||||
++cfgno;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
result = 0;
|
||||
|
||||
err:
|
||||
kfree(buffer);
|
||||
dev->descriptor.bNumConfigurations = cfgno;
|
||||
err2:
|
||||
if (result == -ENOMEM)
|
||||
dev_err(ddev, "out of memory\n");
|
||||
return result;
|
||||
}
|
||||
677
drivers/usb/core/devices.c
Normal file
677
drivers/usb/core/devices.c
Normal file
|
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
* devices.c
|
||||
* (C) Copyright 1999 Randy Dunlap.
|
||||
* (C) Copyright 1999,2000 Thomas Sailer <sailer@ife.ee.ethz.ch>. (proc file per device)
|
||||
* (C) Copyright 1999 Deti Fliegl (new USB architecture)
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*************************************************************
|
||||
*
|
||||
* <mountpoint>/devices contains USB topology, device, config, class,
|
||||
* interface, & endpoint data.
|
||||
*
|
||||
* I considered using /proc/bus/usb/devices/device# for each device
|
||||
* as it is attached or detached, but I didn't like this for some
|
||||
* reason -- maybe it's just too deep of a directory structure.
|
||||
* I also don't like looking in multiple places to gather and view
|
||||
* the data. Having only one file for ./devices also prevents race
|
||||
* conditions that could arise if a program was reading device info
|
||||
* for devices that are being removed (unplugged). (That is, the
|
||||
* program may find a directory for devnum_12 then try to open it,
|
||||
* but it was just unplugged, so the directory is now deleted.
|
||||
* But programs would just have to be prepared for situations like
|
||||
* this in any plug-and-play environment.)
|
||||
*
|
||||
* 1999-12-16: Thomas Sailer <sailer@ife.ee.ethz.ch>
|
||||
* Converted the whole proc stuff to real
|
||||
* read methods. Now not the whole device list needs to fit
|
||||
* into one page, only the device list for one bus.
|
||||
* Added a poll method to /proc/bus/usb/devices, to wake
|
||||
* up an eventual usbd
|
||||
* 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch>
|
||||
* Turned into its own filesystem
|
||||
* 2000-07-05: Ashley Montanaro <ashley@compsoc.man.ac.uk>
|
||||
* Converted file reading routine to dump to buffer once
|
||||
* per device, not per bus
|
||||
*
|
||||
* $Id: devices.c,v 1.5 2000/01/11 13:58:21 tom Exp $
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/usbdevice_fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include "hcd.h"
|
||||
|
||||
#define MAX_TOPO_LEVEL 6
|
||||
|
||||
/* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */
|
||||
#define ALLOW_SERIAL_NUMBER
|
||||
|
||||
static char *format_topo =
|
||||
/* T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=ddd MxCh=dd */
|
||||
"\nT: Bus=%2.2d Lev=%2.2d Prnt=%2.2d Port=%2.2d Cnt=%2.2d Dev#=%3d Spd=%3s MxCh=%2d\n";
|
||||
|
||||
static char *format_string_manufacturer =
|
||||
/* S: Manufacturer=xxxx */
|
||||
"S: Manufacturer=%.100s\n";
|
||||
|
||||
static char *format_string_product =
|
||||
/* S: Product=xxxx */
|
||||
"S: Product=%.100s\n";
|
||||
|
||||
#ifdef ALLOW_SERIAL_NUMBER
|
||||
static char *format_string_serialnumber =
|
||||
/* S: SerialNumber=xxxx */
|
||||
"S: SerialNumber=%.100s\n";
|
||||
#endif
|
||||
|
||||
static char *format_bandwidth =
|
||||
/* B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd */
|
||||
"B: Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d\n";
|
||||
|
||||
static char *format_device1 =
|
||||
/* D: Ver=xx.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd */
|
||||
"D: Ver=%2x.%02x Cls=%02x(%-5s) Sub=%02x Prot=%02x MxPS=%2d #Cfgs=%3d\n";
|
||||
|
||||
static char *format_device2 =
|
||||
/* P: Vendor=xxxx ProdID=xxxx Rev=xx.xx */
|
||||
"P: Vendor=%04x ProdID=%04x Rev=%2x.%02x\n";
|
||||
|
||||
static char *format_config =
|
||||
/* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */
|
||||
"C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n";
|
||||
|
||||
static char *format_iface =
|
||||
/* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/
|
||||
"I: If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n";
|
||||
|
||||
static char *format_endpt =
|
||||
/* E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */
|
||||
"E: Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n";
|
||||
|
||||
|
||||
/*
|
||||
* Need access to the driver and USB bus lists.
|
||||
* extern struct list_head usb_bus_list;
|
||||
* However, these will come from functions that return ptrs to each of them.
|
||||
*/
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(deviceconndiscwq);
|
||||
static unsigned int conndiscevcnt = 0;
|
||||
|
||||
/* this struct stores the poll state for <mountpoint>/devices pollers */
|
||||
struct usb_device_status {
|
||||
unsigned int lastev;
|
||||
};
|
||||
|
||||
struct class_info {
|
||||
int class;
|
||||
char *class_name;
|
||||
};
|
||||
|
||||
static const struct class_info clas_info[] =
|
||||
{ /* max. 5 chars. per name string */
|
||||
{USB_CLASS_PER_INTERFACE, ">ifc"},
|
||||
{USB_CLASS_AUDIO, "audio"},
|
||||
{USB_CLASS_COMM, "comm."},
|
||||
{USB_CLASS_HID, "HID"},
|
||||
{USB_CLASS_HUB, "hub"},
|
||||
{USB_CLASS_PHYSICAL, "PID"},
|
||||
{USB_CLASS_PRINTER, "print"},
|
||||
{USB_CLASS_MASS_STORAGE, "stor."},
|
||||
{USB_CLASS_CDC_DATA, "data"},
|
||||
{USB_CLASS_APP_SPEC, "app."},
|
||||
{USB_CLASS_VENDOR_SPEC, "vend."},
|
||||
{USB_CLASS_STILL_IMAGE, "still"},
|
||||
{USB_CLASS_CSCID, "scard"},
|
||||
{USB_CLASS_CONTENT_SEC, "c-sec"},
|
||||
{-1, "unk."} /* leave as last */
|
||||
};
|
||||
|
||||
/*****************************************************************/
|
||||
|
||||
void usbfs_conn_disc_event(void)
|
||||
{
|
||||
conndiscevcnt++;
|
||||
wake_up(&deviceconndiscwq);
|
||||
}
|
||||
|
||||
static const char *class_decode(const int class)
|
||||
{
|
||||
int ix;
|
||||
|
||||
for (ix = 0; clas_info[ix].class != -1; ix++)
|
||||
if (clas_info[ix].class == class)
|
||||
break;
|
||||
return (clas_info[ix].class_name);
|
||||
}
|
||||
|
||||
static char *usb_dump_endpoint_descriptor (
|
||||
int speed,
|
||||
char *start,
|
||||
char *end,
|
||||
const struct usb_endpoint_descriptor *desc
|
||||
)
|
||||
{
|
||||
char dir, unit, *type;
|
||||
unsigned interval, in, bandwidth = 1;
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
in = (desc->bEndpointAddress & USB_DIR_IN);
|
||||
dir = in ? 'I' : 'O';
|
||||
if (speed == USB_SPEED_HIGH) {
|
||||
switch (le16_to_cpu(desc->wMaxPacketSize) & (0x03 << 11)) {
|
||||
case 1 << 11: bandwidth = 2; break;
|
||||
case 2 << 11: bandwidth = 3; break;
|
||||
}
|
||||
}
|
||||
|
||||
/* this isn't checking for illegal values */
|
||||
switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
|
||||
case USB_ENDPOINT_XFER_CONTROL:
|
||||
type = "Ctrl";
|
||||
if (speed == USB_SPEED_HIGH) /* uframes per NAK */
|
||||
interval = desc->bInterval;
|
||||
else
|
||||
interval = 0;
|
||||
dir = 'B'; /* ctrl is bidirectional */
|
||||
break;
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
type = "Isoc";
|
||||
interval = 1 << (desc->bInterval - 1);
|
||||
break;
|
||||
case USB_ENDPOINT_XFER_BULK:
|
||||
type = "Bulk";
|
||||
if (speed == USB_SPEED_HIGH && !in) /* uframes per NAK */
|
||||
interval = desc->bInterval;
|
||||
else
|
||||
interval = 0;
|
||||
break;
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
type = "Int.";
|
||||
if (speed == USB_SPEED_HIGH) {
|
||||
interval = 1 << (desc->bInterval - 1);
|
||||
} else
|
||||
interval = desc->bInterval;
|
||||
break;
|
||||
default: /* "can't happen" */
|
||||
return start;
|
||||
}
|
||||
interval *= (speed == USB_SPEED_HIGH) ? 125 : 1000;
|
||||
if (interval % 1000)
|
||||
unit = 'u';
|
||||
else {
|
||||
unit = 'm';
|
||||
interval /= 1000;
|
||||
}
|
||||
|
||||
start += sprintf(start, format_endpt, desc->bEndpointAddress, dir,
|
||||
desc->bmAttributes, type,
|
||||
(le16_to_cpu(desc->wMaxPacketSize) & 0x07ff) * bandwidth,
|
||||
interval, unit);
|
||||
return start;
|
||||
}
|
||||
|
||||
static char *usb_dump_interface_descriptor(char *start, char *end,
|
||||
const struct usb_interface_cache *intfc,
|
||||
const struct usb_interface *iface,
|
||||
int setno)
|
||||
{
|
||||
const struct usb_interface_descriptor *desc = &intfc->altsetting[setno].desc;
|
||||
char *driver_name = "";
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
down_read(&usb_bus_type.subsys.rwsem);
|
||||
if (iface)
|
||||
driver_name = (iface->dev.driver
|
||||
? iface->dev.driver->name
|
||||
: "(none)");
|
||||
start += sprintf(start, format_iface,
|
||||
desc->bInterfaceNumber,
|
||||
desc->bAlternateSetting,
|
||||
desc->bNumEndpoints,
|
||||
desc->bInterfaceClass,
|
||||
class_decode(desc->bInterfaceClass),
|
||||
desc->bInterfaceSubClass,
|
||||
desc->bInterfaceProtocol,
|
||||
driver_name);
|
||||
up_read(&usb_bus_type.subsys.rwsem);
|
||||
return start;
|
||||
}
|
||||
|
||||
static char *usb_dump_interface(
|
||||
int speed,
|
||||
char *start,
|
||||
char *end,
|
||||
const struct usb_interface_cache *intfc,
|
||||
const struct usb_interface *iface,
|
||||
int setno
|
||||
) {
|
||||
const struct usb_host_interface *desc = &intfc->altsetting[setno];
|
||||
int i;
|
||||
|
||||
start = usb_dump_interface_descriptor(start, end, intfc, iface, setno);
|
||||
for (i = 0; i < desc->desc.bNumEndpoints; i++) {
|
||||
if (start > end)
|
||||
return start;
|
||||
start = usb_dump_endpoint_descriptor(speed,
|
||||
start, end, &desc->endpoint[i].desc);
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/* TBD:
|
||||
* 0. TBDs
|
||||
* 1. marking active interface altsettings (code lists all, but should mark
|
||||
* which ones are active, if any)
|
||||
*/
|
||||
|
||||
static char *usb_dump_config_descriptor(char *start, char *end, const struct usb_config_descriptor *desc, int active)
|
||||
{
|
||||
if (start > end)
|
||||
return start;
|
||||
start += sprintf(start, format_config,
|
||||
active ? '*' : ' ', /* mark active/actual/current cfg. */
|
||||
desc->bNumInterfaces,
|
||||
desc->bConfigurationValue,
|
||||
desc->bmAttributes,
|
||||
desc->bMaxPower * 2);
|
||||
return start;
|
||||
}
|
||||
|
||||
static char *usb_dump_config (
|
||||
int speed,
|
||||
char *start,
|
||||
char *end,
|
||||
const struct usb_host_config *config,
|
||||
int active
|
||||
)
|
||||
{
|
||||
int i, j;
|
||||
struct usb_interface_cache *intfc;
|
||||
struct usb_interface *interface;
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
if (!config) /* getting these some in 2.3.7; none in 2.3.6 */
|
||||
return start + sprintf(start, "(null Cfg. desc.)\n");
|
||||
start = usb_dump_config_descriptor(start, end, &config->desc, active);
|
||||
for (i = 0; i < config->desc.bNumInterfaces; i++) {
|
||||
intfc = config->intf_cache[i];
|
||||
interface = config->interface[i];
|
||||
for (j = 0; j < intfc->num_altsetting; j++) {
|
||||
if (start > end)
|
||||
return start;
|
||||
start = usb_dump_interface(speed,
|
||||
start, end, intfc, interface, j);
|
||||
}
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the different USB descriptors.
|
||||
*/
|
||||
static char *usb_dump_device_descriptor(char *start, char *end, const struct usb_device_descriptor *desc)
|
||||
{
|
||||
u16 bcdUSB = le16_to_cpu(desc->bcdUSB);
|
||||
u16 bcdDevice = le16_to_cpu(desc->bcdDevice);
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
start += sprintf (start, format_device1,
|
||||
bcdUSB >> 8, bcdUSB & 0xff,
|
||||
desc->bDeviceClass,
|
||||
class_decode (desc->bDeviceClass),
|
||||
desc->bDeviceSubClass,
|
||||
desc->bDeviceProtocol,
|
||||
desc->bMaxPacketSize0,
|
||||
desc->bNumConfigurations);
|
||||
if (start > end)
|
||||
return start;
|
||||
start += sprintf(start, format_device2,
|
||||
le16_to_cpu(desc->idVendor),
|
||||
le16_to_cpu(desc->idProduct),
|
||||
bcdDevice >> 8, bcdDevice & 0xff);
|
||||
return start;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the different strings that this device holds.
|
||||
*/
|
||||
static char *usb_dump_device_strings (char *start, char *end, struct usb_device *dev)
|
||||
{
|
||||
if (start > end)
|
||||
return start;
|
||||
if (dev->manufacturer)
|
||||
start += sprintf(start, format_string_manufacturer, dev->manufacturer);
|
||||
if (start > end)
|
||||
goto out;
|
||||
if (dev->product)
|
||||
start += sprintf(start, format_string_product, dev->product);
|
||||
if (start > end)
|
||||
goto out;
|
||||
#ifdef ALLOW_SERIAL_NUMBER
|
||||
if (dev->serial)
|
||||
start += sprintf(start, format_string_serialnumber, dev->serial);
|
||||
#endif
|
||||
out:
|
||||
return start;
|
||||
}
|
||||
|
||||
static char *usb_dump_desc(char *start, char *end, struct usb_device *dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
|
||||
start = usb_dump_device_descriptor(start, end, &dev->descriptor);
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
|
||||
start = usb_dump_device_strings (start, end, dev);
|
||||
|
||||
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
|
||||
if (start > end)
|
||||
return start;
|
||||
start = usb_dump_config(dev->speed,
|
||||
start, end, dev->config + i,
|
||||
/* active ? */
|
||||
(dev->config + i) == dev->actconfig);
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
#ifdef PROC_EXTRA /* TBD: may want to add this code later */
|
||||
|
||||
static char *usb_dump_hub_descriptor(char *start, char *end, const struct usb_hub_descriptor * desc)
|
||||
{
|
||||
int leng = USB_DT_HUB_NONVAR_SIZE;
|
||||
unsigned char *ptr = (unsigned char *)desc;
|
||||
|
||||
if (start > end)
|
||||
return start;
|
||||
start += sprintf(start, "Interface:");
|
||||
while (leng && start <= end) {
|
||||
start += sprintf(start, " %02x", *ptr);
|
||||
ptr++; leng--;
|
||||
}
|
||||
*start++ = '\n';
|
||||
return start;
|
||||
}
|
||||
|
||||
static char *usb_dump_string(char *start, char *end, const struct usb_device *dev, char *id, int index)
|
||||
{
|
||||
if (start > end)
|
||||
return start;
|
||||
start += sprintf(start, "Interface:");
|
||||
if (index <= dev->maxstring && dev->stringindex && dev->stringindex[index])
|
||||
start += sprintf(start, "%s: %.100s ", id, dev->stringindex[index]);
|
||||
return start;
|
||||
}
|
||||
|
||||
#endif /* PROC_EXTRA */
|
||||
|
||||
/*****************************************************************/
|
||||
|
||||
/* This is a recursive function. Parameters:
|
||||
* buffer - the user-space buffer to write data into
|
||||
* nbytes - the maximum number of bytes to write
|
||||
* skip_bytes - the number of bytes to skip before writing anything
|
||||
* file_offset - the offset into the devices file on completion
|
||||
* The caller must own the device lock.
|
||||
*/
|
||||
static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, loff_t *skip_bytes, loff_t *file_offset,
|
||||
struct usb_device *usbdev, struct usb_bus *bus, int level, int index, int count)
|
||||
{
|
||||
int chix;
|
||||
int ret, cnt = 0;
|
||||
int parent_devnum = 0;
|
||||
char *pages_start, *data_end, *speed;
|
||||
unsigned int length;
|
||||
ssize_t total_written = 0;
|
||||
|
||||
/* don't bother with anything else if we're not writing any data */
|
||||
if (*nbytes <= 0)
|
||||
return 0;
|
||||
|
||||
if (level > MAX_TOPO_LEVEL)
|
||||
return 0;
|
||||
/* allocate 2^1 pages = 8K (on i386); should be more than enough for one device */
|
||||
if (!(pages_start = (char*) __get_free_pages(GFP_KERNEL,1)))
|
||||
return -ENOMEM;
|
||||
|
||||
if (usbdev->parent && usbdev->parent->devnum != -1)
|
||||
parent_devnum = usbdev->parent->devnum;
|
||||
/*
|
||||
* So the root hub's parent is 0 and any device that is
|
||||
* plugged into the root hub has a parent of 0.
|
||||
*/
|
||||
switch (usbdev->speed) {
|
||||
case USB_SPEED_LOW:
|
||||
speed = "1.5"; break;
|
||||
case USB_SPEED_UNKNOWN: /* usb 1.1 root hub code */
|
||||
case USB_SPEED_FULL:
|
||||
speed = "12 "; break;
|
||||
case USB_SPEED_HIGH:
|
||||
speed = "480"; break;
|
||||
default:
|
||||
speed = "?? ";
|
||||
}
|
||||
data_end = pages_start + sprintf(pages_start, format_topo,
|
||||
bus->busnum, level, parent_devnum,
|
||||
index, count, usbdev->devnum,
|
||||
speed, usbdev->maxchild);
|
||||
/*
|
||||
* level = topology-tier level;
|
||||
* parent_devnum = parent device number;
|
||||
* index = parent's connector number;
|
||||
* count = device count at this level
|
||||
*/
|
||||
/* If this is the root hub, display the bandwidth information */
|
||||
if (level == 0) {
|
||||
int max;
|
||||
|
||||
/* high speed reserves 80%, full/low reserves 90% */
|
||||
if (usbdev->speed == USB_SPEED_HIGH)
|
||||
max = 800;
|
||||
else
|
||||
max = FRAME_TIME_MAX_USECS_ALLOC;
|
||||
|
||||
/* report "average" periodic allocation over a microsecond.
|
||||
* the schedules are actually bursty, HCDs need to deal with
|
||||
* that and just compute/report this average.
|
||||
*/
|
||||
data_end += sprintf(data_end, format_bandwidth,
|
||||
bus->bandwidth_allocated, max,
|
||||
(100 * bus->bandwidth_allocated + max / 2)
|
||||
/ max,
|
||||
bus->bandwidth_int_reqs,
|
||||
bus->bandwidth_isoc_reqs);
|
||||
|
||||
}
|
||||
data_end = usb_dump_desc(data_end, pages_start + (2 * PAGE_SIZE) - 256, usbdev);
|
||||
|
||||
if (data_end > (pages_start + (2 * PAGE_SIZE) - 256))
|
||||
data_end += sprintf(data_end, "(truncated)\n");
|
||||
|
||||
length = data_end - pages_start;
|
||||
/* if we can start copying some data to the user */
|
||||
if (length > *skip_bytes) {
|
||||
length -= *skip_bytes;
|
||||
if (length > *nbytes)
|
||||
length = *nbytes;
|
||||
if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) {
|
||||
free_pages((unsigned long)pages_start, 1);
|
||||
return -EFAULT;
|
||||
}
|
||||
*nbytes -= length;
|
||||
*file_offset += length;
|
||||
total_written += length;
|
||||
*buffer += length;
|
||||
*skip_bytes = 0;
|
||||
} else
|
||||
*skip_bytes -= length;
|
||||
|
||||
free_pages((unsigned long)pages_start, 1);
|
||||
|
||||
/* Now look at all of this device's children. */
|
||||
for (chix = 0; chix < usbdev->maxchild; chix++) {
|
||||
struct usb_device *childdev = usbdev->children[chix];
|
||||
|
||||
if (childdev) {
|
||||
down(&childdev->serialize);
|
||||
ret = usb_device_dump(buffer, nbytes, skip_bytes, file_offset, childdev,
|
||||
bus, level + 1, chix, ++cnt);
|
||||
up(&childdev->serialize);
|
||||
if (ret == -EFAULT)
|
||||
return total_written;
|
||||
total_written += ret;
|
||||
}
|
||||
}
|
||||
return total_written;
|
||||
}
|
||||
|
||||
static ssize_t usb_device_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
ssize_t ret, total_written = 0;
|
||||
loff_t skip_bytes = *ppos;
|
||||
|
||||
if (*ppos < 0)
|
||||
return -EINVAL;
|
||||
if (nbytes <= 0)
|
||||
return 0;
|
||||
if (!access_ok(VERIFY_WRITE, buf, nbytes))
|
||||
return -EFAULT;
|
||||
|
||||
down (&usb_bus_list_lock);
|
||||
/* print devices for all busses */
|
||||
list_for_each_entry(bus, &usb_bus_list, bus_list) {
|
||||
/* recurse through all children of the root hub */
|
||||
if (!bus->root_hub)
|
||||
continue;
|
||||
usb_lock_device(bus->root_hub);
|
||||
ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, bus->root_hub, bus, 0, 0, 0);
|
||||
usb_unlock_device(bus->root_hub);
|
||||
if (ret < 0) {
|
||||
up(&usb_bus_list_lock);
|
||||
return ret;
|
||||
}
|
||||
total_written += ret;
|
||||
}
|
||||
up (&usb_bus_list_lock);
|
||||
return total_written;
|
||||
}
|
||||
|
||||
/* Kernel lock for "lastev" protection */
|
||||
static unsigned int usb_device_poll(struct file *file, struct poll_table_struct *wait)
|
||||
{
|
||||
struct usb_device_status *st = (struct usb_device_status *)file->private_data;
|
||||
unsigned int mask = 0;
|
||||
|
||||
lock_kernel();
|
||||
if (!st) {
|
||||
st = kmalloc(sizeof(struct usb_device_status), GFP_KERNEL);
|
||||
if (!st) {
|
||||
unlock_kernel();
|
||||
return POLLIN;
|
||||
}
|
||||
|
||||
/* we may have dropped BKL - need to check for having lost the race */
|
||||
if (file->private_data) {
|
||||
kfree(st);
|
||||
st = file->private_data;
|
||||
goto lost_race;
|
||||
}
|
||||
|
||||
/*
|
||||
* need to prevent the module from being unloaded, since
|
||||
* proc_unregister does not call the release method and
|
||||
* we would have a memory leak
|
||||
*/
|
||||
st->lastev = conndiscevcnt;
|
||||
file->private_data = st;
|
||||
mask = POLLIN;
|
||||
}
|
||||
lost_race:
|
||||
if (file->f_mode & FMODE_READ)
|
||||
poll_wait(file, &deviceconndiscwq, wait);
|
||||
if (st->lastev != conndiscevcnt)
|
||||
mask |= POLLIN;
|
||||
st->lastev = conndiscevcnt;
|
||||
unlock_kernel();
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int usb_device_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_device_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
if (file->private_data) {
|
||||
kfree(file->private_data);
|
||||
file->private_data = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static loff_t usb_device_lseek(struct file * file, loff_t offset, int orig)
|
||||
{
|
||||
loff_t ret;
|
||||
|
||||
lock_kernel();
|
||||
|
||||
switch (orig) {
|
||||
case 0:
|
||||
file->f_pos = offset;
|
||||
ret = file->f_pos;
|
||||
break;
|
||||
case 1:
|
||||
file->f_pos += offset;
|
||||
ret = file->f_pos;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
unlock_kernel();
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct file_operations usbfs_devices_fops = {
|
||||
.llseek = usb_device_lseek,
|
||||
.read = usb_device_read,
|
||||
.poll = usb_device_poll,
|
||||
.open = usb_device_open,
|
||||
.release = usb_device_release,
|
||||
};
|
||||
1483
drivers/usb/core/devio.c
Normal file
1483
drivers/usb/core/devio.c
Normal file
File diff suppressed because it is too large
Load diff
227
drivers/usb/core/file.c
Normal file
227
drivers/usb/core/file.c
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* drivers/usb/file.c
|
||||
*
|
||||
* (C) Copyright Linus Torvalds 1999
|
||||
* (C) Copyright Johannes Erdfelt 1999-2001
|
||||
* (C) Copyright Andreas Gal 1999
|
||||
* (C) Copyright Gregory P. Smith 1999
|
||||
* (C) Copyright Deti Fliegl 1999 (new USB architecture)
|
||||
* (C) Copyright Randy Dunlap 2000
|
||||
* (C) Copyright David Brownell 2000-2001 (kernel hotplug, usb_device_id,
|
||||
more docs, etc)
|
||||
* (C) Copyright Yggdrasil Computing, Inc. 2000
|
||||
* (usb_device_id matching changes by Adam J. Richter)
|
||||
* (C) Copyright Greg Kroah-Hartman 2002-2003
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/devfs_fs_kernel.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#else
|
||||
#undef DEBUG
|
||||
#endif
|
||||
#include <linux/usb.h>
|
||||
|
||||
#define MAX_USB_MINORS 256
|
||||
static struct file_operations *usb_minors[MAX_USB_MINORS];
|
||||
static DEFINE_SPINLOCK(minor_lock);
|
||||
|
||||
static int usb_open(struct inode * inode, struct file * file)
|
||||
{
|
||||
int minor = iminor(inode);
|
||||
struct file_operations *c;
|
||||
int err = -ENODEV;
|
||||
struct file_operations *old_fops, *new_fops = NULL;
|
||||
|
||||
spin_lock (&minor_lock);
|
||||
c = usb_minors[minor];
|
||||
|
||||
if (!c || !(new_fops = fops_get(c))) {
|
||||
spin_unlock(&minor_lock);
|
||||
return err;
|
||||
}
|
||||
spin_unlock(&minor_lock);
|
||||
|
||||
old_fops = file->f_op;
|
||||
file->f_op = new_fops;
|
||||
/* Curiouser and curiouser... NULL ->open() as "no device" ? */
|
||||
if (file->f_op->open)
|
||||
err = file->f_op->open(inode,file);
|
||||
if (err) {
|
||||
fops_put(file->f_op);
|
||||
file->f_op = fops_get(old_fops);
|
||||
}
|
||||
fops_put(old_fops);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct file_operations usb_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = usb_open,
|
||||
};
|
||||
|
||||
static struct class_simple *usb_class;
|
||||
|
||||
int usb_major_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = register_chrdev(USB_MAJOR, "usb", &usb_fops);
|
||||
if (error) {
|
||||
err("unable to get major %d for usb devices", USB_MAJOR);
|
||||
goto out;
|
||||
}
|
||||
|
||||
usb_class = class_simple_create(THIS_MODULE, "usb");
|
||||
if (IS_ERR(usb_class)) {
|
||||
err("class_simple_create failed for usb devices");
|
||||
unregister_chrdev(USB_MAJOR, "usb");
|
||||
goto out;
|
||||
}
|
||||
|
||||
devfs_mk_dir("usb");
|
||||
|
||||
out:
|
||||
return error;
|
||||
}
|
||||
|
||||
void usb_major_cleanup(void)
|
||||
{
|
||||
class_simple_destroy(usb_class);
|
||||
devfs_remove("usb");
|
||||
unregister_chrdev(USB_MAJOR, "usb");
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_register_dev - register a USB device, and ask for a minor number
|
||||
* @intf: pointer to the usb_interface that is being registered
|
||||
* @class_driver: pointer to the usb_class_driver for this device
|
||||
*
|
||||
* This should be called by all USB drivers that use the USB major number.
|
||||
* If CONFIG_USB_DYNAMIC_MINORS is enabled, the minor number will be
|
||||
* dynamically allocated out of the list of available ones. If it is not
|
||||
* enabled, the minor number will be based on the next available free minor,
|
||||
* starting at the class_driver->minor_base.
|
||||
*
|
||||
* This function also creates the devfs file for the usb device, if devfs
|
||||
* is enabled, and creates a usb class device in the sysfs tree.
|
||||
*
|
||||
* usb_deregister_dev() must be called when the driver is done with
|
||||
* the minor numbers given out by this function.
|
||||
*
|
||||
* Returns -EINVAL if something bad happens with trying to register a
|
||||
* device, and 0 on success.
|
||||
*/
|
||||
int usb_register_dev(struct usb_interface *intf,
|
||||
struct usb_class_driver *class_driver)
|
||||
{
|
||||
int retval = -EINVAL;
|
||||
int minor_base = class_driver->minor_base;
|
||||
int minor = 0;
|
||||
char name[BUS_ID_SIZE];
|
||||
char *temp;
|
||||
|
||||
#ifdef CONFIG_USB_DYNAMIC_MINORS
|
||||
/*
|
||||
* We don't care what the device tries to start at, we want to start
|
||||
* at zero to pack the devices into the smallest available space with
|
||||
* no holes in the minor range.
|
||||
*/
|
||||
minor_base = 0;
|
||||
#endif
|
||||
intf->minor = -1;
|
||||
|
||||
dbg ("looking for a minor, starting at %d", minor_base);
|
||||
|
||||
if (class_driver->fops == NULL)
|
||||
goto exit;
|
||||
|
||||
spin_lock (&minor_lock);
|
||||
for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) {
|
||||
if (usb_minors[minor])
|
||||
continue;
|
||||
|
||||
usb_minors[minor] = class_driver->fops;
|
||||
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
spin_unlock (&minor_lock);
|
||||
|
||||
if (retval)
|
||||
goto exit;
|
||||
|
||||
intf->minor = minor;
|
||||
|
||||
/* handle the devfs registration */
|
||||
snprintf(name, BUS_ID_SIZE, class_driver->name, minor - minor_base);
|
||||
devfs_mk_cdev(MKDEV(USB_MAJOR, minor), class_driver->mode, name);
|
||||
|
||||
/* create a usb class device for this usb interface */
|
||||
temp = strrchr(name, '/');
|
||||
if (temp && (temp[1] != 0x00))
|
||||
++temp;
|
||||
else
|
||||
temp = name;
|
||||
intf->class_dev = class_simple_device_add(usb_class, MKDEV(USB_MAJOR, minor), &intf->dev, "%s", temp);
|
||||
if (IS_ERR(intf->class_dev)) {
|
||||
spin_lock (&minor_lock);
|
||||
usb_minors[intf->minor] = NULL;
|
||||
spin_unlock (&minor_lock);
|
||||
devfs_remove (name);
|
||||
retval = PTR_ERR(intf->class_dev);
|
||||
}
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(usb_register_dev);
|
||||
|
||||
/**
|
||||
* usb_deregister_dev - deregister a USB device's dynamic minor.
|
||||
* @intf: pointer to the usb_interface that is being deregistered
|
||||
* @class_driver: pointer to the usb_class_driver for this device
|
||||
*
|
||||
* Used in conjunction with usb_register_dev(). This function is called
|
||||
* when the USB driver is finished with the minor numbers gotten from a
|
||||
* call to usb_register_dev() (usually when the device is disconnected
|
||||
* from the system.)
|
||||
*
|
||||
* This function also cleans up the devfs file for the usb device, if devfs
|
||||
* is enabled, and removes the usb class device from the sysfs tree.
|
||||
*
|
||||
* This should be called by all drivers that use the USB major number.
|
||||
*/
|
||||
void usb_deregister_dev(struct usb_interface *intf,
|
||||
struct usb_class_driver *class_driver)
|
||||
{
|
||||
int minor_base = class_driver->minor_base;
|
||||
char name[BUS_ID_SIZE];
|
||||
|
||||
#ifdef CONFIG_USB_DYNAMIC_MINORS
|
||||
minor_base = 0;
|
||||
#endif
|
||||
|
||||
if (intf->minor == -1)
|
||||
return;
|
||||
|
||||
dbg ("removing %d minor", intf->minor);
|
||||
|
||||
spin_lock (&minor_lock);
|
||||
usb_minors[intf->minor] = NULL;
|
||||
spin_unlock (&minor_lock);
|
||||
|
||||
snprintf(name, BUS_ID_SIZE, class_driver->name, intf->minor - minor_base);
|
||||
devfs_remove (name);
|
||||
class_simple_device_remove(MKDEV(USB_MAJOR, intf->minor));
|
||||
intf->class_dev = NULL;
|
||||
intf->minor = -1;
|
||||
}
|
||||
EXPORT_SYMBOL(usb_deregister_dev);
|
||||
|
||||
|
||||
358
drivers/usb/core/hcd-pci.c
Normal file
358
drivers/usb/core/hcd-pci.c
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* (C) Copyright David Brownell 2000-2002
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#else
|
||||
#undef DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <linux/usb.h>
|
||||
#include "hcd.h"
|
||||
|
||||
|
||||
/* PCI-based HCs are normal, but custom bus glue should be ok */
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
/**
|
||||
* usb_hcd_pci_probe - initialize PCI-based HCDs
|
||||
* @dev: USB Host Controller being probed
|
||||
* @id: pci hotplug id connecting controller to HCD framework
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic PCI 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_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
|
||||
{
|
||||
struct hc_driver *driver;
|
||||
struct usb_hcd *hcd;
|
||||
int retval;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
if (!id || !(driver = (struct hc_driver *) id->driver_data))
|
||||
return -EINVAL;
|
||||
|
||||
if (pci_enable_device (dev) < 0)
|
||||
return -ENODEV;
|
||||
dev->current_state = 0;
|
||||
dev->dev.power.power_state = 0;
|
||||
|
||||
if (!dev->irq) {
|
||||
dev_err (&dev->dev,
|
||||
"Found HC with no IRQ. Check BIOS/PCI %s setup!\n",
|
||||
pci_name(dev));
|
||||
retval = -ENODEV;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd (driver, &dev->dev, pci_name(dev));
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
if (driver->flags & HCD_MEMORY) { // EHCI, OHCI
|
||||
hcd->rsrc_start = pci_resource_start (dev, 0);
|
||||
hcd->rsrc_len = pci_resource_len (dev, 0);
|
||||
if (!request_mem_region (hcd->rsrc_start, hcd->rsrc_len,
|
||||
driver->description)) {
|
||||
dev_dbg (&dev->dev, "controller already in use\n");
|
||||
retval = -EBUSY;
|
||||
goto err2;
|
||||
}
|
||||
hcd->regs = ioremap_nocache (hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (hcd->regs == NULL) {
|
||||
dev_dbg (&dev->dev, "error mapping memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err3;
|
||||
}
|
||||
|
||||
} else { // UHCI
|
||||
int region;
|
||||
|
||||
for (region = 0; region < PCI_ROM_RESOURCE; region++) {
|
||||
if (!(pci_resource_flags (dev, region) &
|
||||
IORESOURCE_IO))
|
||||
continue;
|
||||
|
||||
hcd->rsrc_start = pci_resource_start (dev, region);
|
||||
hcd->rsrc_len = pci_resource_len (dev, region);
|
||||
if (request_region (hcd->rsrc_start, hcd->rsrc_len,
|
||||
driver->description))
|
||||
break;
|
||||
}
|
||||
if (region == PCI_ROM_RESOURCE) {
|
||||
dev_dbg (&dev->dev, "no i/o regions available\n");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_NAMES
|
||||
hcd->product_desc = dev->pretty_name;
|
||||
#endif
|
||||
|
||||
pci_set_master (dev);
|
||||
|
||||
retval = usb_add_hcd (hcd, dev->irq, SA_SHIRQ);
|
||||
if (retval != 0)
|
||||
goto err4;
|
||||
return retval;
|
||||
|
||||
err4:
|
||||
if (driver->flags & HCD_MEMORY) {
|
||||
iounmap (hcd->regs);
|
||||
err3:
|
||||
release_mem_region (hcd->rsrc_start, hcd->rsrc_len);
|
||||
} else
|
||||
release_region (hcd->rsrc_start, hcd->rsrc_len);
|
||||
err2:
|
||||
usb_put_hcd (hcd);
|
||||
err1:
|
||||
pci_disable_device (dev);
|
||||
dev_err (&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL (usb_hcd_pci_probe);
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_pci_remove - shutdown processing for PCI-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_pci_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
* Store this function in the HCD's struct pci_driver as remove().
|
||||
*/
|
||||
void usb_hcd_pci_remove (struct pci_dev *dev)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
hcd = pci_get_drvdata(dev);
|
||||
if (!hcd)
|
||||
return;
|
||||
|
||||
usb_remove_hcd (hcd);
|
||||
if (hcd->driver->flags & HCD_MEMORY) {
|
||||
iounmap (hcd->regs);
|
||||
release_mem_region (hcd->rsrc_start, hcd->rsrc_len);
|
||||
} else {
|
||||
release_region (hcd->rsrc_start, hcd->rsrc_len);
|
||||
}
|
||||
usb_put_hcd (hcd);
|
||||
pci_disable_device(dev);
|
||||
}
|
||||
EXPORT_SYMBOL (usb_hcd_pci_remove);
|
||||
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static char __attribute_used__ *pci_state(u32 state)
|
||||
{
|
||||
switch (state) {
|
||||
case 0: return "D0";
|
||||
case 1: return "D1";
|
||||
case 2: return "D2";
|
||||
case 3: return "D3hot";
|
||||
case 4: return "D3cold";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
|
||||
* @dev: USB Host Controller being suspended
|
||||
* @state: state that the controller is going into
|
||||
*
|
||||
* Store this function in the HCD's struct pci_driver as suspend().
|
||||
*/
|
||||
int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
int retval = 0;
|
||||
int has_pci_pm;
|
||||
|
||||
hcd = pci_get_drvdata(dev);
|
||||
|
||||
/* even when the PCI layer rejects some of the PCI calls
|
||||
* below, HCs can try global suspend and reduce DMA traffic.
|
||||
* PM-sensitive HCDs may already have done this.
|
||||
*/
|
||||
has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
|
||||
if (state > 4)
|
||||
state = 4;
|
||||
|
||||
switch (hcd->state) {
|
||||
|
||||
/* entry if root hub wasn't yet suspended ... from sysfs,
|
||||
* without autosuspend, or if USB_SUSPEND isn't configured.
|
||||
*/
|
||||
case HC_STATE_RUNNING:
|
||||
hcd->state = HC_STATE_QUIESCING;
|
||||
retval = hcd->driver->suspend (hcd, state);
|
||||
if (retval) {
|
||||
dev_dbg (hcd->self.controller,
|
||||
"suspend fail, retval %d\n",
|
||||
retval);
|
||||
break;
|
||||
}
|
||||
hcd->state = HC_STATE_SUSPENDED;
|
||||
/* FALLTHROUGH */
|
||||
|
||||
/* entry with CONFIG_USB_SUSPEND, or hcds that autosuspend: the
|
||||
* controller and/or root hub will already have been suspended,
|
||||
* but it won't be ready for a PCI resume call.
|
||||
*
|
||||
* FIXME only CONFIG_USB_SUSPEND guarantees hub_suspend() will
|
||||
* have been called, otherwise root hub timers still run ...
|
||||
*/
|
||||
case HC_STATE_SUSPENDED:
|
||||
if (state <= dev->current_state)
|
||||
break;
|
||||
|
||||
/* no DMA or IRQs except in D0 */
|
||||
if (!dev->current_state) {
|
||||
pci_save_state (dev);
|
||||
pci_disable_device (dev);
|
||||
free_irq (hcd->irq, hcd);
|
||||
}
|
||||
|
||||
if (!has_pci_pm) {
|
||||
dev_dbg (hcd->self.controller, "--> PCI D0/legacy\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* POLICY: ignore D1/D2/D3hot differences;
|
||||
* we know D3hot will always work.
|
||||
*/
|
||||
retval = pci_set_power_state (dev, state);
|
||||
if (retval < 0 && state < 3) {
|
||||
retval = pci_set_power_state (dev, 3);
|
||||
if (retval == 0)
|
||||
state = 3;
|
||||
}
|
||||
if (retval == 0) {
|
||||
dev_dbg (hcd->self.controller, "--> PCI %s\n",
|
||||
pci_state(dev->current_state));
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
pci_enable_wake (dev, state, hcd->remote_wakeup);
|
||||
pci_enable_wake (dev, 4, hcd->remote_wakeup);
|
||||
#endif
|
||||
} else if (retval < 0) {
|
||||
dev_dbg (&dev->dev, "PCI %s suspend fail, %d\n",
|
||||
pci_state(state), retval);
|
||||
(void) usb_hcd_pci_resume (dev);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_dbg (hcd->self.controller, "hcd state %d; not suspended\n",
|
||||
hcd->state);
|
||||
retval = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* update power_state **ONLY** to make sysfs happier */
|
||||
if (retval == 0)
|
||||
dev->dev.power.power_state = state;
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL (usb_hcd_pci_suspend);
|
||||
|
||||
/**
|
||||
* usb_hcd_pci_resume - power management resume of a PCI-based HCD
|
||||
* @dev: USB Host Controller being resumed
|
||||
*
|
||||
* Store this function in the HCD's struct pci_driver as resume().
|
||||
*/
|
||||
int usb_hcd_pci_resume (struct pci_dev *dev)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
int retval;
|
||||
int has_pci_pm;
|
||||
|
||||
hcd = pci_get_drvdata(dev);
|
||||
if (hcd->state != HC_STATE_SUSPENDED) {
|
||||
dev_dbg (hcd->self.controller,
|
||||
"can't resume, not suspended!\n");
|
||||
return 0;
|
||||
}
|
||||
has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
|
||||
|
||||
/* D3cold resume isn't usually reported this way... */
|
||||
dev_dbg(hcd->self.controller, "resume from PCI %s%s\n",
|
||||
pci_state(dev->current_state),
|
||||
has_pci_pm ? "" : " (legacy)");
|
||||
|
||||
hcd->state = HC_STATE_RESUMING;
|
||||
|
||||
if (has_pci_pm)
|
||||
pci_set_power_state (dev, 0);
|
||||
dev->dev.power.power_state = 0;
|
||||
retval = request_irq (dev->irq, usb_hcd_irq, SA_SHIRQ,
|
||||
hcd->driver->description, hcd);
|
||||
if (retval < 0) {
|
||||
dev_err (hcd->self.controller,
|
||||
"can't restore IRQ after resume!\n");
|
||||
return retval;
|
||||
}
|
||||
hcd->saw_irq = 0;
|
||||
pci_restore_state (dev);
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
pci_enable_wake (dev, dev->current_state, 0);
|
||||
pci_enable_wake (dev, 4, 0);
|
||||
#endif
|
||||
|
||||
retval = hcd->driver->resume (hcd);
|
||||
if (!HC_IS_RUNNING (hcd->state)) {
|
||||
dev_dbg (hcd->self.controller,
|
||||
"resume fail, retval %d\n", retval);
|
||||
usb_hc_died (hcd);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL (usb_hcd_pci_resume);
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
|
||||
1840
drivers/usb/core/hcd.c
Normal file
1840
drivers/usb/core/hcd.c
Normal file
File diff suppressed because it is too large
Load diff
476
drivers/usb/core/hcd.h
Normal file
476
drivers/usb/core/hcd.h
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
/* This file contains declarations of usbcore internals that are mostly
|
||||
* used or exposed by Host Controller Drivers.
|
||||
*/
|
||||
|
||||
/*
|
||||
* USB Packet IDs (PIDs)
|
||||
*/
|
||||
#define USB_PID_UNDEF_0 0xf0
|
||||
#define USB_PID_OUT 0xe1
|
||||
#define USB_PID_ACK 0xd2
|
||||
#define USB_PID_DATA0 0xc3
|
||||
#define USB_PID_PING 0xb4 /* USB 2.0 */
|
||||
#define USB_PID_SOF 0xa5
|
||||
#define USB_PID_NYET 0x96 /* USB 2.0 */
|
||||
#define USB_PID_DATA2 0x87 /* USB 2.0 */
|
||||
#define USB_PID_SPLIT 0x78 /* USB 2.0 */
|
||||
#define USB_PID_IN 0x69
|
||||
#define USB_PID_NAK 0x5a
|
||||
#define USB_PID_DATA1 0x4b
|
||||
#define USB_PID_PREAMBLE 0x3c /* Token mode */
|
||||
#define USB_PID_ERR 0x3c /* USB 2.0: handshake mode */
|
||||
#define USB_PID_SETUP 0x2d
|
||||
#define USB_PID_STALL 0x1e
|
||||
#define USB_PID_MDATA 0x0f /* USB 2.0 */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* USB Host Controller Driver (usb_hcd) framework
|
||||
*
|
||||
* Since "struct usb_bus" is so thin, you can't share much code in it.
|
||||
* This framework is a layer over that, and should be more sharable.
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
struct usb_hcd { /* usb_bus.hcpriv points to this */
|
||||
|
||||
/*
|
||||
* housekeeping
|
||||
*/
|
||||
struct usb_bus self; /* hcd is-a bus */
|
||||
|
||||
const char *product_desc; /* product/vendor string */
|
||||
char irq_descr[24]; /* driver + bus # */
|
||||
|
||||
struct timer_list rh_timer; /* drives root hub */
|
||||
|
||||
/*
|
||||
* hardware info/state
|
||||
*/
|
||||
const struct hc_driver *driver; /* hw-specific hooks */
|
||||
unsigned saw_irq : 1;
|
||||
unsigned can_wakeup:1; /* hw supports wakeup? */
|
||||
unsigned remote_wakeup:1;/* sw should use wakeup? */
|
||||
unsigned rh_registered:1;/* is root hub registered? */
|
||||
|
||||
int irq; /* irq allocated */
|
||||
void __iomem *regs; /* device memory/io */
|
||||
u64 rsrc_start; /* memory/io resource start */
|
||||
u64 rsrc_len; /* memory/io resource length */
|
||||
|
||||
#define HCD_BUFFER_POOLS 4
|
||||
struct dma_pool *pool [HCD_BUFFER_POOLS];
|
||||
|
||||
int state;
|
||||
# define __ACTIVE 0x01
|
||||
# define __SUSPEND 0x04
|
||||
# define __TRANSIENT 0x80
|
||||
|
||||
# define HC_STATE_HALT 0
|
||||
# define HC_STATE_RUNNING (__ACTIVE)
|
||||
# define HC_STATE_QUIESCING (__SUSPEND|__TRANSIENT|__ACTIVE)
|
||||
# define HC_STATE_RESUMING (__SUSPEND|__TRANSIENT)
|
||||
# define HC_STATE_SUSPENDED (__SUSPEND)
|
||||
|
||||
#define HC_IS_RUNNING(state) ((state) & __ACTIVE)
|
||||
#define HC_IS_SUSPENDED(state) ((state) & __SUSPEND)
|
||||
|
||||
/* more shared queuing code would be good; it should support
|
||||
* smarter scheduling, handle transaction translators, etc;
|
||||
* input size of periodic table to an interrupt scheduler.
|
||||
* (ohci 32, uhci 1024, ehci 256/512/1024).
|
||||
*/
|
||||
|
||||
/* The HC driver's private data is stored at the end of
|
||||
* this structure.
|
||||
*/
|
||||
unsigned long hcd_priv[0]
|
||||
__attribute__ ((aligned (sizeof(unsigned long))));
|
||||
};
|
||||
|
||||
/* 2.4 does this a bit differently ... */
|
||||
static inline struct usb_bus *hcd_to_bus (struct usb_hcd *hcd)
|
||||
{
|
||||
return &hcd->self;
|
||||
}
|
||||
|
||||
|
||||
// urb.hcpriv is really hardware-specific
|
||||
|
||||
struct hcd_timeout { /* timeouts we allocate */
|
||||
struct list_head timeout_list;
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* FIXME usb_operations should vanish or become hc_driver,
|
||||
* when usb_bus and usb_hcd become the same thing.
|
||||
*/
|
||||
|
||||
struct usb_operations {
|
||||
int (*get_frame_number) (struct usb_device *usb_dev);
|
||||
int (*submit_urb) (struct urb *urb, int mem_flags);
|
||||
int (*unlink_urb) (struct urb *urb, int status);
|
||||
|
||||
/* allocate dma-consistent buffer for URB_DMA_NOMAPPING */
|
||||
void *(*buffer_alloc)(struct usb_bus *bus, size_t size,
|
||||
int mem_flags,
|
||||
dma_addr_t *dma);
|
||||
void (*buffer_free)(struct usb_bus *bus, size_t size,
|
||||
void *addr, dma_addr_t dma);
|
||||
|
||||
void (*disable)(struct usb_device *udev,
|
||||
struct usb_host_endpoint *ep);
|
||||
|
||||
/* global suspend/resume of bus */
|
||||
int (*hub_suspend)(struct usb_bus *);
|
||||
int (*hub_resume)(struct usb_bus *);
|
||||
};
|
||||
|
||||
/* each driver provides one of these, and hardware init support */
|
||||
|
||||
struct pt_regs;
|
||||
|
||||
struct hc_driver {
|
||||
const char *description; /* "ehci-hcd" etc */
|
||||
const char *product_desc; /* product/vendor string */
|
||||
size_t hcd_priv_size; /* size of private data */
|
||||
|
||||
/* irq handler */
|
||||
irqreturn_t (*irq) (struct usb_hcd *hcd, struct pt_regs *regs);
|
||||
|
||||
int flags;
|
||||
#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */
|
||||
#define HCD_USB11 0x0010 /* USB 1.1 */
|
||||
#define HCD_USB2 0x0020 /* USB 2.0 */
|
||||
|
||||
/* called to init HCD and root hub */
|
||||
int (*reset) (struct usb_hcd *hcd);
|
||||
int (*start) (struct usb_hcd *hcd);
|
||||
|
||||
/* NOTE: these suspend/resume calls relate to the HC as
|
||||
* a whole, not just the root hub; they're for bus glue.
|
||||
*/
|
||||
/* called after all devices were suspended */
|
||||
int (*suspend) (struct usb_hcd *hcd, u32 state);
|
||||
|
||||
/* called before any devices get resumed */
|
||||
int (*resume) (struct usb_hcd *hcd);
|
||||
|
||||
/* cleanly make HCD stop writing memory and doing I/O */
|
||||
void (*stop) (struct usb_hcd *hcd);
|
||||
|
||||
/* return current frame number */
|
||||
int (*get_frame_number) (struct usb_hcd *hcd);
|
||||
|
||||
/* manage i/o requests, device state */
|
||||
int (*urb_enqueue) (struct usb_hcd *hcd,
|
||||
struct usb_host_endpoint *ep,
|
||||
struct urb *urb,
|
||||
int mem_flags);
|
||||
int (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb);
|
||||
|
||||
/* hw synch, freeing endpoint resources that urb_dequeue can't */
|
||||
void (*endpoint_disable)(struct usb_hcd *hcd,
|
||||
struct usb_host_endpoint *ep);
|
||||
|
||||
/* root hub support */
|
||||
int (*hub_status_data) (struct usb_hcd *hcd, char *buf);
|
||||
int (*hub_control) (struct usb_hcd *hcd,
|
||||
u16 typeReq, u16 wValue, u16 wIndex,
|
||||
char *buf, u16 wLength);
|
||||
int (*hub_suspend)(struct usb_hcd *);
|
||||
int (*hub_resume)(struct usb_hcd *);
|
||||
int (*start_port_reset)(struct usb_hcd *, unsigned port_num);
|
||||
};
|
||||
|
||||
extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs);
|
||||
|
||||
extern struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
|
||||
struct device *dev, char *bus_name);
|
||||
extern void usb_put_hcd (struct usb_hcd *hcd);
|
||||
extern int usb_add_hcd(struct usb_hcd *hcd,
|
||||
unsigned int irqnum, unsigned long irqflags);
|
||||
extern void usb_remove_hcd(struct usb_hcd *hcd);
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
struct pci_dev;
|
||||
struct pci_device_id;
|
||||
extern int usb_hcd_pci_probe (struct pci_dev *dev,
|
||||
const struct pci_device_id *id);
|
||||
extern void usb_hcd_pci_remove (struct pci_dev *dev);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
extern int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state);
|
||||
extern int usb_hcd_pci_resume (struct pci_dev *dev);
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
#endif /* CONFIG_PCI */
|
||||
|
||||
/* pci-ish (pdev null is ok) buffer alloc/mapping support */
|
||||
int hcd_buffer_create (struct usb_hcd *hcd);
|
||||
void hcd_buffer_destroy (struct usb_hcd *hcd);
|
||||
|
||||
void *hcd_buffer_alloc (struct usb_bus *bus, size_t size,
|
||||
int mem_flags, dma_addr_t *dma);
|
||||
void hcd_buffer_free (struct usb_bus *bus, size_t size,
|
||||
void *addr, dma_addr_t dma);
|
||||
|
||||
/* generic bus glue, needed for host controllers that don't use PCI */
|
||||
extern irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs *r);
|
||||
extern void usb_hc_died (struct usb_hcd *hcd);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Enumeration is only for the hub driver, or HCD virtual root hubs */
|
||||
extern struct usb_device *usb_alloc_dev(struct usb_device *parent,
|
||||
struct usb_bus *, unsigned port);
|
||||
extern int usb_new_device(struct usb_device *dev);
|
||||
extern void usb_disconnect(struct usb_device **);
|
||||
|
||||
extern int usb_get_configuration(struct usb_device *dev);
|
||||
extern void usb_destroy_configuration(struct usb_device *dev);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* HCD Root Hub support
|
||||
*/
|
||||
|
||||
#include "hub.h"
|
||||
|
||||
/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */
|
||||
#define DeviceRequest \
|
||||
((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8)
|
||||
#define DeviceOutRequest \
|
||||
((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8)
|
||||
|
||||
#define InterfaceRequest \
|
||||
((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8)
|
||||
|
||||
#define EndpointRequest \
|
||||
((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8)
|
||||
#define EndpointOutRequest \
|
||||
((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8)
|
||||
|
||||
/* class requests from the USB 2.0 hub spec, table 11-15 */
|
||||
/* GetBusState and SetHubDescriptor are optional, omitted */
|
||||
#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE)
|
||||
#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE)
|
||||
#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR)
|
||||
#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS)
|
||||
#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS)
|
||||
#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE)
|
||||
#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE)
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Generic bandwidth allocation constants/support
|
||||
*/
|
||||
#define FRAME_TIME_USECS 1000L
|
||||
#define BitTime(bytecount) (7 * 8 * bytecount / 6) /* with integer truncation */
|
||||
/* Trying not to use worst-case bit-stuffing
|
||||
of (7/6 * 8 * bytecount) = 9.33 * bytecount */
|
||||
/* bytecount = data payload byte count */
|
||||
|
||||
#define NS_TO_US(ns) ((ns + 500L) / 1000L)
|
||||
/* convert & round nanoseconds to microseconds */
|
||||
|
||||
extern void usb_claim_bandwidth (struct usb_device *dev, struct urb *urb,
|
||||
int bustime, int isoc);
|
||||
extern void usb_release_bandwidth (struct usb_device *dev, struct urb *urb,
|
||||
int isoc);
|
||||
|
||||
/*
|
||||
* Full/low speed bandwidth allocation constants/support.
|
||||
*/
|
||||
#define BW_HOST_DELAY 1000L /* nanoseconds */
|
||||
#define BW_HUB_LS_SETUP 333L /* nanoseconds */
|
||||
/* 4 full-speed bit times (est.) */
|
||||
|
||||
#define FRAME_TIME_BITS 12000L /* frame = 1 millisecond */
|
||||
#define FRAME_TIME_MAX_BITS_ALLOC (90L * FRAME_TIME_BITS / 100L)
|
||||
#define FRAME_TIME_MAX_USECS_ALLOC (90L * FRAME_TIME_USECS / 100L)
|
||||
|
||||
extern int usb_check_bandwidth (struct usb_device *dev, struct urb *urb);
|
||||
|
||||
/*
|
||||
* Ceiling microseconds (typical) for that many bytes at high speed
|
||||
* ISO is a bit less, no ACK ... from USB 2.0 spec, 5.11.3 (and needed
|
||||
* to preallocate bandwidth)
|
||||
*/
|
||||
#define USB2_HOST_DELAY 5 /* nsec, guess */
|
||||
#define HS_USECS(bytes) NS_TO_US ( ((55 * 8 * 2083)/1000) \
|
||||
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \
|
||||
+ USB2_HOST_DELAY)
|
||||
#define HS_USECS_ISO(bytes) NS_TO_US ( ((38 * 8 * 2083)/1000) \
|
||||
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \
|
||||
+ USB2_HOST_DELAY)
|
||||
|
||||
extern long usb_calc_bus_time (int speed, int is_input,
|
||||
int isoc, int bytecount);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
extern struct usb_bus *usb_alloc_bus (struct usb_operations *);
|
||||
|
||||
extern int usb_hcd_register_root_hub (struct usb_device *usb_dev,
|
||||
struct usb_hcd *hcd);
|
||||
|
||||
extern void usb_hcd_resume_root_hub (struct usb_hcd *hcd);
|
||||
|
||||
extern void usb_set_device_state(struct usb_device *udev,
|
||||
enum usb_device_state new_state);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* exported only within usbcore */
|
||||
|
||||
extern struct list_head usb_bus_list;
|
||||
extern struct semaphore usb_bus_list_lock;
|
||||
extern wait_queue_head_t usb_kill_urb_queue;
|
||||
|
||||
extern struct usb_bus *usb_bus_get (struct usb_bus *bus);
|
||||
extern void usb_bus_put (struct usb_bus *bus);
|
||||
|
||||
extern int usb_find_interface_driver (struct usb_device *dev,
|
||||
struct usb_interface *interface);
|
||||
|
||||
#define usb_endpoint_out(ep_dir) (!((ep_dir) & USB_DIR_IN))
|
||||
|
||||
/*
|
||||
* USB device fs stuff
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_USB_DEVICEFS
|
||||
|
||||
/*
|
||||
* these are expected to be called from the USB core/hub thread
|
||||
* with the kernel lock held
|
||||
*/
|
||||
extern void usbfs_add_bus(struct usb_bus *bus);
|
||||
extern void usbfs_remove_bus(struct usb_bus *bus);
|
||||
extern void usbfs_add_device(struct usb_device *dev);
|
||||
extern void usbfs_remove_device(struct usb_device *dev);
|
||||
extern void usbfs_update_special (void);
|
||||
|
||||
extern int usbfs_init(void);
|
||||
extern void usbfs_cleanup(void);
|
||||
|
||||
#else /* CONFIG_USB_DEVICEFS */
|
||||
|
||||
static inline void usbfs_add_bus(struct usb_bus *bus) {}
|
||||
static inline void usbfs_remove_bus(struct usb_bus *bus) {}
|
||||
static inline void usbfs_add_device(struct usb_device *dev) {}
|
||||
static inline void usbfs_remove_device(struct usb_device *dev) {}
|
||||
static inline void usbfs_update_special (void) {}
|
||||
|
||||
static inline int usbfs_init(void) { return 0; }
|
||||
static inline void usbfs_cleanup(void) { }
|
||||
|
||||
#endif /* CONFIG_USB_DEVICEFS */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE)
|
||||
|
||||
struct usb_mon_operations {
|
||||
void (*urb_submit)(struct usb_bus *bus, struct urb *urb);
|
||||
void (*urb_submit_error)(struct usb_bus *bus, struct urb *urb, int err);
|
||||
void (*urb_complete)(struct usb_bus *bus, struct urb *urb);
|
||||
/* void (*urb_unlink)(struct usb_bus *bus, struct urb *urb); */
|
||||
void (*bus_add)(struct usb_bus *bus);
|
||||
void (*bus_remove)(struct usb_bus *bus);
|
||||
};
|
||||
|
||||
extern struct usb_mon_operations *mon_ops;
|
||||
|
||||
static inline void usbmon_urb_submit(struct usb_bus *bus, struct urb *urb)
|
||||
{
|
||||
if (bus->monitored)
|
||||
(*mon_ops->urb_submit)(bus, urb);
|
||||
}
|
||||
|
||||
static inline void usbmon_urb_submit_error(struct usb_bus *bus, struct urb *urb,
|
||||
int error)
|
||||
{
|
||||
if (bus->monitored)
|
||||
(*mon_ops->urb_submit_error)(bus, urb, error);
|
||||
}
|
||||
|
||||
static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb)
|
||||
{
|
||||
if (bus->monitored)
|
||||
(*mon_ops->urb_complete)(bus, urb);
|
||||
}
|
||||
|
||||
static inline void usbmon_notify_bus_add(struct usb_bus *bus)
|
||||
{
|
||||
if (mon_ops)
|
||||
(*mon_ops->bus_add)(bus);
|
||||
}
|
||||
|
||||
static inline void usbmon_notify_bus_remove(struct usb_bus *bus)
|
||||
{
|
||||
if (mon_ops)
|
||||
(*mon_ops->bus_remove)(bus);
|
||||
}
|
||||
|
||||
int usb_mon_register(struct usb_mon_operations *ops);
|
||||
void usb_mon_deregister(void);
|
||||
|
||||
#else
|
||||
|
||||
static inline void usbmon_urb_submit(struct usb_bus *bus, struct urb *urb) {}
|
||||
static inline void usbmon_urb_submit_error(struct usb_bus *bus, struct urb *urb,
|
||||
int error) {}
|
||||
static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb) {}
|
||||
static inline void usbmon_notify_bus_add(struct usb_bus *bus) {}
|
||||
static inline void usbmon_notify_bus_remove(struct usb_bus *bus) {}
|
||||
|
||||
#endif /* CONFIG_USB_MON */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* hub.h ... DeviceRemovable in 2.4.2-ac11, gone in 2.4.10 */
|
||||
// bleech -- resurfaced in 2.4.11 or 2.4.12
|
||||
#define bitmap DeviceRemovable
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* random stuff */
|
||||
|
||||
#define RUN_CONTEXT (in_irq () ? "in_irq" \
|
||||
: (in_interrupt () ? "in_interrupt" : "can sleep"))
|
||||
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
3057
drivers/usb/core/hub.c
Normal file
3057
drivers/usb/core/hub.c
Normal file
File diff suppressed because it is too large
Load diff
238
drivers/usb/core/hub.h
Normal file
238
drivers/usb/core/hub.h
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
#ifndef __LINUX_HUB_H
|
||||
#define __LINUX_HUB_H
|
||||
|
||||
/*
|
||||
* Hub protocol and driver data structures.
|
||||
*
|
||||
* Some of these are known to the "virtual root hub" code
|
||||
* in host controller drivers.
|
||||
*/
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/compiler.h> /* likely()/unlikely() */
|
||||
|
||||
/*
|
||||
* Hub request types
|
||||
*/
|
||||
|
||||
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE)
|
||||
#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER)
|
||||
|
||||
/*
|
||||
* Hub class requests
|
||||
* See USB 2.0 spec Table 11-16
|
||||
*/
|
||||
#define HUB_CLEAR_TT_BUFFER 8
|
||||
#define HUB_RESET_TT 9
|
||||
#define HUB_GET_TT_STATE 10
|
||||
#define HUB_STOP_TT 11
|
||||
|
||||
/*
|
||||
* Hub Class feature numbers
|
||||
* See USB 2.0 spec Table 11-17
|
||||
*/
|
||||
#define C_HUB_LOCAL_POWER 0
|
||||
#define C_HUB_OVER_CURRENT 1
|
||||
|
||||
/*
|
||||
* Port feature numbers
|
||||
* See USB 2.0 spec Table 11-17
|
||||
*/
|
||||
#define USB_PORT_FEAT_CONNECTION 0
|
||||
#define USB_PORT_FEAT_ENABLE 1
|
||||
#define USB_PORT_FEAT_SUSPEND 2
|
||||
#define USB_PORT_FEAT_OVER_CURRENT 3
|
||||
#define USB_PORT_FEAT_RESET 4
|
||||
#define USB_PORT_FEAT_POWER 8
|
||||
#define USB_PORT_FEAT_LOWSPEED 9
|
||||
#define USB_PORT_FEAT_HIGHSPEED 10
|
||||
#define USB_PORT_FEAT_C_CONNECTION 16
|
||||
#define USB_PORT_FEAT_C_ENABLE 17
|
||||
#define USB_PORT_FEAT_C_SUSPEND 18
|
||||
#define USB_PORT_FEAT_C_OVER_CURRENT 19
|
||||
#define USB_PORT_FEAT_C_RESET 20
|
||||
#define USB_PORT_FEAT_TEST 21
|
||||
#define USB_PORT_FEAT_INDICATOR 22
|
||||
|
||||
/*
|
||||
* Hub Status and Hub Change results
|
||||
* See USB 2.0 spec Table 11-19 and Table 11-20
|
||||
*/
|
||||
struct usb_port_status {
|
||||
__le16 wPortStatus;
|
||||
__le16 wPortChange;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*
|
||||
* wPortStatus bit field
|
||||
* See USB 2.0 spec Table 11-21
|
||||
*/
|
||||
#define USB_PORT_STAT_CONNECTION 0x0001
|
||||
#define USB_PORT_STAT_ENABLE 0x0002
|
||||
#define USB_PORT_STAT_SUSPEND 0x0004
|
||||
#define USB_PORT_STAT_OVERCURRENT 0x0008
|
||||
#define USB_PORT_STAT_RESET 0x0010
|
||||
/* bits 5 to 7 are reserved */
|
||||
#define USB_PORT_STAT_POWER 0x0100
|
||||
#define USB_PORT_STAT_LOW_SPEED 0x0200
|
||||
#define USB_PORT_STAT_HIGH_SPEED 0x0400
|
||||
#define USB_PORT_STAT_TEST 0x0800
|
||||
#define USB_PORT_STAT_INDICATOR 0x1000
|
||||
/* bits 13 to 15 are reserved */
|
||||
|
||||
/*
|
||||
* wPortChange bit field
|
||||
* See USB 2.0 spec Table 11-22
|
||||
* Bits 0 to 4 shown, bits 5 to 15 are reserved
|
||||
*/
|
||||
#define USB_PORT_STAT_C_CONNECTION 0x0001
|
||||
#define USB_PORT_STAT_C_ENABLE 0x0002
|
||||
#define USB_PORT_STAT_C_SUSPEND 0x0004
|
||||
#define USB_PORT_STAT_C_OVERCURRENT 0x0008
|
||||
#define USB_PORT_STAT_C_RESET 0x0010
|
||||
|
||||
/*
|
||||
* wHubCharacteristics (masks)
|
||||
* See USB 2.0 spec Table 11-13, offset 3
|
||||
*/
|
||||
#define HUB_CHAR_LPSM 0x0003 /* D1 .. D0 */
|
||||
#define HUB_CHAR_COMPOUND 0x0004 /* D2 */
|
||||
#define HUB_CHAR_OCPM 0x0018 /* D4 .. D3 */
|
||||
#define HUB_CHAR_TTTT 0x0060 /* D6 .. D5 */
|
||||
#define HUB_CHAR_PORTIND 0x0080 /* D7 */
|
||||
|
||||
struct usb_hub_status {
|
||||
__le16 wHubStatus;
|
||||
__le16 wHubChange;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*
|
||||
* Hub Status & Hub Change bit masks
|
||||
* See USB 2.0 spec Table 11-19 and Table 11-20
|
||||
* Bits 0 and 1 for wHubStatus and wHubChange
|
||||
* Bits 2 to 15 are reserved for both
|
||||
*/
|
||||
#define HUB_STATUS_LOCAL_POWER 0x0001
|
||||
#define HUB_STATUS_OVERCURRENT 0x0002
|
||||
#define HUB_CHANGE_LOCAL_POWER 0x0001
|
||||
#define HUB_CHANGE_OVERCURRENT 0x0002
|
||||
|
||||
|
||||
/*
|
||||
* Hub descriptor
|
||||
* See USB 2.0 spec Table 11-13
|
||||
*/
|
||||
|
||||
#define USB_DT_HUB (USB_TYPE_CLASS | 0x09)
|
||||
#define USB_DT_HUB_NONVAR_SIZE 7
|
||||
|
||||
struct usb_hub_descriptor {
|
||||
__u8 bDescLength;
|
||||
__u8 bDescriptorType;
|
||||
__u8 bNbrPorts;
|
||||
__u16 wHubCharacteristics;
|
||||
__u8 bPwrOn2PwrGood;
|
||||
__u8 bHubContrCurrent;
|
||||
/* add 1 bit for hub status change; round to bytes */
|
||||
__u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];
|
||||
__u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
||||
/* port indicator status selectors, tables 11-7 and 11-25 */
|
||||
#define HUB_LED_AUTO 0
|
||||
#define HUB_LED_AMBER 1
|
||||
#define HUB_LED_GREEN 2
|
||||
#define HUB_LED_OFF 3
|
||||
|
||||
enum hub_led_mode {
|
||||
INDICATOR_AUTO = 0,
|
||||
INDICATOR_CYCLE,
|
||||
/* software blinks for attention: software, hardware, reserved */
|
||||
INDICATOR_GREEN_BLINK, INDICATOR_GREEN_BLINK_OFF,
|
||||
INDICATOR_AMBER_BLINK, INDICATOR_AMBER_BLINK_OFF,
|
||||
INDICATOR_ALT_BLINK, INDICATOR_ALT_BLINK_OFF
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct usb_device;
|
||||
|
||||
/*
|
||||
* As of USB 2.0, full/low speed devices are segregated into trees.
|
||||
* One type grows from USB 1.1 host controllers (OHCI, UHCI etc).
|
||||
* The other type grows from high speed hubs when they connect to
|
||||
* full/low speed devices using "Transaction Translators" (TTs).
|
||||
*
|
||||
* TTs should only be known to the hub driver, and high speed bus
|
||||
* drivers (only EHCI for now). They affect periodic scheduling and
|
||||
* sometimes control/bulk error recovery.
|
||||
*/
|
||||
struct usb_tt {
|
||||
struct usb_device *hub; /* upstream highspeed hub */
|
||||
int multi; /* true means one TT per port */
|
||||
|
||||
/* for control/bulk error recovery (CLEAR_TT_BUFFER) */
|
||||
spinlock_t lock;
|
||||
struct list_head clear_list; /* of usb_tt_clear */
|
||||
struct work_struct kevent;
|
||||
};
|
||||
|
||||
struct usb_tt_clear {
|
||||
struct list_head clear_list;
|
||||
unsigned tt;
|
||||
u16 devinfo;
|
||||
};
|
||||
|
||||
extern void usb_hub_tt_clear_buffer (struct usb_device *dev, int pipe);
|
||||
|
||||
struct usb_hub {
|
||||
struct device *intfdev; /* the "interface" device */
|
||||
struct usb_device *hdev;
|
||||
struct urb *urb; /* for interrupt polling pipe */
|
||||
|
||||
/* buffer for urb ... with extra space in case of babble */
|
||||
char (*buffer)[8];
|
||||
dma_addr_t buffer_dma; /* DMA address for buffer */
|
||||
union {
|
||||
struct usb_hub_status hub;
|
||||
struct usb_port_status port;
|
||||
} *status; /* buffer for status reports */
|
||||
|
||||
int error; /* last reported error */
|
||||
int nerrors; /* track consecutive errors */
|
||||
|
||||
struct list_head event_list; /* hubs w/data or errs ready */
|
||||
unsigned long event_bits[1]; /* status change bitmask */
|
||||
unsigned long change_bits[1]; /* ports with logical connect
|
||||
status change */
|
||||
unsigned long busy_bits[1]; /* ports being reset */
|
||||
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
|
||||
#error event_bits[] is too short!
|
||||
#endif
|
||||
|
||||
struct usb_hub_descriptor *descriptor; /* class descriptor */
|
||||
struct usb_tt tt; /* Transaction Translator */
|
||||
|
||||
u8 power_budget; /* in 2mA units; or zero */
|
||||
|
||||
unsigned quiescing:1;
|
||||
unsigned activating:1;
|
||||
unsigned resume_root_hub:1;
|
||||
|
||||
unsigned has_indicators:1;
|
||||
enum hub_led_mode indicator[USB_MAXCHILDREN];
|
||||
struct work_struct leds;
|
||||
};
|
||||
|
||||
/* use this for low-powered root hubs */
|
||||
static inline void
|
||||
hub_set_power_budget (struct usb_device *hubdev, unsigned mA)
|
||||
{
|
||||
struct usb_hub *hub;
|
||||
|
||||
hub = (struct usb_hub *)
|
||||
usb_get_intfdata (hubdev->actconfig->interface[0]);
|
||||
hub->power_budget = min(mA,(unsigned)500)/2;
|
||||
}
|
||||
|
||||
#endif /* __LINUX_HUB_H */
|
||||
764
drivers/usb/core/inode.c
Normal file
764
drivers/usb/core/inode.c
Normal file
|
|
@ -0,0 +1,764 @@
|
|||
/*****************************************************************************/
|
||||
|
||||
/*
|
||||
* inode.c -- Inode/Dentry functions for the USB device file system.
|
||||
*
|
||||
* Copyright (C) 2000 Thomas Sailer (sailer@ife.ee.ethz.ch)
|
||||
* Copyright (C) 2001,2002,2004 Greg Kroah-Hartman (greg@kroah.com)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* History:
|
||||
* 0.1 04.01.2000 Created
|
||||
* 0.2 10.12.2001 converted to use the vfs layer better
|
||||
*/
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/usbdevice_fs.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/parser.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include "usb.h"
|
||||
|
||||
static struct super_operations usbfs_ops;
|
||||
static struct file_operations default_file_operations;
|
||||
static struct inode_operations usbfs_dir_inode_operations;
|
||||
static struct vfsmount *usbfs_mount;
|
||||
static int usbfs_mount_count; /* = 0 */
|
||||
static int ignore_mount = 0;
|
||||
|
||||
static struct dentry *devices_usbfs_dentry;
|
||||
static int num_buses; /* = 0 */
|
||||
|
||||
static uid_t devuid; /* = 0 */
|
||||
static uid_t busuid; /* = 0 */
|
||||
static uid_t listuid; /* = 0 */
|
||||
static gid_t devgid; /* = 0 */
|
||||
static gid_t busgid; /* = 0 */
|
||||
static gid_t listgid; /* = 0 */
|
||||
static umode_t devmode = S_IWUSR | S_IRUGO;
|
||||
static umode_t busmode = S_IXUGO | S_IRUGO;
|
||||
static umode_t listmode = S_IRUGO;
|
||||
|
||||
enum {
|
||||
Opt_devuid, Opt_devgid, Opt_devmode,
|
||||
Opt_busuid, Opt_busgid, Opt_busmode,
|
||||
Opt_listuid, Opt_listgid, Opt_listmode,
|
||||
Opt_err,
|
||||
};
|
||||
|
||||
static match_table_t tokens = {
|
||||
{Opt_devuid, "devuid=%u"},
|
||||
{Opt_devgid, "devgid=%u"},
|
||||
{Opt_devmode, "devmode=%o"},
|
||||
{Opt_busuid, "busuid=%u"},
|
||||
{Opt_busgid, "busgid=%u"},
|
||||
{Opt_busmode, "busmode=%o"},
|
||||
{Opt_listuid, "listuid=%u"},
|
||||
{Opt_listgid, "listgid=%u"},
|
||||
{Opt_listmode, "listmode=%o"},
|
||||
{Opt_err, NULL}
|
||||
};
|
||||
|
||||
static int parse_options(struct super_block *s, char *data)
|
||||
{
|
||||
char *p;
|
||||
int option;
|
||||
|
||||
/* (re)set to defaults. */
|
||||
devuid = 0;
|
||||
busuid = 0;
|
||||
listuid = 0;
|
||||
devgid = 0;
|
||||
busgid = 0;
|
||||
listgid = 0;
|
||||
devmode = S_IWUSR | S_IRUGO;
|
||||
busmode = S_IXUGO | S_IRUGO;
|
||||
listmode = S_IRUGO;
|
||||
|
||||
while ((p = strsep(&data, ",")) != NULL) {
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
int token;
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case Opt_devuid:
|
||||
if (match_int(&args[0], &option))
|
||||
return -EINVAL;
|
||||
devuid = option;
|
||||
break;
|
||||
case Opt_devgid:
|
||||
if (match_int(&args[0], &option))
|
||||
return -EINVAL;
|
||||
devgid = option;
|
||||
break;
|
||||
case Opt_devmode:
|
||||
if (match_octal(&args[0], &option))
|
||||
return -EINVAL;
|
||||
devmode = option & S_IRWXUGO;
|
||||
break;
|
||||
case Opt_busuid:
|
||||
if (match_int(&args[0], &option))
|
||||
return -EINVAL;
|
||||
busuid = option;
|
||||
break;
|
||||
case Opt_busgid:
|
||||
if (match_int(&args[0], &option))
|
||||
return -EINVAL;
|
||||
busgid = option;
|
||||
break;
|
||||
case Opt_busmode:
|
||||
if (match_octal(&args[0], &option))
|
||||
return -EINVAL;
|
||||
busmode = option & S_IRWXUGO;
|
||||
break;
|
||||
case Opt_listuid:
|
||||
if (match_int(&args[0], &option))
|
||||
return -EINVAL;
|
||||
listuid = option;
|
||||
break;
|
||||
case Opt_listgid:
|
||||
if (match_int(&args[0], &option))
|
||||
return -EINVAL;
|
||||
listgid = option;
|
||||
break;
|
||||
case Opt_listmode:
|
||||
if (match_octal(&args[0], &option))
|
||||
return -EINVAL;
|
||||
listmode = option & S_IRWXUGO;
|
||||
break;
|
||||
default:
|
||||
err("usbfs: unrecognised mount option \"%s\" "
|
||||
"or missing value\n", p);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_special(struct dentry *special)
|
||||
{
|
||||
special->d_inode->i_uid = listuid;
|
||||
special->d_inode->i_gid = listgid;
|
||||
special->d_inode->i_mode = S_IFREG | listmode;
|
||||
}
|
||||
|
||||
static void update_dev(struct dentry *dev)
|
||||
{
|
||||
dev->d_inode->i_uid = devuid;
|
||||
dev->d_inode->i_gid = devgid;
|
||||
dev->d_inode->i_mode = S_IFREG | devmode;
|
||||
}
|
||||
|
||||
static void update_bus(struct dentry *bus)
|
||||
{
|
||||
struct dentry *dev = NULL;
|
||||
|
||||
bus->d_inode->i_uid = busuid;
|
||||
bus->d_inode->i_gid = busgid;
|
||||
bus->d_inode->i_mode = S_IFDIR | busmode;
|
||||
|
||||
down(&bus->d_inode->i_sem);
|
||||
|
||||
list_for_each_entry(dev, &bus->d_subdirs, d_child)
|
||||
if (dev->d_inode)
|
||||
update_dev(dev);
|
||||
|
||||
up(&bus->d_inode->i_sem);
|
||||
}
|
||||
|
||||
static void update_sb(struct super_block *sb)
|
||||
{
|
||||
struct dentry *root = sb->s_root;
|
||||
struct dentry *bus = NULL;
|
||||
|
||||
if (!root)
|
||||
return;
|
||||
|
||||
down(&root->d_inode->i_sem);
|
||||
|
||||
list_for_each_entry(bus, &root->d_subdirs, d_child) {
|
||||
if (bus->d_inode) {
|
||||
switch (S_IFMT & bus->d_inode->i_mode) {
|
||||
case S_IFDIR:
|
||||
update_bus(bus);
|
||||
break;
|
||||
case S_IFREG:
|
||||
update_special(bus);
|
||||
break;
|
||||
default:
|
||||
warn("Unknown node %s mode %x found on remount!\n",bus->d_name.name,bus->d_inode->i_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
up(&root->d_inode->i_sem);
|
||||
}
|
||||
|
||||
static int remount(struct super_block *sb, int *flags, char *data)
|
||||
{
|
||||
/* If this is not a real mount,
|
||||
* i.e. it's a simple_pin_fs from create_special_files,
|
||||
* then ignore it.
|
||||
*/
|
||||
if (ignore_mount)
|
||||
return 0;
|
||||
|
||||
if (parse_options(sb, data)) {
|
||||
warn("usbfs: mount parameter error:");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (usbfs_mount && usbfs_mount->mnt_sb)
|
||||
update_sb(usbfs_mount->mnt_sb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct inode *usbfs_get_inode (struct super_block *sb, int mode, dev_t dev)
|
||||
{
|
||||
struct inode *inode = new_inode(sb);
|
||||
|
||||
if (inode) {
|
||||
inode->i_mode = mode;
|
||||
inode->i_uid = current->fsuid;
|
||||
inode->i_gid = current->fsgid;
|
||||
inode->i_blksize = PAGE_CACHE_SIZE;
|
||||
inode->i_blocks = 0;
|
||||
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
|
||||
switch (mode & S_IFMT) {
|
||||
default:
|
||||
init_special_inode(inode, mode, dev);
|
||||
break;
|
||||
case S_IFREG:
|
||||
inode->i_fop = &default_file_operations;
|
||||
break;
|
||||
case S_IFDIR:
|
||||
inode->i_op = &usbfs_dir_inode_operations;
|
||||
inode->i_fop = &simple_dir_operations;
|
||||
|
||||
/* directory inodes start off with i_nlink == 2 (for "." entry) */
|
||||
inode->i_nlink++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return inode;
|
||||
}
|
||||
|
||||
/* SMP-safe */
|
||||
static int usbfs_mknod (struct inode *dir, struct dentry *dentry, int mode,
|
||||
dev_t dev)
|
||||
{
|
||||
struct inode *inode = usbfs_get_inode(dir->i_sb, mode, dev);
|
||||
int error = -EPERM;
|
||||
|
||||
if (dentry->d_inode)
|
||||
return -EEXIST;
|
||||
|
||||
if (inode) {
|
||||
d_instantiate(dentry, inode);
|
||||
dget(dentry);
|
||||
error = 0;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static int usbfs_mkdir (struct inode *dir, struct dentry *dentry, int mode)
|
||||
{
|
||||
int res;
|
||||
|
||||
mode = (mode & (S_IRWXUGO | S_ISVTX)) | S_IFDIR;
|
||||
res = usbfs_mknod (dir, dentry, mode, 0);
|
||||
if (!res)
|
||||
dir->i_nlink++;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int usbfs_create (struct inode *dir, struct dentry *dentry, int mode)
|
||||
{
|
||||
mode = (mode & S_IALLUGO) | S_IFREG;
|
||||
return usbfs_mknod (dir, dentry, mode, 0);
|
||||
}
|
||||
|
||||
static inline int usbfs_positive (struct dentry *dentry)
|
||||
{
|
||||
return dentry->d_inode && !d_unhashed(dentry);
|
||||
}
|
||||
|
||||
static int usbfs_empty (struct dentry *dentry)
|
||||
{
|
||||
struct list_head *list;
|
||||
|
||||
spin_lock(&dcache_lock);
|
||||
|
||||
list_for_each(list, &dentry->d_subdirs) {
|
||||
struct dentry *de = list_entry(list, struct dentry, d_child);
|
||||
if (usbfs_positive(de)) {
|
||||
spin_unlock(&dcache_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&dcache_lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int usbfs_unlink (struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
down(&inode->i_sem);
|
||||
dentry->d_inode->i_nlink--;
|
||||
dput(dentry);
|
||||
up(&inode->i_sem);
|
||||
d_delete(dentry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usbfs_rmdir(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
int error = -ENOTEMPTY;
|
||||
struct inode * inode = dentry->d_inode;
|
||||
|
||||
down(&inode->i_sem);
|
||||
dentry_unhash(dentry);
|
||||
if (usbfs_empty(dentry)) {
|
||||
dentry->d_inode->i_nlink -= 2;
|
||||
dput(dentry);
|
||||
inode->i_flags |= S_DEAD;
|
||||
dir->i_nlink--;
|
||||
error = 0;
|
||||
}
|
||||
up(&inode->i_sem);
|
||||
if (!error)
|
||||
d_delete(dentry);
|
||||
dput(dentry);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/* default file operations */
|
||||
static ssize_t default_read_file (struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t default_write_file (struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
static loff_t default_file_lseek (struct file *file, loff_t offset, int orig)
|
||||
{
|
||||
loff_t retval = -EINVAL;
|
||||
|
||||
down(&file->f_dentry->d_inode->i_sem);
|
||||
switch(orig) {
|
||||
case 0:
|
||||
if (offset > 0) {
|
||||
file->f_pos = offset;
|
||||
retval = file->f_pos;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if ((offset + file->f_pos) > 0) {
|
||||
file->f_pos += offset;
|
||||
retval = file->f_pos;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
up(&file->f_dentry->d_inode->i_sem);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int default_open (struct inode *inode, struct file *file)
|
||||
{
|
||||
if (inode->u.generic_ip)
|
||||
file->private_data = inode->u.generic_ip;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct file_operations default_file_operations = {
|
||||
.read = default_read_file,
|
||||
.write = default_write_file,
|
||||
.open = default_open,
|
||||
.llseek = default_file_lseek,
|
||||
};
|
||||
|
||||
static struct inode_operations usbfs_dir_inode_operations = {
|
||||
.lookup = simple_lookup,
|
||||
};
|
||||
|
||||
static struct super_operations usbfs_ops = {
|
||||
.statfs = simple_statfs,
|
||||
.drop_inode = generic_delete_inode,
|
||||
.remount_fs = remount,
|
||||
};
|
||||
|
||||
static int usbfs_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct dentry *root;
|
||||
|
||||
sb->s_blocksize = PAGE_CACHE_SIZE;
|
||||
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
|
||||
sb->s_magic = USBDEVICE_SUPER_MAGIC;
|
||||
sb->s_op = &usbfs_ops;
|
||||
sb->s_time_gran = 1;
|
||||
inode = usbfs_get_inode(sb, S_IFDIR | 0755, 0);
|
||||
|
||||
if (!inode) {
|
||||
dbg("%s: could not get inode!",__FUNCTION__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
root = d_alloc_root(inode);
|
||||
if (!root) {
|
||||
dbg("%s: could not get root dentry!",__FUNCTION__);
|
||||
iput(inode);
|
||||
return -ENOMEM;
|
||||
}
|
||||
sb->s_root = root;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dentry * get_dentry(struct dentry *parent, const char *name)
|
||||
{
|
||||
struct qstr qstr;
|
||||
|
||||
qstr.name = name;
|
||||
qstr.len = strlen(name);
|
||||
qstr.hash = full_name_hash(name,qstr.len);
|
||||
return lookup_hash(&qstr,parent);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* fs_create_by_name - create a file, given a name
|
||||
* @name: name of file
|
||||
* @mode: type of file
|
||||
* @parent: dentry of directory to create it in
|
||||
* @dentry: resulting dentry of file
|
||||
*
|
||||
* This function handles both regular files and directories.
|
||||
*/
|
||||
static int fs_create_by_name (const char *name, mode_t mode,
|
||||
struct dentry *parent, struct dentry **dentry)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
/* If the parent is not specified, we create it in the root.
|
||||
* We need the root dentry to do this, which is in the super
|
||||
* block. A pointer to that is in the struct vfsmount that we
|
||||
* have around.
|
||||
*/
|
||||
if (!parent ) {
|
||||
if (usbfs_mount && usbfs_mount->mnt_sb) {
|
||||
parent = usbfs_mount->mnt_sb->s_root;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
dbg("Ah! can not find a parent!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
*dentry = NULL;
|
||||
down(&parent->d_inode->i_sem);
|
||||
*dentry = get_dentry (parent, name);
|
||||
if (!IS_ERR(dentry)) {
|
||||
if ((mode & S_IFMT) == S_IFDIR)
|
||||
error = usbfs_mkdir (parent->d_inode, *dentry, mode);
|
||||
else
|
||||
error = usbfs_create (parent->d_inode, *dentry, mode);
|
||||
} else
|
||||
error = PTR_ERR(dentry);
|
||||
up(&parent->d_inode->i_sem);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static struct dentry *fs_create_file (const char *name, mode_t mode,
|
||||
struct dentry *parent, void *data,
|
||||
struct file_operations *fops,
|
||||
uid_t uid, gid_t gid)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
int error;
|
||||
|
||||
dbg("creating file '%s'",name);
|
||||
|
||||
error = fs_create_by_name (name, mode, parent, &dentry);
|
||||
if (error) {
|
||||
dentry = NULL;
|
||||
} else {
|
||||
if (dentry->d_inode) {
|
||||
if (data)
|
||||
dentry->d_inode->u.generic_ip = data;
|
||||
if (fops)
|
||||
dentry->d_inode->i_fop = fops;
|
||||
dentry->d_inode->i_uid = uid;
|
||||
dentry->d_inode->i_gid = gid;
|
||||
}
|
||||
}
|
||||
|
||||
return dentry;
|
||||
}
|
||||
|
||||
static void fs_remove_file (struct dentry *dentry)
|
||||
{
|
||||
struct dentry *parent = dentry->d_parent;
|
||||
|
||||
if (!parent || !parent->d_inode)
|
||||
return;
|
||||
|
||||
down(&parent->d_inode->i_sem);
|
||||
if (usbfs_positive(dentry)) {
|
||||
if (dentry->d_inode) {
|
||||
if (S_ISDIR(dentry->d_inode->i_mode))
|
||||
usbfs_rmdir(parent->d_inode, dentry);
|
||||
else
|
||||
usbfs_unlink(parent->d_inode, dentry);
|
||||
dput(dentry);
|
||||
}
|
||||
}
|
||||
up(&parent->d_inode->i_sem);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static struct super_block *usb_get_sb(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name, void *data)
|
||||
{
|
||||
return get_sb_single(fs_type, flags, data, usbfs_fill_super);
|
||||
}
|
||||
|
||||
static struct file_system_type usb_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "usbfs",
|
||||
.get_sb = usb_get_sb,
|
||||
.kill_sb = kill_litter_super,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static int create_special_files (void)
|
||||
{
|
||||
struct dentry *parent;
|
||||
int retval;
|
||||
|
||||
/* the simple_pin_fs calls will call remount with no options
|
||||
* without this flag that would overwrite the real mount options (if any)
|
||||
*/
|
||||
ignore_mount = 1;
|
||||
|
||||
/* create the devices special file */
|
||||
retval = simple_pin_fs("usbfs", &usbfs_mount, &usbfs_mount_count);
|
||||
if (retval) {
|
||||
err ("Unable to get usbfs mount");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ignore_mount = 0;
|
||||
|
||||
parent = usbfs_mount->mnt_sb->s_root;
|
||||
devices_usbfs_dentry = fs_create_file ("devices",
|
||||
listmode | S_IFREG, parent,
|
||||
NULL, &usbfs_devices_fops,
|
||||
listuid, listgid);
|
||||
if (devices_usbfs_dentry == NULL) {
|
||||
err ("Unable to create devices usbfs file");
|
||||
retval = -ENODEV;
|
||||
goto error_clean_mounts;
|
||||
}
|
||||
|
||||
goto exit;
|
||||
|
||||
error_clean_mounts:
|
||||
simple_release_fs(&usbfs_mount, &usbfs_mount_count);
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void remove_special_files (void)
|
||||
{
|
||||
if (devices_usbfs_dentry)
|
||||
fs_remove_file (devices_usbfs_dentry);
|
||||
devices_usbfs_dentry = NULL;
|
||||
simple_release_fs(&usbfs_mount, &usbfs_mount_count);
|
||||
}
|
||||
|
||||
void usbfs_update_special (void)
|
||||
{
|
||||
struct inode *inode;
|
||||
|
||||
if (devices_usbfs_dentry) {
|
||||
inode = devices_usbfs_dentry->d_inode;
|
||||
if (inode)
|
||||
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
void usbfs_add_bus(struct usb_bus *bus)
|
||||
{
|
||||
struct dentry *parent;
|
||||
char name[8];
|
||||
int retval;
|
||||
|
||||
/* create the special files if this is the first bus added */
|
||||
if (num_buses == 0) {
|
||||
retval = create_special_files();
|
||||
if (retval)
|
||||
return;
|
||||
}
|
||||
++num_buses;
|
||||
|
||||
sprintf (name, "%03d", bus->busnum);
|
||||
|
||||
parent = usbfs_mount->mnt_sb->s_root;
|
||||
bus->usbfs_dentry = fs_create_file (name, busmode | S_IFDIR, parent,
|
||||
bus, NULL, busuid, busgid);
|
||||
if (bus->usbfs_dentry == NULL) {
|
||||
err ("error creating usbfs bus entry");
|
||||
return;
|
||||
}
|
||||
|
||||
usbfs_update_special();
|
||||
usbfs_conn_disc_event();
|
||||
}
|
||||
|
||||
void usbfs_remove_bus(struct usb_bus *bus)
|
||||
{
|
||||
if (bus->usbfs_dentry) {
|
||||
fs_remove_file (bus->usbfs_dentry);
|
||||
bus->usbfs_dentry = NULL;
|
||||
}
|
||||
|
||||
--num_buses;
|
||||
if (num_buses <= 0) {
|
||||
remove_special_files();
|
||||
num_buses = 0;
|
||||
}
|
||||
|
||||
usbfs_update_special();
|
||||
usbfs_conn_disc_event();
|
||||
}
|
||||
|
||||
void usbfs_add_device(struct usb_device *dev)
|
||||
{
|
||||
char name[8];
|
||||
int i;
|
||||
int i_size;
|
||||
|
||||
sprintf (name, "%03d", dev->devnum);
|
||||
dev->usbfs_dentry = fs_create_file (name, devmode | S_IFREG,
|
||||
dev->bus->usbfs_dentry, dev,
|
||||
&usbfs_device_file_operations,
|
||||
devuid, devgid);
|
||||
if (dev->usbfs_dentry == NULL) {
|
||||
err ("error creating usbfs device entry");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the size of the device's file to be
|
||||
* equal to the size of the device descriptors. */
|
||||
i_size = sizeof (struct usb_device_descriptor);
|
||||
for (i = 0; i < dev->descriptor.bNumConfigurations; ++i) {
|
||||
struct usb_config_descriptor *config =
|
||||
(struct usb_config_descriptor *)dev->rawdescriptors[i];
|
||||
i_size += le16_to_cpu(config->wTotalLength);
|
||||
}
|
||||
if (dev->usbfs_dentry->d_inode)
|
||||
dev->usbfs_dentry->d_inode->i_size = i_size;
|
||||
|
||||
usbfs_update_special();
|
||||
usbfs_conn_disc_event();
|
||||
}
|
||||
|
||||
void usbfs_remove_device(struct usb_device *dev)
|
||||
{
|
||||
struct dev_state *ds;
|
||||
struct siginfo sinfo;
|
||||
|
||||
if (dev->usbfs_dentry) {
|
||||
fs_remove_file (dev->usbfs_dentry);
|
||||
dev->usbfs_dentry = NULL;
|
||||
}
|
||||
while (!list_empty(&dev->filelist)) {
|
||||
ds = list_entry(dev->filelist.next, struct dev_state, list);
|
||||
wake_up_all(&ds->wait);
|
||||
list_del_init(&ds->list);
|
||||
if (ds->discsignr) {
|
||||
sinfo.si_signo = SIGPIPE;
|
||||
sinfo.si_errno = EPIPE;
|
||||
sinfo.si_code = SI_ASYNCIO;
|
||||
sinfo.si_addr = ds->disccontext;
|
||||
send_sig_info(ds->discsignr, &sinfo, ds->disctask);
|
||||
}
|
||||
}
|
||||
usbfs_update_special();
|
||||
usbfs_conn_disc_event();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static struct proc_dir_entry *usbdir = NULL;
|
||||
|
||||
int __init usbfs_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = usb_register(&usbfs_driver);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
retval = register_filesystem(&usb_fs_type);
|
||||
if (retval) {
|
||||
usb_deregister(&usbfs_driver);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* create mount point for usbfs */
|
||||
usbdir = proc_mkdir("usb", proc_bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void usbfs_cleanup(void)
|
||||
{
|
||||
usb_deregister(&usbfs_driver);
|
||||
unregister_filesystem(&usb_fs_type);
|
||||
if (usbdir)
|
||||
remove_proc_entry("usb", proc_bus);
|
||||
}
|
||||
|
||||
1480
drivers/usb/core/message.c
Normal file
1480
drivers/usb/core/message.c
Normal file
File diff suppressed because it is too large
Load diff
112
drivers/usb/core/otg_whitelist.h
Normal file
112
drivers/usb/core/otg_whitelist.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* drivers/usb/core/otg_whitelist.h
|
||||
*
|
||||
* Copyright (C) 2004 Texas Instruments
|
||||
*
|
||||
* 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 OTG Whitelist is the OTG "Targeted Peripheral List". It should
|
||||
* mostly use of USB_DEVICE() or USB_DEVICE_VER() entries..
|
||||
*
|
||||
* YOU _SHOULD_ CHANGE THIS LIST TO MATCH YOUR PRODUCT AND ITS TESTING!
|
||||
*/
|
||||
|
||||
static struct usb_device_id whitelist_table [] = {
|
||||
|
||||
/* hubs are optional in OTG, but very handy ... */
|
||||
{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), },
|
||||
{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), },
|
||||
|
||||
#ifdef CONFIG_USB_PRINTER /* ignoring nonstatic linkage! */
|
||||
/* FIXME actually, printers are NOT supposed to use device classes;
|
||||
* they're supposed to use interface classes...
|
||||
*/
|
||||
{ USB_DEVICE_INFO(7, 1, 1) },
|
||||
{ USB_DEVICE_INFO(7, 1, 2) },
|
||||
{ USB_DEVICE_INFO(7, 1, 3) },
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_CDCETHER
|
||||
/* Linux-USB CDC Ethernet gadget */
|
||||
{ USB_DEVICE(0x0525, 0xa4a1), },
|
||||
/* Linux-USB CDC Ethernet + RNDIS gadget */
|
||||
{ USB_DEVICE(0x0525, 0xa4a2), },
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_USB_TEST) || defined(CONFIG_USB_TEST_MODULE)
|
||||
/* gadget zero, for testing */
|
||||
{ USB_DEVICE(0x0525, 0xa4a0), },
|
||||
#endif
|
||||
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
static int is_targeted(struct usb_device *dev)
|
||||
{
|
||||
struct usb_device_id *id = whitelist_table;
|
||||
|
||||
/* possible in developer configs only! */
|
||||
if (!dev->bus->otg_port)
|
||||
return 1;
|
||||
|
||||
/* HNP test device is _never_ targeted (see OTG spec 6.6.6) */
|
||||
if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a &&
|
||||
le16_to_cpu(dev->descriptor.idProduct) == 0xbadd))
|
||||
return 0;
|
||||
|
||||
/* NOTE: can't use usb_match_id() since interface caches
|
||||
* aren't set up yet. this is cut/paste from that code.
|
||||
*/
|
||||
for (id = whitelist_table; id->match_flags; id++) {
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
|
||||
id->idVendor != le16_to_cpu(dev->descriptor.idVendor))
|
||||
continue;
|
||||
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
|
||||
id->idProduct != le16_to_cpu(dev->descriptor.idProduct))
|
||||
continue;
|
||||
|
||||
/* No need to test id->bcdDevice_lo != 0, since 0 is never
|
||||
greater than any unsigned number. */
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
|
||||
(id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice)))
|
||||
continue;
|
||||
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
|
||||
(id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice)))
|
||||
continue;
|
||||
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
|
||||
(id->bDeviceClass != dev->descriptor.bDeviceClass))
|
||||
continue;
|
||||
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
|
||||
(id->bDeviceSubClass!= dev->descriptor.bDeviceSubClass))
|
||||
continue;
|
||||
|
||||
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
|
||||
(id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
|
||||
continue;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* add other match criteria here ... */
|
||||
|
||||
|
||||
/* OTG MESSAGE: report errors here, customize to match your product */
|
||||
dev_err(&dev->dev, "device v%04x p%04x is not supported\n",
|
||||
le16_to_cpu(dev->descriptor.idVendor),
|
||||
le16_to_cpu(dev->descriptor.idProduct));
|
||||
#ifdef CONFIG_USB_OTG_WHITELIST
|
||||
return 0;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
318
drivers/usb/core/sysfs.c
Normal file
318
drivers/usb/core/sysfs.c
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* drivers/usb/core/sysfs.c
|
||||
*
|
||||
* (C) Copyright 2002 David Brownell
|
||||
* (C) Copyright 2002,2004 Greg Kroah-Hartman
|
||||
* (C) Copyright 2002,2004 IBM Corp.
|
||||
*
|
||||
* All of the sysfs file attributes for usb devices and interfaces.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#else
|
||||
#undef DEBUG
|
||||
#endif
|
||||
#include <linux/usb.h>
|
||||
|
||||
#include "usb.h"
|
||||
|
||||
/* Active configuration fields */
|
||||
#define usb_actconfig_show(field, multiplier, format_string) \
|
||||
static ssize_t show_##field (struct device *dev, char *buf) \
|
||||
{ \
|
||||
struct usb_device *udev; \
|
||||
struct usb_host_config *actconfig; \
|
||||
\
|
||||
udev = to_usb_device (dev); \
|
||||
actconfig = udev->actconfig; \
|
||||
if (actconfig) \
|
||||
return sprintf (buf, format_string, \
|
||||
actconfig->desc.field * multiplier); \
|
||||
else \
|
||||
return 0; \
|
||||
} \
|
||||
|
||||
#define usb_actconfig_attr(field, multiplier, format_string) \
|
||||
usb_actconfig_show(field, multiplier, format_string) \
|
||||
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL);
|
||||
|
||||
usb_actconfig_attr (bNumInterfaces, 1, "%2d\n")
|
||||
usb_actconfig_attr (bmAttributes, 1, "%2x\n")
|
||||
usb_actconfig_attr (bMaxPower, 2, "%3dmA\n")
|
||||
|
||||
static ssize_t show_configuration_string(struct device *dev, char *buf)
|
||||
{
|
||||
struct usb_device *udev;
|
||||
struct usb_host_config *actconfig;
|
||||
int len;
|
||||
|
||||
udev = to_usb_device (dev);
|
||||
actconfig = udev->actconfig;
|
||||
if ((!actconfig) || (!actconfig->string))
|
||||
return 0;
|
||||
len = sprintf(buf, actconfig->string, PAGE_SIZE);
|
||||
if (len < 0)
|
||||
return 0;
|
||||
buf[len] = '\n';
|
||||
buf[len+1] = 0;
|
||||
return len+1;
|
||||
}
|
||||
static DEVICE_ATTR(configuration, S_IRUGO, show_configuration_string, NULL);
|
||||
|
||||
/* configuration value is always present, and r/w */
|
||||
usb_actconfig_show(bConfigurationValue, 1, "%u\n");
|
||||
|
||||
static ssize_t
|
||||
set_bConfigurationValue (struct device *dev, const char *buf, size_t count)
|
||||
{
|
||||
struct usb_device *udev = udev = to_usb_device (dev);
|
||||
int config, value;
|
||||
|
||||
if (sscanf (buf, "%u", &config) != 1 || config > 255)
|
||||
return -EINVAL;
|
||||
usb_lock_device(udev);
|
||||
value = usb_set_configuration (udev, config);
|
||||
usb_unlock_device(udev);
|
||||
return (value < 0) ? value : count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(bConfigurationValue, S_IRUGO | S_IWUSR,
|
||||
show_bConfigurationValue, set_bConfigurationValue);
|
||||
|
||||
/* String fields */
|
||||
#define usb_string_attr(name) \
|
||||
static ssize_t show_##name(struct device *dev, char *buf) \
|
||||
{ \
|
||||
struct usb_device *udev; \
|
||||
int len; \
|
||||
\
|
||||
udev = to_usb_device (dev); \
|
||||
len = snprintf(buf, 256, "%s", udev->name); \
|
||||
if (len < 0) \
|
||||
return 0; \
|
||||
buf[len] = '\n'; \
|
||||
buf[len+1] = 0; \
|
||||
return len+1; \
|
||||
} \
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL);
|
||||
|
||||
usb_string_attr(product);
|
||||
usb_string_attr(manufacturer);
|
||||
usb_string_attr(serial);
|
||||
|
||||
static ssize_t
|
||||
show_speed (struct device *dev, char *buf)
|
||||
{
|
||||
struct usb_device *udev;
|
||||
char *speed;
|
||||
|
||||
udev = to_usb_device (dev);
|
||||
|
||||
switch (udev->speed) {
|
||||
case USB_SPEED_LOW:
|
||||
speed = "1.5";
|
||||
break;
|
||||
case USB_SPEED_UNKNOWN:
|
||||
case USB_SPEED_FULL:
|
||||
speed = "12";
|
||||
break;
|
||||
case USB_SPEED_HIGH:
|
||||
speed = "480";
|
||||
break;
|
||||
default:
|
||||
speed = "unknown";
|
||||
}
|
||||
return sprintf (buf, "%s\n", speed);
|
||||
}
|
||||
static DEVICE_ATTR(speed, S_IRUGO, show_speed, NULL);
|
||||
|
||||
static ssize_t
|
||||
show_devnum (struct device *dev, char *buf)
|
||||
{
|
||||
struct usb_device *udev;
|
||||
|
||||
udev = to_usb_device (dev);
|
||||
return sprintf (buf, "%d\n", udev->devnum);
|
||||
}
|
||||
static DEVICE_ATTR(devnum, S_IRUGO, show_devnum, NULL);
|
||||
|
||||
static ssize_t
|
||||
show_version (struct device *dev, char *buf)
|
||||
{
|
||||
struct usb_device *udev;
|
||||
u16 bcdUSB;
|
||||
|
||||
udev = to_usb_device(dev);
|
||||
bcdUSB = le16_to_cpu(udev->descriptor.bcdUSB);
|
||||
return sprintf(buf, "%2x.%02x\n", bcdUSB >> 8, bcdUSB & 0xff);
|
||||
}
|
||||
static DEVICE_ATTR(version, S_IRUGO, show_version, NULL);
|
||||
|
||||
static ssize_t
|
||||
show_maxchild (struct device *dev, char *buf)
|
||||
{
|
||||
struct usb_device *udev;
|
||||
|
||||
udev = to_usb_device (dev);
|
||||
return sprintf (buf, "%d\n", udev->maxchild);
|
||||
}
|
||||
static DEVICE_ATTR(maxchild, S_IRUGO, show_maxchild, NULL);
|
||||
|
||||
/* Descriptor fields */
|
||||
#define usb_descriptor_attr_le16(field, format_string) \
|
||||
static ssize_t \
|
||||
show_##field (struct device *dev, char *buf) \
|
||||
{ \
|
||||
struct usb_device *udev; \
|
||||
\
|
||||
udev = to_usb_device (dev); \
|
||||
return sprintf (buf, format_string, \
|
||||
le16_to_cpu(udev->descriptor.field)); \
|
||||
} \
|
||||
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL);
|
||||
|
||||
usb_descriptor_attr_le16(idVendor, "%04x\n")
|
||||
usb_descriptor_attr_le16(idProduct, "%04x\n")
|
||||
usb_descriptor_attr_le16(bcdDevice, "%04x\n")
|
||||
|
||||
#define usb_descriptor_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
show_##field (struct device *dev, char *buf) \
|
||||
{ \
|
||||
struct usb_device *udev; \
|
||||
\
|
||||
udev = to_usb_device (dev); \
|
||||
return sprintf (buf, format_string, udev->descriptor.field); \
|
||||
} \
|
||||
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL);
|
||||
|
||||
usb_descriptor_attr (bDeviceClass, "%02x\n")
|
||||
usb_descriptor_attr (bDeviceSubClass, "%02x\n")
|
||||
usb_descriptor_attr (bDeviceProtocol, "%02x\n")
|
||||
usb_descriptor_attr (bNumConfigurations, "%d\n")
|
||||
|
||||
static struct attribute *dev_attrs[] = {
|
||||
/* current configuration's attributes */
|
||||
&dev_attr_bNumInterfaces.attr,
|
||||
&dev_attr_bConfigurationValue.attr,
|
||||
&dev_attr_bmAttributes.attr,
|
||||
&dev_attr_bMaxPower.attr,
|
||||
/* device attributes */
|
||||
&dev_attr_idVendor.attr,
|
||||
&dev_attr_idProduct.attr,
|
||||
&dev_attr_bcdDevice.attr,
|
||||
&dev_attr_bDeviceClass.attr,
|
||||
&dev_attr_bDeviceSubClass.attr,
|
||||
&dev_attr_bDeviceProtocol.attr,
|
||||
&dev_attr_bNumConfigurations.attr,
|
||||
&dev_attr_speed.attr,
|
||||
&dev_attr_devnum.attr,
|
||||
&dev_attr_version.attr,
|
||||
&dev_attr_maxchild.attr,
|
||||
NULL,
|
||||
};
|
||||
static struct attribute_group dev_attr_grp = {
|
||||
.attrs = dev_attrs,
|
||||
};
|
||||
|
||||
void usb_create_sysfs_dev_files (struct usb_device *udev)
|
||||
{
|
||||
struct device *dev = &udev->dev;
|
||||
|
||||
sysfs_create_group(&dev->kobj, &dev_attr_grp);
|
||||
|
||||
if (udev->manufacturer)
|
||||
device_create_file (dev, &dev_attr_manufacturer);
|
||||
if (udev->product)
|
||||
device_create_file (dev, &dev_attr_product);
|
||||
if (udev->serial)
|
||||
device_create_file (dev, &dev_attr_serial);
|
||||
device_create_file (dev, &dev_attr_configuration);
|
||||
}
|
||||
|
||||
void usb_remove_sysfs_dev_files (struct usb_device *udev)
|
||||
{
|
||||
struct device *dev = &udev->dev;
|
||||
|
||||
sysfs_remove_group(&dev->kobj, &dev_attr_grp);
|
||||
|
||||
if (udev->descriptor.iManufacturer)
|
||||
device_remove_file(dev, &dev_attr_manufacturer);
|
||||
if (udev->descriptor.iProduct)
|
||||
device_remove_file(dev, &dev_attr_product);
|
||||
if (udev->descriptor.iSerialNumber)
|
||||
device_remove_file(dev, &dev_attr_serial);
|
||||
device_remove_file (dev, &dev_attr_configuration);
|
||||
}
|
||||
|
||||
/* Interface fields */
|
||||
#define usb_intf_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
show_##field (struct device *dev, char *buf) \
|
||||
{ \
|
||||
struct usb_interface *intf = to_usb_interface (dev); \
|
||||
\
|
||||
return sprintf (buf, format_string, intf->cur_altsetting->desc.field); \
|
||||
} \
|
||||
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL);
|
||||
|
||||
usb_intf_attr (bInterfaceNumber, "%02x\n")
|
||||
usb_intf_attr (bAlternateSetting, "%2d\n")
|
||||
usb_intf_attr (bNumEndpoints, "%02x\n")
|
||||
usb_intf_attr (bInterfaceClass, "%02x\n")
|
||||
usb_intf_attr (bInterfaceSubClass, "%02x\n")
|
||||
usb_intf_attr (bInterfaceProtocol, "%02x\n")
|
||||
|
||||
static ssize_t show_interface_string(struct device *dev, char *buf)
|
||||
{
|
||||
struct usb_interface *intf;
|
||||
struct usb_device *udev;
|
||||
int len;
|
||||
|
||||
intf = to_usb_interface (dev);
|
||||
udev = interface_to_usbdev (intf);
|
||||
len = snprintf(buf, 256, "%s", intf->cur_altsetting->string);
|
||||
if (len < 0)
|
||||
return 0;
|
||||
buf[len] = '\n';
|
||||
buf[len+1] = 0;
|
||||
return len+1;
|
||||
}
|
||||
static DEVICE_ATTR(interface, S_IRUGO, show_interface_string, NULL);
|
||||
|
||||
static struct attribute *intf_attrs[] = {
|
||||
&dev_attr_bInterfaceNumber.attr,
|
||||
&dev_attr_bAlternateSetting.attr,
|
||||
&dev_attr_bNumEndpoints.attr,
|
||||
&dev_attr_bInterfaceClass.attr,
|
||||
&dev_attr_bInterfaceSubClass.attr,
|
||||
&dev_attr_bInterfaceProtocol.attr,
|
||||
NULL,
|
||||
};
|
||||
static struct attribute_group intf_attr_grp = {
|
||||
.attrs = intf_attrs,
|
||||
};
|
||||
|
||||
void usb_create_sysfs_intf_files (struct usb_interface *intf)
|
||||
{
|
||||
sysfs_create_group(&intf->dev.kobj, &intf_attr_grp);
|
||||
|
||||
if (intf->cur_altsetting->string)
|
||||
device_create_file(&intf->dev, &dev_attr_interface);
|
||||
|
||||
}
|
||||
|
||||
void usb_remove_sysfs_intf_files (struct usb_interface *intf)
|
||||
{
|
||||
sysfs_remove_group(&intf->dev.kobj, &intf_attr_grp);
|
||||
|
||||
if (intf->cur_altsetting->string)
|
||||
device_remove_file(&intf->dev, &dev_attr_interface);
|
||||
|
||||
}
|
||||
511
drivers/usb/core/urb.c
Normal file
511
drivers/usb/core/urb.c
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#ifdef CONFIG_USB_DEBUG
|
||||
#define DEBUG
|
||||
#else
|
||||
#undef DEBUG
|
||||
#endif
|
||||
#include <linux/usb.h>
|
||||
#include "hcd.h"
|
||||
|
||||
#define to_urb(d) container_of(d, struct urb, kref)
|
||||
|
||||
static void urb_destroy(struct kref *kref)
|
||||
{
|
||||
struct urb *urb = to_urb(kref);
|
||||
kfree(urb);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_init_urb - initializes a urb so that it can be used by a USB driver
|
||||
* @urb: pointer to the urb to initialize
|
||||
*
|
||||
* Initializes a urb so that the USB subsystem can use it properly.
|
||||
*
|
||||
* If a urb is created with a call to usb_alloc_urb() it is not
|
||||
* necessary to call this function. Only use this if you allocate the
|
||||
* space for a struct urb on your own. If you call this function, be
|
||||
* careful when freeing the memory for your urb that it is no longer in
|
||||
* use by the USB core.
|
||||
*
|
||||
* Only use this function if you _really_ understand what you are doing.
|
||||
*/
|
||||
void usb_init_urb(struct urb *urb)
|
||||
{
|
||||
if (urb) {
|
||||
memset(urb, 0, sizeof(*urb));
|
||||
kref_init(&urb->kref);
|
||||
spin_lock_init(&urb->lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_alloc_urb - creates a new urb for a USB driver to use
|
||||
* @iso_packets: number of iso packets for this urb
|
||||
* @mem_flags: the type of memory to allocate, see kmalloc() for a list of
|
||||
* valid options for this.
|
||||
*
|
||||
* Creates an urb for the USB driver to use, initializes a few internal
|
||||
* structures, incrementes the usage counter, and returns a pointer to it.
|
||||
*
|
||||
* If no memory is available, NULL is returned.
|
||||
*
|
||||
* If the driver want to use this urb for interrupt, control, or bulk
|
||||
* endpoints, pass '0' as the number of iso packets.
|
||||
*
|
||||
* The driver must call usb_free_urb() when it is finished with the urb.
|
||||
*/
|
||||
struct urb *usb_alloc_urb(int iso_packets, int mem_flags)
|
||||
{
|
||||
struct urb *urb;
|
||||
|
||||
urb = (struct urb *)kmalloc(sizeof(struct urb) +
|
||||
iso_packets * sizeof(struct usb_iso_packet_descriptor),
|
||||
mem_flags);
|
||||
if (!urb) {
|
||||
err("alloc_urb: kmalloc failed");
|
||||
return NULL;
|
||||
}
|
||||
usb_init_urb(urb);
|
||||
return urb;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_free_urb - frees the memory used by a urb when all users of it are finished
|
||||
* @urb: pointer to the urb to free, may be NULL
|
||||
*
|
||||
* Must be called when a user of a urb is finished with it. When the last user
|
||||
* of the urb calls this function, the memory of the urb is freed.
|
||||
*
|
||||
* Note: The transfer buffer associated with the urb is not freed, that must be
|
||||
* done elsewhere.
|
||||
*/
|
||||
void usb_free_urb(struct urb *urb)
|
||||
{
|
||||
if (urb)
|
||||
kref_put(&urb->kref, urb_destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_get_urb - increments the reference count of the urb
|
||||
* @urb: pointer to the urb to modify, may be NULL
|
||||
*
|
||||
* This must be called whenever a urb is transferred from a device driver to a
|
||||
* host controller driver. This allows proper reference counting to happen
|
||||
* for urbs.
|
||||
*
|
||||
* A pointer to the urb with the incremented reference counter is returned.
|
||||
*/
|
||||
struct urb * usb_get_urb(struct urb *urb)
|
||||
{
|
||||
if (urb)
|
||||
kref_get(&urb->kref);
|
||||
return urb;
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* usb_submit_urb - issue an asynchronous transfer request for an endpoint
|
||||
* @urb: pointer to the urb describing the request
|
||||
* @mem_flags: the type of memory to allocate, see kmalloc() for a list
|
||||
* of valid options for this.
|
||||
*
|
||||
* This submits a transfer request, and transfers control of the URB
|
||||
* describing that request to the USB subsystem. Request completion will
|
||||
* be indicated later, asynchronously, by calling the completion handler.
|
||||
* The three types of completion are success, error, and unlink
|
||||
* (a software-induced fault, also called "request cancelation").
|
||||
*
|
||||
* URBs may be submitted in interrupt context.
|
||||
*
|
||||
* The caller must have correctly initialized the URB before submitting
|
||||
* it. Functions such as usb_fill_bulk_urb() and usb_fill_control_urb() are
|
||||
* available to ensure that most fields are correctly initialized, for
|
||||
* the particular kind of transfer, although they will not initialize
|
||||
* any transfer flags.
|
||||
*
|
||||
* Successful submissions return 0; otherwise this routine returns a
|
||||
* negative error number. If the submission is successful, the complete()
|
||||
* callback from the URB will be called exactly once, when the USB core and
|
||||
* Host Controller Driver (HCD) are finished with the URB. When the completion
|
||||
* function is called, control of the URB is returned to the device
|
||||
* driver which issued the request. The completion handler may then
|
||||
* immediately free or reuse that URB.
|
||||
*
|
||||
* With few exceptions, USB device drivers should never access URB fields
|
||||
* provided by usbcore or the HCD until its complete() is called.
|
||||
* The exceptions relate to periodic transfer scheduling. For both
|
||||
* interrupt and isochronous urbs, as part of successful URB submission
|
||||
* urb->interval is modified to reflect the actual transfer period used
|
||||
* (normally some power of two units). And for isochronous urbs,
|
||||
* urb->start_frame is modified to reflect when the URB's transfers were
|
||||
* scheduled to start. Not all isochronous transfer scheduling policies
|
||||
* will work, but most host controller drivers should easily handle ISO
|
||||
* queues going from now until 10-200 msec into the future.
|
||||
*
|
||||
* For control endpoints, the synchronous usb_control_msg() call is
|
||||
* often used (in non-interrupt context) instead of this call.
|
||||
* That is often used through convenience wrappers, for the requests
|
||||
* that are standardized in the USB 2.0 specification. For bulk
|
||||
* endpoints, a synchronous usb_bulk_msg() call is available.
|
||||
*
|
||||
* Request Queuing:
|
||||
*
|
||||
* URBs may be submitted to endpoints before previous ones complete, to
|
||||
* minimize the impact of interrupt latencies and system overhead on data
|
||||
* throughput. With that queuing policy, an endpoint's queue would never
|
||||
* be empty. This is required for continuous isochronous data streams,
|
||||
* and may also be required for some kinds of interrupt transfers. Such
|
||||
* queuing also maximizes bandwidth utilization by letting USB controllers
|
||||
* start work on later requests before driver software has finished the
|
||||
* completion processing for earlier (successful) requests.
|
||||
*
|
||||
* As of Linux 2.6, all USB endpoint transfer queues support depths greater
|
||||
* than one. This was previously a HCD-specific behavior, except for ISO
|
||||
* transfers. Non-isochronous endpoint queues are inactive during cleanup
|
||||
* after faults (transfer errors or cancelation).
|
||||
*
|
||||
* Reserved Bandwidth Transfers:
|
||||
*
|
||||
* Periodic transfers (interrupt or isochronous) are performed repeatedly,
|
||||
* using the interval specified in the urb. Submitting the first urb to
|
||||
* the endpoint reserves the bandwidth necessary to make those transfers.
|
||||
* If the USB subsystem can't allocate sufficient bandwidth to perform
|
||||
* the periodic request, submitting such a periodic request should fail.
|
||||
*
|
||||
* Device drivers must explicitly request that repetition, by ensuring that
|
||||
* some URB is always on the endpoint's queue (except possibly for short
|
||||
* periods during completion callacks). When there is no longer an urb
|
||||
* queued, the endpoint's bandwidth reservation is canceled. This means
|
||||
* drivers can use their completion handlers to ensure they keep bandwidth
|
||||
* they need, by reinitializing and resubmitting the just-completed urb
|
||||
* until the driver longer needs that periodic bandwidth.
|
||||
*
|
||||
* Memory Flags:
|
||||
*
|
||||
* The general rules for how to decide which mem_flags to use
|
||||
* are the same as for kmalloc. There are four
|
||||
* different possible values; GFP_KERNEL, GFP_NOFS, GFP_NOIO and
|
||||
* GFP_ATOMIC.
|
||||
*
|
||||
* GFP_NOFS is not ever used, as it has not been implemented yet.
|
||||
*
|
||||
* GFP_ATOMIC is used when
|
||||
* (a) you are inside a completion handler, an interrupt, bottom half,
|
||||
* tasklet or timer, or
|
||||
* (b) you are holding a spinlock or rwlock (does not apply to
|
||||
* semaphores), or
|
||||
* (c) current->state != TASK_RUNNING, this is the case only after
|
||||
* you've changed it.
|
||||
*
|
||||
* GFP_NOIO is used in the block io path and error handling of storage
|
||||
* devices.
|
||||
*
|
||||
* All other situations use GFP_KERNEL.
|
||||
*
|
||||
* Some more specific rules for mem_flags can be inferred, such as
|
||||
* (1) start_xmit, timeout, and receive methods of network drivers must
|
||||
* use GFP_ATOMIC (they are called with a spinlock held);
|
||||
* (2) queuecommand methods of scsi drivers must use GFP_ATOMIC (also
|
||||
* called with a spinlock held);
|
||||
* (3) If you use a kernel thread with a network driver you must use
|
||||
* GFP_NOIO, unless (b) or (c) apply;
|
||||
* (4) after you have done a down() you can use GFP_KERNEL, unless (b) or (c)
|
||||
* apply or your are in a storage driver's block io path;
|
||||
* (5) USB probe and disconnect can use GFP_KERNEL unless (b) or (c) apply; and
|
||||
* (6) changing firmware on a running storage or net device uses
|
||||
* GFP_NOIO, unless b) or c) apply
|
||||
*
|
||||
*/
|
||||
int usb_submit_urb(struct urb *urb, int mem_flags)
|
||||
{
|
||||
int pipe, temp, max;
|
||||
struct usb_device *dev;
|
||||
struct usb_operations *op;
|
||||
int is_out;
|
||||
|
||||
if (!urb || urb->hcpriv || !urb->complete)
|
||||
return -EINVAL;
|
||||
if (!(dev = urb->dev) ||
|
||||
(dev->state < USB_STATE_DEFAULT) ||
|
||||
(!dev->bus) || (dev->devnum <= 0))
|
||||
return -ENODEV;
|
||||
if (dev->state == USB_STATE_SUSPENDED)
|
||||
return -EHOSTUNREACH;
|
||||
if (!(op = dev->bus->op) || !op->submit_urb)
|
||||
return -ENODEV;
|
||||
|
||||
urb->status = -EINPROGRESS;
|
||||
urb->actual_length = 0;
|
||||
urb->bandwidth = 0;
|
||||
|
||||
/* Lots of sanity checks, so HCDs can rely on clean data
|
||||
* and don't need to duplicate tests
|
||||
*/
|
||||
pipe = urb->pipe;
|
||||
temp = usb_pipetype (pipe);
|
||||
is_out = usb_pipeout (pipe);
|
||||
|
||||
if (!usb_pipecontrol (pipe) && dev->state < USB_STATE_CONFIGURED)
|
||||
return -ENODEV;
|
||||
|
||||
/* FIXME there should be a sharable lock protecting us against
|
||||
* config/altsetting changes and disconnects, kicking in here.
|
||||
* (here == before maxpacket, and eventually endpoint type,
|
||||
* checks get made.)
|
||||
*/
|
||||
|
||||
max = usb_maxpacket (dev, pipe, is_out);
|
||||
if (max <= 0) {
|
||||
dev_dbg(&dev->dev,
|
||||
"bogus endpoint ep%d%s in %s (bad maxpacket %d)\n",
|
||||
usb_pipeendpoint (pipe), is_out ? "out" : "in",
|
||||
__FUNCTION__, max);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
/* periodic transfers limit size per frame/uframe,
|
||||
* but drivers only control those sizes for ISO.
|
||||
* while we're checking, initialize return status.
|
||||
*/
|
||||
if (temp == PIPE_ISOCHRONOUS) {
|
||||
int n, len;
|
||||
|
||||
/* "high bandwidth" mode, 1-3 packets/uframe? */
|
||||
if (dev->speed == USB_SPEED_HIGH) {
|
||||
int mult = 1 + ((max >> 11) & 0x03);
|
||||
max &= 0x07ff;
|
||||
max *= mult;
|
||||
}
|
||||
|
||||
if (urb->number_of_packets <= 0)
|
||||
return -EINVAL;
|
||||
for (n = 0; n < urb->number_of_packets; n++) {
|
||||
len = urb->iso_frame_desc [n].length;
|
||||
if (len < 0 || len > max)
|
||||
return -EMSGSIZE;
|
||||
urb->iso_frame_desc [n].status = -EXDEV;
|
||||
urb->iso_frame_desc [n].actual_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* the I/O buffer must be mapped/unmapped, except when length=0 */
|
||||
if (urb->transfer_buffer_length < 0)
|
||||
return -EMSGSIZE;
|
||||
|
||||
#ifdef DEBUG
|
||||
/* stuff that drivers shouldn't do, but which shouldn't
|
||||
* cause problems in HCDs if they get it wrong.
|
||||
*/
|
||||
{
|
||||
unsigned int orig_flags = urb->transfer_flags;
|
||||
unsigned int allowed;
|
||||
|
||||
/* enforce simple/standard policy */
|
||||
allowed = URB_ASYNC_UNLINK; // affects later unlinks
|
||||
allowed |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);
|
||||
allowed |= URB_NO_INTERRUPT;
|
||||
switch (temp) {
|
||||
case PIPE_BULK:
|
||||
if (is_out)
|
||||
allowed |= URB_ZERO_PACKET;
|
||||
/* FALLTHROUGH */
|
||||
case PIPE_CONTROL:
|
||||
allowed |= URB_NO_FSBR; /* only affects UHCI */
|
||||
/* FALLTHROUGH */
|
||||
default: /* all non-iso endpoints */
|
||||
if (!is_out)
|
||||
allowed |= URB_SHORT_NOT_OK;
|
||||
break;
|
||||
case PIPE_ISOCHRONOUS:
|
||||
allowed |= URB_ISO_ASAP;
|
||||
break;
|
||||
}
|
||||
urb->transfer_flags &= allowed;
|
||||
|
||||
/* fail if submitter gave bogus flags */
|
||||
if (urb->transfer_flags != orig_flags) {
|
||||
err ("BOGUS urb flags, %x --> %x",
|
||||
orig_flags, urb->transfer_flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Force periodic transfer intervals to be legal values that are
|
||||
* a power of two (so HCDs don't need to).
|
||||
*
|
||||
* FIXME want bus->{intr,iso}_sched_horizon values here. Each HC
|
||||
* supports different values... this uses EHCI/UHCI defaults (and
|
||||
* EHCI can use smaller non-default values).
|
||||
*/
|
||||
switch (temp) {
|
||||
case PIPE_ISOCHRONOUS:
|
||||
case PIPE_INTERRUPT:
|
||||
/* too small? */
|
||||
if (urb->interval <= 0)
|
||||
return -EINVAL;
|
||||
/* too big? */
|
||||
switch (dev->speed) {
|
||||
case USB_SPEED_HIGH: /* units are microframes */
|
||||
// NOTE usb handles 2^15
|
||||
if (urb->interval > (1024 * 8))
|
||||
urb->interval = 1024 * 8;
|
||||
temp = 1024 * 8;
|
||||
break;
|
||||
case USB_SPEED_FULL: /* units are frames/msec */
|
||||
case USB_SPEED_LOW:
|
||||
if (temp == PIPE_INTERRUPT) {
|
||||
if (urb->interval > 255)
|
||||
return -EINVAL;
|
||||
// NOTE ohci only handles up to 32
|
||||
temp = 128;
|
||||
} else {
|
||||
if (urb->interval > 1024)
|
||||
urb->interval = 1024;
|
||||
// NOTE usb and ohci handle up to 2^15
|
||||
temp = 1024;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
/* power of two? */
|
||||
while (temp > urb->interval)
|
||||
temp >>= 1;
|
||||
urb->interval = temp;
|
||||
}
|
||||
|
||||
return op->submit_urb (urb, mem_flags);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* usb_unlink_urb - abort/cancel a transfer request for an endpoint
|
||||
* @urb: pointer to urb describing a previously submitted request,
|
||||
* may be NULL
|
||||
*
|
||||
* This routine cancels an in-progress request. URBs complete only
|
||||
* once per submission, and may be canceled only once per submission.
|
||||
* Successful cancelation means the requests's completion handler will
|
||||
* be called with a status code indicating that the request has been
|
||||
* canceled (rather than any other code) and will quickly be removed
|
||||
* from host controller data structures.
|
||||
*
|
||||
* In the past, clearing the URB_ASYNC_UNLINK transfer flag for the
|
||||
* URB indicated that the request was synchronous. This usage is now
|
||||
* deprecated; if the flag is clear the call will be forwarded to
|
||||
* usb_kill_urb() and the return value will be 0. In the future, drivers
|
||||
* should call usb_kill_urb() directly for synchronous unlinking.
|
||||
*
|
||||
* When the URB_ASYNC_UNLINK transfer flag for the URB is set, this
|
||||
* request is asynchronous. Success is indicated by returning -EINPROGRESS,
|
||||
* at which time the URB will normally have been unlinked but not yet
|
||||
* given back to the device driver. When it is called, the completion
|
||||
* function will see urb->status == -ECONNRESET. Failure is indicated
|
||||
* by any other return value. Unlinking will fail when the URB is not
|
||||
* currently "linked" (i.e., it was never submitted, or it was unlinked
|
||||
* before, or the hardware is already finished with it), even if the
|
||||
* completion handler has not yet run.
|
||||
*
|
||||
* Unlinking and Endpoint Queues:
|
||||
*
|
||||
* Host Controller Drivers (HCDs) place all the URBs for a particular
|
||||
* endpoint in a queue. Normally the queue advances as the controller
|
||||
* hardware processes each request. But when an URB terminates with any
|
||||
* fault (such as an error, or being unlinked) its queue stops, at least
|
||||
* until that URB's completion routine returns. It is guaranteed that
|
||||
* the queue will not restart until all its unlinked URBs have been fully
|
||||
* retired, with their completion routines run, even if that's not until
|
||||
* some time after the original completion handler returns.
|
||||
*
|
||||
* This means that USB device drivers can safely build deep queues for
|
||||
* large or complex transfers, and clean them up reliably after any sort
|
||||
* of aborted transfer by unlinking all pending URBs at the first fault.
|
||||
*
|
||||
* Note that an URB terminating early because a short packet was received
|
||||
* will count as an error if and only if the URB_SHORT_NOT_OK flag is set.
|
||||
* Also, that all unlinks performed in any URB completion handler must
|
||||
* be asynchronous.
|
||||
*
|
||||
* Queues for isochronous endpoints are treated differently, because they
|
||||
* advance at fixed rates. Such queues do not stop when an URB is unlinked.
|
||||
* An unlinked URB may leave a gap in the stream of packets. It is undefined
|
||||
* whether such gaps can be filled in.
|
||||
*
|
||||
* When a control URB terminates with an error, it is likely that the
|
||||
* status stage of the transfer will not take place, even if it is merely
|
||||
* a soft error resulting from a short-packet with URB_SHORT_NOT_OK set.
|
||||
*/
|
||||
int usb_unlink_urb(struct urb *urb)
|
||||
{
|
||||
if (!urb)
|
||||
return -EINVAL;
|
||||
if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
|
||||
#ifdef CONFIG_DEBUG_KERNEL
|
||||
if (printk_ratelimit()) {
|
||||
printk(KERN_NOTICE "usb_unlink_urb() is deprecated for "
|
||||
"synchronous unlinks. Use usb_kill_urb() instead.\n");
|
||||
WARN_ON(1);
|
||||
}
|
||||
#endif
|
||||
usb_kill_urb(urb);
|
||||
return 0;
|
||||
}
|
||||
if (!(urb->dev && urb->dev->bus && urb->dev->bus->op))
|
||||
return -ENODEV;
|
||||
return urb->dev->bus->op->unlink_urb(urb, -ECONNRESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_kill_urb - cancel a transfer request and wait for it to finish
|
||||
* @urb: pointer to URB describing a previously submitted request,
|
||||
* may be NULL
|
||||
*
|
||||
* This routine cancels an in-progress request. It is guaranteed that
|
||||
* upon return all completion handlers will have finished and the URB
|
||||
* will be totally idle and available for reuse. These features make
|
||||
* this an ideal way to stop I/O in a disconnect() callback or close()
|
||||
* function. If the request has not already finished or been unlinked
|
||||
* the completion handler will see urb->status == -ENOENT.
|
||||
*
|
||||
* While the routine is running, attempts to resubmit the URB will fail
|
||||
* with error -EPERM. Thus even if the URB's completion handler always
|
||||
* tries to resubmit, it will not succeed and the URB will become idle.
|
||||
*
|
||||
* This routine may not be used in an interrupt context (such as a bottom
|
||||
* half or a completion handler), or when holding a spinlock, or in other
|
||||
* situations where the caller can't schedule().
|
||||
*/
|
||||
void usb_kill_urb(struct urb *urb)
|
||||
{
|
||||
if (!(urb && urb->dev && urb->dev->bus && urb->dev->bus->op))
|
||||
return;
|
||||
spin_lock_irq(&urb->lock);
|
||||
++urb->reject;
|
||||
spin_unlock_irq(&urb->lock);
|
||||
|
||||
urb->dev->bus->op->unlink_urb(urb, -ENOENT);
|
||||
wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0);
|
||||
|
||||
spin_lock_irq(&urb->lock);
|
||||
--urb->reject;
|
||||
spin_unlock_irq(&urb->lock);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(usb_init_urb);
|
||||
EXPORT_SYMBOL(usb_alloc_urb);
|
||||
EXPORT_SYMBOL(usb_free_urb);
|
||||
EXPORT_SYMBOL(usb_get_urb);
|
||||
EXPORT_SYMBOL(usb_submit_urb);
|
||||
EXPORT_SYMBOL(usb_unlink_urb);
|
||||
EXPORT_SYMBOL(usb_kill_urb);
|
||||
|
||||
1573
drivers/usb/core/usb.c
Normal file
1573
drivers/usb/core/usb.c
Normal file
File diff suppressed because it is too large
Load diff
46
drivers/usb/core/usb.h
Normal file
46
drivers/usb/core/usb.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* Functions local to drivers/usb/core/ */
|
||||
|
||||
extern void usb_create_sysfs_dev_files (struct usb_device *dev);
|
||||
extern void usb_remove_sysfs_dev_files (struct usb_device *dev);
|
||||
extern void usb_create_sysfs_intf_files (struct usb_interface *intf);
|
||||
extern void usb_remove_sysfs_intf_files (struct usb_interface *intf);
|
||||
|
||||
extern void usb_disable_endpoint (struct usb_device *dev, unsigned int epaddr);
|
||||
extern void usb_disable_interface (struct usb_device *dev,
|
||||
struct usb_interface *intf);
|
||||
extern void usb_release_interface_cache(struct kref *ref);
|
||||
extern void usb_disable_device (struct usb_device *dev, int skip_ep0);
|
||||
|
||||
extern int usb_get_device_descriptor(struct usb_device *dev,
|
||||
unsigned int size);
|
||||
extern int usb_set_configuration(struct usb_device *dev, int configuration);
|
||||
|
||||
extern void usb_lock_all_devices(void);
|
||||
extern void usb_unlock_all_devices(void);
|
||||
|
||||
extern void usb_kick_khubd(struct usb_device *dev);
|
||||
extern void usb_resume_root_hub(struct usb_device *dev);
|
||||
|
||||
/* for labeling diagnostics */
|
||||
extern const char *usbcore_name;
|
||||
|
||||
/* usbfs stuff */
|
||||
extern struct usb_driver usbfs_driver;
|
||||
extern struct file_operations usbfs_devices_fops;
|
||||
extern struct file_operations usbfs_device_file_operations;
|
||||
extern void usbfs_conn_disc_event(void);
|
||||
|
||||
struct dev_state {
|
||||
struct list_head list; /* state list */
|
||||
struct usb_device *dev;
|
||||
struct file *file;
|
||||
spinlock_t lock; /* protects the async urb lists */
|
||||
struct list_head async_pending;
|
||||
struct list_head async_completed;
|
||||
wait_queue_head_t wait; /* wake up if a request completed */
|
||||
unsigned int discsignr;
|
||||
struct task_struct *disctask;
|
||||
void __user *disccontext;
|
||||
unsigned long ifclaimed;
|
||||
};
|
||||
|
||||
389
drivers/usb/gadget/Kconfig
Normal file
389
drivers/usb/gadget/Kconfig
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
#
|
||||
# USB Gadget support on a system involves
|
||||
# (a) a peripheral controller, and
|
||||
# (b) the gadget driver using it.
|
||||
#
|
||||
# NOTE: Gadget support ** DOES NOT ** depend on host-side CONFIG_USB !!
|
||||
#
|
||||
# - Host systems (like PCs) need CONFIG_USB (with "A" jacks).
|
||||
# - Peripherals (like PDAs) need CONFIG_USB_GADGET (with "B" jacks).
|
||||
# - Some systems have both kinds of of controller.
|
||||
#
|
||||
# With help from a special transceiver and a "Mini-AB" jack, systems with
|
||||
# both kinds of controller can also support "USB On-the-Go" (CONFIG_USB_OTG).
|
||||
#
|
||||
menu "USB Gadget Support"
|
||||
|
||||
config USB_GADGET
|
||||
tristate "Support for USB Gadgets"
|
||||
help
|
||||
USB is a master/slave protocol, organized with one master
|
||||
host (such as a PC) controlling up to 127 peripheral devices.
|
||||
The USB hardware is asymmetric, which makes it easier to set up:
|
||||
you can't connect a "to-the-host" connector to a peripheral.
|
||||
|
||||
Linux can run in the host, or in the peripheral. In both cases
|
||||
you need a low level bus controller driver, and some software
|
||||
talking to it. Peripheral controllers are often discrete silicon,
|
||||
or are integrated with the CPU in a microcontroller. The more
|
||||
familiar host side controllers have names like like "EHCI", "OHCI",
|
||||
or "UHCI", and are usually integrated into southbridges on PC
|
||||
motherboards.
|
||||
|
||||
Enable this configuration option if you want to run Linux inside
|
||||
a USB peripheral device. Configure one hardware driver for your
|
||||
peripheral/device side bus controller, and a "gadget driver" for
|
||||
your peripheral protocol. (If you use modular gadget drivers,
|
||||
you may configure more than one.)
|
||||
|
||||
If in doubt, say "N" and don't enable these drivers; most people
|
||||
don't have this kind of hardware (except maybe inside Linux PDAs).
|
||||
|
||||
For more information, see <http://www.linux-usb.org/gadget> and
|
||||
the kernel DocBook documentation for this API.
|
||||
|
||||
config USB_GADGET_DEBUG_FILES
|
||||
boolean "Debugging information files"
|
||||
depends on USB_GADGET && PROC_FS
|
||||
help
|
||||
Some of the drivers in the "gadget" framework can expose
|
||||
debugging information in files such as /proc/driver/udc
|
||||
(for a peripheral controller). The information in these
|
||||
files may help when you're troubleshooting or bringing up a
|
||||
driver on a new board. Enable these files by choosing "Y"
|
||||
here. If in doubt, or to conserve kernel memory, say "N".
|
||||
|
||||
#
|
||||
# USB Peripheral Controller Support
|
||||
#
|
||||
choice
|
||||
prompt "USB Peripheral Controller"
|
||||
depends on USB_GADGET
|
||||
help
|
||||
A USB device uses a controller to talk to its host.
|
||||
Systems should have only one such upstream link.
|
||||
Many controller drivers are platform-specific; these
|
||||
often need board-specific hooks.
|
||||
|
||||
config USB_GADGET_NET2280
|
||||
boolean "NetChip 2280"
|
||||
depends on PCI
|
||||
select USB_GADGET_DUALSPEED
|
||||
help
|
||||
NetChip 2280 is a PCI based USB peripheral controller which
|
||||
supports both full and high speed USB 2.0 data transfers.
|
||||
|
||||
It has six configurable endpoints, as well as endpoint zero
|
||||
(for control transfers) and several endpoints with dedicated
|
||||
functions.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "net2280" and force all
|
||||
gadget drivers to also be dynamically linked.
|
||||
|
||||
config USB_NET2280
|
||||
tristate
|
||||
depends on USB_GADGET_NET2280
|
||||
default USB_GADGET
|
||||
|
||||
config USB_GADGET_PXA2XX
|
||||
boolean "PXA 25x or IXP 4xx"
|
||||
depends on (ARCH_PXA && PXA25x) || ARCH_IXP4XX
|
||||
help
|
||||
Intel's PXA 25x series XScale ARM-5TE processors include
|
||||
an integrated full speed USB 1.1 device controller. The
|
||||
controller in the IXP 4xx series is register-compatible.
|
||||
|
||||
It has fifteen fixed-function endpoints, as well as endpoint
|
||||
zero (for control transfers).
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "pxa2xx_udc" and force all
|
||||
gadget drivers to also be dynamically linked.
|
||||
|
||||
config USB_PXA2XX
|
||||
tristate
|
||||
depends on USB_GADGET_PXA2XX
|
||||
default USB_GADGET
|
||||
|
||||
# if there's only one gadget driver, using only two bulk endpoints,
|
||||
# don't waste memory for the other endpoints
|
||||
config USB_PXA2XX_SMALL
|
||||
depends on USB_GADGET_PXA2XX
|
||||
bool
|
||||
default n if USB_ETH_RNDIS
|
||||
default y if USB_ZERO
|
||||
default y if USB_ETH
|
||||
default y if USB_G_SERIAL
|
||||
|
||||
config USB_GADGET_GOKU
|
||||
boolean "Toshiba TC86C001 'Goku-S'"
|
||||
depends on PCI
|
||||
help
|
||||
The Toshiba TC86C001 is a PCI device which includes controllers
|
||||
for full speed USB devices, IDE, I2C, SIO, plus a USB host (OHCI).
|
||||
|
||||
The device controller has three configurable (bulk or interrupt)
|
||||
endpoints, plus endpoint zero (for control transfers).
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "goku_udc" and to force all
|
||||
gadget drivers to also be dynamically linked.
|
||||
|
||||
config USB_GOKU
|
||||
tristate
|
||||
depends on USB_GADGET_GOKU
|
||||
default USB_GADGET
|
||||
|
||||
|
||||
config USB_GADGET_LH7A40X
|
||||
boolean "LH7A40X"
|
||||
depends on ARCH_LH7A40X
|
||||
help
|
||||
This driver provides USB Device Controller driver for LH7A40x
|
||||
|
||||
config USB_LH7A40X
|
||||
tristate
|
||||
depends on USB_GADGET_LH7A40X
|
||||
default USB_GADGET
|
||||
|
||||
|
||||
config USB_GADGET_OMAP
|
||||
boolean "OMAP USB Device Controller"
|
||||
depends on ARCH_OMAP
|
||||
select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3
|
||||
help
|
||||
Many Texas Instruments OMAP processors have flexible full
|
||||
speed USB device controllers, with support for up to 30
|
||||
endpoints (plus endpoint zero). This driver supports the
|
||||
controller in the OMAP 1611, and should work with controllers
|
||||
in other OMAP processors too, given minor tweaks.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "omap_udc" and force all
|
||||
gadget drivers to also be dynamically linked.
|
||||
|
||||
config USB_OMAP
|
||||
tristate
|
||||
depends on USB_GADGET_OMAP
|
||||
default USB_GADGET
|
||||
|
||||
config USB_OTG
|
||||
boolean "OTG Support"
|
||||
depends on USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD
|
||||
help
|
||||
The most notable feature of USB OTG is support for a
|
||||
"Dual-Role" device, which can act as either a device
|
||||
or a host. The initial role choice can be changed
|
||||
later, when two dual-role devices talk to each other.
|
||||
|
||||
Select this only if your OMAP board has a Mini-AB connector.
|
||||
|
||||
|
||||
config USB_GADGET_DUMMY_HCD
|
||||
boolean "Dummy HCD (DEVELOPMENT)"
|
||||
depends on USB && EXPERIMENTAL
|
||||
select USB_GADGET_DUALSPEED
|
||||
help
|
||||
This host controller driver emulates USB, looping all data transfer
|
||||
requests back to a USB "gadget driver" in the same host. The host
|
||||
side is the master; the gadget side is the slave. Gadget drivers
|
||||
can be high, full, or low speed; and they have access to endpoints
|
||||
like those from NET2280, PXA2xx, or SA1100 hardware.
|
||||
|
||||
This may help in some stages of creating a driver to embed in a
|
||||
Linux device, since it lets you debug several parts of the gadget
|
||||
driver without its hardware or drivers being involved.
|
||||
|
||||
Since such a gadget side driver needs to interoperate with a host
|
||||
side Linux-USB device driver, this may help to debug both sides
|
||||
of a USB protocol stack.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "dummy_hcd" and force all
|
||||
gadget drivers to also be dynamically linked.
|
||||
|
||||
config USB_DUMMY_HCD
|
||||
tristate
|
||||
depends on USB_GADGET_DUMMY_HCD
|
||||
default USB_GADGET
|
||||
|
||||
# NOTE: Please keep dummy_hcd LAST so that "real hardware" appears
|
||||
# first and will be selected by default.
|
||||
|
||||
endchoice
|
||||
|
||||
config USB_GADGET_DUALSPEED
|
||||
bool
|
||||
depends on USB_GADGET
|
||||
default n
|
||||
help
|
||||
Means that gadget drivers should include extra descriptors
|
||||
and code to handle dual-speed controllers.
|
||||
|
||||
#
|
||||
# USB Gadget Drivers
|
||||
#
|
||||
choice
|
||||
tristate "USB Gadget Drivers"
|
||||
depends on USB_GADGET
|
||||
default USB_ETH
|
||||
help
|
||||
A Linux "Gadget Driver" talks to the USB Peripheral Controller
|
||||
driver through the abstract "gadget" API. Some other operating
|
||||
systems call these "client" drivers, of which "class drivers"
|
||||
are a subset (implementing a USB device class specification).
|
||||
A gadget driver implements one or more USB functions using
|
||||
the peripheral hardware.
|
||||
|
||||
Gadget drivers are hardware-neutral, or "platform independent",
|
||||
except that they sometimes must understand quirks or limitations
|
||||
of the particular controllers they work with. For example, when
|
||||
a controller doesn't support alternate configurations or provide
|
||||
enough of the right types of endpoints, the gadget driver might
|
||||
not be able work with that controller, or might need to implement
|
||||
a less common variant of a device class protocol.
|
||||
|
||||
# this first set of drivers all depend on bulk-capable hardware.
|
||||
|
||||
config USB_ZERO
|
||||
tristate "Gadget Zero (DEVELOPMENT)"
|
||||
depends on EXPERIMENTAL
|
||||
help
|
||||
Gadget Zero is a two-configuration device. It either sinks and
|
||||
sources bulk data; or it loops back a configurable number of
|
||||
transfers. It also implements control requests, for "chapter 9"
|
||||
conformance. The driver needs only two bulk-capable endpoints, so
|
||||
it can work on top of most device-side usb controllers. It's
|
||||
useful for testing, and is also a working example showing how
|
||||
USB "gadget drivers" can be written.
|
||||
|
||||
Make this be the first driver you try using on top of any new
|
||||
USB peripheral controller driver. Then you can use host-side
|
||||
test software, like the "usbtest" driver, to put your hardware
|
||||
and its driver through a basic set of functional tests.
|
||||
|
||||
Gadget Zero also works with the host-side "usb-skeleton" driver,
|
||||
and with many kinds of host-side test software. You may need
|
||||
to tweak product and vendor IDs before host software knows about
|
||||
this device, and arrange to select an appropriate configuration.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "g_zero".
|
||||
|
||||
config USB_ZERO_HNPTEST
|
||||
boolean "HNP Test Device"
|
||||
depends on USB_ZERO && USB_OTG
|
||||
help
|
||||
You can configure this device to enumerate using the device
|
||||
identifiers of the USB-OTG test device. That means that when
|
||||
this gadget connects to another OTG device, with this one using
|
||||
the "B-Peripheral" role, that device will use HNP to let this
|
||||
one serve as the USB host instead (in the "B-Host" role).
|
||||
|
||||
config USB_ETH
|
||||
tristate "Ethernet Gadget (with CDC Ethernet support)"
|
||||
depends on NET
|
||||
help
|
||||
This driver implements Ethernet style communication, in either
|
||||
of two ways:
|
||||
|
||||
- The "Communication Device Class" (CDC) Ethernet Control Model.
|
||||
That protocol is often avoided with pure Ethernet adapters, in
|
||||
favor of simpler vendor-specific hardware, but is widely
|
||||
supported by firmware for smart network devices.
|
||||
|
||||
- On hardware can't implement that protocol, a simple CDC subset
|
||||
is used, placing fewer demands on USB.
|
||||
|
||||
RNDIS support is a third option, more demanding than that subset.
|
||||
|
||||
Within the USB device, this gadget driver exposes a network device
|
||||
"usbX", where X depends on what other networking devices you have.
|
||||
Treat it like a two-node Ethernet link: host, and gadget.
|
||||
|
||||
The Linux-USB host-side "usbnet" driver interoperates with this
|
||||
driver, so that deep I/O queues can be supported. On 2.4 kernels,
|
||||
use "CDCEther" instead, if you're using the CDC option. That CDC
|
||||
mode should also interoperate with standard CDC Ethernet class
|
||||
drivers on other host operating systems.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "g_ether".
|
||||
|
||||
config USB_ETH_RNDIS
|
||||
bool "RNDIS support (EXPERIMENTAL)"
|
||||
depends on USB_ETH && EXPERIMENTAL
|
||||
default y
|
||||
help
|
||||
Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol,
|
||||
and Microsoft provides redistributable binary RNDIS drivers for
|
||||
older versions of Windows.
|
||||
|
||||
If you say "y" here, the Ethernet gadget driver will try to provide
|
||||
a second device configuration, supporting RNDIS to talk to such
|
||||
Microsoft USB hosts.
|
||||
|
||||
To make MS-Windows work with this, use Documentation/usb/linux.inf
|
||||
as the "driver info file". For versions of MS-Windows older than
|
||||
XP, you'll need to download drivers from Microsoft's website; a URL
|
||||
is given in comments found in that info file.
|
||||
|
||||
config USB_GADGETFS
|
||||
tristate "Gadget Filesystem (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
help
|
||||
This driver provides a filesystem based API that lets user mode
|
||||
programs implement a single-configuration USB device, including
|
||||
endpoint I/O and control requests that don't relate to enumeration.
|
||||
All endpoints, transfer speeds, and transfer types supported by
|
||||
the hardware are available, through read() and write() calls.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "gadgetfs".
|
||||
|
||||
config USB_FILE_STORAGE
|
||||
tristate "File-backed Storage Gadget"
|
||||
help
|
||||
The File-backed Storage Gadget acts as a USB Mass Storage
|
||||
disk drive. As its storage repository it can use a regular
|
||||
file or a block device (in much the same way as the "loop"
|
||||
device driver), specified as a module parameter.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "g_file_storage".
|
||||
|
||||
config USB_FILE_STORAGE_TEST
|
||||
bool "File-backed Storage Gadget testing version"
|
||||
depends on USB_FILE_STORAGE
|
||||
default n
|
||||
help
|
||||
Say "y" to generate the larger testing version of the
|
||||
File-backed Storage Gadget, useful for probing the
|
||||
behavior of USB Mass Storage hosts. Not needed for
|
||||
normal operation.
|
||||
|
||||
config USB_G_SERIAL
|
||||
tristate "Serial Gadget (with CDC ACM support)"
|
||||
help
|
||||
The Serial Gadget talks to the Linux-USB generic serial driver.
|
||||
This driver supports a CDC-ACM module option, which can be used
|
||||
to interoperate with MS-Windows hosts or with the Linux-USB
|
||||
"cdc-acm" driver.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module called "g_serial".
|
||||
|
||||
For more information, see Documentation/usb/gadget_serial.txt
|
||||
which includes instructions and a "driver info file" needed to
|
||||
make MS-Windows work with this driver.
|
||||
|
||||
|
||||
# put drivers that need isochronous transfer support (for audio
|
||||
# or video class gadget drivers), or specific hardware, here.
|
||||
|
||||
# - none yet
|
||||
|
||||
endchoice
|
||||
|
||||
endmenu
|
||||
30
drivers/usb/gadget/Makefile
Normal file
30
drivers/usb/gadget/Makefile
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# USB peripheral controller drivers
|
||||
#
|
||||
obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o
|
||||
obj-$(CONFIG_USB_NET2280) += net2280.o
|
||||
obj-$(CONFIG_USB_PXA2XX) += pxa2xx_udc.o
|
||||
obj-$(CONFIG_USB_GOKU) += goku_udc.o
|
||||
obj-$(CONFIG_USB_OMAP) += omap_udc.o
|
||||
obj-$(CONFIG_USB_LH7A40X) += lh7a40x_udc.o
|
||||
|
||||
#
|
||||
# USB gadget drivers
|
||||
#
|
||||
g_zero-objs := zero.o usbstring.o config.o epautoconf.o
|
||||
g_ether-objs := ether.o usbstring.o config.o epautoconf.o
|
||||
g_serial-objs := serial.o usbstring.o config.o epautoconf.o
|
||||
gadgetfs-objs := inode.o
|
||||
g_file_storage-objs := file_storage.o usbstring.o config.o \
|
||||
epautoconf.o
|
||||
|
||||
ifeq ($(CONFIG_USB_ETH_RNDIS),y)
|
||||
g_ether-objs += rndis.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_USB_ZERO) += g_zero.o
|
||||
obj-$(CONFIG_USB_ETH) += g_ether.o
|
||||
obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o
|
||||
obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o
|
||||
obj-$(CONFIG_USB_G_SERIAL) += g_serial.o
|
||||
|
||||
117
drivers/usb/gadget/config.c
Normal file
117
drivers/usb/gadget/config.c
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* usb/gadget/config.c -- simplify building config descriptors
|
||||
*
|
||||
* Copyright (C) 2003 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/usb_ch9.h>
|
||||
#include <linux/usb_gadget.h>
|
||||
|
||||
|
||||
/**
|
||||
* usb_descriptor_fillbuf - fill buffer with descriptors
|
||||
* @buf: Buffer to be filled
|
||||
* @buflen: Size of buf
|
||||
* @src: Array of descriptor pointers, terminated by null pointer.
|
||||
*
|
||||
* Copies descriptors into the buffer, returning the length or a
|
||||
* negative error code if they can't all be copied. Useful when
|
||||
* assembling descriptors for an associated set of interfaces used
|
||||
* as part of configuring a composite device; or in other cases where
|
||||
* sets of descriptors need to be marshaled.
|
||||
*/
|
||||
int
|
||||
usb_descriptor_fillbuf(void *buf, unsigned buflen,
|
||||
const struct usb_descriptor_header **src)
|
||||
{
|
||||
u8 *dest = buf;
|
||||
|
||||
if (!src)
|
||||
return -EINVAL;
|
||||
|
||||
/* fill buffer from src[] until null descriptor ptr */
|
||||
for (; 0 != *src; src++) {
|
||||
unsigned len = (*src)->bLength;
|
||||
|
||||
if (len > buflen)
|
||||
return -EINVAL;
|
||||
memcpy(dest, *src, len);
|
||||
buflen -= len;
|
||||
dest += len;
|
||||
}
|
||||
return dest - (u8 *)buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* usb_gadget_config_buf - builts a complete configuration descriptor
|
||||
* @config: Header for the descriptor, including characteristics such
|
||||
* as power requirements and number of interfaces.
|
||||
* @desc: Null-terminated vector of pointers to the descriptors (interface,
|
||||
* endpoint, etc) defining all functions in this device configuration.
|
||||
* @buf: Buffer for the resulting configuration descriptor.
|
||||
* @length: Length of buffer. If this is not big enough to hold the
|
||||
* entire configuration descriptor, an error code will be returned.
|
||||
*
|
||||
* This copies descriptors into the response buffer, building a descriptor
|
||||
* for that configuration. It returns the buffer length or a negative
|
||||
* status code. The config.wTotalLength field is set to match the length
|
||||
* of the result, but other descriptor fields (including power usage and
|
||||
* interface count) must be set by the caller.
|
||||
*
|
||||
* Gadget drivers could use this when constructing a config descriptor
|
||||
* in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the
|
||||
* resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed.
|
||||
*/
|
||||
int usb_gadget_config_buf(
|
||||
const struct usb_config_descriptor *config,
|
||||
void *buf,
|
||||
unsigned length,
|
||||
const struct usb_descriptor_header **desc
|
||||
)
|
||||
{
|
||||
struct usb_config_descriptor *cp = buf;
|
||||
int len;
|
||||
|
||||
/* config descriptor first */
|
||||
if (length < USB_DT_CONFIG_SIZE || !desc)
|
||||
return -EINVAL;
|
||||
*cp = *config;
|
||||
|
||||
/* then interface/endpoint/class/vendor/... */
|
||||
len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf,
|
||||
length - USB_DT_CONFIG_SIZE, desc);
|
||||
if (len < 0)
|
||||
return len;
|
||||
len += USB_DT_CONFIG_SIZE;
|
||||
if (len > 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
/* patch up the config descriptor */
|
||||
cp->bLength = USB_DT_CONFIG_SIZE;
|
||||
cp->bDescriptorType = USB_DT_CONFIG;
|
||||
cp->wTotalLength = cpu_to_le16(len);
|
||||
cp->bmAttributes |= USB_CONFIG_ATT_ONE;
|
||||
return len;
|
||||
}
|
||||
|
||||
1793
drivers/usb/gadget/dummy_hcd.c
Normal file
1793
drivers/usb/gadget/dummy_hcd.c
Normal file
File diff suppressed because it is too large
Load diff
310
drivers/usb/gadget/epautoconf.c
Normal file
310
drivers/usb/gadget/epautoconf.c
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* epautoconf.c -- endpoint autoconfiguration for usb gadget drivers
|
||||
*
|
||||
* Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include <linux/usb_ch9.h>
|
||||
#include <linux/usb_gadget.h>
|
||||
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/* we must assign addresses for configurable endpoints (like net2280) */
|
||||
static __initdata unsigned epnum;
|
||||
|
||||
// #define MANY_ENDPOINTS
|
||||
#ifdef MANY_ENDPOINTS
|
||||
/* more than 15 configurable endpoints */
|
||||
static __initdata unsigned in_epnum;
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This should work with endpoints from controller drivers sharing the
|
||||
* same endpoint naming convention. By example:
|
||||
*
|
||||
* - ep1, ep2, ... address is fixed, not direction or type
|
||||
* - ep1in, ep2out, ... address and direction are fixed, not type
|
||||
* - ep1-bulk, ep2-bulk, ... address and type are fixed, not direction
|
||||
* - ep1in-bulk, ep2out-iso, ... all three are fixed
|
||||
* - ep-* ... no functionality restrictions
|
||||
*
|
||||
* Type suffixes are "-bulk", "-iso", or "-int". Numbers are decimal.
|
||||
* Less common restrictions are implied by gadget_is_*().
|
||||
*
|
||||
* NOTE: each endpoint is unidirectional, as specified by its USB
|
||||
* descriptor; and isn't specific to a configuration or altsetting.
|
||||
*/
|
||||
static int __init
|
||||
ep_matches (
|
||||
struct usb_gadget *gadget,
|
||||
struct usb_ep *ep,
|
||||
struct usb_endpoint_descriptor *desc
|
||||
)
|
||||
{
|
||||
u8 type;
|
||||
const char *tmp;
|
||||
u16 max;
|
||||
|
||||
/* endpoint already claimed? */
|
||||
if (0 != ep->driver_data)
|
||||
return 0;
|
||||
|
||||
/* only support ep0 for portable CONTROL traffic */
|
||||
type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
||||
if (USB_ENDPOINT_XFER_CONTROL == type)
|
||||
return 0;
|
||||
|
||||
/* some other naming convention */
|
||||
if ('e' != ep->name[0])
|
||||
return 0;
|
||||
|
||||
/* type-restriction: "-iso", "-bulk", or "-int".
|
||||
* direction-restriction: "in", "out".
|
||||
*/
|
||||
if ('-' != ep->name[2]) {
|
||||
tmp = strrchr (ep->name, '-');
|
||||
if (tmp) {
|
||||
switch (type) {
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
/* bulk endpoints handle interrupt transfers,
|
||||
* except the toggle-quirky iso-synch kind
|
||||
*/
|
||||
if ('s' == tmp[2]) // == "-iso"
|
||||
return 0;
|
||||
/* for now, avoid PXA "interrupt-in";
|
||||
* it's documented as never using DATA1.
|
||||
*/
|
||||
if (gadget_is_pxa (gadget)
|
||||
&& 'i' == tmp [1])
|
||||
return 0;
|
||||
break;
|
||||
case USB_ENDPOINT_XFER_BULK:
|
||||
if ('b' != tmp[1]) // != "-bulk"
|
||||
return 0;
|
||||
break;
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
if ('s' != tmp[2]) // != "-iso"
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
tmp = ep->name + strlen (ep->name);
|
||||
}
|
||||
|
||||
/* direction-restriction: "..in-..", "out-.." */
|
||||
tmp--;
|
||||
if (!isdigit (*tmp)) {
|
||||
if (desc->bEndpointAddress & USB_DIR_IN) {
|
||||
if ('n' != *tmp)
|
||||
return 0;
|
||||
} else {
|
||||
if ('t' != *tmp)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* endpoint maxpacket size is an input parameter, except for bulk
|
||||
* where it's an output parameter representing the full speed limit.
|
||||
* the usb spec fixes high speed bulk maxpacket at 512 bytes.
|
||||
*/
|
||||
max = 0x7ff & le16_to_cpup (&desc->wMaxPacketSize);
|
||||
switch (type) {
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
/* INT: limit 64 bytes full speed, 1024 high speed */
|
||||
if (!gadget->is_dualspeed && max > 64)
|
||||
return 0;
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
/* ISO: limit 1023 bytes full speed, 1024 high speed */
|
||||
if (ep->maxpacket < max)
|
||||
return 0;
|
||||
if (!gadget->is_dualspeed && max > 1023)
|
||||
return 0;
|
||||
|
||||
/* BOTH: "high bandwidth" works only at high speed */
|
||||
if ((desc->wMaxPacketSize & __constant_cpu_to_le16(3<<11))) {
|
||||
if (!gadget->is_dualspeed)
|
||||
return 0;
|
||||
/* configure your hardware with enough buffering!! */
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* MATCH!! */
|
||||
|
||||
/* report address */
|
||||
if (isdigit (ep->name [2])) {
|
||||
u8 num = simple_strtol (&ep->name [2], NULL, 10);
|
||||
desc->bEndpointAddress |= num;
|
||||
#ifdef MANY_ENDPOINTS
|
||||
} else if (desc->bEndpointAddress & USB_DIR_IN) {
|
||||
if (++in_epnum > 15)
|
||||
return 0;
|
||||
desc->bEndpointAddress = USB_DIR_IN | in_epnum;
|
||||
#endif
|
||||
} else {
|
||||
if (++epnum > 15)
|
||||
return 0;
|
||||
desc->bEndpointAddress |= epnum;
|
||||
}
|
||||
|
||||
/* report (variable) full speed bulk maxpacket */
|
||||
if (USB_ENDPOINT_XFER_BULK == type) {
|
||||
int size = ep->maxpacket;
|
||||
|
||||
/* min() doesn't work on bitfields with gcc-3.5 */
|
||||
if (size > 64)
|
||||
size = 64;
|
||||
desc->wMaxPacketSize = cpu_to_le16(size);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct usb_ep * __init
|
||||
find_ep (struct usb_gadget *gadget, const char *name)
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
|
||||
list_for_each_entry (ep, &gadget->ep_list, ep_list) {
|
||||
if (0 == strcmp (ep->name, name))
|
||||
return ep;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_ep_autoconfig - choose an endpoint matching the descriptor
|
||||
* @gadget: The device to which the endpoint must belong.
|
||||
* @desc: Endpoint descriptor, with endpoint direction and transfer mode
|
||||
* initialized. For periodic transfers, the maximum packet
|
||||
* size must also be initialized. This is modified on success.
|
||||
*
|
||||
* By choosing an endpoint to use with the specified descriptor, this
|
||||
* routine simplifies writing gadget drivers that work with multiple
|
||||
* USB device controllers. The endpoint would be passed later to
|
||||
* usb_ep_enable(), along with some descriptor.
|
||||
*
|
||||
* That second descriptor won't always be the same as the first one.
|
||||
* For example, isochronous endpoints can be autoconfigured for high
|
||||
* bandwidth, and then used in several lower bandwidth altsettings.
|
||||
* Also, high and full speed descriptors will be different.
|
||||
*
|
||||
* Be sure to examine and test the results of autoconfiguration on your
|
||||
* hardware. This code may not make the best choices about how to use the
|
||||
* USB controller, and it can't know all the restrictions that may apply.
|
||||
* Some combinations of driver and hardware won't be able to autoconfigure.
|
||||
*
|
||||
* On success, this returns an un-claimed usb_ep, and modifies the endpoint
|
||||
* descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value
|
||||
* is initialized as if the endpoint were used at full speed. To prevent
|
||||
* the endpoint from being returned by a later autoconfig call, claim it
|
||||
* by assigning ep->driver_data to some non-null value.
|
||||
*
|
||||
* On failure, this returns a null endpoint descriptor.
|
||||
*/
|
||||
struct usb_ep * __init usb_ep_autoconfig (
|
||||
struct usb_gadget *gadget,
|
||||
struct usb_endpoint_descriptor *desc
|
||||
)
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
u8 type;
|
||||
|
||||
type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
||||
|
||||
/* First, apply chip-specific "best usage" knowledge.
|
||||
* This might make a good usb_gadget_ops hook ...
|
||||
*/
|
||||
if (gadget_is_net2280 (gadget) && type == USB_ENDPOINT_XFER_INT) {
|
||||
/* ep-e, ep-f are PIO with only 64 byte fifos */
|
||||
ep = find_ep (gadget, "ep-e");
|
||||
if (ep && ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
ep = find_ep (gadget, "ep-f");
|
||||
if (ep && ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
|
||||
} else if (gadget_is_goku (gadget)) {
|
||||
if (USB_ENDPOINT_XFER_INT == type) {
|
||||
/* single buffering is enough */
|
||||
ep = find_ep (gadget, "ep3-bulk");
|
||||
if (ep && ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
} else if (USB_ENDPOINT_XFER_BULK == type
|
||||
&& (USB_DIR_IN & desc->bEndpointAddress)) {
|
||||
/* DMA may be available */
|
||||
ep = find_ep (gadget, "ep2-bulk");
|
||||
if (ep && ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
}
|
||||
|
||||
} else if (gadget_is_sh (gadget) && USB_ENDPOINT_XFER_INT == type) {
|
||||
/* single buffering is enough; maybe 8 byte fifo is too */
|
||||
ep = find_ep (gadget, "ep3in-bulk");
|
||||
if (ep && ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
|
||||
} else if (gadget_is_mq11xx (gadget) && USB_ENDPOINT_XFER_INT == type) {
|
||||
ep = find_ep (gadget, "ep1-bulk");
|
||||
if (ep && ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
}
|
||||
|
||||
/* Second, look at endpoints until an unclaimed one looks usable */
|
||||
list_for_each_entry (ep, &gadget->ep_list, ep_list) {
|
||||
if (ep_matches (gadget, ep, desc))
|
||||
return ep;
|
||||
}
|
||||
|
||||
/* Fail */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_ep_autoconfig_reset - reset endpoint autoconfig state
|
||||
* @gadget: device for which autoconfig state will be reset
|
||||
*
|
||||
* Use this for devices where one configuration may need to assign
|
||||
* endpoint resources very differently from the next one. It clears
|
||||
* state such as ep->driver_data and the record of assigned endpoints
|
||||
* used by usb_ep_autoconfig().
|
||||
*/
|
||||
void __init usb_ep_autoconfig_reset (struct usb_gadget *gadget)
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
|
||||
list_for_each_entry (ep, &gadget->ep_list, ep_list) {
|
||||
ep->driver_data = NULL;
|
||||
}
|
||||
#ifdef MANY_ENDPOINTS
|
||||
in_epnum = 0;
|
||||
#endif
|
||||
epnum = 0;
|
||||
}
|
||||
|
||||
2660
drivers/usb/gadget/ether.c
Normal file
2660
drivers/usb/gadget/ether.c
Normal file
File diff suppressed because it is too large
Load diff
4139
drivers/usb/gadget/file_storage.c
Normal file
4139
drivers/usb/gadget/file_storage.c
Normal file
File diff suppressed because it is too large
Load diff
92
drivers/usb/gadget/gadget_chips.h
Normal file
92
drivers/usb/gadget/gadget_chips.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* USB device controllers have lots of quirks. Use these macros in
|
||||
* gadget drivers or other code that needs to deal with them, and which
|
||||
* autoconfigures instead of using early binding to the hardware.
|
||||
*
|
||||
* This could eventually work like the ARM mach_is_*() stuff, driven by
|
||||
* some config file that gets updated as new hardware is supported.
|
||||
*
|
||||
* NOTE: some of these controller drivers may not be available yet.
|
||||
*/
|
||||
#ifdef CONFIG_USB_GADGET_NET2280
|
||||
#define gadget_is_net2280(g) !strcmp("net2280", (g)->name)
|
||||
#else
|
||||
#define gadget_is_net2280(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_DUMMY_HCD
|
||||
#define gadget_is_dummy(g) !strcmp("dummy_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_dummy(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_PXA2XX
|
||||
#define gadget_is_pxa(g) !strcmp("pxa2xx_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_pxa(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_GOKU
|
||||
#define gadget_is_goku(g) !strcmp("goku_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_goku(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_SUPERH
|
||||
#define gadget_is_sh(g) !strcmp("sh_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_sh(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_SA1100
|
||||
#define gadget_is_sa1100(g) !strcmp("sa1100_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_sa1100(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_LH7A40X
|
||||
#define gadget_is_lh7a40x(g) !strcmp("lh7a40x_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_lh7a40x(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_MQ11XX
|
||||
#define gadget_is_mq11xx(g) !strcmp("mq11xx_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_mq11xx(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_OMAP
|
||||
#define gadget_is_omap(g) !strcmp("omap_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_omap(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_N9604
|
||||
#define gadget_is_n9604(g) !strcmp("n9604_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_n9604(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_PXA27X
|
||||
#define gadget_is_pxa27x(g) !strcmp("pxa27x_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_pxa27x(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_S3C2410
|
||||
#define gadget_is_s3c2410(g) !strcmp("s3c2410_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_s3c2410(g) 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_AT91
|
||||
#define gadget_is_at91(g) !strcmp("at91_udc", (g)->name)
|
||||
#else
|
||||
#define gadget_is_at91(g) 0
|
||||
#endif
|
||||
|
||||
// CONFIG_USB_GADGET_SX2
|
||||
// CONFIG_USB_GADGET_AU1X00
|
||||
// ...
|
||||
|
||||
1984
drivers/usb/gadget/goku_udc.c
Normal file
1984
drivers/usb/gadget/goku_udc.c
Normal file
File diff suppressed because it is too large
Load diff
290
drivers/usb/gadget/goku_udc.h
Normal file
290
drivers/usb/gadget/goku_udc.h
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* Toshiba TC86C001 ("Goku-S") USB Device Controller driver
|
||||
*
|
||||
* Copyright (C) 2000-2002 Lineo
|
||||
* by Stuart Lynne, Tom Rushworth, and Bruce Balden
|
||||
* Copyright (C) 2002 Toshiba Corporation
|
||||
* Copyright (C) 2003 MontaVista Software (source@mvista.com)
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PCI BAR 0 points to these registers.
|
||||
*/
|
||||
struct goku_udc_regs {
|
||||
/* irq management */
|
||||
u32 int_status; /* 0x000 */
|
||||
u32 int_enable;
|
||||
#define INT_SUSPEND 0x00001 /* or resume */
|
||||
#define INT_USBRESET 0x00002
|
||||
#define INT_ENDPOINT0 0x00004
|
||||
#define INT_SETUP 0x00008
|
||||
#define INT_STATUS 0x00010
|
||||
#define INT_STATUSNAK 0x00020
|
||||
#define INT_EPxDATASET(n) (0x00020 << (n)) /* 0 < n < 4 */
|
||||
# define INT_EP1DATASET 0x00040
|
||||
# define INT_EP2DATASET 0x00080
|
||||
# define INT_EP3DATASET 0x00100
|
||||
#define INT_EPnNAK(n) (0x00100 < (n)) /* 0 < n < 4 */
|
||||
# define INT_EP1NAK 0x00200
|
||||
# define INT_EP2NAK 0x00400
|
||||
# define INT_EP3NAK 0x00800
|
||||
#define INT_SOF 0x01000
|
||||
#define INT_ERR 0x02000
|
||||
#define INT_MSTWRSET 0x04000
|
||||
#define INT_MSTWREND 0x08000
|
||||
#define INT_MSTWRTMOUT 0x10000
|
||||
#define INT_MSTRDEND 0x20000
|
||||
#define INT_SYSERROR 0x40000
|
||||
#define INT_PWRDETECT 0x80000
|
||||
|
||||
#define INT_DEVWIDE (INT_PWRDETECT|INT_SYSERROR/*|INT_ERR*/|INT_USBRESET|INT_SUSPEND)
|
||||
#define INT_EP0 (INT_SETUP|INT_ENDPOINT0/*|INT_STATUS*/|INT_STATUSNAK)
|
||||
|
||||
u32 dma_master;
|
||||
#define MST_EOPB_DIS 0x0800
|
||||
#define MST_EOPB_ENA 0x0400
|
||||
#define MST_TIMEOUT_DIS 0x0200
|
||||
#define MST_TIMEOUT_ENA 0x0100
|
||||
#define MST_RD_EOPB 0x0080 /* write-only */
|
||||
#define MST_RD_RESET 0x0040
|
||||
#define MST_WR_RESET 0x0020
|
||||
#define MST_RD_ENA 0x0004 /* 1:start, 0:ignore */
|
||||
#define MST_WR_ENA 0x0002 /* 1:start, 0:ignore */
|
||||
#define MST_CONNECTION 0x0001 /* 0 for ep1out/ep2in */
|
||||
|
||||
#define MST_R_BITS (MST_EOPB_DIS|MST_EOPB_ENA \
|
||||
|MST_RD_ENA|MST_RD_RESET)
|
||||
#define MST_W_BITS (MST_TIMEOUT_DIS|MST_TIMEOUT_ENA \
|
||||
|MST_WR_ENA|MST_WR_RESET)
|
||||
#define MST_RW_BITS (MST_R_BITS|MST_W_BITS \
|
||||
|MST_CONNECTION)
|
||||
|
||||
/* these values assume (dma_master & MST_CONNECTION) == 0 */
|
||||
#define UDC_MSTWR_ENDPOINT 1
|
||||
#define UDC_MSTRD_ENDPOINT 2
|
||||
|
||||
/* dma master write */
|
||||
u32 out_dma_start;
|
||||
u32 out_dma_end;
|
||||
u32 out_dma_current;
|
||||
|
||||
/* dma master read */
|
||||
u32 in_dma_start;
|
||||
u32 in_dma_end;
|
||||
u32 in_dma_current;
|
||||
|
||||
u32 power_detect;
|
||||
#define PW_DETECT 0x04
|
||||
#define PW_RESETB 0x02
|
||||
#define PW_PULLUP 0x01
|
||||
|
||||
u8 _reserved0 [0x1d8];
|
||||
|
||||
/* endpoint registers */
|
||||
u32 ep_fifo [4]; /* 0x200 */
|
||||
u8 _reserved1 [0x10];
|
||||
u32 ep_mode [4]; /* only 1-3 valid */
|
||||
u8 _reserved2 [0x10];
|
||||
|
||||
u32 ep_status [4];
|
||||
#define EPxSTATUS_TOGGLE 0x40
|
||||
#define EPxSTATUS_SUSPEND 0x20
|
||||
#define EPxSTATUS_EP_MASK (0x07<<2)
|
||||
# define EPxSTATUS_EP_READY (0<<2)
|
||||
# define EPxSTATUS_EP_DATAIN (1<<2)
|
||||
# define EPxSTATUS_EP_FULL (2<<2)
|
||||
# define EPxSTATUS_EP_TX_ERR (3<<2)
|
||||
# define EPxSTATUS_EP_RX_ERR (4<<2)
|
||||
# define EPxSTATUS_EP_BUSY (5<<2)
|
||||
# define EPxSTATUS_EP_STALL (6<<2)
|
||||
# define EPxSTATUS_EP_INVALID (7<<2)
|
||||
#define EPxSTATUS_FIFO_DISABLE 0x02
|
||||
#define EPxSTATUS_STAGE_ERROR 0x01
|
||||
|
||||
u8 _reserved3 [0x10];
|
||||
u32 EPxSizeLA[4];
|
||||
#define PACKET_ACTIVE (1<<7)
|
||||
#define DATASIZE 0x7f
|
||||
u8 _reserved3a [0x10];
|
||||
u32 EPxSizeLB[4]; /* only 1,2 valid */
|
||||
u8 _reserved3b [0x10];
|
||||
u32 EPxSizeHA[4]; /* only 1-3 valid */
|
||||
u8 _reserved3c [0x10];
|
||||
u32 EPxSizeHB[4]; /* only 1,2 valid */
|
||||
u8 _reserved4[0x30];
|
||||
|
||||
/* SETUP packet contents */
|
||||
u32 bRequestType; /* 0x300 */
|
||||
u32 bRequest;
|
||||
u32 wValueL;
|
||||
u32 wValueH;
|
||||
u32 wIndexL;
|
||||
u32 wIndexH;
|
||||
u32 wLengthL;
|
||||
u32 wLengthH;
|
||||
|
||||
/* command interaction/handshaking */
|
||||
u32 SetupRecv; /* 0x320 */
|
||||
u32 CurrConfig;
|
||||
u32 StdRequest;
|
||||
u32 Request;
|
||||
u32 DataSet;
|
||||
#define DATASET_A(epnum) (1<<(2*(epnum)))
|
||||
#define DATASET_B(epnum) (2<<(2*(epnum)))
|
||||
#define DATASET_AB(epnum) (3<<(2*(epnum)))
|
||||
u8 _reserved5[4];
|
||||
|
||||
u32 UsbState;
|
||||
#define USBSTATE_CONFIGURED 0x04
|
||||
#define USBSTATE_ADDRESSED 0x02
|
||||
#define USBSTATE_DEFAULT 0x01
|
||||
|
||||
u32 EOP;
|
||||
|
||||
u32 Command; /* 0x340 */
|
||||
#define COMMAND_SETDATA0 2
|
||||
#define COMMAND_RESET 3
|
||||
#define COMMAND_STALL 4
|
||||
#define COMMAND_INVALID 5
|
||||
#define COMMAND_FIFO_DISABLE 7
|
||||
#define COMMAND_FIFO_ENABLE 8
|
||||
#define COMMAND_INIT_DESCRIPTOR 9
|
||||
#define COMMAND_FIFO_CLEAR 10 /* also stall */
|
||||
#define COMMAND_STALL_CLEAR 11
|
||||
#define COMMAND_EP(n) ((n) << 4)
|
||||
|
||||
u32 EPxSingle;
|
||||
u8 _reserved6[4];
|
||||
u32 EPxBCS;
|
||||
u8 _reserved7[8];
|
||||
u32 IntControl;
|
||||
#define ICONTROL_STATUSNAK 1
|
||||
u8 _reserved8[4];
|
||||
|
||||
u32 reqmode; // 0x360 standard request mode, low 8 bits
|
||||
#define G_REQMODE_SET_INTF (1<<7)
|
||||
#define G_REQMODE_GET_INTF (1<<6)
|
||||
#define G_REQMODE_SET_CONF (1<<5)
|
||||
#define G_REQMODE_GET_CONF (1<<4)
|
||||
#define G_REQMODE_GET_DESC (1<<3)
|
||||
#define G_REQMODE_SET_FEAT (1<<2)
|
||||
#define G_REQMODE_CLEAR_FEAT (1<<1)
|
||||
#define G_REQMODE_GET_STATUS (1<<0)
|
||||
|
||||
u32 ReqMode;
|
||||
u8 _reserved9[0x18];
|
||||
u32 PortStatus; /* 0x380 */
|
||||
u8 _reserved10[8];
|
||||
u32 address;
|
||||
u32 buff_test;
|
||||
u8 _reserved11[4];
|
||||
u32 UsbReady;
|
||||
u8 _reserved12[4];
|
||||
u32 SetDescStall; /* 0x3a0 */
|
||||
u8 _reserved13[0x45c];
|
||||
|
||||
/* hardware could handle limited GET_DESCRIPTOR duties */
|
||||
#define DESC_LEN 0x80
|
||||
u32 descriptors[DESC_LEN]; /* 0x800 */
|
||||
u8 _reserved14[0x600];
|
||||
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#define MAX_FIFO_SIZE 64
|
||||
#define MAX_EP0_SIZE 8 /* ep0 fifo is bigger, though */
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* DRIVER DATA STRUCTURES and UTILITIES */
|
||||
|
||||
struct goku_ep {
|
||||
struct usb_ep ep;
|
||||
struct goku_udc *dev;
|
||||
unsigned long irqs;
|
||||
|
||||
unsigned num:8,
|
||||
dma:1,
|
||||
is_in:1,
|
||||
stopped:1;
|
||||
|
||||
/* analogous to a host-side qh */
|
||||
struct list_head queue;
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
|
||||
u32 __iomem *reg_fifo;
|
||||
u32 __iomem *reg_mode;
|
||||
u32 __iomem *reg_status;
|
||||
};
|
||||
|
||||
struct goku_request {
|
||||
struct usb_request req;
|
||||
struct list_head queue;
|
||||
|
||||
unsigned mapped:1;
|
||||
};
|
||||
|
||||
enum ep0state {
|
||||
EP0_DISCONNECT, /* no host */
|
||||
EP0_IDLE, /* between STATUS ack and SETUP report */
|
||||
EP0_IN, EP0_OUT, /* data stage */
|
||||
EP0_STATUS, /* status stage */
|
||||
EP0_STALL, /* data or status stages */
|
||||
EP0_SUSPEND, /* usb suspend */
|
||||
};
|
||||
|
||||
struct goku_udc {
|
||||
/* each pci device provides one gadget, several endpoints */
|
||||
struct usb_gadget gadget;
|
||||
spinlock_t lock;
|
||||
struct goku_ep ep[4];
|
||||
struct usb_gadget_driver *driver;
|
||||
|
||||
enum ep0state ep0state;
|
||||
unsigned got_irq:1,
|
||||
got_region:1,
|
||||
req_config:1,
|
||||
configured:1,
|
||||
enabled:1;
|
||||
|
||||
/* pci state used to access those endpoints */
|
||||
struct pci_dev *pdev;
|
||||
struct goku_udc_regs __iomem *regs;
|
||||
u32 int_enable;
|
||||
|
||||
/* statistics... */
|
||||
unsigned long irqs;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define xprintk(dev,level,fmt,args...) \
|
||||
printk(level "%s %s: " fmt , driver_name , \
|
||||
pci_name(dev->pdev) , ## args)
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_DEBUG , fmt , ## args)
|
||||
#else
|
||||
#define DBG(dev,fmt,args...) \
|
||||
do { } while (0)
|
||||
#endif /* DEBUG */
|
||||
|
||||
#ifdef VERBOSE
|
||||
#define VDBG DBG
|
||||
#else
|
||||
#define VDBG(dev,fmt,args...) \
|
||||
do { } while (0)
|
||||
#endif /* VERBOSE */
|
||||
|
||||
#define ERROR(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_ERR , fmt , ## args)
|
||||
#define WARN(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_WARNING , fmt , ## args)
|
||||
#define INFO(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_INFO , fmt , ## args)
|
||||
|
||||
2110
drivers/usb/gadget/inode.c
Normal file
2110
drivers/usb/gadget/inode.c
Normal file
File diff suppressed because it is too large
Load diff
2167
drivers/usb/gadget/lh7a40x_udc.c
Normal file
2167
drivers/usb/gadget/lh7a40x_udc.c
Normal file
File diff suppressed because it is too large
Load diff
261
drivers/usb/gadget/lh7a40x_udc.h
Normal file
261
drivers/usb/gadget/lh7a40x_udc.h
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* linux/drivers/usb/gadget/lh7a40x_udc.h
|
||||
* Sharp LH7A40x on-chip full speed USB device controllers
|
||||
*
|
||||
* Copyright (C) 2004 Mikko Lahteenmaki, Nordic ID
|
||||
* Copyright (C) 2004 Bo Henriksen, Nordic ID
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LH7A40X_H_
|
||||
#define __LH7A40X_H_
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/dma.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <asm/hardware.h>
|
||||
|
||||
#include <linux/usb_ch9.h>
|
||||
#include <linux/usb_gadget.h>
|
||||
|
||||
/*
|
||||
* Memory map
|
||||
*/
|
||||
|
||||
#define USB_FA 0x80000200 // function address register
|
||||
#define USB_PM 0x80000204 // power management register
|
||||
|
||||
#define USB_IN_INT 0x80000208 // IN interrupt register bank (EP0-EP3)
|
||||
#define USB_OUT_INT 0x80000210 // OUT interrupt register bank (EP2)
|
||||
#define USB_INT 0x80000218 // interrupt register bank
|
||||
|
||||
#define USB_IN_INT_EN 0x8000021C // IN interrupt enable register bank
|
||||
#define USB_OUT_INT_EN 0x80000224 // OUT interrupt enable register bank
|
||||
#define USB_INT_EN 0x8000022C // USB interrupt enable register bank
|
||||
|
||||
#define USB_FRM_NUM1 0x80000230 // Frame number1 register
|
||||
#define USB_FRM_NUM2 0x80000234 // Frame number2 register
|
||||
#define USB_INDEX 0x80000238 // index register
|
||||
|
||||
#define USB_IN_MAXP 0x80000240 // IN MAXP register
|
||||
#define USB_IN_CSR1 0x80000244 // IN CSR1 register/EP0 CSR register
|
||||
#define USB_EP0_CSR 0x80000244 // IN CSR1 register/EP0 CSR register
|
||||
#define USB_IN_CSR2 0x80000248 // IN CSR2 register
|
||||
#define USB_OUT_MAXP 0x8000024C // OUT MAXP register
|
||||
|
||||
#define USB_OUT_CSR1 0x80000250 // OUT CSR1 register
|
||||
#define USB_OUT_CSR2 0x80000254 // OUT CSR2 register
|
||||
#define USB_OUT_FIFO_WC1 0x80000258 // OUT FIFO write count1 register
|
||||
#define USB_OUT_FIFO_WC2 0x8000025C // OUT FIFO write count2 register
|
||||
|
||||
#define USB_RESET 0x8000044C // USB reset register
|
||||
|
||||
#define USB_EP0_FIFO 0x80000280
|
||||
#define USB_EP1_FIFO 0x80000284
|
||||
#define USB_EP2_FIFO 0x80000288
|
||||
#define USB_EP3_FIFO 0x8000028c
|
||||
|
||||
/*
|
||||
* USB reset register
|
||||
*/
|
||||
#define USB_RESET_APB (1<<1) //resets USB APB control side WRITE
|
||||
#define USB_RESET_IO (1<<0) //resets USB IO side WRITE
|
||||
|
||||
/*
|
||||
* USB function address register
|
||||
*/
|
||||
#define USB_FA_ADDR_UPDATE (1<<7)
|
||||
#define USB_FA_FUNCTION_ADDR (0x7F)
|
||||
|
||||
/*
|
||||
* Power Management register
|
||||
*/
|
||||
#define PM_USB_DCP (1<<5)
|
||||
#define PM_USB_ENABLE (1<<4)
|
||||
#define PM_USB_RESET (1<<3)
|
||||
#define PM_UC_RESUME (1<<2)
|
||||
#define PM_SUSPEND_MODE (1<<1)
|
||||
#define PM_ENABLE_SUSPEND (1<<0)
|
||||
|
||||
/*
|
||||
* IN interrupt register
|
||||
*/
|
||||
#define USB_IN_INT_EP3 (1<<3)
|
||||
#define USB_IN_INT_EP1 (1<<1)
|
||||
#define USB_IN_INT_EP0 (1<<0)
|
||||
|
||||
/*
|
||||
* OUT interrupt register
|
||||
*/
|
||||
#define USB_OUT_INT_EP2 (1<<2)
|
||||
|
||||
/*
|
||||
* USB interrupt register
|
||||
*/
|
||||
#define USB_INT_RESET_INT (1<<2)
|
||||
#define USB_INT_RESUME_INT (1<<1)
|
||||
#define USB_INT_SUSPEND_INT (1<<0)
|
||||
|
||||
/*
|
||||
* USB interrupt enable register
|
||||
*/
|
||||
#define USB_INT_EN_USB_RESET_INTER (1<<2)
|
||||
#define USB_INT_EN_RESUME_INTER (1<<1)
|
||||
#define USB_INT_EN_SUSPEND_INTER (1<<0)
|
||||
|
||||
/*
|
||||
* INCSR1 register
|
||||
*/
|
||||
#define USB_IN_CSR1_CLR_DATA_TOGGLE (1<<6)
|
||||
#define USB_IN_CSR1_SENT_STALL (1<<5)
|
||||
#define USB_IN_CSR1_SEND_STALL (1<<4)
|
||||
#define USB_IN_CSR1_FIFO_FLUSH (1<<3)
|
||||
#define USB_IN_CSR1_FIFO_NOT_EMPTY (1<<1)
|
||||
#define USB_IN_CSR1_IN_PKT_RDY (1<<0)
|
||||
|
||||
/*
|
||||
* INCSR2 register
|
||||
*/
|
||||
#define USB_IN_CSR2_AUTO_SET (1<<7)
|
||||
#define USB_IN_CSR2_USB_DMA_EN (1<<4)
|
||||
|
||||
/*
|
||||
* OUT CSR1 register
|
||||
*/
|
||||
#define USB_OUT_CSR1_CLR_DATA_REG (1<<7)
|
||||
#define USB_OUT_CSR1_SENT_STALL (1<<6)
|
||||
#define USB_OUT_CSR1_SEND_STALL (1<<5)
|
||||
#define USB_OUT_CSR1_FIFO_FLUSH (1<<4)
|
||||
#define USB_OUT_CSR1_FIFO_FULL (1<<1)
|
||||
#define USB_OUT_CSR1_OUT_PKT_RDY (1<<0)
|
||||
|
||||
/*
|
||||
* OUT CSR2 register
|
||||
*/
|
||||
#define USB_OUT_CSR2_AUTO_CLR (1<<7)
|
||||
#define USB_OUT_CSR2_USB_DMA_EN (1<<4)
|
||||
|
||||
/*
|
||||
* EP0 CSR
|
||||
*/
|
||||
#define EP0_CLR_SETUP_END (1<<7) /* Clear "Setup Ends" Bit (w) */
|
||||
#define EP0_CLR_OUT (1<<6) /* Clear "Out packet ready" Bit (w) */
|
||||
#define EP0_SEND_STALL (1<<5) /* Send STALL Handshake (rw) */
|
||||
#define EP0_SETUP_END (1<<4) /* Setup Ends (r) */
|
||||
|
||||
#define EP0_DATA_END (1<<3) /* Data end (rw) */
|
||||
#define EP0_SENT_STALL (1<<2) /* Sent Stall Handshake (r) */
|
||||
#define EP0_IN_PKT_RDY (1<<1) /* In packet ready (rw) */
|
||||
#define EP0_OUT_PKT_RDY (1<<0) /* Out packet ready (r) */
|
||||
|
||||
/* general CSR */
|
||||
#define OUT_PKT_RDY (1<<0)
|
||||
#define IN_PKT_RDY (1<<0)
|
||||
|
||||
/*
|
||||
* IN/OUT MAXP register
|
||||
*/
|
||||
#define USB_OUT_MAXP_MAXP (0xF)
|
||||
#define USB_IN_MAXP_MAXP (0xF)
|
||||
|
||||
// Max packet size
|
||||
//#define EP0_PACKETSIZE 0x10
|
||||
#define EP0_PACKETSIZE 0x8
|
||||
#define EP0_MAXPACKETSIZE 0x10
|
||||
|
||||
#define UDC_MAX_ENDPOINTS 4
|
||||
|
||||
#define WAIT_FOR_SETUP 0
|
||||
#define DATA_STATE_XMIT 1
|
||||
#define DATA_STATE_NEED_ZLP 2
|
||||
#define WAIT_FOR_OUT_STATUS 3
|
||||
#define DATA_STATE_RECV 4
|
||||
|
||||
/* ********************************************************************************************* */
|
||||
/* IO
|
||||
*/
|
||||
|
||||
typedef enum ep_type {
|
||||
ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt
|
||||
} ep_type_t;
|
||||
|
||||
struct lh7a40x_ep {
|
||||
struct usb_ep ep;
|
||||
struct lh7a40x_udc *dev;
|
||||
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
struct list_head queue;
|
||||
unsigned long pio_irqs;
|
||||
|
||||
u8 stopped;
|
||||
u8 bEndpointAddress;
|
||||
u8 bmAttributes;
|
||||
|
||||
ep_type_t ep_type;
|
||||
u32 fifo;
|
||||
u32 csr1;
|
||||
u32 csr2;
|
||||
};
|
||||
|
||||
struct lh7a40x_request {
|
||||
struct usb_request req;
|
||||
struct list_head queue;
|
||||
};
|
||||
|
||||
struct lh7a40x_udc {
|
||||
struct usb_gadget gadget;
|
||||
struct usb_gadget_driver *driver;
|
||||
struct device *dev;
|
||||
spinlock_t lock;
|
||||
|
||||
int ep0state;
|
||||
struct lh7a40x_ep ep[UDC_MAX_ENDPOINTS];
|
||||
|
||||
unsigned char usb_address;
|
||||
|
||||
unsigned req_pending:1, req_std:1, req_config:1;
|
||||
};
|
||||
|
||||
extern struct lh7a40x_udc *the_controller;
|
||||
|
||||
#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN)==USB_DIR_IN)
|
||||
#define ep_index(EP) ((EP)->bEndpointAddress&0xF)
|
||||
#define ep_maxpacket(EP) ((EP)->ep.maxpacket)
|
||||
|
||||
#endif
|
||||
217
drivers/usb/gadget/ndis.h
Normal file
217
drivers/usb/gadget/ndis.h
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* ndis.h
|
||||
*
|
||||
* ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de>
|
||||
*
|
||||
* Thanks to the cygwin development team,
|
||||
* espacially to Casper S. Hornstrup <chorns@users.sourceforge.net>
|
||||
*
|
||||
* THIS SOFTWARE IS NOT COPYRIGHTED
|
||||
*
|
||||
* This source code is offered for use in the public domain. You may
|
||||
* use, modify or distribute it freely.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful but
|
||||
* WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
|
||||
* DISCLAIMED. This includes but is not limited to warranties of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_NDIS_H
|
||||
#define _LINUX_NDIS_H
|
||||
|
||||
|
||||
#define NDIS_STATUS_MULTICAST_FULL 0xC0010009
|
||||
#define NDIS_STATUS_MULTICAST_EXISTS 0xC001000A
|
||||
#define NDIS_STATUS_MULTICAST_NOT_FOUND 0xC001000B
|
||||
|
||||
enum NDIS_DEVICE_POWER_STATE {
|
||||
NdisDeviceStateUnspecified = 0,
|
||||
NdisDeviceStateD0,
|
||||
NdisDeviceStateD1,
|
||||
NdisDeviceStateD2,
|
||||
NdisDeviceStateD3,
|
||||
NdisDeviceStateMaximum
|
||||
};
|
||||
|
||||
struct NDIS_PM_WAKE_UP_CAPABILITIES {
|
||||
enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp;
|
||||
enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp;
|
||||
enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp;
|
||||
};
|
||||
|
||||
/* NDIS_PNP_CAPABILITIES.Flags constants */
|
||||
#define NDIS_DEVICE_WAKE_UP_ENABLE 0x00000001
|
||||
#define NDIS_DEVICE_WAKE_ON_PATTERN_MATCH_ENABLE 0x00000002
|
||||
#define NDIS_DEVICE_WAKE_ON_MAGIC_PACKET_ENABLE 0x00000004
|
||||
|
||||
struct NDIS_PNP_CAPABILITIES {
|
||||
u32 Flags;
|
||||
struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities;
|
||||
};
|
||||
|
||||
struct NDIS_PM_PACKET_PATTERN {
|
||||
u32 Priority;
|
||||
u32 Reserved;
|
||||
u32 MaskSize;
|
||||
u32 PatternOffset;
|
||||
u32 PatternSize;
|
||||
u32 PatternFlags;
|
||||
};
|
||||
|
||||
|
||||
/* Required Object IDs (OIDs) */
|
||||
#define OID_GEN_SUPPORTED_LIST 0x00010101
|
||||
#define OID_GEN_HARDWARE_STATUS 0x00010102
|
||||
#define OID_GEN_MEDIA_SUPPORTED 0x00010103
|
||||
#define OID_GEN_MEDIA_IN_USE 0x00010104
|
||||
#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105
|
||||
#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106
|
||||
#define OID_GEN_LINK_SPEED 0x00010107
|
||||
#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108
|
||||
#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109
|
||||
#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A
|
||||
#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B
|
||||
#define OID_GEN_VENDOR_ID 0x0001010C
|
||||
#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D
|
||||
#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E
|
||||
#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F
|
||||
#define OID_GEN_DRIVER_VERSION 0x00010110
|
||||
#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111
|
||||
#define OID_GEN_PROTOCOL_OPTIONS 0x00010112
|
||||
#define OID_GEN_MAC_OPTIONS 0x00010113
|
||||
#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114
|
||||
#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115
|
||||
#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116
|
||||
#define OID_GEN_SUPPORTED_GUIDS 0x00010117
|
||||
#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118
|
||||
#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119
|
||||
#define OID_GEN_MACHINE_NAME 0x0001021A
|
||||
#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B
|
||||
#define OID_GEN_VLAN_ID 0x0001021C
|
||||
|
||||
/* Optional OIDs */
|
||||
#define OID_GEN_MEDIA_CAPABILITIES 0x00010201
|
||||
#define OID_GEN_PHYSICAL_MEDIUM 0x00010202
|
||||
|
||||
/* Required statistics OIDs */
|
||||
#define OID_GEN_XMIT_OK 0x00020101
|
||||
#define OID_GEN_RCV_OK 0x00020102
|
||||
#define OID_GEN_XMIT_ERROR 0x00020103
|
||||
#define OID_GEN_RCV_ERROR 0x00020104
|
||||
#define OID_GEN_RCV_NO_BUFFER 0x00020105
|
||||
|
||||
/* Optional statistics OIDs */
|
||||
#define OID_GEN_DIRECTED_BYTES_XMIT 0x00020201
|
||||
#define OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202
|
||||
#define OID_GEN_MULTICAST_BYTES_XMIT 0x00020203
|
||||
#define OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204
|
||||
#define OID_GEN_BROADCAST_BYTES_XMIT 0x00020205
|
||||
#define OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206
|
||||
#define OID_GEN_DIRECTED_BYTES_RCV 0x00020207
|
||||
#define OID_GEN_DIRECTED_FRAMES_RCV 0x00020208
|
||||
#define OID_GEN_MULTICAST_BYTES_RCV 0x00020209
|
||||
#define OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A
|
||||
#define OID_GEN_BROADCAST_BYTES_RCV 0x0002020B
|
||||
#define OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C
|
||||
#define OID_GEN_RCV_CRC_ERROR 0x0002020D
|
||||
#define OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E
|
||||
#define OID_GEN_GET_TIME_CAPS 0x0002020F
|
||||
#define OID_GEN_GET_NETCARD_TIME 0x00020210
|
||||
#define OID_GEN_NETCARD_LOAD 0x00020211
|
||||
#define OID_GEN_DEVICE_PROFILE 0x00020212
|
||||
#define OID_GEN_INIT_TIME_MS 0x00020213
|
||||
#define OID_GEN_RESET_COUNTS 0x00020214
|
||||
#define OID_GEN_MEDIA_SENSE_COUNTS 0x00020215
|
||||
#define OID_GEN_FRIENDLY_NAME 0x00020216
|
||||
#define OID_GEN_MINIPORT_INFO 0x00020217
|
||||
#define OID_GEN_RESET_VERIFY_PARAMETERS 0x00020218
|
||||
|
||||
/* IEEE 802.3 (Ethernet) OIDs */
|
||||
#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001
|
||||
|
||||
#define OID_802_3_PERMANENT_ADDRESS 0x01010101
|
||||
#define OID_802_3_CURRENT_ADDRESS 0x01010102
|
||||
#define OID_802_3_MULTICAST_LIST 0x01010103
|
||||
#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104
|
||||
#define OID_802_3_MAC_OPTIONS 0x01010105
|
||||
#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101
|
||||
#define OID_802_3_XMIT_ONE_COLLISION 0x01020102
|
||||
#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103
|
||||
#define OID_802_3_XMIT_DEFERRED 0x01020201
|
||||
#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202
|
||||
#define OID_802_3_RCV_OVERRUN 0x01020203
|
||||
#define OID_802_3_XMIT_UNDERRUN 0x01020204
|
||||
#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205
|
||||
#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206
|
||||
#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207
|
||||
|
||||
/* OID_GEN_MINIPORT_INFO constants */
|
||||
#define NDIS_MINIPORT_BUS_MASTER 0x00000001
|
||||
#define NDIS_MINIPORT_WDM_DRIVER 0x00000002
|
||||
#define NDIS_MINIPORT_SG_LIST 0x00000004
|
||||
#define NDIS_MINIPORT_SUPPORTS_MEDIA_QUERY 0x00000008
|
||||
#define NDIS_MINIPORT_INDICATES_PACKETS 0x00000010
|
||||
#define NDIS_MINIPORT_IGNORE_PACKET_QUEUE 0x00000020
|
||||
#define NDIS_MINIPORT_IGNORE_REQUEST_QUEUE 0x00000040
|
||||
#define NDIS_MINIPORT_IGNORE_TOKEN_RING_ERRORS 0x00000080
|
||||
#define NDIS_MINIPORT_INTERMEDIATE_DRIVER 0x00000100
|
||||
#define NDIS_MINIPORT_IS_NDIS_5 0x00000200
|
||||
#define NDIS_MINIPORT_IS_CO 0x00000400
|
||||
#define NDIS_MINIPORT_DESERIALIZE 0x00000800
|
||||
#define NDIS_MINIPORT_REQUIRES_MEDIA_POLLING 0x00001000
|
||||
#define NDIS_MINIPORT_SUPPORTS_MEDIA_SENSE 0x00002000
|
||||
#define NDIS_MINIPORT_NETBOOT_CARD 0x00004000
|
||||
#define NDIS_MINIPORT_PM_SUPPORTED 0x00008000
|
||||
#define NDIS_MINIPORT_SUPPORTS_MAC_ADDRESS_OVERWRITE 0x00010000
|
||||
#define NDIS_MINIPORT_USES_SAFE_BUFFER_APIS 0x00020000
|
||||
#define NDIS_MINIPORT_HIDDEN 0x00040000
|
||||
#define NDIS_MINIPORT_SWENUM 0x00080000
|
||||
#define NDIS_MINIPORT_SURPRISE_REMOVE_OK 0x00100000
|
||||
#define NDIS_MINIPORT_NO_HALT_ON_SUSPEND 0x00200000
|
||||
#define NDIS_MINIPORT_HARDWARE_DEVICE 0x00400000
|
||||
#define NDIS_MINIPORT_SUPPORTS_CANCEL_SEND_PACKETS 0x00800000
|
||||
#define NDIS_MINIPORT_64BITS_DMA 0x01000000
|
||||
|
||||
#define NDIS_MEDIUM_802_3 0x00000000
|
||||
#define NDIS_MEDIUM_802_5 0x00000001
|
||||
#define NDIS_MEDIUM_FDDI 0x00000002
|
||||
#define NDIS_MEDIUM_WAN 0x00000003
|
||||
#define NDIS_MEDIUM_LOCAL_TALK 0x00000004
|
||||
#define NDIS_MEDIUM_DIX 0x00000005
|
||||
#define NDIS_MEDIUM_ARCENT_RAW 0x00000006
|
||||
#define NDIS_MEDIUM_ARCENT_878_2 0x00000007
|
||||
#define NDIS_MEDIUM_ATM 0x00000008
|
||||
#define NDIS_MEDIUM_WIRELESS_LAN 0x00000009
|
||||
#define NDIS_MEDIUM_IRDA 0x0000000A
|
||||
#define NDIS_MEDIUM_BPC 0x0000000B
|
||||
#define NDIS_MEDIUM_CO_WAN 0x0000000C
|
||||
#define NDIS_MEDIUM_1394 0x0000000D
|
||||
|
||||
#define NDIS_PACKET_TYPE_DIRECTED 0x00000001
|
||||
#define NDIS_PACKET_TYPE_MULTICAST 0x00000002
|
||||
#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004
|
||||
#define NDIS_PACKET_TYPE_BROADCAST 0x00000008
|
||||
#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010
|
||||
#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020
|
||||
#define NDIS_PACKET_TYPE_SMT 0x00000040
|
||||
#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080
|
||||
#define NDIS_PACKET_TYPE_GROUP 0x00000100
|
||||
#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200
|
||||
#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400
|
||||
#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800
|
||||
|
||||
#define NDIS_MEDIA_STATE_CONNECTED 0x00000000
|
||||
#define NDIS_MEDIA_STATE_DISCONNECTED 0x00000001
|
||||
|
||||
#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA 0x00000001
|
||||
#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED 0x00000002
|
||||
#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND 0x00000004
|
||||
#define NDIS_MAC_OPTION_NO_LOOPBACK 0x00000008
|
||||
#define NDIS_MAC_OPTION_FULL_DUPLEX 0x00000010
|
||||
#define NDIS_MAC_OPTION_EOTX_INDICATION 0x00000020
|
||||
#define NDIS_MAC_OPTION_8021P_PRIORITY 0x00000040
|
||||
#define NDIS_MAC_OPTION_RESERVED 0x80000000
|
||||
|
||||
#endif /* _LINUX_NDIS_H */
|
||||
2967
drivers/usb/gadget/net2280.c
Normal file
2967
drivers/usb/gadget/net2280.c
Normal file
File diff suppressed because it is too large
Load diff
728
drivers/usb/gadget/net2280.h
Normal file
728
drivers/usb/gadget/net2280.h
Normal file
|
|
@ -0,0 +1,728 @@
|
|||
/*
|
||||
* NetChip 2280 high/full speed USB device controller.
|
||||
* Unlike many such controllers, this one talks PCI.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2002 NetChip Technology, Inc. (http://www.netchip.com)
|
||||
* Copyright (C) 2003 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* NET2280 MEMORY MAPPED REGISTERS
|
||||
*
|
||||
* The register layout came from the chip documentation, and the bit
|
||||
* number definitions were extracted from chip specification.
|
||||
*
|
||||
* Use the shift operator ('<<') to build bit masks, with readl/writel
|
||||
* to access the registers through PCI.
|
||||
*/
|
||||
|
||||
/* main registers, BAR0 + 0x0000 */
|
||||
struct net2280_regs {
|
||||
// offset 0x0000
|
||||
u32 devinit;
|
||||
#define LOCAL_CLOCK_FREQUENCY 8
|
||||
#define FORCE_PCI_RESET 7
|
||||
#define PCI_ID 6
|
||||
#define PCI_ENABLE 5
|
||||
#define FIFO_SOFT_RESET 4
|
||||
#define CFG_SOFT_RESET 3
|
||||
#define PCI_SOFT_RESET 2
|
||||
#define USB_SOFT_RESET 1
|
||||
#define M8051_RESET 0
|
||||
u32 eectl;
|
||||
#define EEPROM_ADDRESS_WIDTH 23
|
||||
#define EEPROM_CHIP_SELECT_ACTIVE 22
|
||||
#define EEPROM_PRESENT 21
|
||||
#define EEPROM_VALID 20
|
||||
#define EEPROM_BUSY 19
|
||||
#define EEPROM_CHIP_SELECT_ENABLE 18
|
||||
#define EEPROM_BYTE_READ_START 17
|
||||
#define EEPROM_BYTE_WRITE_START 16
|
||||
#define EEPROM_READ_DATA 8
|
||||
#define EEPROM_WRITE_DATA 0
|
||||
u32 eeclkfreq;
|
||||
u32 _unused0;
|
||||
// offset 0x0010
|
||||
|
||||
u32 pciirqenb0; /* interrupt PCI master ... */
|
||||
#define SETUP_PACKET_INTERRUPT_ENABLE 7
|
||||
#define ENDPOINT_F_INTERRUPT_ENABLE 6
|
||||
#define ENDPOINT_E_INTERRUPT_ENABLE 5
|
||||
#define ENDPOINT_D_INTERRUPT_ENABLE 4
|
||||
#define ENDPOINT_C_INTERRUPT_ENABLE 3
|
||||
#define ENDPOINT_B_INTERRUPT_ENABLE 2
|
||||
#define ENDPOINT_A_INTERRUPT_ENABLE 1
|
||||
#define ENDPOINT_0_INTERRUPT_ENABLE 0
|
||||
u32 pciirqenb1;
|
||||
#define PCI_INTERRUPT_ENABLE 31
|
||||
#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27
|
||||
#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26
|
||||
#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25
|
||||
#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20
|
||||
#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19
|
||||
#define PCI_TARGET_ABORT_ASSERTED_INTERRUPT_ENABLE 18
|
||||
#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17
|
||||
#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16
|
||||
#define GPIO_INTERRUPT_ENABLE 13
|
||||
#define DMA_D_INTERRUPT_ENABLE 12
|
||||
#define DMA_C_INTERRUPT_ENABLE 11
|
||||
#define DMA_B_INTERRUPT_ENABLE 10
|
||||
#define DMA_A_INTERRUPT_ENABLE 9
|
||||
#define EEPROM_DONE_INTERRUPT_ENABLE 8
|
||||
#define VBUS_INTERRUPT_ENABLE 7
|
||||
#define CONTROL_STATUS_INTERRUPT_ENABLE 6
|
||||
#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4
|
||||
#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3
|
||||
#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2
|
||||
#define RESUME_INTERRUPT_ENABLE 1
|
||||
#define SOF_INTERRUPT_ENABLE 0
|
||||
u32 cpu_irqenb0; /* ... or onboard 8051 */
|
||||
#define SETUP_PACKET_INTERRUPT_ENABLE 7
|
||||
#define ENDPOINT_F_INTERRUPT_ENABLE 6
|
||||
#define ENDPOINT_E_INTERRUPT_ENABLE 5
|
||||
#define ENDPOINT_D_INTERRUPT_ENABLE 4
|
||||
#define ENDPOINT_C_INTERRUPT_ENABLE 3
|
||||
#define ENDPOINT_B_INTERRUPT_ENABLE 2
|
||||
#define ENDPOINT_A_INTERRUPT_ENABLE 1
|
||||
#define ENDPOINT_0_INTERRUPT_ENABLE 0
|
||||
u32 cpu_irqenb1;
|
||||
#define CPU_INTERRUPT_ENABLE 31
|
||||
#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27
|
||||
#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26
|
||||
#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25
|
||||
#define PCI_INTA_INTERRUPT_ENABLE 24
|
||||
#define PCI_PME_INTERRUPT_ENABLE 23
|
||||
#define PCI_SERR_INTERRUPT_ENABLE 22
|
||||
#define PCI_PERR_INTERRUPT_ENABLE 21
|
||||
#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20
|
||||
#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19
|
||||
#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17
|
||||
#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16
|
||||
#define GPIO_INTERRUPT_ENABLE 13
|
||||
#define DMA_D_INTERRUPT_ENABLE 12
|
||||
#define DMA_C_INTERRUPT_ENABLE 11
|
||||
#define DMA_B_INTERRUPT_ENABLE 10
|
||||
#define DMA_A_INTERRUPT_ENABLE 9
|
||||
#define EEPROM_DONE_INTERRUPT_ENABLE 8
|
||||
#define VBUS_INTERRUPT_ENABLE 7
|
||||
#define CONTROL_STATUS_INTERRUPT_ENABLE 6
|
||||
#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4
|
||||
#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3
|
||||
#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2
|
||||
#define RESUME_INTERRUPT_ENABLE 1
|
||||
#define SOF_INTERRUPT_ENABLE 0
|
||||
|
||||
// offset 0x0020
|
||||
u32 _unused1;
|
||||
u32 usbirqenb1;
|
||||
#define USB_INTERRUPT_ENABLE 31
|
||||
#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27
|
||||
#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26
|
||||
#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25
|
||||
#define PCI_INTA_INTERRUPT_ENABLE 24
|
||||
#define PCI_PME_INTERRUPT_ENABLE 23
|
||||
#define PCI_SERR_INTERRUPT_ENABLE 22
|
||||
#define PCI_PERR_INTERRUPT_ENABLE 21
|
||||
#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20
|
||||
#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19
|
||||
#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17
|
||||
#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16
|
||||
#define GPIO_INTERRUPT_ENABLE 13
|
||||
#define DMA_D_INTERRUPT_ENABLE 12
|
||||
#define DMA_C_INTERRUPT_ENABLE 11
|
||||
#define DMA_B_INTERRUPT_ENABLE 10
|
||||
#define DMA_A_INTERRUPT_ENABLE 9
|
||||
#define EEPROM_DONE_INTERRUPT_ENABLE 8
|
||||
#define VBUS_INTERRUPT_ENABLE 7
|
||||
#define CONTROL_STATUS_INTERRUPT_ENABLE 6
|
||||
#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4
|
||||
#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3
|
||||
#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2
|
||||
#define RESUME_INTERRUPT_ENABLE 1
|
||||
#define SOF_INTERRUPT_ENABLE 0
|
||||
u32 irqstat0;
|
||||
#define INTA_ASSERTED 12
|
||||
#define SETUP_PACKET_INTERRUPT 7
|
||||
#define ENDPOINT_F_INTERRUPT 6
|
||||
#define ENDPOINT_E_INTERRUPT 5
|
||||
#define ENDPOINT_D_INTERRUPT 4
|
||||
#define ENDPOINT_C_INTERRUPT 3
|
||||
#define ENDPOINT_B_INTERRUPT 2
|
||||
#define ENDPOINT_A_INTERRUPT 1
|
||||
#define ENDPOINT_0_INTERRUPT 0
|
||||
u32 irqstat1;
|
||||
#define POWER_STATE_CHANGE_INTERRUPT 27
|
||||
#define PCI_ARBITER_TIMEOUT_INTERRUPT 26
|
||||
#define PCI_PARITY_ERROR_INTERRUPT 25
|
||||
#define PCI_INTA_INTERRUPT 24
|
||||
#define PCI_PME_INTERRUPT 23
|
||||
#define PCI_SERR_INTERRUPT 22
|
||||
#define PCI_PERR_INTERRUPT 21
|
||||
#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT 20
|
||||
#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT 19
|
||||
#define PCI_RETRY_ABORT_INTERRUPT 17
|
||||
#define PCI_MASTER_CYCLE_DONE_INTERRUPT 16
|
||||
#define GPIO_INTERRUPT 13
|
||||
#define DMA_D_INTERRUPT 12
|
||||
#define DMA_C_INTERRUPT 11
|
||||
#define DMA_B_INTERRUPT 10
|
||||
#define DMA_A_INTERRUPT 9
|
||||
#define EEPROM_DONE_INTERRUPT 8
|
||||
#define VBUS_INTERRUPT 7
|
||||
#define CONTROL_STATUS_INTERRUPT 6
|
||||
#define ROOT_PORT_RESET_INTERRUPT 4
|
||||
#define SUSPEND_REQUEST_INTERRUPT 3
|
||||
#define SUSPEND_REQUEST_CHANGE_INTERRUPT 2
|
||||
#define RESUME_INTERRUPT 1
|
||||
#define SOF_INTERRUPT 0
|
||||
// offset 0x0030
|
||||
u32 idxaddr;
|
||||
u32 idxdata;
|
||||
u32 fifoctl;
|
||||
#define PCI_BASE2_RANGE 16
|
||||
#define IGNORE_FIFO_AVAILABILITY 3
|
||||
#define PCI_BASE2_SELECT 2
|
||||
#define FIFO_CONFIGURATION_SELECT 0
|
||||
u32 _unused2;
|
||||
// offset 0x0040
|
||||
u32 memaddr;
|
||||
#define START 28
|
||||
#define DIRECTION 27
|
||||
#define FIFO_DIAGNOSTIC_SELECT 24
|
||||
#define MEMORY_ADDRESS 0
|
||||
u32 memdata0;
|
||||
u32 memdata1;
|
||||
u32 _unused3;
|
||||
// offset 0x0050
|
||||
u32 gpioctl;
|
||||
#define GPIO3_LED_SELECT 12
|
||||
#define GPIO3_INTERRUPT_ENABLE 11
|
||||
#define GPIO2_INTERRUPT_ENABLE 10
|
||||
#define GPIO1_INTERRUPT_ENABLE 9
|
||||
#define GPIO0_INTERRUPT_ENABLE 8
|
||||
#define GPIO3_OUTPUT_ENABLE 7
|
||||
#define GPIO2_OUTPUT_ENABLE 6
|
||||
#define GPIO1_OUTPUT_ENABLE 5
|
||||
#define GPIO0_OUTPUT_ENABLE 4
|
||||
#define GPIO3_DATA 3
|
||||
#define GPIO2_DATA 2
|
||||
#define GPIO1_DATA 1
|
||||
#define GPIO0_DATA 0
|
||||
u32 gpiostat;
|
||||
#define GPIO3_INTERRUPT 3
|
||||
#define GPIO2_INTERRUPT 2
|
||||
#define GPIO1_INTERRUPT 1
|
||||
#define GPIO0_INTERRUPT 0
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* usb control, BAR0 + 0x0080 */
|
||||
struct net2280_usb_regs {
|
||||
// offset 0x0080
|
||||
u32 stdrsp;
|
||||
#define STALL_UNSUPPORTED_REQUESTS 31
|
||||
#define SET_TEST_MODE 16
|
||||
#define GET_OTHER_SPEED_CONFIGURATION 15
|
||||
#define GET_DEVICE_QUALIFIER 14
|
||||
#define SET_ADDRESS 13
|
||||
#define ENDPOINT_SET_CLEAR_HALT 12
|
||||
#define DEVICE_SET_CLEAR_DEVICE_REMOTE_WAKEUP 11
|
||||
#define GET_STRING_DESCRIPTOR_2 10
|
||||
#define GET_STRING_DESCRIPTOR_1 9
|
||||
#define GET_STRING_DESCRIPTOR_0 8
|
||||
#define GET_SET_INTERFACE 6
|
||||
#define GET_SET_CONFIGURATION 5
|
||||
#define GET_CONFIGURATION_DESCRIPTOR 4
|
||||
#define GET_DEVICE_DESCRIPTOR 3
|
||||
#define GET_ENDPOINT_STATUS 2
|
||||
#define GET_INTERFACE_STATUS 1
|
||||
#define GET_DEVICE_STATUS 0
|
||||
u32 prodvendid;
|
||||
#define PRODUCT_ID 16
|
||||
#define VENDOR_ID 0
|
||||
u32 relnum;
|
||||
u32 usbctl;
|
||||
#define SERIAL_NUMBER_INDEX 16
|
||||
#define PRODUCT_ID_STRING_ENABLE 13
|
||||
#define VENDOR_ID_STRING_ENABLE 12
|
||||
#define USB_ROOT_PORT_WAKEUP_ENABLE 11
|
||||
#define VBUS_PIN 10
|
||||
#define TIMED_DISCONNECT 9
|
||||
#define SUSPEND_IMMEDIATELY 7
|
||||
#define SELF_POWERED_USB_DEVICE 6
|
||||
#define REMOTE_WAKEUP_SUPPORT 5
|
||||
#define PME_POLARITY 4
|
||||
#define USB_DETECT_ENABLE 3
|
||||
#define PME_WAKEUP_ENABLE 2
|
||||
#define DEVICE_REMOTE_WAKEUP_ENABLE 1
|
||||
#define SELF_POWERED_STATUS 0
|
||||
// offset 0x0090
|
||||
u32 usbstat;
|
||||
#define HIGH_SPEED 7
|
||||
#define FULL_SPEED 6
|
||||
#define GENERATE_RESUME 5
|
||||
#define GENERATE_DEVICE_REMOTE_WAKEUP 4
|
||||
u32 xcvrdiag;
|
||||
#define FORCE_HIGH_SPEED_MODE 31
|
||||
#define FORCE_FULL_SPEED_MODE 30
|
||||
#define USB_TEST_MODE 24
|
||||
#define LINE_STATE 16
|
||||
#define TRANSCEIVER_OPERATION_MODE 2
|
||||
#define TRANSCEIVER_SELECT 1
|
||||
#define TERMINATION_SELECT 0
|
||||
u32 setup0123;
|
||||
u32 setup4567;
|
||||
// offset 0x0090
|
||||
u32 _unused0;
|
||||
u32 ouraddr;
|
||||
#define FORCE_IMMEDIATE 7
|
||||
#define OUR_USB_ADDRESS 0
|
||||
u32 ourconfig;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* pci control, BAR0 + 0x0100 */
|
||||
struct net2280_pci_regs {
|
||||
// offset 0x0100
|
||||
u32 pcimstctl;
|
||||
#define PCI_ARBITER_PARK_SELECT 13
|
||||
#define PCI_MULTI LEVEL_ARBITER 12
|
||||
#define PCI_RETRY_ABORT_ENABLE 11
|
||||
#define DMA_MEMORY_WRITE_AND_INVALIDATE_ENABLE 10
|
||||
#define DMA_READ_MULTIPLE_ENABLE 9
|
||||
#define DMA_READ_LINE_ENABLE 8
|
||||
#define PCI_MASTER_COMMAND_SELECT 6
|
||||
#define MEM_READ_OR_WRITE 0
|
||||
#define IO_READ_OR_WRITE 1
|
||||
#define CFG_READ_OR_WRITE 2
|
||||
#define PCI_MASTER_START 5
|
||||
#define PCI_MASTER_READ_WRITE 4
|
||||
#define PCI_MASTER_WRITE 0
|
||||
#define PCI_MASTER_READ 1
|
||||
#define PCI_MASTER_BYTE_WRITE_ENABLES 0
|
||||
u32 pcimstaddr;
|
||||
u32 pcimstdata;
|
||||
u32 pcimststat;
|
||||
#define PCI_ARBITER_CLEAR 2
|
||||
#define PCI_EXTERNAL_ARBITER 1
|
||||
#define PCI_HOST_MODE 0
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* dma control, BAR0 + 0x0180 ... array of four structs like this,
|
||||
* for channels 0..3. see also struct net2280_dma: descriptor
|
||||
* that can be loaded into some of these registers.
|
||||
*/
|
||||
struct net2280_dma_regs { /* [11.7] */
|
||||
// offset 0x0180, 0x01a0, 0x01c0, 0x01e0,
|
||||
u32 dmactl;
|
||||
#define DMA_SCATTER_GATHER_DONE_INTERRUPT_ENABLE 25
|
||||
#define DMA_CLEAR_COUNT_ENABLE 21
|
||||
#define DESCRIPTOR_POLLING_RATE 19
|
||||
#define POLL_CONTINUOUS 0
|
||||
#define POLL_1_USEC 1
|
||||
#define POLL_100_USEC 2
|
||||
#define POLL_1_MSEC 3
|
||||
#define DMA_VALID_BIT_POLLING_ENABLE 18
|
||||
#define DMA_VALID_BIT_ENABLE 17
|
||||
#define DMA_SCATTER_GATHER_ENABLE 16
|
||||
#define DMA_OUT_AUTO_START_ENABLE 4
|
||||
#define DMA_PREEMPT_ENABLE 3
|
||||
#define DMA_FIFO_VALIDATE 2
|
||||
#define DMA_ENABLE 1
|
||||
#define DMA_ADDRESS_HOLD 0
|
||||
u32 dmastat;
|
||||
#define DMA_SCATTER_GATHER_DONE_INTERRUPT 25
|
||||
#define DMA_TRANSACTION_DONE_INTERRUPT 24
|
||||
#define DMA_ABORT 1
|
||||
#define DMA_START 0
|
||||
u32 _unused0 [2];
|
||||
// offset 0x0190, 0x01b0, 0x01d0, 0x01f0,
|
||||
u32 dmacount;
|
||||
#define VALID_BIT 31
|
||||
#define DMA_DIRECTION 30
|
||||
#define DMA_DONE_INTERRUPT_ENABLE 29
|
||||
#define END_OF_CHAIN 28
|
||||
#define DMA_BYTE_COUNT_MASK ((1<<24)-1)
|
||||
#define DMA_BYTE_COUNT 0
|
||||
u32 dmaaddr;
|
||||
u32 dmadesc;
|
||||
u32 _unused1;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* dedicated endpoint registers, BAR0 + 0x0200 */
|
||||
|
||||
struct net2280_dep_regs { /* [11.8] */
|
||||
// offset 0x0200, 0x0210, 0x220, 0x230, 0x240
|
||||
u32 dep_cfg;
|
||||
// offset 0x0204, 0x0214, 0x224, 0x234, 0x244
|
||||
u32 dep_rsp;
|
||||
u32 _unused [2];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* configurable endpoint registers, BAR0 + 0x0300 ... array of seven structs
|
||||
* like this, for ep0 then the configurable endpoints A..F
|
||||
* ep0 reserved for control; E and F have only 64 bytes of fifo
|
||||
*/
|
||||
struct net2280_ep_regs { /* [11.9] */
|
||||
// offset 0x0300, 0x0320, 0x0340, 0x0360, 0x0380, 0x03a0, 0x03c0
|
||||
u32 ep_cfg;
|
||||
#define ENDPOINT_BYTE_COUNT 16
|
||||
#define ENDPOINT_ENABLE 10
|
||||
#define ENDPOINT_TYPE 8
|
||||
#define ENDPOINT_DIRECTION 7
|
||||
#define ENDPOINT_NUMBER 0
|
||||
u32 ep_rsp;
|
||||
#define SET_NAK_OUT_PACKETS 15
|
||||
#define SET_EP_HIDE_STATUS_PHASE 14
|
||||
#define SET_EP_FORCE_CRC_ERROR 13
|
||||
#define SET_INTERRUPT_MODE 12
|
||||
#define SET_CONTROL_STATUS_PHASE_HANDSHAKE 11
|
||||
#define SET_NAK_OUT_PACKETS_MODE 10
|
||||
#define SET_ENDPOINT_TOGGLE 9
|
||||
#define SET_ENDPOINT_HALT 8
|
||||
#define CLEAR_NAK_OUT_PACKETS 7
|
||||
#define CLEAR_EP_HIDE_STATUS_PHASE 6
|
||||
#define CLEAR_EP_FORCE_CRC_ERROR 5
|
||||
#define CLEAR_INTERRUPT_MODE 4
|
||||
#define CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE 3
|
||||
#define CLEAR_NAK_OUT_PACKETS_MODE 2
|
||||
#define CLEAR_ENDPOINT_TOGGLE 1
|
||||
#define CLEAR_ENDPOINT_HALT 0
|
||||
u32 ep_irqenb;
|
||||
#define SHORT_PACKET_OUT_DONE_INTERRUPT_ENABLE 6
|
||||
#define SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE 5
|
||||
#define DATA_PACKET_RECEIVED_INTERRUPT_ENABLE 3
|
||||
#define DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE 2
|
||||
#define DATA_OUT_PING_TOKEN_INTERRUPT_ENABLE 1
|
||||
#define DATA_IN_TOKEN_INTERRUPT_ENABLE 0
|
||||
u32 ep_stat;
|
||||
#define FIFO_VALID_COUNT 24
|
||||
#define HIGH_BANDWIDTH_OUT_TRANSACTION_PID 22
|
||||
#define TIMEOUT 21
|
||||
#define USB_STALL_SENT 20
|
||||
#define USB_IN_NAK_SENT 19
|
||||
#define USB_IN_ACK_RCVD 18
|
||||
#define USB_OUT_PING_NAK_SENT 17
|
||||
#define USB_OUT_ACK_SENT 16
|
||||
#define FIFO_OVERFLOW 13
|
||||
#define FIFO_UNDERFLOW 12
|
||||
#define FIFO_FULL 11
|
||||
#define FIFO_EMPTY 10
|
||||
#define FIFO_FLUSH 9
|
||||
#define SHORT_PACKET_OUT_DONE_INTERRUPT 6
|
||||
#define SHORT_PACKET_TRANSFERRED_INTERRUPT 5
|
||||
#define NAK_OUT_PACKETS 4
|
||||
#define DATA_PACKET_RECEIVED_INTERRUPT 3
|
||||
#define DATA_PACKET_TRANSMITTED_INTERRUPT 2
|
||||
#define DATA_OUT_PING_TOKEN_INTERRUPT 1
|
||||
#define DATA_IN_TOKEN_INTERRUPT 0
|
||||
// offset 0x0310, 0x0330, 0x0350, 0x0370, 0x0390, 0x03b0, 0x03d0
|
||||
u32 ep_avail;
|
||||
u32 ep_data;
|
||||
u32 _unused0 [2];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
/* indexed registers [11.10] are accessed indirectly
|
||||
* caller must own the device lock.
|
||||
*/
|
||||
|
||||
static inline u32
|
||||
get_idx_reg (struct net2280_regs __iomem *regs, u32 index)
|
||||
{
|
||||
writel (index, ®s->idxaddr);
|
||||
/* NOTE: synchs device/cpu memory views */
|
||||
return readl (®s->idxdata);
|
||||
}
|
||||
|
||||
static inline void
|
||||
set_idx_reg (struct net2280_regs __iomem *regs, u32 index, u32 value)
|
||||
{
|
||||
writel (index, ®s->idxaddr);
|
||||
writel (value, ®s->idxdata);
|
||||
/* posted, may not be visible yet */
|
||||
}
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
|
||||
#define REG_DIAG 0x0
|
||||
#define RETRY_COUNTER 16
|
||||
#define FORCE_PCI_SERR 11
|
||||
#define FORCE_PCI_INTERRUPT 10
|
||||
#define FORCE_USB_INTERRUPT 9
|
||||
#define FORCE_CPU_INTERRUPT 8
|
||||
#define ILLEGAL_BYTE_ENABLES 5
|
||||
#define FAST_TIMES 4
|
||||
#define FORCE_RECEIVE_ERROR 2
|
||||
#define FORCE_TRANSMIT_CRC_ERROR 0
|
||||
#define REG_FRAME 0x02 /* from last sof */
|
||||
#define REG_CHIPREV 0x03 /* in bcd */
|
||||
#define REG_HS_NAK_RATE 0x0a /* NAK per N uframes */
|
||||
|
||||
#define CHIPREV_1 0x0100
|
||||
#define CHIPREV_1A 0x0110
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
/* ep a-f highspeed and fullspeed maxpacket, addresses
|
||||
* computed from ep->num
|
||||
*/
|
||||
#define REG_EP_MAXPKT(dev,num) (((num) + 1) * 0x10 + \
|
||||
(((dev)->gadget.speed == USB_SPEED_HIGH) ? 0 : 1))
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* [8.3] for scatter/gather i/o
|
||||
* use struct net2280_dma_regs bitfields
|
||||
*/
|
||||
struct net2280_dma {
|
||||
__le32 dmacount;
|
||||
__le32 dmaaddr; /* the buffer */
|
||||
__le32 dmadesc; /* next dma descriptor */
|
||||
__le32 _reserved;
|
||||
} __attribute__ ((aligned (16)));
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* DRIVER DATA STRUCTURES and UTILITIES */
|
||||
|
||||
struct net2280_ep {
|
||||
struct usb_ep ep;
|
||||
struct net2280_ep_regs __iomem *regs;
|
||||
struct net2280_dma_regs __iomem *dma;
|
||||
struct net2280_dma *dummy;
|
||||
dma_addr_t td_dma; /* of dummy */
|
||||
struct net2280 *dev;
|
||||
unsigned long irqs;
|
||||
|
||||
/* analogous to a host-side qh */
|
||||
struct list_head queue;
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
unsigned num : 8,
|
||||
fifo_size : 12,
|
||||
in_fifo_validate : 1,
|
||||
out_overflow : 1,
|
||||
stopped : 1,
|
||||
is_in : 1,
|
||||
is_iso : 1;
|
||||
};
|
||||
|
||||
static inline void allow_status (struct net2280_ep *ep)
|
||||
{
|
||||
/* ep0 only */
|
||||
writel ( (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE)
|
||||
| (1 << CLEAR_NAK_OUT_PACKETS)
|
||||
| (1 << CLEAR_NAK_OUT_PACKETS_MODE)
|
||||
, &ep->regs->ep_rsp);
|
||||
ep->stopped = 1;
|
||||
}
|
||||
|
||||
/* count (<= 4) bytes in the next fifo write will be valid */
|
||||
static inline void set_fifo_bytecount (struct net2280_ep *ep, unsigned count)
|
||||
{
|
||||
writeb (count, 2 + (u8 __iomem *) &ep->regs->ep_cfg);
|
||||
}
|
||||
|
||||
struct net2280_request {
|
||||
struct usb_request req;
|
||||
struct net2280_dma *td;
|
||||
dma_addr_t td_dma;
|
||||
struct list_head queue;
|
||||
unsigned mapped : 1,
|
||||
valid : 1;
|
||||
};
|
||||
|
||||
struct net2280 {
|
||||
/* each pci device provides one gadget, several endpoints */
|
||||
struct usb_gadget gadget;
|
||||
spinlock_t lock;
|
||||
struct net2280_ep ep [7];
|
||||
struct usb_gadget_driver *driver;
|
||||
unsigned enabled : 1,
|
||||
protocol_stall : 1,
|
||||
softconnect : 1,
|
||||
got_irq : 1,
|
||||
region : 1;
|
||||
u16 chiprev;
|
||||
|
||||
/* pci state used to access those endpoints */
|
||||
struct pci_dev *pdev;
|
||||
struct net2280_regs __iomem *regs;
|
||||
struct net2280_usb_regs __iomem *usb;
|
||||
struct net2280_pci_regs __iomem *pci;
|
||||
struct net2280_dma_regs __iomem *dma;
|
||||
struct net2280_dep_regs __iomem *dep;
|
||||
struct net2280_ep_regs __iomem *epregs;
|
||||
|
||||
struct pci_pool *requests;
|
||||
// statistics...
|
||||
};
|
||||
|
||||
static inline void set_halt (struct net2280_ep *ep)
|
||||
{
|
||||
/* ep0 and bulk/intr endpoints */
|
||||
writel ( (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE)
|
||||
/* set NAK_OUT for erratum 0114 */
|
||||
| ((ep->dev->chiprev == CHIPREV_1) << SET_NAK_OUT_PACKETS)
|
||||
| (1 << SET_ENDPOINT_HALT)
|
||||
, &ep->regs->ep_rsp);
|
||||
}
|
||||
|
||||
static inline void clear_halt (struct net2280_ep *ep)
|
||||
{
|
||||
/* ep0 and bulk/intr endpoints */
|
||||
writel ( (1 << CLEAR_ENDPOINT_HALT)
|
||||
| (1 << CLEAR_ENDPOINT_TOGGLE)
|
||||
/* unless the gadget driver left a short packet in the
|
||||
* fifo, this reverses the erratum 0114 workaround.
|
||||
*/
|
||||
| ((ep->dev->chiprev == CHIPREV_1) << CLEAR_NAK_OUT_PACKETS)
|
||||
, &ep->regs->ep_rsp);
|
||||
}
|
||||
|
||||
#ifdef USE_RDK_LEDS
|
||||
|
||||
static inline void net2280_led_init (struct net2280 *dev)
|
||||
{
|
||||
/* LED3 (green) is on during USB activity. note erratum 0113. */
|
||||
writel ((1 << GPIO3_LED_SELECT)
|
||||
| (1 << GPIO3_OUTPUT_ENABLE)
|
||||
| (1 << GPIO2_OUTPUT_ENABLE)
|
||||
| (1 << GPIO1_OUTPUT_ENABLE)
|
||||
| (1 << GPIO0_OUTPUT_ENABLE)
|
||||
, &dev->regs->gpioctl);
|
||||
}
|
||||
|
||||
/* indicate speed with bi-color LED 0/1 */
|
||||
static inline
|
||||
void net2280_led_speed (struct net2280 *dev, enum usb_device_speed speed)
|
||||
{
|
||||
u32 val = readl (&dev->regs->gpioctl);
|
||||
switch (speed) {
|
||||
case USB_SPEED_HIGH: /* green */
|
||||
val &= ~(1 << GPIO0_DATA);
|
||||
val |= (1 << GPIO1_DATA);
|
||||
break;
|
||||
case USB_SPEED_FULL: /* red */
|
||||
val &= ~(1 << GPIO1_DATA);
|
||||
val |= (1 << GPIO0_DATA);
|
||||
break;
|
||||
default: /* (off/black) */
|
||||
val &= ~((1 << GPIO1_DATA) | (1 << GPIO0_DATA));
|
||||
break;
|
||||
}
|
||||
writel (val, &dev->regs->gpioctl);
|
||||
}
|
||||
|
||||
/* indicate power with LED 2 */
|
||||
static inline void net2280_led_active (struct net2280 *dev, int is_active)
|
||||
{
|
||||
u32 val = readl (&dev->regs->gpioctl);
|
||||
|
||||
// FIXME this LED never seems to turn on.
|
||||
if (is_active)
|
||||
val |= GPIO2_DATA;
|
||||
else
|
||||
val &= ~GPIO2_DATA;
|
||||
writel (val, &dev->regs->gpioctl);
|
||||
}
|
||||
static inline void net2280_led_shutdown (struct net2280 *dev)
|
||||
{
|
||||
/* turn off all four GPIO*_DATA bits */
|
||||
writel (readl (&dev->regs->gpioctl) & ~0x0f,
|
||||
&dev->regs->gpioctl);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define net2280_led_init(dev) do { } while (0)
|
||||
#define net2280_led_speed(dev, speed) do { } while (0)
|
||||
#define net2280_led_shutdown(dev) do { } while (0)
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define xprintk(dev,level,fmt,args...) \
|
||||
printk(level "%s %s: " fmt , driver_name , \
|
||||
pci_name(dev->pdev) , ## args)
|
||||
|
||||
#ifdef DEBUG
|
||||
#undef DEBUG
|
||||
#define DEBUG(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_DEBUG , fmt , ## args)
|
||||
#else
|
||||
#define DEBUG(dev,fmt,args...) \
|
||||
do { } while (0)
|
||||
#endif /* DEBUG */
|
||||
|
||||
#ifdef VERBOSE
|
||||
#define VDEBUG DEBUG
|
||||
#else
|
||||
#define VDEBUG(dev,fmt,args...) \
|
||||
do { } while (0)
|
||||
#endif /* VERBOSE */
|
||||
|
||||
#define ERROR(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_ERR , fmt , ## args)
|
||||
#define WARN(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_WARNING , fmt , ## args)
|
||||
#define INFO(dev,fmt,args...) \
|
||||
xprintk(dev , KERN_INFO , fmt , ## args)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline void start_out_naking (struct net2280_ep *ep)
|
||||
{
|
||||
/* NOTE: hardware races lurk here, and PING protocol issues */
|
||||
writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp);
|
||||
/* synch with device */
|
||||
readl (&ep->regs->ep_rsp);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static inline void assert_out_naking (struct net2280_ep *ep, const char *where)
|
||||
{
|
||||
u32 tmp = readl (&ep->regs->ep_stat);
|
||||
|
||||
if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) {
|
||||
DEBUG (ep->dev, "%s %s %08x !NAK\n",
|
||||
ep->ep.name, where, tmp);
|
||||
writel ((1 << SET_NAK_OUT_PACKETS),
|
||||
&ep->regs->ep_rsp);
|
||||
}
|
||||
}
|
||||
#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep,__FUNCTION__)
|
||||
#else
|
||||
#define ASSERT_OUT_NAKING(ep) do {} while (0)
|
||||
#endif
|
||||
|
||||
static inline void stop_out_naking (struct net2280_ep *ep)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
tmp = readl (&ep->regs->ep_stat);
|
||||
if ((tmp & (1 << NAK_OUT_PACKETS)) != 0)
|
||||
writel ((1 << CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp);
|
||||
}
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
2872
drivers/usb/gadget/omap_udc.c
Normal file
2872
drivers/usb/gadget/omap_udc.c
Normal file
File diff suppressed because it is too large
Load diff
208
drivers/usb/gadget/omap_udc.h
Normal file
208
drivers/usb/gadget/omap_udc.h
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* omap_udc.h -- for omap 3.2 udc, with OTG support
|
||||
*
|
||||
* 2004 (C) Texas Instruments, Inc.
|
||||
* 2004 (C) David Brownell
|
||||
*/
|
||||
|
||||
/*
|
||||
* USB device/endpoint management registers
|
||||
*/
|
||||
#define UDC_REG(offset) __REG16(UDC_BASE + (offset))
|
||||
|
||||
#define UDC_REV_REG UDC_REG(0x0) /* Revision */
|
||||
#define UDC_EP_NUM_REG UDC_REG(0x4) /* Which endpoint */
|
||||
# define UDC_SETUP_SEL (1 << 6)
|
||||
# define UDC_EP_SEL (1 << 5)
|
||||
# define UDC_EP_DIR (1 << 4)
|
||||
/* low 4 bits for endpoint number */
|
||||
#define UDC_DATA_REG UDC_REG(0x08) /* Endpoint FIFO */
|
||||
#define UDC_CTRL_REG UDC_REG(0x0C) /* Endpoint control */
|
||||
# define UDC_CLR_HALT (1 << 7)
|
||||
# define UDC_SET_HALT (1 << 6)
|
||||
# define UDC_SET_FIFO_EN (1 << 2)
|
||||
# define UDC_CLR_EP (1 << 1)
|
||||
# define UDC_RESET_EP (1 << 0)
|
||||
#define UDC_STAT_FLG_REG UDC_REG(0x10) /* Endpoint status */
|
||||
# define UDC_NO_RXPACKET (1 << 15)
|
||||
# define UDC_MISS_IN (1 << 14)
|
||||
# define UDC_DATA_FLUSH (1 << 13)
|
||||
# define UDC_ISO_ERR (1 << 12)
|
||||
# define UDC_ISO_FIFO_EMPTY (1 << 9)
|
||||
# define UDC_ISO_FIFO_FULL (1 << 8)
|
||||
# define UDC_EP_HALTED (1 << 6)
|
||||
# define UDC_STALL (1 << 5)
|
||||
# define UDC_NAK (1 << 4)
|
||||
# define UDC_ACK (1 << 3)
|
||||
# define UDC_FIFO_EN (1 << 2)
|
||||
# define UDC_NON_ISO_FIFO_EMPTY (1 << 1)
|
||||
# define UDC_NON_ISO_FIFO_FULL (1 << 0)
|
||||
#define UDC_RXFSTAT_REG UDC_REG(0x14) /* OUT bytecount */
|
||||
#define UDC_SYSCON1_REG UDC_REG(0x18) /* System config 1 */
|
||||
# define UDC_CFG_LOCK (1 << 8)
|
||||
# define UDC_DATA_ENDIAN (1 << 7)
|
||||
# define UDC_DMA_ENDIAN (1 << 6)
|
||||
# define UDC_NAK_EN (1 << 4)
|
||||
# define UDC_AUTODECODE_DIS (1 << 3)
|
||||
# define UDC_SELF_PWR (1 << 2)
|
||||
# define UDC_SOFF_DIS (1 << 1)
|
||||
# define UDC_PULLUP_EN (1 << 0)
|
||||
#define UDC_SYSCON2_REG UDC_REG(0x1C) /* System config 2 */
|
||||
# define UDC_RMT_WKP (1 << 6)
|
||||
# define UDC_STALL_CMD (1 << 5)
|
||||
# define UDC_DEV_CFG (1 << 3)
|
||||
# define UDC_CLR_CFG (1 << 2)
|
||||
#define UDC_DEVSTAT_REG UDC_REG(0x20) /* Device status */
|
||||
# define UDC_B_HNP_ENABLE (1 << 9)
|
||||
# define UDC_A_HNP_SUPPORT (1 << 8)
|
||||
# define UDC_A_ALT_HNP_SUPPORT (1 << 7)
|
||||
# define UDC_R_WK_OK (1 << 6)
|
||||
# define UDC_USB_RESET (1 << 5)
|
||||
# define UDC_SUS (1 << 4)
|
||||
# define UDC_CFG (1 << 3)
|
||||
# define UDC_ADD (1 << 2)
|
||||
# define UDC_DEF (1 << 1)
|
||||
# define UDC_ATT (1 << 0)
|
||||
#define UDC_SOF_REG UDC_REG(0x24) /* Start of frame */
|
||||
# define UDC_FT_LOCK (1 << 12)
|
||||
# define UDC_TS_OK (1 << 11)
|
||||
# define UDC_TS 0x03ff
|
||||
#define UDC_IRQ_EN_REG UDC_REG(0x28) /* Interrupt enable */
|
||||
# define UDC_SOF_IE (1 << 7)
|
||||
# define UDC_EPN_RX_IE (1 << 5)
|
||||
# define UDC_EPN_TX_IE (1 << 4)
|
||||
# define UDC_DS_CHG_IE (1 << 3)
|
||||
# define UDC_EP0_IE (1 << 0)
|
||||
#define UDC_DMA_IRQ_EN_REG UDC_REG(0x2C) /* DMA irq enable */
|
||||
/* rx/tx dma channels numbered 1-3 not 0-2 */
|
||||
# define UDC_TX_DONE_IE(n) (1 << (4 * (n) - 2))
|
||||
# define UDC_RX_CNT_IE(n) (1 << (4 * (n) - 3))
|
||||
# define UDC_RX_EOT_IE(n) (1 << (4 * (n) - 4))
|
||||
#define UDC_IRQ_SRC_REG UDC_REG(0x30) /* Interrupt source */
|
||||
# define UDC_TXN_DONE (1 << 10)
|
||||
# define UDC_RXN_CNT (1 << 9)
|
||||
# define UDC_RXN_EOT (1 << 8)
|
||||
# define UDC_SOF (1 << 7)
|
||||
# define UDC_EPN_RX (1 << 5)
|
||||
# define UDC_EPN_TX (1 << 4)
|
||||
# define UDC_DS_CHG (1 << 3)
|
||||
# define UDC_SETUP (1 << 2)
|
||||
# define UDC_EP0_RX (1 << 1)
|
||||
# define UDC_EP0_TX (1 << 0)
|
||||
# define UDC_IRQ_SRC_MASK 0x7bf
|
||||
#define UDC_EPN_STAT_REG UDC_REG(0x34) /* EP irq status */
|
||||
#define UDC_DMAN_STAT_REG UDC_REG(0x38) /* DMA irq status */
|
||||
# define UDC_DMA_RX_SB (1 << 12)
|
||||
# define UDC_DMA_RX_SRC(x) (((x)>>8) & 0xf)
|
||||
# define UDC_DMA_TX_SRC(x) (((x)>>0) & 0xf)
|
||||
|
||||
|
||||
/* DMA configuration registers: up to three channels in each direction. */
|
||||
#define UDC_RXDMA_CFG_REG UDC_REG(0x40) /* 3 eps for RX DMA */
|
||||
#define UDC_TXDMA_CFG_REG UDC_REG(0x44) /* 3 eps for TX DMA */
|
||||
#define UDC_DATA_DMA_REG UDC_REG(0x48) /* rx/tx fifo addr */
|
||||
|
||||
/* rx/tx dma control, numbering channels 1-3 not 0-2 */
|
||||
#define UDC_TXDMA_REG(chan) UDC_REG(0x50 - 4 + 4 * (chan))
|
||||
# define UDC_TXN_EOT (1 << 15) /* bytes vs packets */
|
||||
# define UDC_TXN_START (1 << 14) /* start transfer */
|
||||
# define UDC_TXN_TSC 0x03ff /* units in xfer */
|
||||
#define UDC_RXDMA_REG(chan) UDC_REG(0x60 - 4 + 4 * (chan))
|
||||
# define UDC_RXN_STOP (1 << 15) /* enable EOT irq */
|
||||
# define UDC_RXN_TC 0x00ff /* packets in xfer */
|
||||
|
||||
|
||||
/*
|
||||
* Endpoint configuration registers (used before CFG_LOCK is set)
|
||||
* UDC_EP_TX_REG(0) is unused
|
||||
*/
|
||||
#define UDC_EP_RX_REG(endpoint) UDC_REG(0x80 + (endpoint)*4)
|
||||
# define UDC_EPN_RX_VALID (1 << 15)
|
||||
# define UDC_EPN_RX_DB (1 << 14)
|
||||
/* buffer size in bits 13, 12 */
|
||||
# define UDC_EPN_RX_ISO (1 << 11)
|
||||
/* buffer pointer in low 11 bits */
|
||||
#define UDC_EP_TX_REG(endpoint) UDC_REG(0xc0 + (endpoint)*4)
|
||||
/* same bitfields as in RX_REG */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
struct omap_req {
|
||||
struct usb_request req;
|
||||
struct list_head queue;
|
||||
unsigned dma_bytes;
|
||||
unsigned mapped:1;
|
||||
};
|
||||
|
||||
struct omap_ep {
|
||||
struct usb_ep ep;
|
||||
struct list_head queue;
|
||||
unsigned long irqs;
|
||||
struct list_head iso;
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
char name[14];
|
||||
u16 maxpacket;
|
||||
u8 bEndpointAddress;
|
||||
u8 bmAttributes;
|
||||
unsigned double_buf:1;
|
||||
unsigned stopped:1;
|
||||
unsigned fnf:1;
|
||||
unsigned has_dma:1;
|
||||
u8 ackwait;
|
||||
u8 dma_channel;
|
||||
u16 dma_counter;
|
||||
int lch;
|
||||
struct omap_udc *udc;
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
struct omap_udc {
|
||||
struct usb_gadget gadget;
|
||||
struct usb_gadget_driver *driver;
|
||||
spinlock_t lock;
|
||||
struct omap_ep ep[32];
|
||||
u16 devstat;
|
||||
struct otg_transceiver *transceiver;
|
||||
struct list_head iso;
|
||||
unsigned softconnect:1;
|
||||
unsigned vbus_active:1;
|
||||
unsigned ep0_pending:1;
|
||||
unsigned ep0_in:1;
|
||||
unsigned ep0_set_config:1;
|
||||
unsigned ep0_reset_config:1;
|
||||
unsigned ep0_setup:1;
|
||||
|
||||
struct completion *done;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(stuff...) printk(KERN_DEBUG "udc: " stuff)
|
||||
#else
|
||||
#define DBG(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#ifdef VERBOSE
|
||||
# define VDBG DBG
|
||||
#else
|
||||
# define VDBG(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#define ERR(stuff...) printk(KERN_ERR "udc: " stuff)
|
||||
#define WARN(stuff...) printk(KERN_WARNING "udc: " stuff)
|
||||
#define INFO(stuff...) printk(KERN_INFO "udc: " stuff)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define MOD_CONF_CTRL_0_REG __REG32(MOD_CONF_CTRL_0)
|
||||
#define VBUS_W2FC_1510 (1 << 17) /* 0 gpio0, 1 dvdd2 pin */
|
||||
|
||||
#define FUNC_MUX_CTRL_0_REG __REG32(FUNC_MUX_CTRL_0)
|
||||
#define VBUS_CTRL_1510 (1 << 19) /* 1 connected (software) */
|
||||
#define VBUS_MODE_1510 (1 << 18) /* 0 hardware, 1 software */
|
||||
|
||||
#define HMC_1510 ((MOD_CONF_CTRL_0_REG >> 1) & 0x3f)
|
||||
#define HMC_1610 (OTG_SYSCON_2_REG & 0x3f)
|
||||
#define HMC (cpu_is_omap15xx() ? HMC_1510 : HMC_1610)
|
||||
|
||||
2648
drivers/usb/gadget/pxa2xx_udc.c
Normal file
2648
drivers/usb/gadget/pxa2xx_udc.c
Normal file
File diff suppressed because it is too large
Load diff
320
drivers/usb/gadget/pxa2xx_udc.h
Normal file
320
drivers/usb/gadget/pxa2xx_udc.h
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* linux/drivers/usb/gadget/pxa2xx_udc.h
|
||||
* Intel PXA2xx on-chip full speed USB device controller
|
||||
*
|
||||
* Copyright (C) 2003 Robert Schwebel <r.schwebel@pengutronix.de>, Pengutronix
|
||||
* Copyright (C) 2003 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_USB_GADGET_PXA2XX_H
|
||||
#define __LINUX_USB_GADGET_PXA2XX_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* pxa2xx has this (move to include/asm-arm/arch-pxa/pxa-regs.h) */
|
||||
#define UFNRH_SIR (1 << 7) /* SOF interrupt request */
|
||||
#define UFNRH_SIM (1 << 6) /* SOF interrupt mask */
|
||||
#define UFNRH_IPE14 (1 << 5) /* ISO packet error, ep14 */
|
||||
#define UFNRH_IPE9 (1 << 4) /* ISO packet error, ep9 */
|
||||
#define UFNRH_IPE4 (1 << 3) /* ISO packet error, ep4 */
|
||||
|
||||
/* pxa255 has this (move to include/asm-arm/arch-pxa/pxa-regs.h) */
|
||||
#define UDCCFR UDC_RES2 /* UDC Control Function Register */
|
||||
#define UDCCFR_AREN (1 << 7) /* ACK response enable (now) */
|
||||
#define UDCCFR_ACM (1 << 2) /* ACK control mode (wait for AREN) */
|
||||
|
||||
/* latest pxa255 errata define new "must be one" bits in UDCCFR */
|
||||
#define UDCCFR_MB1 (0xff & ~(UDCCFR_AREN|UDCCFR_ACM))
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
struct pxa2xx_udc;
|
||||
|
||||
struct pxa2xx_ep {
|
||||
struct usb_ep ep;
|
||||
struct pxa2xx_udc *dev;
|
||||
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
struct list_head queue;
|
||||
unsigned long pio_irqs;
|
||||
unsigned long dma_irqs;
|
||||
short dma;
|
||||
|
||||
unsigned short fifo_size;
|
||||
u8 bEndpointAddress;
|
||||
u8 bmAttributes;
|
||||
|
||||
unsigned stopped : 1;
|
||||
unsigned dma_fixup : 1;
|
||||
|
||||
/* UDCCS = UDC Control/Status for this EP
|
||||
* UBCR = UDC Byte Count Remaining (contents of OUT fifo)
|
||||
* UDDR = UDC Endpoint Data Register (the fifo)
|
||||
* DRCM = DMA Request Channel Map
|
||||
*/
|
||||
volatile u32 *reg_udccs;
|
||||
volatile u32 *reg_ubcr;
|
||||
volatile u32 *reg_uddr;
|
||||
#ifdef USE_DMA
|
||||
volatile u32 *reg_drcmr;
|
||||
#define drcmr(n) .reg_drcmr = & DRCMR ## n ,
|
||||
#else
|
||||
#define drcmr(n)
|
||||
#endif
|
||||
};
|
||||
|
||||
struct pxa2xx_request {
|
||||
struct usb_request req;
|
||||
struct list_head queue;
|
||||
};
|
||||
|
||||
enum ep0_state {
|
||||
EP0_IDLE,
|
||||
EP0_IN_DATA_PHASE,
|
||||
EP0_OUT_DATA_PHASE,
|
||||
EP0_END_XFER,
|
||||
EP0_STALL,
|
||||
};
|
||||
|
||||
#define EP0_FIFO_SIZE ((unsigned)16)
|
||||
#define BULK_FIFO_SIZE ((unsigned)64)
|
||||
#define ISO_FIFO_SIZE ((unsigned)256)
|
||||
#define INT_FIFO_SIZE ((unsigned)8)
|
||||
|
||||
struct udc_stats {
|
||||
struct ep0stats {
|
||||
unsigned long ops;
|
||||
unsigned long bytes;
|
||||
} read, write;
|
||||
unsigned long irqs;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_USB_PXA2XX_SMALL
|
||||
/* when memory's tight, SMALL config saves code+data. */
|
||||
#undef USE_DMA
|
||||
#define PXA_UDC_NUM_ENDPOINTS 3
|
||||
#endif
|
||||
|
||||
#ifndef PXA_UDC_NUM_ENDPOINTS
|
||||
#define PXA_UDC_NUM_ENDPOINTS 16
|
||||
#endif
|
||||
|
||||
struct pxa2xx_udc {
|
||||
struct usb_gadget gadget;
|
||||
struct usb_gadget_driver *driver;
|
||||
|
||||
enum ep0_state ep0state;
|
||||
struct udc_stats stats;
|
||||
unsigned got_irq : 1,
|
||||
vbus : 1,
|
||||
pullup : 1,
|
||||
has_cfr : 1,
|
||||
req_pending : 1,
|
||||
req_std : 1,
|
||||
req_config : 1;
|
||||
|
||||
#define start_watchdog(dev) mod_timer(&dev->timer, jiffies + (HZ/200))
|
||||
struct timer_list timer;
|
||||
|
||||
struct device *dev;
|
||||
struct pxa2xx_udc_mach_info *mach;
|
||||
u64 dma_mask;
|
||||
struct pxa2xx_ep ep [PXA_UDC_NUM_ENDPOINTS];
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_ARCH_LUBBOCK
|
||||
#include <asm/arch/lubbock.h>
|
||||
/* lubbock can also report usb connect/disconnect irqs */
|
||||
|
||||
#ifdef DEBUG
|
||||
#define HEX_DISPLAY(n) if (machine_is_lubbock()) { LUB_HEXLED = (n); }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* LEDs are only for debug */
|
||||
#ifndef HEX_DISPLAY
|
||||
#define HEX_DISPLAY(n) do {} while(0)
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <asm/leds.h>
|
||||
|
||||
#define LED_CONNECTED_ON leds_event(led_green_on)
|
||||
#define LED_CONNECTED_OFF do { \
|
||||
leds_event(led_green_off); \
|
||||
HEX_DISPLAY(0); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
#ifndef LED_CONNECTED_ON
|
||||
#define LED_CONNECTED_ON do {} while(0)
|
||||
#define LED_CONNECTED_OFF do {} while(0)
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct pxa2xx_udc *the_controller;
|
||||
|
||||
/* one GPIO should be used to detect host disconnect */
|
||||
static inline int is_usb_connected(void)
|
||||
{
|
||||
if (!the_controller->mach->udc_is_connected)
|
||||
return 1;
|
||||
return the_controller->mach->udc_is_connected();
|
||||
}
|
||||
|
||||
/* one GPIO should force the host to see this device (or not) */
|
||||
static inline void make_usb_disappear(void)
|
||||
{
|
||||
if (!the_controller->mach->udc_command)
|
||||
return;
|
||||
the_controller->mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT);
|
||||
}
|
||||
|
||||
static inline void let_usb_appear(void)
|
||||
{
|
||||
if (!the_controller->mach->udc_command)
|
||||
return;
|
||||
the_controller->mach->udc_command(PXA2XX_UDC_CMD_CONNECT);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Debugging support vanishes in non-debug builds. DBG_NORMAL should be
|
||||
* mostly silent during normal use/testing, with no timing side-effects.
|
||||
*/
|
||||
#define DBG_NORMAL 1 /* error paths, device state transitions */
|
||||
#define DBG_VERBOSE 2 /* add some success path trace info */
|
||||
#define DBG_NOISY 3 /* ... even more: request level */
|
||||
#define DBG_VERY_NOISY 4 /* ... even more: packet level */
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
static const char *state_name[] = {
|
||||
"EP0_IDLE",
|
||||
"EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE",
|
||||
"EP0_END_XFER", "EP0_STALL"
|
||||
};
|
||||
|
||||
#define DMSG(stuff...) printk(KERN_DEBUG "udc: " stuff)
|
||||
|
||||
#ifdef VERBOSE
|
||||
# define UDC_DEBUG DBG_VERBOSE
|
||||
#else
|
||||
# define UDC_DEBUG DBG_NORMAL
|
||||
#endif
|
||||
|
||||
static void __attribute__ ((__unused__))
|
||||
dump_udccr(const char *label)
|
||||
{
|
||||
u32 udccr = UDCCR;
|
||||
DMSG("%s %02X =%s%s%s%s%s%s%s%s\n",
|
||||
label, udccr,
|
||||
(udccr & UDCCR_REM) ? " rem" : "",
|
||||
(udccr & UDCCR_RSTIR) ? " rstir" : "",
|
||||
(udccr & UDCCR_SRM) ? " srm" : "",
|
||||
(udccr & UDCCR_SUSIR) ? " susir" : "",
|
||||
(udccr & UDCCR_RESIR) ? " resir" : "",
|
||||
(udccr & UDCCR_RSM) ? " rsm" : "",
|
||||
(udccr & UDCCR_UDA) ? " uda" : "",
|
||||
(udccr & UDCCR_UDE) ? " ude" : "");
|
||||
}
|
||||
|
||||
static void __attribute__ ((__unused__))
|
||||
dump_udccs0(const char *label)
|
||||
{
|
||||
u32 udccs0 = UDCCS0;
|
||||
|
||||
DMSG("%s %s %02X =%s%s%s%s%s%s%s%s\n",
|
||||
label, state_name[the_controller->ep0state], udccs0,
|
||||
(udccs0 & UDCCS0_SA) ? " sa" : "",
|
||||
(udccs0 & UDCCS0_RNE) ? " rne" : "",
|
||||
(udccs0 & UDCCS0_FST) ? " fst" : "",
|
||||
(udccs0 & UDCCS0_SST) ? " sst" : "",
|
||||
(udccs0 & UDCCS0_DRWF) ? " dwrf" : "",
|
||||
(udccs0 & UDCCS0_FTF) ? " ftf" : "",
|
||||
(udccs0 & UDCCS0_IPR) ? " ipr" : "",
|
||||
(udccs0 & UDCCS0_OPR) ? " opr" : "");
|
||||
}
|
||||
|
||||
static void __attribute__ ((__unused__))
|
||||
dump_state(struct pxa2xx_udc *dev)
|
||||
{
|
||||
u32 tmp;
|
||||
unsigned i;
|
||||
|
||||
DMSG("%s %s, uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n",
|
||||
is_usb_connected() ? "host " : "disconnected",
|
||||
state_name[dev->ep0state],
|
||||
UICR1, UICR0, USIR1, USIR0, UFNRH, UFNRL);
|
||||
dump_udccr("udccr");
|
||||
if (dev->has_cfr) {
|
||||
tmp = UDCCFR;
|
||||
DMSG("udccfr %02X =%s%s\n", tmp,
|
||||
(tmp & UDCCFR_AREN) ? " aren" : "",
|
||||
(tmp & UDCCFR_ACM) ? " acm" : "");
|
||||
}
|
||||
|
||||
if (!dev->driver) {
|
||||
DMSG("no gadget driver bound\n");
|
||||
return;
|
||||
} else
|
||||
DMSG("ep0 driver '%s'\n", dev->driver->driver.name);
|
||||
|
||||
if (!is_usb_connected())
|
||||
return;
|
||||
|
||||
dump_udccs0 ("udccs0");
|
||||
DMSG("ep0 IN %lu/%lu, OUT %lu/%lu\n",
|
||||
dev->stats.write.bytes, dev->stats.write.ops,
|
||||
dev->stats.read.bytes, dev->stats.read.ops);
|
||||
|
||||
for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) {
|
||||
if (dev->ep [i].desc == 0)
|
||||
continue;
|
||||
DMSG ("udccs%d = %02x\n", i, *dev->ep->reg_udccs);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define DMSG(stuff...) do{}while(0)
|
||||
|
||||
#define dump_udccr(x) do{}while(0)
|
||||
#define dump_udccs0(x) do{}while(0)
|
||||
#define dump_state(x) do{}while(0)
|
||||
|
||||
#define UDC_DEBUG ((unsigned)0)
|
||||
|
||||
#endif
|
||||
|
||||
#define DBG(lvl, stuff...) do{if ((lvl) <= UDC_DEBUG) DMSG(stuff);}while(0)
|
||||
|
||||
#define WARN(stuff...) printk(KERN_WARNING "udc: " stuff)
|
||||
#define INFO(stuff...) printk(KERN_INFO "udc: " stuff)
|
||||
|
||||
|
||||
#endif /* __LINUX_USB_GADGET_PXA2XX_H */
|
||||
1428
drivers/usb/gadget/rndis.c
Normal file
1428
drivers/usb/gadget/rndis.c
Normal file
File diff suppressed because it is too large
Load diff
348
drivers/usb/gadget/rndis.h
Normal file
348
drivers/usb/gadget/rndis.h
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* RNDIS Definitions for Remote NDIS
|
||||
*
|
||||
* Version: $Id: rndis.h,v 1.15 2004/03/25 21:33:46 robert Exp $
|
||||
*
|
||||
* Authors: Benedikt Spranger, Pengutronix
|
||||
* Robert Schwebel, Pengutronix
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This software was originally developed in conformance with
|
||||
* Microsoft's Remote NDIS Specification License Agreement.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_RNDIS_H
|
||||
#define _LINUX_RNDIS_H
|
||||
|
||||
#include "ndis.h"
|
||||
|
||||
#define RNDIS_MAXIMUM_FRAME_SIZE 1518
|
||||
#define RNDIS_MAX_TOTAL_SIZE 1558
|
||||
|
||||
/* Remote NDIS Versions */
|
||||
#define RNDIS_MAJOR_VERSION 1
|
||||
#define RNDIS_MINOR_VERSION 0
|
||||
|
||||
/* Status Values */
|
||||
#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */
|
||||
#define RNDIS_STATUS_FAILURE 0xC0000001U /* Unspecified error */
|
||||
#define RNDIS_STATUS_INVALID_DATA 0xC0010015U /* Invalid data */
|
||||
#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBU /* Unsupported request */
|
||||
#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BU /* Device connected */
|
||||
#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CU /* Device disconnected */
|
||||
/* For all not specified status messages:
|
||||
* RNDIS_STATUS_Xxx -> NDIS_STATUS_Xxx
|
||||
*/
|
||||
|
||||
/* Message Set for Connectionless (802.3) Devices */
|
||||
#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002U /* Initialize device */
|
||||
#define REMOTE_NDIS_HALT_MSG 0x00000003U
|
||||
#define REMOTE_NDIS_QUERY_MSG 0x00000004U
|
||||
#define REMOTE_NDIS_SET_MSG 0x00000005U
|
||||
#define REMOTE_NDIS_RESET_MSG 0x00000006U
|
||||
#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007U
|
||||
#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008U
|
||||
|
||||
/* Message completion */
|
||||
#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002U
|
||||
#define REMOTE_NDIS_QUERY_CMPLT 0x80000004U
|
||||
#define REMOTE_NDIS_SET_CMPLT 0x80000005U
|
||||
#define REMOTE_NDIS_RESET_CMPLT 0x80000006U
|
||||
#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008U
|
||||
|
||||
/* Device Flags */
|
||||
#define RNDIS_DF_CONNECTIONLESS 0x00000001U
|
||||
#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002U
|
||||
|
||||
#define RNDIS_MEDIUM_802_3 0x00000000U
|
||||
|
||||
/* from drivers/net/sk98lin/h/skgepnmi.h */
|
||||
#define OID_PNP_CAPABILITIES 0xFD010100
|
||||
#define OID_PNP_SET_POWER 0xFD010101
|
||||
#define OID_PNP_QUERY_POWER 0xFD010102
|
||||
#define OID_PNP_ADD_WAKE_UP_PATTERN 0xFD010103
|
||||
#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xFD010104
|
||||
#define OID_PNP_ENABLE_WAKE_UP 0xFD010106
|
||||
|
||||
|
||||
/* supported OIDs */
|
||||
static const u32 oid_supported_list [] =
|
||||
{
|
||||
/* the general stuff */
|
||||
OID_GEN_SUPPORTED_LIST,
|
||||
OID_GEN_HARDWARE_STATUS,
|
||||
OID_GEN_MEDIA_SUPPORTED,
|
||||
OID_GEN_MEDIA_IN_USE,
|
||||
OID_GEN_MAXIMUM_FRAME_SIZE,
|
||||
OID_GEN_LINK_SPEED,
|
||||
OID_GEN_TRANSMIT_BLOCK_SIZE,
|
||||
OID_GEN_RECEIVE_BLOCK_SIZE,
|
||||
OID_GEN_VENDOR_ID,
|
||||
OID_GEN_VENDOR_DESCRIPTION,
|
||||
OID_GEN_VENDOR_DRIVER_VERSION,
|
||||
OID_GEN_CURRENT_PACKET_FILTER,
|
||||
OID_GEN_MAXIMUM_TOTAL_SIZE,
|
||||
OID_GEN_MEDIA_CONNECT_STATUS,
|
||||
OID_GEN_PHYSICAL_MEDIUM,
|
||||
#if 0
|
||||
OID_GEN_RNDIS_CONFIG_PARAMETER,
|
||||
#endif
|
||||
|
||||
/* the statistical stuff */
|
||||
OID_GEN_XMIT_OK,
|
||||
OID_GEN_RCV_OK,
|
||||
OID_GEN_XMIT_ERROR,
|
||||
OID_GEN_RCV_ERROR,
|
||||
OID_GEN_RCV_NO_BUFFER,
|
||||
#ifdef RNDIS_OPTIONAL_STATS
|
||||
OID_GEN_DIRECTED_BYTES_XMIT,
|
||||
OID_GEN_DIRECTED_FRAMES_XMIT,
|
||||
OID_GEN_MULTICAST_BYTES_XMIT,
|
||||
OID_GEN_MULTICAST_FRAMES_XMIT,
|
||||
OID_GEN_BROADCAST_BYTES_XMIT,
|
||||
OID_GEN_BROADCAST_FRAMES_XMIT,
|
||||
OID_GEN_DIRECTED_BYTES_RCV,
|
||||
OID_GEN_DIRECTED_FRAMES_RCV,
|
||||
OID_GEN_MULTICAST_BYTES_RCV,
|
||||
OID_GEN_MULTICAST_FRAMES_RCV,
|
||||
OID_GEN_BROADCAST_BYTES_RCV,
|
||||
OID_GEN_BROADCAST_FRAMES_RCV,
|
||||
OID_GEN_RCV_CRC_ERROR,
|
||||
OID_GEN_TRANSMIT_QUEUE_LENGTH,
|
||||
#endif /* RNDIS_OPTIONAL_STATS */
|
||||
|
||||
/* mandatory 802.3 */
|
||||
/* the general stuff */
|
||||
OID_802_3_PERMANENT_ADDRESS,
|
||||
OID_802_3_CURRENT_ADDRESS,
|
||||
OID_802_3_MULTICAST_LIST,
|
||||
OID_802_3_MAC_OPTIONS,
|
||||
OID_802_3_MAXIMUM_LIST_SIZE,
|
||||
|
||||
/* the statistical stuff */
|
||||
OID_802_3_RCV_ERROR_ALIGNMENT,
|
||||
OID_802_3_XMIT_ONE_COLLISION,
|
||||
OID_802_3_XMIT_MORE_COLLISIONS,
|
||||
#ifdef RNDIS_OPTIONAL_STATS
|
||||
OID_802_3_XMIT_DEFERRED,
|
||||
OID_802_3_XMIT_MAX_COLLISIONS,
|
||||
OID_802_3_RCV_OVERRUN,
|
||||
OID_802_3_XMIT_UNDERRUN,
|
||||
OID_802_3_XMIT_HEARTBEAT_FAILURE,
|
||||
OID_802_3_XMIT_TIMES_CRS_LOST,
|
||||
OID_802_3_XMIT_LATE_COLLISIONS,
|
||||
#endif /* RNDIS_OPTIONAL_STATS */
|
||||
|
||||
#ifdef RNDIS_PM
|
||||
/* PM and wakeup are mandatory for USB: */
|
||||
|
||||
/* power management */
|
||||
OID_PNP_CAPABILITIES,
|
||||
OID_PNP_QUERY_POWER,
|
||||
OID_PNP_SET_POWER,
|
||||
|
||||
/* wake up host */
|
||||
OID_PNP_ENABLE_WAKE_UP,
|
||||
OID_PNP_ADD_WAKE_UP_PATTERN,
|
||||
OID_PNP_REMOVE_WAKE_UP_PATTERN,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
typedef struct rndis_init_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 MajorVersion;
|
||||
__le32 MinorVersion;
|
||||
__le32 MaxTransferSize;
|
||||
} rndis_init_msg_type;
|
||||
|
||||
typedef struct rndis_init_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
__le32 MajorVersion;
|
||||
__le32 MinorVersion;
|
||||
__le32 DeviceFlags;
|
||||
__le32 Medium;
|
||||
__le32 MaxPacketsPerTransfer;
|
||||
__le32 MaxTransferSize;
|
||||
__le32 PacketAlignmentFactor;
|
||||
__le32 AFListOffset;
|
||||
__le32 AFListSize;
|
||||
} rndis_init_cmplt_type;
|
||||
|
||||
typedef struct rndis_halt_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
} rndis_halt_msg_type;
|
||||
|
||||
typedef struct rndis_query_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 OID;
|
||||
__le32 InformationBufferLength;
|
||||
__le32 InformationBufferOffset;
|
||||
__le32 DeviceVcHandle;
|
||||
} rndis_query_msg_type;
|
||||
|
||||
typedef struct rndis_query_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
__le32 InformationBufferLength;
|
||||
__le32 InformationBufferOffset;
|
||||
} rndis_query_cmplt_type;
|
||||
|
||||
typedef struct rndis_set_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 OID;
|
||||
__le32 InformationBufferLength;
|
||||
__le32 InformationBufferOffset;
|
||||
__le32 DeviceVcHandle;
|
||||
} rndis_set_msg_type;
|
||||
|
||||
typedef struct rndis_set_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
} rndis_set_cmplt_type;
|
||||
|
||||
typedef struct rndis_reset_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 Reserved;
|
||||
} rndis_reset_msg_type;
|
||||
|
||||
typedef struct rndis_reset_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 Status;
|
||||
__le32 AddressingReset;
|
||||
} rndis_reset_cmplt_type;
|
||||
|
||||
typedef struct rndis_indicate_status_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 Status;
|
||||
__le32 StatusBufferLength;
|
||||
__le32 StatusBufferOffset;
|
||||
} rndis_indicate_status_msg_type;
|
||||
|
||||
typedef struct rndis_keepalive_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
} rndis_keepalive_msg_type;
|
||||
|
||||
typedef struct rndis_keepalive_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
} rndis_keepalive_cmplt_type;
|
||||
|
||||
struct rndis_packet_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 DataOffset;
|
||||
__le32 DataLength;
|
||||
__le32 OOBDataOffset;
|
||||
__le32 OOBDataLength;
|
||||
__le32 NumOOBDataElements;
|
||||
__le32 PerPacketInfoOffset;
|
||||
__le32 PerPacketInfoLength;
|
||||
__le32 VcHandle;
|
||||
__le32 Reserved;
|
||||
};
|
||||
|
||||
struct rndis_config_parameter
|
||||
{
|
||||
__le32 ParameterNameOffset;
|
||||
__le32 ParameterNameLength;
|
||||
__le32 ParameterType;
|
||||
__le32 ParameterValueOffset;
|
||||
__le32 ParameterValueLength;
|
||||
};
|
||||
|
||||
/* implementation specific */
|
||||
enum rndis_state
|
||||
{
|
||||
RNDIS_UNINITIALIZED,
|
||||
RNDIS_INITIALIZED,
|
||||
RNDIS_DATA_INITIALIZED,
|
||||
};
|
||||
|
||||
typedef struct rndis_resp_t
|
||||
{
|
||||
struct list_head list;
|
||||
u8 *buf;
|
||||
u32 length;
|
||||
int send;
|
||||
} rndis_resp_t;
|
||||
|
||||
typedef struct rndis_params
|
||||
{
|
||||
u8 confignr;
|
||||
int used;
|
||||
enum rndis_state state;
|
||||
u32 filter;
|
||||
u32 medium;
|
||||
u32 speed;
|
||||
u32 media_state;
|
||||
const u8 *host_mac;
|
||||
struct net_device *dev;
|
||||
struct net_device_stats *stats;
|
||||
u32 vendorID;
|
||||
const char *vendorDescr;
|
||||
int (*ack) (struct net_device *);
|
||||
struct list_head resp_queue;
|
||||
} rndis_params;
|
||||
|
||||
/* RNDIS Message parser and other useless functions */
|
||||
int rndis_msg_parser (u8 configNr, u8 *buf);
|
||||
int rndis_register (int (*rndis_control_ack) (struct net_device *));
|
||||
void rndis_deregister (int configNr);
|
||||
int rndis_set_param_dev (u8 configNr, struct net_device *dev,
|
||||
struct net_device_stats *stats);
|
||||
int rndis_set_param_vendor (u8 configNr, u32 vendorID,
|
||||
const char *vendorDescr);
|
||||
int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed);
|
||||
void rndis_add_hdr (struct sk_buff *skb);
|
||||
int rndis_rm_hdr (u8 *buf, u32 *length);
|
||||
u8 *rndis_get_next_response (int configNr, u32 *length);
|
||||
void rndis_free_response (int configNr, u8 *buf);
|
||||
|
||||
int rndis_signal_connect (int configNr);
|
||||
int rndis_signal_disconnect (int configNr);
|
||||
int rndis_state (int configNr);
|
||||
extern void rndis_set_host_mac (int configNr, const u8 *addr);
|
||||
|
||||
int __init rndis_init (void);
|
||||
void rndis_exit (void);
|
||||
|
||||
#endif /* _LINUX_RNDIS_H */
|
||||
2436
drivers/usb/gadget/serial.c
Normal file
2436
drivers/usb/gadget/serial.c
Normal file
File diff suppressed because it is too large
Load diff
136
drivers/usb/gadget/usbstring.c
Normal file
136
drivers/usb/gadget/usbstring.c
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (C) 2003 David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <linux/usb_ch9.h>
|
||||
#include <linux/usb_gadget.h>
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
|
||||
static int utf8_to_utf16le(const char *s, __le16 *cp, unsigned len)
|
||||
{
|
||||
int count = 0;
|
||||
u8 c;
|
||||
u16 uchar;
|
||||
|
||||
/* this insists on correct encodings, though not minimal ones.
|
||||
* BUT it currently rejects legit 4-byte UTF-8 code points,
|
||||
* which need surrogate pairs. (Unicode 3.1 can use them.)
|
||||
*/
|
||||
while (len != 0 && (c = (u8) *s++) != 0) {
|
||||
if (unlikely(c & 0x80)) {
|
||||
// 2-byte sequence:
|
||||
// 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
|
||||
if ((c & 0xe0) == 0xc0) {
|
||||
uchar = (c & 0x1f) << 6;
|
||||
|
||||
c = (u8) *s++;
|
||||
if ((c & 0xc0) != 0xc0)
|
||||
goto fail;
|
||||
c &= 0x3f;
|
||||
uchar |= c;
|
||||
|
||||
// 3-byte sequence (most CJKV characters):
|
||||
// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
|
||||
} else if ((c & 0xf0) == 0xe0) {
|
||||
uchar = (c & 0x0f) << 12;
|
||||
|
||||
c = (u8) *s++;
|
||||
if ((c & 0xc0) != 0xc0)
|
||||
goto fail;
|
||||
c &= 0x3f;
|
||||
uchar |= c << 6;
|
||||
|
||||
c = (u8) *s++;
|
||||
if ((c & 0xc0) != 0xc0)
|
||||
goto fail;
|
||||
c &= 0x3f;
|
||||
uchar |= c;
|
||||
|
||||
/* no bogus surrogates */
|
||||
if (0xd800 <= uchar && uchar <= 0xdfff)
|
||||
goto fail;
|
||||
|
||||
// 4-byte sequence (surrogate pairs, currently rare):
|
||||
// 11101110wwwwzzzzyy + 110111yyyyxxxxxx
|
||||
// = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
|
||||
// (uuuuu = wwww + 1)
|
||||
// FIXME accept the surrogate code points (only)
|
||||
|
||||
} else
|
||||
goto fail;
|
||||
} else
|
||||
uchar = c;
|
||||
put_unaligned (cpu_to_le16 (uchar), cp++);
|
||||
count++;
|
||||
len--;
|
||||
}
|
||||
return count;
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* usb_gadget_get_string - fill out a string descriptor
|
||||
* @table: of c strings encoded using UTF-8
|
||||
* @id: string id, from low byte of wValue in get string descriptor
|
||||
* @buf: at least 256 bytes
|
||||
*
|
||||
* Finds the UTF-8 string matching the ID, and converts it into a
|
||||
* string descriptor in utf16-le.
|
||||
* Returns length of descriptor (always even) or negative errno
|
||||
*
|
||||
* If your driver needs stings in multiple languages, you'll probably
|
||||
* "switch (wIndex) { ... }" in your ep0 string descriptor logic,
|
||||
* using this routine after choosing which set of UTF-8 strings to use.
|
||||
* Note that US-ASCII is a strict subset of UTF-8; any string bytes with
|
||||
* the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1
|
||||
* characters (which are also widely used in C strings).
|
||||
*/
|
||||
int
|
||||
usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf)
|
||||
{
|
||||
struct usb_string *s;
|
||||
int len;
|
||||
|
||||
/* descriptor 0 has the language id */
|
||||
if (id == 0) {
|
||||
buf [0] = 4;
|
||||
buf [1] = USB_DT_STRING;
|
||||
buf [2] = (u8) table->language;
|
||||
buf [3] = (u8) (table->language >> 8);
|
||||
return 4;
|
||||
}
|
||||
for (s = table->strings; s && s->s; s++)
|
||||
if (s->id == id)
|
||||
break;
|
||||
|
||||
/* unrecognized: stall. */
|
||||
if (!s || !s->s)
|
||||
return -EINVAL;
|
||||
|
||||
/* string descriptors have length, tag, then UTF16-LE text */
|
||||
len = min ((size_t) 126, strlen (s->s));
|
||||
memset (buf + 2, 0, 2 * len); /* zero all the bytes */
|
||||
len = utf8_to_utf16le(s->s, (__le16 *)&buf[2], len);
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
buf [0] = (len + 1) * 2;
|
||||
buf [1] = USB_DT_STRING;
|
||||
return buf [0];
|
||||
}
|
||||
|
||||
1357
drivers/usb/gadget/zero.c
Normal file
1357
drivers/usb/gadget/zero.c
Normal file
File diff suppressed because it is too large
Load diff
126
drivers/usb/host/Kconfig
Normal file
126
drivers/usb/host/Kconfig
Normal file
|
|
@ -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
Normal file
10
drivers/usb/host/Makefile
Normal file
|
|
@ -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
Normal file
755
drivers/usb/host/ehci-dbg.c
Normal file
|
|
@ -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
Normal file
1261
drivers/usb/host/ehci-hcd.c
Normal file
File diff suppressed because it is too large
Load diff
553
drivers/usb/host/ehci-hub.c
Normal file
553
drivers/usb/host/ehci-hub.c
Normal file
|
|
@ -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
Normal file
237
drivers/usb/host/ehci-mem.c
Normal file
|
|
@ -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
Normal file
1090
drivers/usb/host/ehci-q.c
Normal file
File diff suppressed because it is too large
Load diff
1999
drivers/usb/host/ehci-sched.c
Normal file
1999
drivers/usb/host/ehci-sched.c
Normal file
File diff suppressed because it is too large
Load diff
637
drivers/usb/host/ehci.h
Normal file
637
drivers/usb/host/ehci.h
Normal file
|
|
@ -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
Normal file
4556
drivers/usb/host/hc_crisv10.c
Normal file
File diff suppressed because it is too large
Load diff
289
drivers/usb/host/hc_crisv10.h
Normal file
289
drivers/usb/host/hc_crisv10.h
Normal file
|
|
@ -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
Normal file
284
drivers/usb/host/ohci-au1xxx.c
Normal file
|
|
@ -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
Normal file
707
drivers/usb/host/ohci-dbg.c
Normal file
|
|
@ -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
Normal file
925
drivers/usb/host/ohci-hcd.c
Normal file
|
|
@ -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
Normal file
643
drivers/usb/host/ohci-hub.c
Normal file
|
|
@ -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
Normal file
266
drivers/usb/host/ohci-lh7a404.c
Normal file
|
|
@ -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
Normal file
139
drivers/usb/host/ohci-mem.c
Normal file
|
|
@ -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
Normal file
560
drivers/usb/host/ohci-omap.c
Normal file
|
|
@ -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
Normal file
264
drivers/usb/host/ohci-pci.c
Normal file
|
|
@ -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
Normal file
234
drivers/usb/host/ohci-ppc-soc.c
Normal file
|
|
@ -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
Normal file
383
drivers/usb/host/ohci-pxa27x.c
Normal file
|
|
@ -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
Normal file
1107
drivers/usb/host/ohci-q.c
Normal file
File diff suppressed because it is too large
Load diff
289
drivers/usb/host/ohci-sa1111.c
Normal file
289
drivers/usb/host/ohci-sa1111.c
Normal file
|
|
@ -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
Normal file
636
drivers/usb/host/ohci.h
Normal file
|
|
@ -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
Normal file
1851
drivers/usb/host/sl811-hcd.c
Normal file
File diff suppressed because it is too large
Load diff
266
drivers/usb/host/sl811.h
Normal file
266
drivers/usb/host/sl811.h
Normal file
|
|
@ -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
Normal file
587
drivers/usb/host/uhci-debug.c
Normal file
|
|
@ -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
Normal file
919
drivers/usb/host/uhci-hcd.c
Normal file
|
|
@ -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
Normal file
454
drivers/usb/host/uhci-hcd.h
Normal file
|
|
@ -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
Normal file
299
drivers/usb/host/uhci-hub.c
Normal file
|
|
@ -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
Normal file
1539
drivers/usb/host/uhci-q.c
Normal file
File diff suppressed because it is too large
Load diff
30
drivers/usb/image/Kconfig
Normal file
30
drivers/usb/image/Kconfig
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# USB Imageing devices configuration
|
||||
#
|
||||
comment "USB Imaging devices"
|
||||
depends on USB
|
||||
|
||||
config USB_MDC800
|
||||
tristate "USB Mustek MDC800 Digital Camera support (EXPERIMENTAL)"
|
||||
depends on USB && EXPERIMENTAL
|
||||
---help---
|
||||
Say Y here if you want to connect this type of still camera to
|
||||
your computer's USB port. This driver can be used with gphoto 0.4.3
|
||||
and higher (look at <http://www.gphoto.org/>).
|
||||
To use it create a device node with "mknod /dev/mustek c 180 32" and
|
||||
configure it in your software.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called mdc800.
|
||||
|
||||
config USB_MICROTEK
|
||||
tristate "Microtek X6USB scanner support"
|
||||
depends on USB && SCSI
|
||||
help
|
||||
Say Y here if you want support for the Microtek X6USB and
|
||||
possibly the Phantom 336CX, Phantom C6 and ScanMaker V6U(S)L.
|
||||
Support for anything but the X6 is experimental.
|
||||
Please report failures and successes.
|
||||
The scanner will appear as a scsi generic device to the rest
|
||||
of the system. Scsi support is required.
|
||||
This driver can be compiled as a module, called microtek.
|
||||
6
drivers/usb/image/Makefile
Normal file
6
drivers/usb/image/Makefile
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# Makefile for USB Image drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_USB_MDC800) += mdc800.o
|
||||
obj-$(CONFIG_USB_MICROTEK) += microtek.o
|
||||
1063
drivers/usb/image/mdc800.c
Normal file
1063
drivers/usb/image/mdc800.c
Normal file
File diff suppressed because it is too large
Load diff
862
drivers/usb/image/microtek.c
Normal file
862
drivers/usb/image/microtek.c
Normal file
|
|
@ -0,0 +1,862 @@
|
|||
/* Driver for Microtek Scanmaker X6 USB scanner, and possibly others.
|
||||
*
|
||||
* (C) Copyright 2000 John Fremlin <vii@penguinpowered.com>
|
||||
* (C) Copyright 2000 Oliver Neukum <Oliver.Neukum@lrz.uni-muenchen.de>
|
||||
*
|
||||
* Parts shamelessly stolen from usb-storage and copyright by their
|
||||
* authors. Thanks to Matt Dharm for giving us permission!
|
||||
*
|
||||
* This driver implements a SCSI host controller driver and a USB
|
||||
* device driver. To avoid confusion, all the USB related stuff is
|
||||
* prefixed by mts_usb_ and all the SCSI stuff by mts_scsi_.
|
||||
*
|
||||
* Microtek (www.microtek.com) did not release the specifications for
|
||||
* their USB protocol to us, so we had to reverse engineer them. We
|
||||
* don't know for which models they are valid.
|
||||
*
|
||||
* The X6 USB has three bulk endpoints, one output (0x1) down which
|
||||
* commands and outgoing data are sent, and two input: 0x82 from which
|
||||
* normal data is read from the scanner (in packets of maximum 32
|
||||
* bytes) and from which the status byte is read, and 0x83 from which
|
||||
* the results of a scan (or preview) are read in up to 64 * 1024 byte
|
||||
* chunks by the Windows driver. We don't know how much it is possible
|
||||
* to read at a time from 0x83.
|
||||
*
|
||||
* It seems possible to read (with URB transfers) everything from 0x82
|
||||
* in one go, without bothering to read in 32 byte chunks.
|
||||
*
|
||||
* There seems to be an optimisation of a further READ implicit if
|
||||
* you simply read from 0x83.
|
||||
*
|
||||
* Guessed protocol:
|
||||
*
|
||||
* Send raw SCSI command to EP 0x1
|
||||
*
|
||||
* If there is data to receive:
|
||||
* If the command was READ datatype=image:
|
||||
* Read a lot of data from EP 0x83
|
||||
* Else:
|
||||
* Read data from EP 0x82
|
||||
* Else:
|
||||
* If there is data to transmit:
|
||||
* Write it to EP 0x1
|
||||
*
|
||||
* Read status byte from EP 0x82
|
||||
*
|
||||
* References:
|
||||
*
|
||||
* The SCSI command set for the scanner is available from
|
||||
* ftp://ftp.microtek.com/microtek/devpack/
|
||||
*
|
||||
* Microtek NV sent us a more up to date version of the document. If
|
||||
* you want it, just send mail.
|
||||
*
|
||||
* Status:
|
||||
*
|
||||
* Untested with multiple scanners.
|
||||
* Untested on SMP.
|
||||
* Untested on a bigendian machine.
|
||||
*
|
||||
* History:
|
||||
*
|
||||
* 20000417 starting history
|
||||
* 20000417 fixed load oops
|
||||
* 20000417 fixed unload oops
|
||||
* 20000419 fixed READ IMAGE detection
|
||||
* 20000424 started conversion to use URBs
|
||||
* 20000502 handled short transfers as errors
|
||||
* 20000513 rename and organisation of functions (john)
|
||||
* 20000513 added IDs for all products supported by Windows driver (john)
|
||||
* 20000514 Rewrote mts_scsi_queuecommand to use URBs (john)
|
||||
* 20000514 Version 0.0.8j
|
||||
* 20000514 Fix reporting of non-existant devices to SCSI layer (john)
|
||||
* 20000514 Added MTS_DEBUG_INT (john)
|
||||
* 20000514 Changed "usb-microtek" to "microtek" for consistency (john)
|
||||
* 20000514 Stupid bug fixes (john)
|
||||
* 20000514 Version 0.0.9j
|
||||
* 20000515 Put transfer context and URB in mts_desc (john)
|
||||
* 20000515 Added prelim turn off debugging support (john)
|
||||
* 20000515 Version 0.0.10j
|
||||
* 20000515 Fixed up URB allocation (clear URB on alloc) (john)
|
||||
* 20000515 Version 0.0.11j
|
||||
* 20000516 Removed unnecessary spinlock in mts_transfer_context (john)
|
||||
* 20000516 Removed unnecessary up on instance lock in mts_remove_nolock (john)
|
||||
* 20000516 Implemented (badly) scsi_abort (john)
|
||||
* 20000516 Version 0.0.12j
|
||||
* 20000517 Hopefully removed mts_remove_nolock quasideadlock (john)
|
||||
* 20000517 Added mts_debug_dump to print ll USB info (john)
|
||||
* 20000518 Tweaks and documentation updates (john)
|
||||
* 20000518 Version 0.0.13j
|
||||
* 20000518 Cleaned up abort handling (john)
|
||||
* 20000523 Removed scsi_command and various scsi_..._resets (john)
|
||||
* 20000523 Added unlink URB on scsi_abort, now OHCI supports it (john)
|
||||
* 20000523 Fixed last tiresome compile warning (john)
|
||||
* 20000523 Version 0.0.14j (though version 0.1 has come out?)
|
||||
* 20000602 Added primitive reset
|
||||
* 20000602 Version 0.2.0
|
||||
* 20000603 various cosmetic changes
|
||||
* 20000603 Version 0.2.1
|
||||
* 20000620 minor cosmetic changes
|
||||
* 20000620 Version 0.2.2
|
||||
* 20000822 Hopefully fixed deadlock in mts_remove_nolock()
|
||||
* 20000822 Fixed minor race in mts_transfer_cleanup()
|
||||
* 20000822 Fixed deadlock on submission error in queuecommand
|
||||
* 20000822 Version 0.2.3
|
||||
* 20000913 Reduced module size if debugging is off
|
||||
* 20000913 Version 0.2.4
|
||||
* 20010210 New abort logic
|
||||
* 20010210 Version 0.3.0
|
||||
* 20010217 Merged scatter/gather
|
||||
* 20010218 Version 0.4.0
|
||||
* 20010218 Cosmetic fixes
|
||||
* 20010218 Version 0.4.1
|
||||
* 20010306 Abort while using scatter/gather
|
||||
* 20010306 Version 0.4.2
|
||||
* 20010311 Remove all timeouts and tidy up generally (john)
|
||||
* 20010320 check return value of scsi_register()
|
||||
* 20010320 Version 0.4.3
|
||||
* 20010408 Identify version on module load.
|
||||
* 20011003 Fix multiple requests
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/signal.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/proc_fs.h>
|
||||
|
||||
#include <asm/atomic.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include "../../scsi/scsi.h"
|
||||
#include <scsi/scsi_host.h>
|
||||
|
||||
#include "microtek.h"
|
||||
|
||||
/*
|
||||
* Version Information
|
||||
*/
|
||||
#define DRIVER_VERSION "v0.4.3"
|
||||
#define DRIVER_AUTHOR "John Fremlin <[email protected]>, Oliver Neukum <[email protected]>"
|
||||
#define DRIVER_DESC "Microtek Scanmaker X6 USB scanner driver"
|
||||
|
||||
/* Should we do debugging? */
|
||||
|
||||
//#define MTS_DO_DEBUG
|
||||
|
||||
/* USB layer driver interface */
|
||||
|
||||
static int mts_usb_probe(struct usb_interface *intf,
|
||||
const struct usb_device_id *id);
|
||||
static void mts_usb_disconnect(struct usb_interface *intf);
|
||||
|
||||
static struct usb_device_id mts_usb_ids [];
|
||||
|
||||
static struct usb_driver mts_usb_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "microtekX6",
|
||||
.probe = mts_usb_probe,
|
||||
.disconnect = mts_usb_disconnect,
|
||||
.id_table = mts_usb_ids,
|
||||
};
|
||||
|
||||
|
||||
/* Internal driver stuff */
|
||||
|
||||
#define MTS_VERSION "0.4.3"
|
||||
#define MTS_NAME "microtek usb (rev " MTS_VERSION "): "
|
||||
|
||||
#define MTS_WARNING(x...) \
|
||||
printk( KERN_WARNING MTS_NAME x )
|
||||
#define MTS_ERROR(x...) \
|
||||
printk( KERN_ERR MTS_NAME x )
|
||||
#define MTS_INT_ERROR(x...) \
|
||||
MTS_ERROR(x)
|
||||
#define MTS_MESSAGE(x...) \
|
||||
printk( KERN_INFO MTS_NAME x )
|
||||
|
||||
#if defined MTS_DO_DEBUG
|
||||
|
||||
#define MTS_DEBUG(x...) \
|
||||
printk( KERN_DEBUG MTS_NAME x )
|
||||
|
||||
#define MTS_DEBUG_GOT_HERE() \
|
||||
MTS_DEBUG("got to %s:%d (%s)\n", __FILE__, (int)__LINE__, __PRETTY_FUNCTION__ )
|
||||
#define MTS_DEBUG_INT() \
|
||||
do { MTS_DEBUG_GOT_HERE(); \
|
||||
MTS_DEBUG("transfer = 0x%x context = 0x%x\n",(int)transfer,(int)context ); \
|
||||
MTS_DEBUG("status = 0x%x data-length = 0x%x sent = 0x%x\n",(int)transfer->status,(int)context->data_length, (int)transfer->actual_length ); \
|
||||
mts_debug_dump(context->instance);\
|
||||
} while(0)
|
||||
#else
|
||||
|
||||
#define MTS_NUL_STATEMENT do { } while(0)
|
||||
|
||||
#define MTS_DEBUG(x...) MTS_NUL_STATEMENT
|
||||
#define MTS_DEBUG_GOT_HERE() MTS_NUL_STATEMENT
|
||||
#define MTS_DEBUG_INT() MTS_NUL_STATEMENT
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#define MTS_INT_INIT()\
|
||||
struct mts_transfer_context* context = (struct mts_transfer_context*)transfer->context; \
|
||||
MTS_DEBUG_INT();\
|
||||
|
||||
#ifdef MTS_DO_DEBUG
|
||||
|
||||
static inline void mts_debug_dump(struct mts_desc* desc) {
|
||||
MTS_DEBUG("desc at 0x%x: toggle = %02x%02x\n",
|
||||
(int)desc,
|
||||
(int)desc->usb_dev->toggle[1],(int)desc->usb_dev->toggle[0]
|
||||
);
|
||||
MTS_DEBUG("ep_out=%x ep_response=%x ep_image=%x\n",
|
||||
usb_sndbulkpipe(desc->usb_dev,desc->ep_out),
|
||||
usb_rcvbulkpipe(desc->usb_dev,desc->ep_response),
|
||||
usb_rcvbulkpipe(desc->usb_dev,desc->ep_image)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static inline void mts_show_command(Scsi_Cmnd *srb)
|
||||
{
|
||||
char *what = NULL;
|
||||
|
||||
switch (srb->cmnd[0]) {
|
||||
case TEST_UNIT_READY: what = "TEST_UNIT_READY"; break;
|
||||
case REZERO_UNIT: what = "REZERO_UNIT"; break;
|
||||
case REQUEST_SENSE: what = "REQUEST_SENSE"; break;
|
||||
case FORMAT_UNIT: what = "FORMAT_UNIT"; break;
|
||||
case READ_BLOCK_LIMITS: what = "READ_BLOCK_LIMITS"; break;
|
||||
case REASSIGN_BLOCKS: what = "REASSIGN_BLOCKS"; break;
|
||||
case READ_6: what = "READ_6"; break;
|
||||
case WRITE_6: what = "WRITE_6"; break;
|
||||
case SEEK_6: what = "SEEK_6"; break;
|
||||
case READ_REVERSE: what = "READ_REVERSE"; break;
|
||||
case WRITE_FILEMARKS: what = "WRITE_FILEMARKS"; break;
|
||||
case SPACE: what = "SPACE"; break;
|
||||
case INQUIRY: what = "INQUIRY"; break;
|
||||
case RECOVER_BUFFERED_DATA: what = "RECOVER_BUFFERED_DATA"; break;
|
||||
case MODE_SELECT: what = "MODE_SELECT"; break;
|
||||
case RESERVE: what = "RESERVE"; break;
|
||||
case RELEASE: what = "RELEASE"; break;
|
||||
case COPY: what = "COPY"; break;
|
||||
case ERASE: what = "ERASE"; break;
|
||||
case MODE_SENSE: what = "MODE_SENSE"; break;
|
||||
case START_STOP: what = "START_STOP"; break;
|
||||
case RECEIVE_DIAGNOSTIC: what = "RECEIVE_DIAGNOSTIC"; break;
|
||||
case SEND_DIAGNOSTIC: what = "SEND_DIAGNOSTIC"; break;
|
||||
case ALLOW_MEDIUM_REMOVAL: what = "ALLOW_MEDIUM_REMOVAL"; break;
|
||||
case SET_WINDOW: what = "SET_WINDOW"; break;
|
||||
case READ_CAPACITY: what = "READ_CAPACITY"; break;
|
||||
case READ_10: what = "READ_10"; break;
|
||||
case WRITE_10: what = "WRITE_10"; break;
|
||||
case SEEK_10: what = "SEEK_10"; break;
|
||||
case WRITE_VERIFY: what = "WRITE_VERIFY"; break;
|
||||
case VERIFY: what = "VERIFY"; break;
|
||||
case SEARCH_HIGH: what = "SEARCH_HIGH"; break;
|
||||
case SEARCH_EQUAL: what = "SEARCH_EQUAL"; break;
|
||||
case SEARCH_LOW: what = "SEARCH_LOW"; break;
|
||||
case SET_LIMITS: what = "SET_LIMITS"; break;
|
||||
case READ_POSITION: what = "READ_POSITION"; break;
|
||||
case SYNCHRONIZE_CACHE: what = "SYNCHRONIZE_CACHE"; break;
|
||||
case LOCK_UNLOCK_CACHE: what = "LOCK_UNLOCK_CACHE"; break;
|
||||
case READ_DEFECT_DATA: what = "READ_DEFECT_DATA"; break;
|
||||
case MEDIUM_SCAN: what = "MEDIUM_SCAN"; break;
|
||||
case COMPARE: what = "COMPARE"; break;
|
||||
case COPY_VERIFY: what = "COPY_VERIFY"; break;
|
||||
case WRITE_BUFFER: what = "WRITE_BUFFER"; break;
|
||||
case READ_BUFFER: what = "READ_BUFFER"; break;
|
||||
case UPDATE_BLOCK: what = "UPDATE_BLOCK"; break;
|
||||
case READ_LONG: what = "READ_LONG"; break;
|
||||
case WRITE_LONG: what = "WRITE_LONG"; break;
|
||||
case CHANGE_DEFINITION: what = "CHANGE_DEFINITION"; break;
|
||||
case WRITE_SAME: what = "WRITE_SAME"; break;
|
||||
case READ_TOC: what = "READ_TOC"; break;
|
||||
case LOG_SELECT: what = "LOG_SELECT"; break;
|
||||
case LOG_SENSE: what = "LOG_SENSE"; break;
|
||||
case MODE_SELECT_10: what = "MODE_SELECT_10"; break;
|
||||
case MODE_SENSE_10: what = "MODE_SENSE_10"; break;
|
||||
case MOVE_MEDIUM: what = "MOVE_MEDIUM"; break;
|
||||
case READ_12: what = "READ_12"; break;
|
||||
case WRITE_12: what = "WRITE_12"; break;
|
||||
case WRITE_VERIFY_12: what = "WRITE_VERIFY_12"; break;
|
||||
case SEARCH_HIGH_12: what = "SEARCH_HIGH_12"; break;
|
||||
case SEARCH_EQUAL_12: what = "SEARCH_EQUAL_12"; break;
|
||||
case SEARCH_LOW_12: what = "SEARCH_LOW_12"; break;
|
||||
case READ_ELEMENT_STATUS: what = "READ_ELEMENT_STATUS"; break;
|
||||
case SEND_VOLUME_TAG: what = "SEND_VOLUME_TAG"; break;
|
||||
case WRITE_LONG_2: what = "WRITE_LONG_2"; break;
|
||||
default:
|
||||
MTS_DEBUG("can't decode command\n");
|
||||
goto out;
|
||||
break;
|
||||
}
|
||||
MTS_DEBUG( "Command %s (%d bytes)\n", what, srb->cmd_len);
|
||||
|
||||
out:
|
||||
MTS_DEBUG( " %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
srb->cmnd[0], srb->cmnd[1], srb->cmnd[2], srb->cmnd[3], srb->cmnd[4], srb->cmnd[5],
|
||||
srb->cmnd[6], srb->cmnd[7], srb->cmnd[8], srb->cmnd[9]);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline void mts_show_command(Scsi_Cmnd * dummy)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void mts_debug_dump(struct mts_desc* dummy)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static inline void mts_urb_abort(struct mts_desc* desc) {
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
mts_debug_dump(desc);
|
||||
|
||||
usb_kill_urb( desc->urb );
|
||||
}
|
||||
|
||||
static int mts_scsi_abort (Scsi_Cmnd *srb)
|
||||
{
|
||||
struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]);
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
|
||||
mts_urb_abort(desc);
|
||||
|
||||
return SCSI_ABORT_PENDING;
|
||||
}
|
||||
|
||||
static int mts_scsi_host_reset (Scsi_Cmnd *srb)
|
||||
{
|
||||
struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]);
|
||||
int result, rc;
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
mts_debug_dump(desc);
|
||||
|
||||
rc = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf);
|
||||
if (rc < 0)
|
||||
return FAILED;
|
||||
result = usb_reset_device(desc->usb_dev);;
|
||||
if (rc)
|
||||
usb_unlock_device(desc->usb_dev);
|
||||
return result ? FAILED : SUCCESS;
|
||||
}
|
||||
|
||||
static
|
||||
int mts_scsi_queuecommand (Scsi_Cmnd *srb, mts_scsi_cmnd_callback callback );
|
||||
|
||||
static void mts_transfer_cleanup( struct urb *transfer );
|
||||
static void mts_do_sg(struct urb * transfer, struct pt_regs *regs);
|
||||
|
||||
|
||||
inline static
|
||||
void mts_int_submit_urb (struct urb* transfer,
|
||||
int pipe,
|
||||
void* data,
|
||||
unsigned length,
|
||||
usb_complete_t callback )
|
||||
/* Interrupt context! */
|
||||
|
||||
/* Holding transfer->context->lock! */
|
||||
{
|
||||
int res;
|
||||
|
||||
MTS_INT_INIT();
|
||||
|
||||
usb_fill_bulk_urb(transfer,
|
||||
context->instance->usb_dev,
|
||||
pipe,
|
||||
data,
|
||||
length,
|
||||
callback,
|
||||
context
|
||||
);
|
||||
|
||||
transfer->status = 0;
|
||||
|
||||
res = usb_submit_urb( transfer, GFP_ATOMIC );
|
||||
if ( unlikely(res) ) {
|
||||
MTS_INT_ERROR( "could not submit URB! Error was %d\n",(int)res );
|
||||
context->srb->result = DID_ERROR << 16;
|
||||
mts_transfer_cleanup(transfer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void mts_transfer_cleanup( struct urb *transfer )
|
||||
/* Interrupt context! */
|
||||
{
|
||||
MTS_INT_INIT();
|
||||
|
||||
if ( likely(context->final_callback != NULL) )
|
||||
context->final_callback(context->srb);
|
||||
|
||||
}
|
||||
|
||||
static void mts_transfer_done( struct urb *transfer, struct pt_regs *regs )
|
||||
{
|
||||
MTS_INT_INIT();
|
||||
|
||||
context->srb->result &= MTS_SCSI_ERR_MASK;
|
||||
context->srb->result |= (unsigned)context->status<<1;
|
||||
|
||||
mts_transfer_cleanup(transfer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void mts_get_status( struct urb *transfer )
|
||||
/* Interrupt context! */
|
||||
{
|
||||
MTS_INT_INIT();
|
||||
|
||||
mts_int_submit_urb(transfer,
|
||||
usb_rcvbulkpipe(context->instance->usb_dev,
|
||||
context->instance->ep_response),
|
||||
&context->status,
|
||||
1,
|
||||
mts_transfer_done );
|
||||
}
|
||||
|
||||
static void mts_data_done( struct urb* transfer, struct pt_regs *regs )
|
||||
/* Interrupt context! */
|
||||
{
|
||||
MTS_INT_INIT();
|
||||
|
||||
if ( context->data_length != transfer->actual_length ) {
|
||||
context->srb->resid = context->data_length - transfer->actual_length;
|
||||
} else if ( unlikely(transfer->status) ) {
|
||||
context->srb->result = (transfer->status == -ENOENT ? DID_ABORT : DID_ERROR)<<16;
|
||||
}
|
||||
|
||||
mts_get_status(transfer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void mts_command_done( struct urb *transfer, struct pt_regs *regs )
|
||||
/* Interrupt context! */
|
||||
{
|
||||
MTS_INT_INIT();
|
||||
|
||||
if ( unlikely(transfer->status) ) {
|
||||
if (transfer->status == -ENOENT) {
|
||||
/* We are being killed */
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
context->srb->result = DID_ABORT<<16;
|
||||
} else {
|
||||
/* A genuine error has occurred */
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
|
||||
context->srb->result = DID_ERROR<<16;
|
||||
}
|
||||
mts_transfer_cleanup(transfer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->srb->cmnd[0] == REQUEST_SENSE) {
|
||||
mts_int_submit_urb(transfer,
|
||||
context->data_pipe,
|
||||
context->srb->sense_buffer,
|
||||
context->data_length,
|
||||
mts_data_done);
|
||||
} else { if ( context->data ) {
|
||||
mts_int_submit_urb(transfer,
|
||||
context->data_pipe,
|
||||
context->data,
|
||||
context->data_length,
|
||||
context->srb->use_sg ? mts_do_sg : mts_data_done);
|
||||
} else {
|
||||
mts_get_status(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void mts_do_sg (struct urb* transfer, struct pt_regs *regs)
|
||||
{
|
||||
struct scatterlist * sg;
|
||||
MTS_INT_INIT();
|
||||
|
||||
MTS_DEBUG("Processing fragment %d of %d\n", context->fragment,context->srb->use_sg);
|
||||
|
||||
if (unlikely(transfer->status)) {
|
||||
context->srb->result = (transfer->status == -ENOENT ? DID_ABORT : DID_ERROR)<<16;
|
||||
mts_transfer_cleanup(transfer);
|
||||
}
|
||||
|
||||
sg = context->srb->buffer;
|
||||
context->fragment++;
|
||||
mts_int_submit_urb(transfer,
|
||||
context->data_pipe,
|
||||
page_address(sg[context->fragment].page) +
|
||||
sg[context->fragment].offset,
|
||||
sg[context->fragment].length,
|
||||
context->fragment + 1 == context->srb->use_sg ? mts_data_done : mts_do_sg);
|
||||
return;
|
||||
}
|
||||
|
||||
static const u8 mts_read_image_sig[] = { 0x28, 00, 00, 00 };
|
||||
static const u8 mts_read_image_sig_len = 4;
|
||||
static const unsigned char mts_direction[256/8] = {
|
||||
0x28, 0x81, 0x14, 0x14, 0x20, 0x01, 0x90, 0x77,
|
||||
0x0C, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
|
||||
#define MTS_DIRECTION_IS_IN(x) ((mts_direction[x>>3] >> (x & 7)) & 1)
|
||||
|
||||
static void
|
||||
mts_build_transfer_context( Scsi_Cmnd *srb, struct mts_desc* desc )
|
||||
{
|
||||
int pipe;
|
||||
struct scatterlist * sg;
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
|
||||
desc->context.instance = desc;
|
||||
desc->context.srb = srb;
|
||||
desc->context.fragment = 0;
|
||||
|
||||
if (!srb->use_sg) {
|
||||
if ( !srb->bufflen ){
|
||||
desc->context.data = NULL;
|
||||
desc->context.data_length = 0;
|
||||
return;
|
||||
} else {
|
||||
desc->context.data = srb->buffer;
|
||||
desc->context.data_length = srb->bufflen;
|
||||
MTS_DEBUG("length = %d or %d\n",
|
||||
srb->request_bufflen, srb->bufflen);
|
||||
}
|
||||
} else {
|
||||
MTS_DEBUG("Using scatter/gather\n");
|
||||
sg = srb->buffer;
|
||||
desc->context.data = page_address(sg[0].page) + sg[0].offset;
|
||||
desc->context.data_length = sg[0].length;
|
||||
}
|
||||
|
||||
|
||||
/* can't rely on srb->sc_data_direction */
|
||||
|
||||
/* Brutally ripped from usb-storage */
|
||||
|
||||
if ( !memcmp( srb->cmnd, mts_read_image_sig, mts_read_image_sig_len )
|
||||
) { pipe = usb_rcvbulkpipe(desc->usb_dev,desc->ep_image);
|
||||
MTS_DEBUG( "transfering from desc->ep_image == %d\n",
|
||||
(int)desc->ep_image );
|
||||
} else if ( MTS_DIRECTION_IS_IN(srb->cmnd[0]) ) {
|
||||
pipe = usb_rcvbulkpipe(desc->usb_dev,desc->ep_response);
|
||||
MTS_DEBUG( "transfering from desc->ep_response == %d\n",
|
||||
(int)desc->ep_response);
|
||||
} else {
|
||||
MTS_DEBUG("transfering to desc->ep_out == %d\n",
|
||||
(int)desc->ep_out);
|
||||
pipe = usb_sndbulkpipe(desc->usb_dev,desc->ep_out);
|
||||
}
|
||||
desc->context.data_pipe = pipe;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
int mts_scsi_queuecommand( Scsi_Cmnd *srb, mts_scsi_cmnd_callback callback )
|
||||
{
|
||||
struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]);
|
||||
int err = 0;
|
||||
int res;
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
mts_show_command(srb);
|
||||
mts_debug_dump(desc);
|
||||
|
||||
if ( srb->device->lun || srb->device->id || srb->device->channel ) {
|
||||
|
||||
MTS_DEBUG("Command to LUN=%d ID=%d CHANNEL=%d from SCSI layer\n",(int)srb->device->lun,(int)srb->device->id, (int)srb->device->channel );
|
||||
|
||||
MTS_DEBUG("this device doesn't exist\n");
|
||||
|
||||
srb->result = DID_BAD_TARGET << 16;
|
||||
|
||||
if(likely(callback != NULL))
|
||||
callback(srb);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
usb_fill_bulk_urb(desc->urb,
|
||||
desc->usb_dev,
|
||||
usb_sndbulkpipe(desc->usb_dev,desc->ep_out),
|
||||
srb->cmnd,
|
||||
srb->cmd_len,
|
||||
mts_command_done,
|
||||
&desc->context
|
||||
);
|
||||
|
||||
|
||||
mts_build_transfer_context( srb, desc );
|
||||
desc->context.final_callback = callback;
|
||||
|
||||
/* here we need ATOMIC as we are called with the iolock */
|
||||
res=usb_submit_urb(desc->urb, GFP_ATOMIC);
|
||||
|
||||
if(unlikely(res)){
|
||||
MTS_ERROR("error %d submitting URB\n",(int)res);
|
||||
srb->result = DID_ERROR << 16;
|
||||
|
||||
if(likely(callback != NULL))
|
||||
callback(srb);
|
||||
|
||||
}
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static Scsi_Host_Template mts_scsi_host_template = {
|
||||
.module = THIS_MODULE,
|
||||
.name = "microtekX6",
|
||||
.proc_name = "microtekX6",
|
||||
.queuecommand = mts_scsi_queuecommand,
|
||||
.eh_abort_handler = mts_scsi_abort,
|
||||
.eh_host_reset_handler = mts_scsi_host_reset,
|
||||
.sg_tablesize = SG_ALL,
|
||||
.can_queue = 1,
|
||||
.this_id = -1,
|
||||
.cmd_per_lun = 1,
|
||||
.use_clustering = 1,
|
||||
.emulated = 1,
|
||||
};
|
||||
|
||||
struct vendor_product
|
||||
{
|
||||
char* name;
|
||||
enum
|
||||
{
|
||||
mts_sup_unknown=0,
|
||||
mts_sup_alpha,
|
||||
mts_sup_full
|
||||
}
|
||||
support_status;
|
||||
} ;
|
||||
|
||||
|
||||
/* These are taken from the msmUSB.inf file on the Windows driver CD */
|
||||
const static struct vendor_product mts_supported_products[] =
|
||||
{
|
||||
{ "Phantom 336CX", mts_sup_unknown},
|
||||
{ "Phantom 336CX", mts_sup_unknown},
|
||||
{ "Scanmaker X6", mts_sup_alpha},
|
||||
{ "Phantom C6", mts_sup_unknown},
|
||||
{ "Phantom 336CX", mts_sup_unknown},
|
||||
{ "ScanMaker V6USL", mts_sup_unknown},
|
||||
{ "ScanMaker V6USL", mts_sup_unknown},
|
||||
{ "Scanmaker V6UL", mts_sup_unknown},
|
||||
{ "Scanmaker V6UPL", mts_sup_alpha},
|
||||
};
|
||||
|
||||
/* The entries of microtek_table must correspond, line-by-line to
|
||||
the entries of mts_supported_products[]. */
|
||||
|
||||
static struct usb_device_id mts_usb_ids [] =
|
||||
{
|
||||
{ USB_DEVICE(0x4ce, 0x0300) },
|
||||
{ USB_DEVICE(0x5da, 0x0094) },
|
||||
{ USB_DEVICE(0x5da, 0x0099) },
|
||||
{ USB_DEVICE(0x5da, 0x009a) },
|
||||
{ USB_DEVICE(0x5da, 0x00a0) },
|
||||
{ USB_DEVICE(0x5da, 0x00a3) },
|
||||
{ USB_DEVICE(0x5da, 0x80a3) },
|
||||
{ USB_DEVICE(0x5da, 0x80ac) },
|
||||
{ USB_DEVICE(0x5da, 0x00b6) },
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE (usb, mts_usb_ids);
|
||||
|
||||
|
||||
static int mts_usb_probe(struct usb_interface *intf,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
int i;
|
||||
int ep_out = -1;
|
||||
int ep_in_set[3]; /* this will break if we have more than three endpoints
|
||||
which is why we check */
|
||||
int *ep_in_current = ep_in_set;
|
||||
int err_retval = -ENOMEM;
|
||||
|
||||
struct mts_desc * new_desc;
|
||||
struct vendor_product const* p;
|
||||
struct usb_device *dev = interface_to_usbdev (intf);
|
||||
|
||||
/* the current altsetting on the interface we're probing */
|
||||
struct usb_host_interface *altsetting;
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
MTS_DEBUG( "usb-device descriptor at %x\n", (int)dev );
|
||||
|
||||
MTS_DEBUG( "product id = 0x%x, vendor id = 0x%x\n",
|
||||
le16_to_cpu(dev->descriptor.idProduct),
|
||||
le16_to_cpu(dev->descriptor.idVendor) );
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
|
||||
p = &mts_supported_products[id - mts_usb_ids];
|
||||
|
||||
MTS_DEBUG_GOT_HERE();
|
||||
|
||||
MTS_DEBUG( "found model %s\n", p->name );
|
||||
if ( p->support_status != mts_sup_full )
|
||||
MTS_MESSAGE( "model %s is not known to be fully supported, reports welcome!\n",
|
||||
p->name );
|
||||
|
||||
/* the current altsetting on the interface we're probing */
|
||||
altsetting = intf->cur_altsetting;
|
||||
|
||||
|
||||
/* Check if the config is sane */
|
||||
|
||||
if ( altsetting->desc.bNumEndpoints != MTS_EP_TOTAL ) {
|
||||
MTS_WARNING( "expecting %d got %d endpoints! Bailing out.\n",
|
||||
(int)MTS_EP_TOTAL, (int)altsetting->desc.bNumEndpoints );
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for( i = 0; i < altsetting->desc.bNumEndpoints; i++ ) {
|
||||
if ((altsetting->endpoint[i].desc.bmAttributes &
|
||||
USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) {
|
||||
|
||||
MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n",
|
||||
(int)altsetting->endpoint[i].desc.bEndpointAddress );
|
||||
} else {
|
||||
if (altsetting->endpoint[i].desc.bEndpointAddress &
|
||||
USB_DIR_IN)
|
||||
*ep_in_current++
|
||||
= altsetting->endpoint[i].desc.bEndpointAddress &
|
||||
USB_ENDPOINT_NUMBER_MASK;
|
||||
else {
|
||||
if ( ep_out != -1 ) {
|
||||
MTS_WARNING( "can only deal with one output endpoints. Bailing out." );
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ep_out = altsetting->endpoint[i].desc.bEndpointAddress &
|
||||
USB_ENDPOINT_NUMBER_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( ep_out == -1 ) {
|
||||
MTS_WARNING( "couldn't find an output bulk endpoint. Bailing out.\n" );
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
||||
new_desc = kmalloc(sizeof(struct mts_desc), GFP_KERNEL);
|
||||
if (!new_desc)
|
||||
goto out;
|
||||
|
||||
memset(new_desc, 0, sizeof(*new_desc));
|
||||
new_desc->urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!new_desc->urb)
|
||||
goto out_kfree;
|
||||
|
||||
new_desc->usb_dev = dev;
|
||||
new_desc->usb_intf = intf;
|
||||
init_MUTEX(&new_desc->lock);
|
||||
|
||||
/* endpoints */
|
||||
new_desc->ep_out = ep_out;
|
||||
new_desc->ep_response = ep_in_set[0];
|
||||
new_desc->ep_image = ep_in_set[1];
|
||||
|
||||
if ( new_desc->ep_out != MTS_EP_OUT )
|
||||
MTS_WARNING( "will this work? Command EP is not usually %d\n",
|
||||
(int)new_desc->ep_out );
|
||||
|
||||
if ( new_desc->ep_response != MTS_EP_RESPONSE )
|
||||
MTS_WARNING( "will this work? Response EP is not usually %d\n",
|
||||
(int)new_desc->ep_response );
|
||||
|
||||
if ( new_desc->ep_image != MTS_EP_IMAGE )
|
||||
MTS_WARNING( "will this work? Image data EP is not usually %d\n",
|
||||
(int)new_desc->ep_image );
|
||||
|
||||
new_desc->host = scsi_host_alloc(&mts_scsi_host_template,
|
||||
sizeof(new_desc));
|
||||
if (!new_desc->host)
|
||||
goto out_free_urb;
|
||||
|
||||
new_desc->host->hostdata[0] = (unsigned long)new_desc;
|
||||
if (scsi_add_host(new_desc->host, NULL)) {
|
||||
err_retval = -EIO;
|
||||
goto out_free_urb;
|
||||
}
|
||||
scsi_scan_host(new_desc->host);
|
||||
|
||||
usb_set_intfdata(intf, new_desc);
|
||||
return 0;
|
||||
|
||||
out_free_urb:
|
||||
usb_free_urb(new_desc->urb);
|
||||
out_kfree:
|
||||
kfree(new_desc);
|
||||
out:
|
||||
return err_retval;
|
||||
}
|
||||
|
||||
static void mts_usb_disconnect (struct usb_interface *intf)
|
||||
{
|
||||
struct mts_desc *desc = usb_get_intfdata(intf);
|
||||
|
||||
usb_set_intfdata(intf, NULL);
|
||||
|
||||
usb_kill_urb(desc->urb);
|
||||
scsi_remove_host(desc->host);
|
||||
|
||||
scsi_host_put(desc->host);
|
||||
usb_free_urb(desc->urb);
|
||||
kfree(desc);
|
||||
}
|
||||
|
||||
|
||||
static int __init microtek_drv_init(void)
|
||||
{
|
||||
return usb_register(&mts_usb_driver);
|
||||
}
|
||||
|
||||
static void __exit microtek_drv_exit(void)
|
||||
{
|
||||
usb_deregister(&mts_usb_driver);
|
||||
}
|
||||
|
||||
module_init(microtek_drv_init);
|
||||
module_exit(microtek_drv_exit);
|
||||
|
||||
MODULE_AUTHOR( DRIVER_AUTHOR );
|
||||
MODULE_DESCRIPTION( DRIVER_DESC );
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
55
drivers/usb/image/microtek.h
Normal file
55
drivers/usb/image/microtek.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Driver for Microtek Scanmaker X6 USB scanner and possibly others.
|
||||
*
|
||||
* (C) Copyright 2000 John Fremlin <vii@penguinpowered.com>
|
||||
* (C) Copyright 2000 Oliver Neukum <Oliver.Neukum@lrz.uni-muenchen.de>
|
||||
*
|
||||
* See microtek.c for history
|
||||
*
|
||||
*/
|
||||
|
||||
typedef void (*mts_scsi_cmnd_callback)(Scsi_Cmnd *);
|
||||
|
||||
|
||||
struct mts_transfer_context
|
||||
{
|
||||
struct mts_desc* instance;
|
||||
mts_scsi_cmnd_callback final_callback;
|
||||
Scsi_Cmnd *srb;
|
||||
|
||||
void* data;
|
||||
unsigned data_length;
|
||||
int data_pipe;
|
||||
int fragment;
|
||||
|
||||
u8 status; /* status returned from ep_response after command completion */
|
||||
};
|
||||
|
||||
|
||||
struct mts_desc {
|
||||
struct mts_desc *next;
|
||||
struct mts_desc *prev;
|
||||
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *usb_intf;
|
||||
|
||||
/* Endpoint addresses */
|
||||
u8 ep_out;
|
||||
u8 ep_response;
|
||||
u8 ep_image;
|
||||
|
||||
struct Scsi_Host * host;
|
||||
struct semaphore lock;
|
||||
|
||||
struct urb *urb;
|
||||
struct mts_transfer_context context;
|
||||
};
|
||||
|
||||
|
||||
#define MTS_EP_OUT 0x1
|
||||
#define MTS_EP_RESPONSE 0x2
|
||||
#define MTS_EP_IMAGE 0x3
|
||||
#define MTS_EP_TOTAL 0x3
|
||||
|
||||
#define MTS_SCSI_ERR_MASK ~0x3fu
|
||||
|
||||
237
drivers/usb/input/Kconfig
Normal file
237
drivers/usb/input/Kconfig
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
#
|
||||
# USB Input driver configuration
|
||||
#
|
||||
comment "USB Input Devices"
|
||||
depends on USB
|
||||
|
||||
config USB_HID
|
||||
tristate "USB Human Interface Device (full HID) support"
|
||||
depends on USB
|
||||
---help---
|
||||
Say Y here if you want full HID support to connect keyboards,
|
||||
mice, joysticks, graphic tablets, or any other HID based devices
|
||||
to your computer via USB. You also need to select HID Input layer
|
||||
support (below) if you want to use keyboards, mice, joysticks and
|
||||
the like ... as well as Uninterruptible Power Supply (UPS) and
|
||||
monitor control devices.
|
||||
|
||||
You can't use this driver and the HIDBP (Boot Protocol) keyboard
|
||||
and mouse drivers at the same time. More information is available:
|
||||
<file:Documentation/input/input.txt>.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usbhid.
|
||||
|
||||
comment "Input core support is needed for USB HID input layer or HIDBP support"
|
||||
depends on USB_HID && INPUT=n
|
||||
|
||||
config USB_HIDINPUT
|
||||
bool "HID input layer support"
|
||||
default y
|
||||
depends on INPUT && USB_HID
|
||||
help
|
||||
Say Y here if you want to use a USB keyboard, mouse or joystick,
|
||||
or any other HID input device.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config HID_FF
|
||||
bool "Force feedback support (EXPERIMENTAL)"
|
||||
depends on USB_HIDINPUT && EXPERIMENTAL
|
||||
help
|
||||
Say Y here is you want force feedback support for a few HID devices.
|
||||
See below for a list of supported devices.
|
||||
|
||||
See <file:Documentation/input/ff.txt> for a description of the force
|
||||
feedback API.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config HID_PID
|
||||
bool "PID Devices (Microsoft Sidewinder Force Feedback 2)"
|
||||
depends on HID_FF
|
||||
help
|
||||
Say Y here if you have a PID-compliant joystick and wish to enable force
|
||||
feedback for it. The Microsoft Sidewinder Force Feedback 2 is one such
|
||||
device.
|
||||
|
||||
config LOGITECH_FF
|
||||
bool "Logitech WingMan *3D support"
|
||||
depends on HID_FF
|
||||
help
|
||||
Say Y here if you have one of these devices:
|
||||
- Logitech WingMan Cordless RumblePad
|
||||
- Logitech WingMan Force 3D
|
||||
and if you want to enable force feedback for them.
|
||||
Note: if you say N here, this device will still be supported, but without
|
||||
force feedback.
|
||||
|
||||
config THRUSTMASTER_FF
|
||||
bool "ThrustMaster FireStorm Dual Power 2 support (EXPERIMENTAL)"
|
||||
depends on HID_FF && EXPERIMENTAL
|
||||
help
|
||||
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2,
|
||||
and want to enable force feedback support for it.
|
||||
Note: if you say N here, this device will still be supported, but without
|
||||
force feedback.
|
||||
|
||||
config USB_HIDDEV
|
||||
bool "/dev/hiddev raw HID device support"
|
||||
depends on USB_HID
|
||||
help
|
||||
Say Y here if you want to support HID devices (from the USB
|
||||
specification standpoint) that aren't strictly user interface
|
||||
devices, like monitor controls and Uninterruptable Power Supplies.
|
||||
|
||||
This module supports these devices separately using a separate
|
||||
event interface on /dev/usb/hiddevX (char 180:96 to 180:111).
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
menu "USB HID Boot Protocol drivers"
|
||||
depends on USB!=n && USB_HID!=y
|
||||
|
||||
config USB_KBD
|
||||
tristate "USB HIDBP Keyboard (simple Boot) support"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here only if you are absolutely sure that you don't want
|
||||
to use the generic HID driver for your USB keyboard and prefer
|
||||
to use the keyboard in its limited Boot Protocol mode instead.
|
||||
|
||||
This is almost certainly not what you want. This is mostly
|
||||
useful for embedded applications or simple keyboards.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usbkbd.
|
||||
|
||||
If even remotely unsure, say N.
|
||||
|
||||
config USB_MOUSE
|
||||
tristate "USB HIDBP Mouse (simple Boot) support"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here only if you are absolutely sure that you don't want
|
||||
to use the generic HID driver for your USB mouse and prefer
|
||||
to use the mouse in its limited Boot Protocol mode instead.
|
||||
|
||||
This is almost certainly not what you want. This is mostly
|
||||
useful for embedded applications or simple mice.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called usbmouse.
|
||||
|
||||
If even remotely unsure, say N.
|
||||
|
||||
endmenu
|
||||
|
||||
config USB_AIPTEK
|
||||
tristate "Aiptek 6000U/8000U tablet support"
|
||||
depends on USB && INPUT
|
||||
help
|
||||
Say Y here if you want to use the USB version of the Aiptek 6000U
|
||||
or Aiptek 8000U tablet. Make sure to say Y to "Mouse support"
|
||||
(CONFIG_INPUT_MOUSEDEV) and/or "Event interface support"
|
||||
(CONFIG_INPUT_EVDEV) as well.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called aiptek.
|
||||
|
||||
config USB_WACOM
|
||||
tristate "Wacom Intuos/Graphire tablet support"
|
||||
depends on USB && INPUT
|
||||
help
|
||||
Say Y here if you want to use the USB version of the Wacom Intuos
|
||||
or Graphire tablet. Make sure to say Y to "Mouse support"
|
||||
(CONFIG_INPUT_MOUSEDEV) and/or "Event interface support"
|
||||
(CONFIG_INPUT_EVDEV) as well.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called wacom.
|
||||
|
||||
config USB_KBTAB
|
||||
tristate "KB Gear JamStudio tablet support"
|
||||
depends on USB && INPUT
|
||||
help
|
||||
Say Y here if you want to use the USB version of the KB Gear
|
||||
JamStudio tablet. Make sure to say Y to "Mouse support"
|
||||
(CONFIG_INPUT_MOUSEDEV) and/or "Event interface support"
|
||||
(CONFIG_INPUT_EVDEV) as well.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called kbtab.
|
||||
|
||||
config USB_POWERMATE
|
||||
tristate "Griffin PowerMate and Contour Jog support"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here if you want to use Griffin PowerMate or Contour Jog devices.
|
||||
These are aluminum dials which can measure clockwise and anticlockwise
|
||||
rotation. The dial also acts as a pushbutton. The base contains an LED
|
||||
which can be instructed to pulse or to switch to a particular intensity.
|
||||
|
||||
You can download userspace tools from
|
||||
<http://sowerbutts.com/powermate/>.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called powermate.
|
||||
|
||||
config USB_MTOUCH
|
||||
tristate "MicroTouch USB Touchscreen Driver"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here if you want to use a MicroTouch (Now 3M) USB
|
||||
Touchscreen controller.
|
||||
|
||||
See <file:Documentation/usb/mtouch.txt> for additional information.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called mtouchusb.
|
||||
|
||||
config USB_EGALAX
|
||||
tristate "eGalax TouchKit USB Touchscreen Driver"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here if you want to use a eGalax TouchKit USB
|
||||
Touchscreen controller.
|
||||
|
||||
The driver has been tested on a Xenarc 700TSV monitor
|
||||
with eGalax touchscreen.
|
||||
|
||||
Have a look at <http://linux.chapter7.ch/touchkit/> for
|
||||
a usage description and the required user-space stuff.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called touchkitusb.
|
||||
|
||||
config USB_XPAD
|
||||
tristate "X-Box gamepad support"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here if you want to use the X-Box pad with your computer.
|
||||
Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
|
||||
and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
|
||||
|
||||
For information about how to connect the X-Box pad to USB, see
|
||||
<file:Documentation/input/xpad.txt>.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called xpad.
|
||||
|
||||
config USB_ATI_REMOTE
|
||||
tristate "ATI / X10 USB RF remote control"
|
||||
depends on USB && INPUT
|
||||
---help---
|
||||
Say Y here if you want to use an ATI or X10 "Lola" USB remote control.
|
||||
These are RF remotes with USB receivers.
|
||||
The ATI remote comes with many of ATI's All-In-Wonder video cards.
|
||||
The X10 "Lola" remote is available at:
|
||||
<http://www.x10.com/products/lola_sg1.htm>
|
||||
This driver provides mouse pointer, left and right mouse buttons,
|
||||
and maps all the other remote buttons to keypress events.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called ati_remote.
|
||||
|
||||
39
drivers/usb/input/Makefile
Normal file
39
drivers/usb/input/Makefile
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Makefile for the USB input drivers
|
||||
#
|
||||
|
||||
# Multipart objects.
|
||||
usbhid-objs := hid-core.o
|
||||
|
||||
# Optional parts of multipart objects.
|
||||
|
||||
ifeq ($(CONFIG_USB_HIDDEV),y)
|
||||
usbhid-objs += hiddev.o
|
||||
endif
|
||||
ifeq ($(CONFIG_USB_HIDINPUT),y)
|
||||
usbhid-objs += hid-input.o
|
||||
endif
|
||||
ifeq ($(CONFIG_HID_PID),y)
|
||||
usbhid-objs += pid.o
|
||||
endif
|
||||
ifeq ($(CONFIG_LOGITECH_FF),y)
|
||||
usbhid-objs += hid-lgff.o
|
||||
endif
|
||||
ifeq ($(CONFIG_THRUSTMASTER_FF),y)
|
||||
usbhid-objs += hid-tmff.o
|
||||
endif
|
||||
ifeq ($(CONFIG_HID_FF),y)
|
||||
usbhid-objs += hid-ff.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_USB_AIPTEK) += aiptek.o
|
||||
obj-$(CONFIG_USB_ATI_REMOTE) += ati_remote.o
|
||||
obj-$(CONFIG_USB_HID) += usbhid.o
|
||||
obj-$(CONFIG_USB_KBD) += usbkbd.o
|
||||
obj-$(CONFIG_USB_KBTAB) += kbtab.o
|
||||
obj-$(CONFIG_USB_MOUSE) += usbmouse.o
|
||||
obj-$(CONFIG_USB_MTOUCH) += mtouchusb.o
|
||||
obj-$(CONFIG_USB_EGALAX) += touchkitusb.o
|
||||
obj-$(CONFIG_USB_POWERMATE) += powermate.o
|
||||
obj-$(CONFIG_USB_WACOM) += wacom.o
|
||||
obj-$(CONFIG_USB_XPAD) += xpad.o
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue