Creation of Cybook 2416 (actually Gen4) repository

This commit is contained in:
mlt
2009-12-18 17:10:00 +00:00
committed by godzil
commit 76f20f4d40
13791 changed files with 6812321 additions and 0 deletions

232
drivers/usb/host/Kconfig Normal file
View File

@@ -0,0 +1,232 @@
#
# 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 && USB_ARCH_HAS_EHCI
---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 the time of 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 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 that's originally
from ARC, and has since changed hands a few times.
config USB_EHCI_TT_NEWSCHED
bool "Improved Transaction Translator scheduling (EXPERIMENTAL)"
depends on USB_EHCI_HCD && EXPERIMENTAL
---help---
This changes the periodic scheduling code to fill more of the low
and full speed bandwidth available from the Transaction Translator
(TT) in USB 2.0 hubs. Without this, only one transfer will be
issued in each microframe, significantly reducing the number of
periodic low/fullspeed transfers possible.
If you have multiple periodic low/fullspeed devices connected to a
highspeed USB hub which is connected to a highspeed USB Host
Controller, and some of those devices will not work correctly
(possibly due to "ENOSPC" or "-28" errors), say Y.
If unsure, say N.
config USB_EHCI_BIG_ENDIAN_MMIO
bool
depends on USB_EHCI_HCD
default n
config USB_ISP116X_HCD
tristate "ISP116X HCD support"
depends on USB
---help---
The ISP1160 and ISP1161 chips are USB host controllers. Enable this
option if your board has this chip. If unsure, say N.
This driver does not support isochronous transfers.
To compile this driver as a module, choose M here: the
module will be called isp116x-hcd.
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
select I2C if ARCH_PNX4008
---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_DESC
select USB_OHCI_BIG_ENDIAN_MMIO
---help---
Enables support for the USB controller on the MPC52xx or
STB03xxx processor chip. If unsure, say Y.
config USB_OHCI_HCD_PPC_OF
bool "OHCI support for PPC USB controller on OF platform bus"
depends on USB_OHCI_HCD && PPC_OF
default y
---help---
Enables support for the USB controller PowerPC present on the
OpenFirmware platform bus.
config USB_OHCI_HCD_PPC_OF_BE
bool "Support big endian HC"
depends on USB_OHCI_HCD_PPC_OF
default y
select USB_OHCI_BIG_ENDIAN_DESC
select USB_OHCI_BIG_ENDIAN_MMIO
config USB_OHCI_HCD_PPC_OF_LE
bool "Support little endian HC"
depends on USB_OHCI_HCD_PPC_OF
default n
select USB_OHCI_LITTLE_ENDIAN
config USB_OHCI_HCD_PCI
bool "OHCI support for PCI-bus USB controllers"
depends on USB_OHCI_HCD && PCI && (STB03xxx || PPC_MPC52xx || USB_OHCI_HCD_PPC_OF)
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_DESC
bool
depends on USB_OHCI_HCD
default n
config USB_OHCI_BIG_ENDIAN_MMIO
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_U132_HCD
tristate "Elan U132 Adapter Host Controller"
depends on USB && USB_FTDI_ELAN
default M
help
The U132 adapter is a USB to CardBus adapter specifically designed
for PC cards that contain an OHCI host controller. Typical PC cards
are the Orange Mobile 3G Option GlobeTrotter Fusion card. The U132
adapter will *NOT* work with PC cards that do not contain an OHCI
controller.
For those PC cards that contain multiple OHCI controllers only the
first one is used.
The driver consists of two modules, the "ftdi-elan" module is a
USB client driver that interfaces to the FTDI chip within ELAN's
USB-to-PCMCIA adapter, and this "u132-hcd" module is a USB host
controller driver that talks to the OHCI controller within the
CardBus cards that are inserted in the U132 adapter.
This driver has been tested with a CardBus OHCI USB adapter, and
worked with a USB PEN Drive inserted into the first USB port of
the PCCARD. A rather pointless thing to do, but useful for testing.
It is safe to say M here.
See also <http://www.elandigitalsystems.com/support/ufaq/u132linux.php>
config USB_SL811_HCD
tristate "SL811HS HCD support"
depends on USB
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.
config USB_OTG_HOST
tristate "USB OTG Host support"
depends on USB
help
S3C6400 OTG Host.
config USB_SL811_CS
tristate "CF/PCMCIA support for SL811HS HCD"
depends on USB_SL811_HCD && PCMCIA
help
Wraps a PCMCIA driver around the SL811HS HCD, supporting the RATOC
REX-CFU1U CF card (often used with PDAs). If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called "sl811_cs".

19
drivers/usb/host/Makefile Normal file
View File

@@ -0,0 +1,19 @@
#
# Makefile for USB Host Controller Drivers
#
ifeq ($(CONFIG_USB_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG
endif
obj-$(CONFIG_PCI) += pci-quirks.o
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-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_USB_SL811_CS) += sl811_cs.o
obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o
obj-$(CONFIG_ETRAX_ARCH_V10) += hc_crisv10.o
obj-$(CONFIG_USB_OTG_HOST) += usb_otg/

View File

@@ -0,0 +1,279 @@
/*
* EHCI HCD (Host Controller Driver) for USB.
*
* (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net>
*
* Bus Glue for AMD Alchemy Au1xxx
*
* Based on "ohci-au1xxx.c" by Matt Porter <mporter@kernel.crashing.org>
*
* Modified for AMD Alchemy Au1200 EHC
* by K.Boge <karsten.boge@amd.com>
*
* This file is licenced under the GPL.
*/
#include <linux/platform_device.h>
#include <asm/mach-au1x00/au1000.h>
#define USB_HOST_CONFIG (USB_MSR_BASE + USB_MSR_MCFG)
#define USB_MCFG_PFEN (1<<31)
#define USB_MCFG_RDCOMB (1<<30)
#define USB_MCFG_SSDEN (1<<23)
#define USB_MCFG_PHYPLLEN (1<<19)
#define USB_MCFG_EHCCLKEN (1<<17)
#define USB_MCFG_UCAM (1<<7)
#define USB_MCFG_EBMEN (1<<3)
#define USB_MCFG_EMEMEN (1<<2)
#define USBH_ENABLE_CE (USB_MCFG_PHYPLLEN | USB_MCFG_EHCCLKEN)
#ifdef CONFIG_DMA_COHERENT
#define USBH_ENABLE_INIT (USBH_ENABLE_CE \
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
| USB_MCFG_SSDEN | USB_MCFG_UCAM \
| USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
#else
#define USBH_ENABLE_INIT (USBH_ENABLE_CE \
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
| USB_MCFG_SSDEN \
| USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
#endif
#define USBH_DISABLE (USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
extern int usb_disabled(void);
/*-------------------------------------------------------------------------*/
static void au1xxx_start_ehc(struct platform_device *dev)
{
pr_debug(__FILE__ ": starting Au1xxx EHCI USB Controller\n");
/* write HW defaults again in case Yamon cleared them */
if (au_readl(USB_HOST_CONFIG) == 0) {
au_writel(0x00d02000, USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
udelay(1000);
}
/* enable host controller */
au_writel(USBH_ENABLE_CE | au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
udelay(1000);
au_writel(USBH_ENABLE_INIT | au_readl(USB_HOST_CONFIG),
USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
udelay(1000);
pr_debug(__FILE__ ": Clock to USB host has been enabled\n");
}
static void au1xxx_stop_ehc(struct platform_device *dev)
{
pr_debug(__FILE__ ": stopping Au1xxx EHCI USB Controller\n");
/* Disable mem */
au_writel(~USBH_DISABLE & au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
udelay(1000);
/* Disable clock */
au_writel(~USB_MCFG_EHCCLKEN & au_readl(USB_HOST_CONFIG),
USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
}
/*-------------------------------------------------------------------------*/
/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */
/**
* usb_ehci_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_ehci_au1xxx_probe(const struct hc_driver *driver,
struct usb_hcd **hcd_out, struct platform_device *dev)
{
int retval;
struct usb_hcd *hcd;
struct ehci_hcd *ehci;
#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
/* Au1200 AB USB does not support coherent memory */
if (!(read_c0_prid() & 0xff)) {
pr_info("%s: this is chip revision AB!\n", dev->name);
pr_info("%s: update your board or re-configure the kernel\n",
dev->name);
return -ENODEV;
}
#endif
au1xxx_start_ehc(dev);
if (dev->resource[1].flags != IORESOURCE_IRQ) {
pr_debug("resource[1] is not IORESOURCE_IRQ");
retval = -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;
}
ehci = hcd_to_ehci(hcd);
ehci->caps = hcd->regs;
ehci->regs = hcd->regs + HC_LENGTH(readl(&ehci->caps->hc_capbase));
/* cache this readonly data; minimize chip reads */
ehci->hcs_params = readl(&ehci->caps->hcs_params);
/* ehci_hcd_init(hcd_to_ehci(hcd)); */
retval =
usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED | IRQF_SHARED);
if (retval == 0)
return retval;
au1xxx_stop_ehc(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_ehci_hcd_au1xxx_remove - shutdown processing for Au1xxx-based HCDs
* @dev: USB Host Controller being removed
* Context: !in_interrupt()
*
* Reverses the effect of usb_ehci_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_ehci_au1xxx_remove(struct usb_hcd *hcd, struct platform_device *dev)
{
usb_remove_hcd(hcd);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
au1xxx_stop_ehc(dev);
}
/*-------------------------------------------------------------------------*/
static const struct hc_driver ehci_au1xxx_hc_driver = {
.description = hcd_name,
.product_desc = "Au1xxx EHCI",
.hcd_priv_size = sizeof(struct ehci_hcd),
/*
* generic hardware linkage
*/
.irq = ehci_irq,
.flags = HCD_MEMORY | HCD_USB2,
/*
* basic lifecycle operations
*/
.reset = ehci_init,
.start = ehci_run,
.stop = ehci_stop,
.shutdown = ehci_shutdown,
/*
* managing i/o requests and associated device resources
*/
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
/*
* scheduling support
*/
.get_frame_number = ehci_get_frame,
/*
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
#ifdef CONFIG_PM
.hub_suspend = ehci_hub_suspend,
.hub_resume = ehci_hub_resume,
#endif
};
/*-------------------------------------------------------------------------*/
static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev)
{
struct usb_hcd *hcd = NULL;
int ret;
pr_debug("In ehci_hcd_au1xxx_drv_probe\n");
if (usb_disabled())
return -ENODEV;
ret = usb_ehci_au1xxx_probe(&ehci_au1xxx_hc_driver, &hcd, pdev);
return ret;
}
static int ehci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_ehci_au1xxx_remove(hcd, pdev);
return 0;
}
/*TBD*/
/*static int ehci_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 ehci_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;
}
*/
MODULE_ALIAS("au1xxx-ehci");
static struct platform_driver ehci_hcd_au1xxx_driver = {
.probe = ehci_hcd_au1xxx_drv_probe,
.remove = ehci_hcd_au1xxx_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
/*.suspend = ehci_hcd_au1xxx_drv_suspend, */
/*.resume = ehci_hcd_au1xxx_drv_resume, */
.driver = {
.name = "au1xxx-ehci",
.bus = &platform_bus_type
}
};

805
drivers/usb/host/ehci-dbg.c Normal file
View File

@@ -0,0 +1,805 @@
/*
* 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 = ehci_readl(ehci, &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 = ehci_readl(ehci, &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 sysfs */
#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 == cpu_to_le32(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 = class_get_devdata(class_dev);
hcd = bus_to_hcd(bus);
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, GFP_ATOMIC)))
return 0;
seen_count = 0;
bus = class_get_devdata(class_dev);
hcd = bus_to_hcd(bus);
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 */
& (QH_CMASK | QH_SMASK),
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 = class_get_devdata(class_dev);
hcd = bus_to_hcd(bus);
ehci = hcd_to_ehci (hcd);
next = buf;
size = PAGE_SIZE;
spin_lock_irqsave (&ehci->lock, flags);
if (bus->controller->power.power_state.event) {
size = scnprintf (next, size,
"bus %s, device %s (driver " DRIVER_VERSION ")\n"
"%s\n"
"SUSPENDED (no register access)\n",
hcd->self.controller->bus->name,
hcd->self.controller->bus_id,
hcd->product_desc);
goto done;
}
/* Capability Registers */
i = HC_VERSION(ehci_readl(ehci, &ehci->caps->hc_capbase));
temp = scnprintf (next, size,
"bus %s, device %s (driver " DRIVER_VERSION ")\n"
"%s\n"
"EHCI %x.%02x, hcd state %d\n",
hcd->self.controller->bus->name,
hcd->self.controller->bus_id,
hcd->product_desc,
i >> 8, i & 0x0ff, hcd->state);
size -= temp;
next += temp;
#ifdef CONFIG_PCI
/* EHCI 0.96 and later may have "extended capabilities" */
if (hcd->self.controller->bus == &pci_bus_type) {
struct pci_dev *pdev;
u32 offset, cap, cap2;
unsigned count = 256/4;
pdev = to_pci_dev(ehci_to_hcd(ehci)->self.controller);
offset = HCC_EXT_CAPS (ehci_readl(ehci, &ehci->caps->hcc_params));
while (offset && count--) {
pci_read_config_dword (pdev, offset, &cap);
switch (cap & 0xff) {
case 1:
temp = scnprintf (next, size,
"ownership %08x%s%s\n", cap,
(cap & (1 << 24)) ? " linux" : "",
(cap & (1 << 16)) ? " firmware" : "");
size -= temp;
next += temp;
offset += 4;
pci_read_config_dword (pdev, offset, &cap2);
temp = scnprintf (next, size,
"SMI sts/enable 0x%08x\n", cap2);
size -= temp;
next += temp;
break;
case 0: /* illegal reserved capability */
cap = 0;
/* FALLTHROUGH */
default: /* unknown */
break;
}
temp = (cap >> 8) & 0xff;
}
}
#endif
// FIXME interpret both types of params
i = ehci_readl(ehci, &ehci->caps->hcs_params);
temp = scnprintf (next, size, "structural params 0x%08x\n", i);
size -= temp;
next += temp;
i = ehci_readl(ehci, &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,
ehci_readl(ehci, &ehci->regs->status));
temp = scnprintf (next, size, fmt, temp, scratch);
size -= temp;
next += temp;
temp = dbg_command_buf (scratch, sizeof scratch, label,
ehci_readl(ehci, &ehci->regs->command));
temp = scnprintf (next, size, fmt, temp, scratch);
size -= temp;
next += temp;
temp = dbg_intr_buf (scratch, sizeof scratch, label,
ehci_readl(ehci, &ehci->regs->intr_enable));
temp = scnprintf (next, size, fmt, temp, scratch);
size -= temp;
next += temp;
temp = scnprintf (next, size, "uframe %04x\n",
ehci_readl(ehci, &ehci->regs->frame_index));
size -= temp;
next += temp;
for (i = 1; i <= HCS_N_PORTS (ehci->hcs_params); i++) {
temp = dbg_port_buf (scratch, sizeof scratch, label, i,
ehci_readl(ehci, &ehci->regs->port_status [i - 1]));
temp = scnprintf (next, size, fmt, temp, scratch);
size -= temp;
next += temp;
if (i == HCS_DEBUG_PORT(ehci->hcs_params) && ehci->debug) {
temp = scnprintf (next, size,
" debug control %08x\n",
ehci_readl(ehci, &ehci->debug->control));
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;
int retval;
retval = class_device_create_file(cldev, &class_device_attr_async);
retval = class_device_create_file(cldev, &class_device_attr_periodic);
retval = 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 */

337
drivers/usb/host/ehci-fsl.c Normal file
View File

@@ -0,0 +1,337 @@
/*
* (C) Copyright David Brownell 2000-2002
* Copyright (c) 2005 MontaVista Software
*
* 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.
*
* Ported to 834x by Randy Vinson <rvinson@mvista.com> using code provided
* by Hunter Wu.
*/
#include <linux/platform_device.h>
#include <linux/fsl_devices.h>
#include "ehci-fsl.h"
/* FIXME: Power Managment is un-ported so temporarily disable it */
#undef CONFIG_PM
/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */
/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */
/**
* usb_hcd_fsl_probe - initialize FSL-based HCDs
* @drvier: Driver to be used for this HCD
* @pdev: USB Host Controller being probed
* Context: !in_interrupt()
*
* Allocates basic resources for this USB host controller.
*
*/
int usb_hcd_fsl_probe(const struct hc_driver *driver,
struct platform_device *pdev)
{
struct fsl_usb2_platform_data *pdata;
struct usb_hcd *hcd;
struct resource *res;
int irq;
int retval;
unsigned int temp;
pr_debug("initializing FSL-SOC USB Controller\n");
/* Need platform data for setup */
pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev,
"No platform data for %s.\n", pdev->dev.bus_id);
return -ENODEV;
}
/*
* This is a host mode driver, verify that we're supposed to be
* in host mode.
*/
if (!((pdata->operating_mode == FSL_USB2_DR_HOST) ||
(pdata->operating_mode == FSL_USB2_MPH_HOST))) {
dev_err(&pdev->dev,
"Non Host Mode configured for %s. Wrong driver linked.\n",
pdev->dev.bus_id);
return -ENODEV;
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev,
"Found HC with no IRQ. Check %s setup!\n",
pdev->dev.bus_id);
return -ENODEV;
}
irq = res->start;
hcd = usb_create_hcd(driver, &pdev->dev, pdev->dev.bus_id);
if (!hcd) {
retval = -ENOMEM;
goto err1;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev,
"Found HC with no register addr. Check %s setup!\n",
pdev->dev.bus_id);
retval = -ENODEV;
goto err2;
}
hcd->rsrc_start = res->start;
hcd->rsrc_len = res->end - res->start + 1;
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
driver->description)) {
dev_dbg(&pdev->dev, "controller already in use\n");
retval = -EBUSY;
goto err2;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (hcd->regs == NULL) {
dev_dbg(&pdev->dev, "error mapping memory\n");
retval = -EFAULT;
goto err3;
}
/* Enable USB controller */
temp = in_be32(hcd->regs + 0x500);
out_be32(hcd->regs + 0x500, temp | 0x4);
/* Set to Host mode */
temp = in_le32(hcd->regs + 0x1a8);
out_le32(hcd->regs + 0x1a8, temp | 0x3);
retval = usb_add_hcd(hcd, irq, IRQF_SHARED);
if (retval != 0)
goto err4;
return retval;
err4:
iounmap(hcd->regs);
err3:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err2:
usb_put_hcd(hcd);
err1:
dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, retval);
return retval;
}
/* may be called without controller electrically present */
/* may be called with controller, bus, and devices active */
/**
* usb_hcd_fsl_remove - shutdown processing for FSL-based HCDs
* @dev: USB Host Controller being removed
* Context: !in_interrupt()
*
* Reverses the effect of usb_hcd_fsl_probe().
*
*/
void usb_hcd_fsl_remove(struct usb_hcd *hcd, struct platform_device *pdev)
{
usb_remove_hcd(hcd);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
}
static void mpc83xx_setup_phy(struct ehci_hcd *ehci,
enum fsl_usb2_phy_modes phy_mode,
unsigned int port_offset)
{
u32 portsc = 0;
switch (phy_mode) {
case FSL_USB2_PHY_ULPI:
portsc |= PORT_PTS_ULPI;
break;
case FSL_USB2_PHY_SERIAL:
portsc |= PORT_PTS_SERIAL;
break;
case FSL_USB2_PHY_UTMI_WIDE:
portsc |= PORT_PTS_PTW;
/* fall through */
case FSL_USB2_PHY_UTMI:
portsc |= PORT_PTS_UTMI;
break;
case FSL_USB2_PHY_NONE:
break;
}
ehci_writel(ehci, portsc, &ehci->regs->port_status[port_offset]);
}
static void mpc83xx_usb_setup(struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct fsl_usb2_platform_data *pdata;
void __iomem *non_ehci = hcd->regs;
pdata =
(struct fsl_usb2_platform_data *)hcd->self.controller->
platform_data;
/* Enable PHY interface in the control reg. */
out_be32(non_ehci + FSL_SOC_USB_CTRL, 0x00000004);
out_be32(non_ehci + FSL_SOC_USB_SNOOP1, 0x0000001b);
if (pdata->operating_mode == FSL_USB2_DR_HOST)
mpc83xx_setup_phy(ehci, pdata->phy_mode, 0);
if (pdata->operating_mode == FSL_USB2_MPH_HOST) {
unsigned int chip, rev, svr;
svr = mfspr(SPRN_SVR);
chip = svr >> 16;
rev = (svr >> 4) & 0xf;
/* Deal with USB Erratum #14 on MPC834x Rev 1.0 & 1.1 chips */
if ((rev == 1) && (chip >= 0x8050) && (chip <= 0x8055))
ehci->has_fsl_port_bug = 1;
if (pdata->port_enables & FSL_USB2_PORT0_ENABLED)
mpc83xx_setup_phy(ehci, pdata->phy_mode, 0);
if (pdata->port_enables & FSL_USB2_PORT1_ENABLED)
mpc83xx_setup_phy(ehci, pdata->phy_mode, 1);
}
/* put controller in host mode. */
ehci_writel(ehci, 0x00000003, non_ehci + FSL_SOC_USB_USBMODE);
out_be32(non_ehci + FSL_SOC_USB_PRICTRL, 0x0000000c);
out_be32(non_ehci + FSL_SOC_USB_AGECNTTHRSH, 0x00000040);
out_be32(non_ehci + FSL_SOC_USB_SICTRL, 0x00000001);
}
/* called after powerup, by probe or system-pm "wakeup" */
static int ehci_fsl_reinit(struct ehci_hcd *ehci)
{
mpc83xx_usb_setup(ehci_to_hcd(ehci));
ehci_port_power(ehci, 0);
return 0;
}
/* called during probe() after chip reset completes */
static int ehci_fsl_setup(struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
int retval;
/* EHCI registers start at offset 0x100 */
ehci->caps = hcd->regs + 0x100;
ehci->regs = hcd->regs + 0x100 +
HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
dbg_hcs_params(ehci, "reset");
dbg_hcc_params(ehci, "reset");
/* cache this readonly data; minimize chip reads */
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
retval = ehci_halt(ehci);
if (retval)
return retval;
/* data structure init */
retval = ehci_init(hcd);
if (retval)
return retval;
ehci->is_tdi_rh_tt = 1;
ehci->sbrn = 0x20;
ehci_reset(ehci);
retval = ehci_fsl_reinit(ehci);
return retval;
}
static const struct hc_driver ehci_fsl_hc_driver = {
.description = hcd_name,
.product_desc = "Freescale On-Chip EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),
/*
* generic hardware linkage
*/
.irq = ehci_irq,
.flags = HCD_USB2,
/*
* basic lifecycle operations
*/
.reset = ehci_fsl_setup,
.start = ehci_run,
#ifdef CONFIG_PM
.suspend = ehci_bus_suspend,
.resume = ehci_bus_resume,
#endif
.stop = ehci_stop,
.shutdown = ehci_shutdown,
/*
* managing i/o requests and associated device resources
*/
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
/*
* scheduling support
*/
.get_frame_number = ehci_get_frame,
/*
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
};
static int ehci_fsl_drv_probe(struct platform_device *pdev)
{
if (usb_disabled())
return -ENODEV;
return usb_hcd_fsl_probe(&ehci_fsl_hc_driver, pdev);
}
static int ehci_fsl_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_fsl_remove(hcd, pdev);
return 0;
}
MODULE_ALIAS("fsl-ehci");
static struct platform_driver ehci_fsl_driver = {
.probe = ehci_fsl_drv_probe,
.remove = ehci_fsl_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
.driver = {
.name = "fsl-ehci",
},
};

View File

@@ -0,0 +1,37 @@
/* Copyright (c) 2005 freescale semiconductor
* Copyright (c) 2005 MontaVista Software
*
* 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 _EHCI_FSL_H
#define _EHCI_FSL_H
/* offsets for the non-ehci registers in the FSL SOC USB controller */
#define FSL_SOC_USB_ULPIVP 0x170
#define FSL_SOC_USB_PORTSC1 0x184
#define PORT_PTS_MSK (3<<30)
#define PORT_PTS_UTMI (0<<30)
#define PORT_PTS_ULPI (2<<30)
#define PORT_PTS_SERIAL (3<<30)
#define PORT_PTS_PTW (1<<28)
#define FSL_SOC_USB_PORTSC2 0x188
#define FSL_SOC_USB_USBMODE 0x1a8
#define FSL_SOC_USB_SNOOP1 0x400 /* NOTE: big-endian */
#define FSL_SOC_USB_SNOOP2 0x404 /* NOTE: big-endian */
#define FSL_SOC_USB_AGECNTTHRSH 0x408 /* NOTE: big-endian */
#define FSL_SOC_USB_SICTRL 0x40c /* NOTE: big-endian */
#define FSL_SOC_USB_PRICTRL 0x410 /* NOTE: big-endian */
#define FSL_SOC_USB_CTRL 0x500 /* NOTE: big-endian */
#endif /* _EHCI_FSL_H */

1008
drivers/usb/host/ehci-hcd.c Normal file

File diff suppressed because it is too large Load Diff

752
drivers/usb/host/ehci-hub.c Normal file
View File

@@ -0,0 +1,752 @@
/*
* Copyright (C) 2001-2004 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_bus_suspend (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
int port;
int mask;
ehci_dbg(ehci, "suspend root hub\n");
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 = ehci_readl(ehci, &ehci->regs->command);
if (ehci->reclaim)
ehci->reclaim_ready = 1;
ehci_work(ehci);
/* Unlike other USB host controller types, EHCI doesn't have
* any notion of "global" or bus-wide suspend. The driver has
* to manually suspend all the active unsuspended ports, and
* then manually resume them in the bus_resume() routine.
*/
ehci->bus_suspended = 0;
while (port--) {
u32 __iomem *reg = &ehci->regs->port_status [port];
u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
u32 t2 = t1;
/* keep track of which ports we suspend */
if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) &&
!(t1 & PORT_SUSPEND)) {
t2 |= PORT_SUSPEND;
set_bit(port, &ehci->bus_suspended);
}
/* enable remote wakeup on all ports */
if (device_may_wakeup(&hcd->self.root_hub->dev))
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);
ehci_writel(ehci, t2, reg);
}
}
/* turn off now-idle HC */
del_timer_sync (&ehci->watchdog);
ehci_halt (ehci);
hcd->state = HC_STATE_SUSPENDED;
/* allow remote wakeup */
mask = INTR_MASK;
if (!device_may_wakeup(&hcd->self.root_hub->dev))
mask &= ~STS_PCD;
ehci_writel(ehci, mask, &ehci->regs->intr_enable);
ehci_readl(ehci, &ehci->regs->intr_enable);
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_bus_resume (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
u32 temp;
int i;
if (time_before (jiffies, ehci->next_statechange))
msleep(5);
spin_lock_irq (&ehci->lock);
/* Ideally and we've got a real resume here, and no port's power
* was lost. (For PCI, that means Vaux was maintained.) But we
* could instead be restoring a swsusp snapshot -- so that BIOS was
* the last user of the controller, not reset/pm hardware keeping
* state we gave to it.
*/
temp = ehci_readl(ehci, &ehci->regs->intr_enable);
ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss");
/* at least some APM implementations will try to deliver
* IRQs right away, so delay them until we're ready.
*/
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
/* re-init operational registers */
ehci_writel(ehci, 0, &ehci->regs->segment);
ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list);
ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next);
/* restore CMD_RUN, framelist size, and irq threshold */
ehci_writel(ehci, ehci->command, &ehci->regs->command);
/* manually resume the ports we suspended during bus_suspend() */
i = HCS_N_PORTS (ehci->hcs_params);
while (i--) {
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
temp &= ~(PORT_RWC_BITS
| PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E);
if (test_bit(i, &ehci->bus_suspended) &&
(temp & PORT_SUSPEND)) {
ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
temp |= PORT_RESUME;
}
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
}
i = HCS_N_PORTS (ehci->hcs_params);
mdelay (20);
while (i--) {
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
if (test_bit(i, &ehci->bus_suspended) &&
(temp & PORT_SUSPEND)) {
temp &= ~(PORT_RWC_BITS | PORT_RESUME);
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
ehci_vdbg (ehci, "resumed port %d\n", i + 1);
}
}
(void) ehci_readl(ehci, &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;
ehci_writel(ehci, 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 */
ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
spin_unlock_irq (&ehci->lock);
return 0;
}
#else
#define ehci_bus_suspend NULL
#define ehci_bus_resume NULL
#endif /* CONFIG_PM */
/*-------------------------------------------------------------------------*/
/* Display the ports dedicated to the companion controller */
static ssize_t show_companion(struct class_device *class_dev, char *buf)
{
struct ehci_hcd *ehci;
int nports, index, n;
int count = PAGE_SIZE;
char *ptr = buf;
ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
nports = HCS_N_PORTS(ehci->hcs_params);
for (index = 0; index < nports; ++index) {
if (test_bit(index, &ehci->companion_ports)) {
n = scnprintf(ptr, count, "%d\n", index + 1);
ptr += n;
count -= n;
}
}
return ptr - buf;
}
/*
* Dedicate or undedicate a port to the companion controller.
* Syntax is "[-]portnum", where a leading '-' sign means
* return control of the port to the EHCI controller.
*/
static ssize_t store_companion(struct class_device *class_dev,
const char *buf, size_t count)
{
struct ehci_hcd *ehci;
int portnum, new_owner, try;
u32 __iomem *status_reg;
u32 port_status;
ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
new_owner = PORT_OWNER; /* Owned by companion */
if (sscanf(buf, "%d", &portnum) != 1)
return -EINVAL;
if (portnum < 0) {
portnum = - portnum;
new_owner = 0; /* Owned by EHCI */
}
if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params))
return -ENOENT;
status_reg = &ehci->regs->port_status[--portnum];
if (new_owner)
set_bit(portnum, &ehci->companion_ports);
else
clear_bit(portnum, &ehci->companion_ports);
/*
* The controller won't set the OWNER bit if the port is
* enabled, so this loop will sometimes require at least two
* iterations: one to disable the port and one to set OWNER.
*/
for (try = 4; try > 0; --try) {
spin_lock_irq(&ehci->lock);
port_status = ehci_readl(ehci, status_reg);
if ((port_status & PORT_OWNER) == new_owner
|| (port_status & (PORT_OWNER | PORT_CONNECT))
== 0)
try = 0;
else {
port_status ^= PORT_OWNER;
port_status &= ~(PORT_PE | PORT_RWC_BITS);
ehci_writel(ehci, port_status, status_reg);
}
spin_unlock_irq(&ehci->lock);
if (try > 1)
msleep(5);
}
return count;
}
static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion);
static inline void create_companion_file(struct ehci_hcd *ehci)
{
int i;
/* with integrated TT there is no companion! */
if (!ehci_is_TDI(ehci))
i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev,
&class_device_attr_companion);
}
static inline void remove_companion_file(struct ehci_hcd *ehci)
{
/* with integrated TT there is no companion! */
if (!ehci_is_TDI(ehci))
class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev,
&class_device_attr_companion);
}
/*-------------------------------------------------------------------------*/
static int check_reset_complete (
struct ehci_hcd *ehci,
int index,
u32 __iomem *status_reg,
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;
port_status &= ~PORT_RWC_BITS;
ehci_writel(ehci, port_status, status_reg);
} 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;
u32 mask;
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++;
}
/* Some boards (mostly VIA?) report bogus overcurrent indications,
* causing massive log spam unless we completely ignore them. It
* may be relevant that VIA VT8235 controlers, where PORT_POWER is
* always set, seem to clear PORT_OCC and PORT_CSC when writing to
* PORT_POWER; that's surprising, but maybe within-spec.
*/
if (!ignore_oc)
mask = PORT_CSC | PORT_PEC | PORT_OCC;
else
mask = PORT_CSC | PORT_PEC;
// PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND
/* 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 = ehci_readl(ehci, &ehci->regs->port_status [i]);
/*
* Return status information even for ports with OWNER set.
* Otherwise khubd wouldn't see the disconnect event when a
* high-speed device is switched over to the companion
* controller by the user.
*/
if (!(temp & PORT_CONNECT))
ehci->reset_done [i] = 0;
if ((temp & mask) != 0
|| ((temp & PORT_RESUME) != 0
&& time_after_eq(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 */
else
temp |= 0x0002; /* no power switching */
#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 __iomem *status_reg = &ehci->regs->port_status[wIndex - 1];
u32 temp, status;
unsigned long flags;
int retval = 0;
unsigned selector;
/*
* 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 = ehci_readl(ehci, status_reg);
/*
* Even if OWNER is set, so the port is owned by the
* companion controller, khubd needs to be able to clear
* the port-change status bits (especially
* USB_PORT_FEAT_C_CONNECTION).
*/
switch (wValue) {
case USB_PORT_FEAT_ENABLE:
ehci_writel(ehci, temp & ~PORT_PE, status_reg);
break;
case USB_PORT_FEAT_C_ENABLE:
ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_PEC,
status_reg);
break;
case USB_PORT_FEAT_SUSPEND:
if (temp & PORT_RESET)
goto error;
if (ehci->no_selective_suspend)
break;
if (temp & PORT_SUSPEND) {
if ((temp & PORT_PE) == 0)
goto error;
/* resume signaling for 20 msec */
temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
ehci_writel(ehci, temp | PORT_RESUME,
status_reg);
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))
ehci_writel(ehci,
temp & ~(PORT_RWC_BITS | PORT_POWER),
status_reg);
break;
case USB_PORT_FEAT_C_CONNECTION:
ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC,
status_reg);
break;
case USB_PORT_FEAT_C_OVER_CURRENT:
ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_OCC,
status_reg);
break;
case USB_PORT_FEAT_C_RESET:
/* GetPortStatus clears reset */
break;
default:
goto error;
}
ehci_readl(ehci, &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 = ehci_readl(ehci, status_reg);
// 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) && !ignore_oc)
status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
/* whoever resumes must GetPortStatus to complete it!! */
if (temp & PORT_RESUME) {
/* Remote Wakeup received? */
if (!ehci->reset_done[wIndex]) {
/* resume signaling for 20 msec */
ehci->reset_done[wIndex] = jiffies
+ msecs_to_jiffies(20);
/* check the port again */
mod_timer(&ehci_to_hcd(ehci)->rh_timer,
ehci->reset_done[wIndex]);
}
/* resume completed? */
else if (time_after_eq(jiffies,
ehci->reset_done[wIndex])) {
status |= 1 << USB_PORT_FEAT_C_SUSPEND;
ehci->reset_done[wIndex] = 0;
/* stop resume signaling */
temp = ehci_readl(ehci, status_reg);
ehci_writel(ehci,
temp & ~(PORT_RWC_BITS | PORT_RESUME),
status_reg);
retval = handshake(ehci, status_reg,
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_eq(jiffies,
ehci->reset_done[wIndex])) {
status |= 1 << USB_PORT_FEAT_C_RESET;
ehci->reset_done [wIndex] = 0;
/* force reset to complete */
ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET),
status_reg);
/* REVISIT: some hardware needs 550+ usec to clear
* this bit; seems too long to spin routinely...
*/
retval = handshake(ehci, status_reg,
PORT_RESET, 0, 750);
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, status_reg,
ehci_readl(ehci, status_reg));
}
/* transfer dedicated ports to the companion hc */
if ((temp & PORT_CONNECT) &&
test_bit(wIndex, &ehci->companion_ports)) {
temp &= ~PORT_RWC_BITS;
temp |= PORT_OWNER;
ehci_writel(ehci, temp, status_reg);
ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1);
temp = ehci_readl(ehci, status_reg);
}
/*
* Even if OWNER is set, there's no harm letting khubd
* see the wPortStatus values (they should all be 0 except
* for PORT_POWER anyway).
*/
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);
put_unaligned(cpu_to_le32 (status), (__le32 *) buf);
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:
selector = wIndex >> 8;
wIndex &= 0xff;
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
temp = ehci_readl(ehci, status_reg);
if (temp & PORT_OWNER)
break;
temp &= ~PORT_RWC_BITS;
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
if (ehci->no_selective_suspend)
break;
if ((temp & PORT_PE) == 0
|| (temp & PORT_RESET) != 0)
goto error;
if (device_may_wakeup(&hcd->self.root_hub->dev))
temp |= PORT_WAKE_BITS;
ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
break;
case USB_PORT_FEAT_POWER:
if (HCS_PPC (ehci->hcs_params))
ehci_writel(ehci, temp | PORT_POWER,
status_reg);
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);
}
ehci_writel(ehci, temp, status_reg);
break;
/* For downstream facing ports (these): one hub port is put
* into test mode according to USB2 11.24.2.13, then the hub
* must be reset (which for root hub now means rmmod+modprobe,
* or else system reboot). See EHCI 2.3.9 and 4.14 for info
* about the EHCI-specific stuff.
*/
case USB_PORT_FEAT_TEST:
if (!selector || selector > 5)
goto error;
ehci_quiesce(ehci);
ehci_halt(ehci);
temp |= selector << 16;
ehci_writel(ehci, temp, status_reg);
break;
default:
goto error;
}
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
break;
default:
error:
/* "stall" on error */
retval = -EPIPE;
}
spin_unlock_irqrestore (&ehci->lock, flags);
return retval;
}

231
drivers/usb/host/ehci-mem.c Normal file
View File

@@ -0,0 +1,231 @@
/*
* 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, gfp_t 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);
dma_pool_free (ehci->qh_pool, qh, qh->qh_dma);
}
static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t 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 */
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, gfp_t 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 = kcalloc(ehci->periodic_size, sizeof(void *), flags);
if (ehci->pshadow != NULL)
return 0;
fail:
ehci_dbg (ehci, "couldn't init memory\n");
ehci_mem_cleanup (ehci);
return -ENOMEM;
}

397
drivers/usb/host/ehci-pci.c Normal file
View File

@@ -0,0 +1,397 @@
/*
* EHCI HCD (Host Controller Driver) PCI Bus Glue.
*
* Copyright (c) 2000-2004 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 CONFIG_PCI
#error "This file is PCI bus glue. CONFIG_PCI must be defined."
#endif
/*-------------------------------------------------------------------------*/
/* called after powerup, by probe or system-pm "wakeup" */
static int ehci_pci_reinit(struct ehci_hcd *ehci, struct pci_dev *pdev)
{
u32 temp;
int retval;
/* optional debug port, normally in the first BAR */
temp = pci_find_capability(pdev, 0x0a);
if (temp) {
pci_read_config_dword(pdev, temp, &temp);
temp >>= 16;
if ((temp & (3 << 13)) == (1 << 13)) {
temp &= 0x1fff;
ehci->debug = ehci_to_hcd(ehci)->regs + temp;
temp = ehci_readl(ehci, &ehci->debug->control);
ehci_info(ehci, "debug port %d%s\n",
HCS_DEBUG_PORT(ehci->hcs_params),
(temp & DBGP_ENABLED)
? " IN USE"
: "");
if (!(temp & DBGP_ENABLED))
ehci->debug = NULL;
}
}
/* we expect static quirk code to handle the "extended capabilities"
* (currently just BIOS handoff) allowed starting with EHCI 0.96
*/
/* PCI Memory-Write-Invalidate cycle support is optional (uncommon) */
retval = pci_set_mwi(pdev);
if (!retval)
ehci_dbg(ehci, "MWI active\n");
ehci_port_power(ehci, 0);
return 0;
}
/* called during probe() after chip reset completes */
static int ehci_pci_setup(struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
u32 temp;
int retval;
switch (pdev->vendor) {
case PCI_VENDOR_ID_TOSHIBA_2:
/* celleb's companion chip */
if (pdev->device == 0x01b5) {
#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO
ehci->big_endian_mmio = 1;
#else
ehci_warn(ehci,
"unsupported big endian Toshiba quirk\n");
#endif
}
break;
}
ehci->caps = hcd->regs;
ehci->regs = hcd->regs +
HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
dbg_hcs_params(ehci, "reset");
dbg_hcc_params(ehci, "reset");
/* ehci_init() causes memory for DMA transfers to be
* allocated. Thus, any vendor-specific workarounds based on
* limiting the type of memory used for DMA transfers must
* happen before ehci_init() is called. */
switch (pdev->vendor) {
case PCI_VENDOR_ID_NVIDIA:
/* NVidia reports that certain chips don't handle
* QH, ITD, or SITD addresses above 2GB. (But TD,
* data buffer, and periodic schedule are normal.)
*/
switch (pdev->device) {
case 0x003c: /* MCP04 */
case 0x005b: /* CK804 */
case 0x00d8: /* CK8 */
case 0x00e8: /* CK8S */
if (pci_set_consistent_dma_mask(pdev,
DMA_31BIT_MASK) < 0)
ehci_warn(ehci, "can't enable NVidia "
"workaround for >2GB RAM\n");
break;
}
break;
}
/* cache this readonly data; minimize chip reads */
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
retval = ehci_halt(ehci);
if (retval)
return retval;
/* data structure init */
retval = ehci_init(hcd);
if (retval)
return retval;
switch (pdev->vendor) {
case PCI_VENDOR_ID_TDI:
if (pdev->device == PCI_DEVICE_ID_TDI_EHCI) {
ehci->is_tdi_rh_tt = 1;
tdi_reset(ehci);
}
break;
case PCI_VENDOR_ID_AMD:
/* AMD8111 EHCI doesn't work, according to AMD errata */
if (pdev->device == 0x7463) {
ehci_info(ehci, "ignoring AMD8111 (errata)\n");
retval = -EIO;
goto done;
}
break;
case PCI_VENDOR_ID_NVIDIA:
switch (pdev->device) {
/* Some NForce2 chips have problems with selective suspend;
* fixed in newer silicon.
*/
case 0x0068:
pci_read_config_dword(pdev, PCI_REVISION_ID, &temp);
if ((temp & 0xff) < 0xa4)
ehci->no_selective_suspend = 1;
break;
}
break;
}
if (ehci_is_TDI(ehci))
ehci_reset(ehci);
/* at least the Genesys GL880S needs fixup here */
temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
temp &= 0x0f;
if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
ehci_dbg(ehci, "bogus port configuration: "
"cc=%d x pcc=%d < ports=%d\n",
HCS_N_CC(ehci->hcs_params),
HCS_N_PCC(ehci->hcs_params),
HCS_N_PORTS(ehci->hcs_params));
switch (pdev->vendor) {
case 0x17a0: /* GENESYS */
/* GL880S: should be PORTS=2 */
temp |= (ehci->hcs_params & ~0xf);
ehci->hcs_params = temp;
break;
case PCI_VENDOR_ID_NVIDIA:
/* NF4: should be PCC=10 */
break;
}
}
/* Serial Bus Release Number is at PCI 0x60 offset */
pci_read_config_byte(pdev, 0x60, &ehci->sbrn);
/* Workaround current PCI init glitch: wakeup bits aren't
* being set from PCI PM capability.
*/
if (!device_can_wakeup(&pdev->dev)) {
u16 port_wake;
pci_read_config_word(pdev, 0x62, &port_wake);
if (port_wake & 0x0001)
device_init_wakeup(&pdev->dev, 1);
}
#ifdef CONFIG_USB_SUSPEND
/* REVISIT: the controller works fine for wakeup iff the root hub
* itself is "globally" suspended, but usbcore currently doesn't
* understand such things.
*
* System suspend currently expects to be able to suspend the entire
* device tree, device-at-a-time. If we failed selective suspend
* reports, system suspend would fail; so the root hub code must claim
* success. That's lying to usbcore, and it matters for for runtime
* PM scenarios with selective suspend and remote wakeup...
*/
if (ehci->no_selective_suspend && device_can_wakeup(&pdev->dev))
ehci_warn(ehci, "selective suspend/wakeup unavailable\n");
#endif
retval = ehci_pci_reinit(ehci, pdev);
done:
return retval;
}
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PM
/* suspend/resume, section 4.3 */
/* These routines rely on the PCI bus glue
* to handle powerdown and wakeup, and currently also on
* transceivers that don't need any software attention to set up
* the right sort of wakeup.
* Also they depend on separate root hub suspend/resume.
*/
static int ehci_pci_suspend(struct usb_hcd *hcd, pm_message_t message)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
unsigned long flags;
int rc = 0;
if (time_before(jiffies, ehci->next_statechange))
msleep(10);
/* Root hub was already suspended. Disable irq emission and
* mark HW unaccessible, bail out if RH has been resumed. Use
* the spinlock to properly synchronize with possible pending
* RH suspend or resume activity.
*
* This is still racy as hcd->state is manipulated outside of
* any locks =P But that will be a different fix.
*/
spin_lock_irqsave (&ehci->lock, flags);
if (hcd->state != HC_STATE_SUSPENDED) {
rc = -EINVAL;
goto bail;
}
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
(void)ehci_readl(ehci, &ehci->regs->intr_enable);
/* make sure snapshot being resumed re-enumerates everything */
if (message.event == PM_EVENT_PRETHAW) {
ehci_halt(ehci);
ehci_reset(ehci);
}
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
bail:
spin_unlock_irqrestore (&ehci->lock, flags);
// could save FLADJ in case of Vaux power loss
// ... we'd only use it to handle clock skew
return rc;
}
static int ehci_pci_resume(struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
// maybe restore FLADJ
if (time_before(jiffies, ehci->next_statechange))
msleep(100);
/* Mark hardware accessible again as we are out of D3 state by now */
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
/* If CF is still set, we maintained PCI Vaux power.
* Just undo the effect of ehci_pci_suspend().
*/
if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
int mask = INTR_MASK;
if (!device_may_wakeup(&hcd->self.root_hub->dev))
mask &= ~STS_PCD;
ehci_writel(ehci, mask, &ehci->regs->intr_enable);
ehci_readl(ehci, &ehci->regs->intr_enable);
return 0;
}
ehci_dbg(ehci, "lost power, restarting\n");
usb_root_hub_lost_power(hcd->self.root_hub);
/* Else reset, to cope with power loss or flush-to-storage
* style "resume" having let BIOS kick in during reboot.
*/
(void) ehci_halt(ehci);
(void) ehci_reset(ehci);
(void) ehci_pci_reinit(ehci, pdev);
/* emptying the schedule aborts any urbs */
spin_lock_irq(&ehci->lock);
if (ehci->reclaim)
ehci->reclaim_ready = 1;
ehci_work(ehci);
spin_unlock_irq(&ehci->lock);
/* here we "know" root ports should always stay powered */
ehci_port_power(ehci, 1);
ehci_writel(ehci, ehci->command, &ehci->regs->command);
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
hcd->state = HC_STATE_SUSPENDED;
return 0;
}
#endif
static const struct hc_driver ehci_pci_hc_driver = {
.description = hcd_name,
.product_desc = "EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),
/*
* generic hardware linkage
*/
.irq = ehci_irq,
.flags = HCD_MEMORY | HCD_USB2,
/*
* basic lifecycle operations
*/
.reset = ehci_pci_setup,
.start = ehci_run,
#ifdef CONFIG_PM
.suspend = ehci_pci_suspend,
.resume = ehci_pci_resume,
#endif
.stop = ehci_stop,
.shutdown = ehci_shutdown,
/*
* managing i/o requests and associated device resources
*/
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
/*
* scheduling support
*/
.get_frame_number = ehci_get_frame,
/*
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
};
/*-------------------------------------------------------------------------*/
/* PCI driver selection metadata; PCI hotplugging uses this */
static const struct pci_device_id pci_ids [] = { {
/* handle any USB 2.0 EHCI controller */
PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_EHCI, ~0),
.driver_data = (unsigned long) &ehci_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 ehci_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
.shutdown = usb_hcd_pci_shutdown,
};

193
drivers/usb/host/ehci-ps3.c Normal file
View File

@@ -0,0 +1,193 @@
/*
* PS3 EHCI Host Controller driver
*
* Copyright (C) 2006 Sony Computer Entertainment Inc.
* Copyright 2006 Sony Corp.
*
* 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; version 2 of the License.
*
* 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 <asm/ps3.h>
static int ps3_ehci_hc_reset(struct usb_hcd *hcd)
{
int result;
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
ehci->big_endian_mmio = 1;
ehci->caps = hcd->regs;
ehci->regs = hcd->regs + HC_LENGTH(ehci_readl(ehci,
&ehci->caps->hc_capbase));
dbg_hcs_params(ehci, "reset");
dbg_hcc_params(ehci, "reset");
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
result = ehci_halt(ehci);
if (result)
return result;
result = ehci_init(hcd);
if (result)
return result;
ehci_port_power(ehci, 0);
return result;
}
static const struct hc_driver ps3_ehci_hc_driver = {
.description = hcd_name,
.product_desc = "PS3 EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),
.irq = ehci_irq,
.flags = HCD_MEMORY | HCD_USB2,
.reset = ps3_ehci_hc_reset,
.start = ehci_run,
.stop = ehci_stop,
.shutdown = ehci_shutdown,
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
.get_frame_number = ehci_get_frame,
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
#if defined(CONFIG_PM)
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
#endif
};
#if !defined(DEBUG)
#undef dev_dbg
static inline int __attribute__ ((format (printf, 2, 3))) dev_dbg(
const struct device *_dev, const char *fmt, ...) {return 0;}
#endif
static int ps3_ehci_sb_probe(struct ps3_system_bus_device *dev)
{
int result;
struct usb_hcd *hcd;
unsigned int virq;
static u64 dummy_mask = DMA_32BIT_MASK;
if (usb_disabled()) {
result = -ENODEV;
goto fail_start;
}
result = ps3_mmio_region_create(dev->m_region);
if (result) {
dev_dbg(&dev->core, "%s:%d: ps3_map_mmio_region failed\n",
__func__, __LINE__);
result = -EPERM;
goto fail_mmio;
}
dev_dbg(&dev->core, "%s:%d: mmio mapped_addr %lxh\n", __func__,
__LINE__, dev->m_region->lpar_addr);
result = ps3_alloc_io_irq(PS3_BINDING_CPU_ANY, dev->interrupt_id, &virq);
if (result) {
dev_dbg(&dev->core, "%s:%d: ps3_construct_io_irq(%d) failed.\n",
__func__, __LINE__, virq);
result = -EPERM;
goto fail_irq;
}
dev->core.power.power_state = PMSG_ON;
dev->core.dma_mask = &dummy_mask; /* FIXME: for improper usb code */
hcd = usb_create_hcd(&ps3_ehci_hc_driver, &dev->core, dev->core.bus_id);
if (!hcd) {
dev_dbg(&dev->core, "%s:%d: usb_create_hcd failed\n", __func__,
__LINE__);
result = -ENOMEM;
goto fail_create_hcd;
}
hcd->rsrc_start = dev->m_region->lpar_addr;
hcd->rsrc_len = dev->m_region->len;
hcd->regs = ioremap(dev->m_region->lpar_addr, dev->m_region->len);
if (!hcd->regs) {
dev_dbg(&dev->core, "%s:%d: ioremap failed\n", __func__,
__LINE__);
result = -EPERM;
goto fail_ioremap;
}
dev_dbg(&dev->core, "%s:%d: hcd->rsrc_start %lxh\n", __func__, __LINE__,
(unsigned long)hcd->rsrc_start);
dev_dbg(&dev->core, "%s:%d: hcd->rsrc_len %lxh\n", __func__, __LINE__,
(unsigned long)hcd->rsrc_len);
dev_dbg(&dev->core, "%s:%d: hcd->regs %lxh\n", __func__, __LINE__,
(unsigned long)hcd->regs);
dev_dbg(&dev->core, "%s:%d: virq %lu\n", __func__, __LINE__,
(unsigned long)virq);
ps3_system_bus_set_driver_data(dev, hcd);
result = usb_add_hcd(hcd, virq, IRQF_DISABLED);
if (result) {
dev_dbg(&dev->core, "%s:%d: usb_add_hcd failed (%d)\n",
__func__, __LINE__, result);
goto fail_add_hcd;
}
return result;
fail_add_hcd:
iounmap(hcd->regs);
fail_ioremap:
usb_put_hcd(hcd);
fail_create_hcd:
ps3_free_io_irq(virq);
fail_irq:
ps3_free_mmio_region(dev->m_region);
fail_mmio:
fail_start:
return result;
}
static int ps3_ehci_sb_remove(struct ps3_system_bus_device *dev)
{
struct usb_hcd *hcd =
(struct usb_hcd *)ps3_system_bus_get_driver_data(dev);
usb_put_hcd(hcd);
ps3_system_bus_set_driver_data(dev, NULL);
return 0;
}
MODULE_ALIAS("ps3-ehci");
static struct ps3_system_bus_driver ps3_ehci_sb_driver = {
.match_id = PS3_MATCH_ID_EHCI,
.core = {
.name = "ps3-ehci-driver",
},
.probe = ps3_ehci_sb_probe,
.remove = ps3_ehci_sb_remove,
};

1116
drivers/usb/host/ehci-q.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

707
drivers/usb/host/ehci.h Normal file
View File

@@ -0,0 +1,707 @@
/*
* 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 */
/* glue to PCI and HCD framework */
struct ehci_caps __iomem *caps;
struct ehci_regs __iomem *regs;
struct ehci_dbg_port __iomem *debug;
__u32 hcs_params; /* cached register copy */
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];
/* bit vectors (one bit per port) */
unsigned long bus_suspended; /* which ports were
already suspended at the start of a bus suspend */
unsigned long companion_ports; /* which ports are
dedicated to the companion controller */
/* 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;
unsigned long actions;
unsigned stamp;
unsigned long next_statechange;
u32 command;
/* SILICON QUIRKS */
unsigned is_tdi_rh_tt:1; /* TDI roothub with TT */
unsigned no_selective_suspend:1;
unsigned has_fsl_port_bug:1; /* FreeScale */
unsigned big_endian_mmio:1;
u8 sbrn; /* packed release number */
/* 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 */
#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC)
} __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)&0x07)
# 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 */
__le32 *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 */
#define QH_SMASK 0x000000ff
#define QH_CMASK 0x0000ff00
#define QH_HUBADDR 0x007f0000
#define QH_HUBPORT 0x3f800000
#define QH_MULT 0xc0000000
__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 */
u16 tt_usecs; /* tt downstream bandwidth */
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 tt_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
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PPC_83xx
/* Some Freescale processors have an erratum in which the TT
* port number in the queue head was 0..N-1 instead of 1..N.
*/
#define ehci_has_fsl_portno_bug(e) ((e)->has_fsl_port_bug)
#else
#define ehci_has_fsl_portno_bug(e) (0)
#endif
/*
* While most USB host controllers implement their registers in
* little-endian format, a minority (celleb companion chip) 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_EHCI_BIG_ENDIAN_MMIO
#define ehci_big_endian_mmio(e) ((e)->big_endian_mmio)
#else
#define ehci_big_endian_mmio(e) 0
#endif
static inline unsigned int ehci_readl (const struct ehci_hcd *ehci,
__u32 __iomem * regs)
{
#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO
return ehci_big_endian_mmio(ehci) ?
readl_be(regs) :
readl(regs);
#else
return readl(regs);
#endif
}
static inline void ehci_writel (const struct ehci_hcd *ehci,
const unsigned int val, __u32 __iomem *regs)
{
#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO
ehci_big_endian_mmio(ehci) ?
writel_be(val, regs) :
writel(val, regs);
#else
writel(val, regs);
#endif
}
/*-------------------------------------------------------------------------*/
#ifndef DEBUG
#define STUB_DEBUG_FILES
#endif /* DEBUG */
/*-------------------------------------------------------------------------*/
#endif /* __LINUX_EHCI_HCD_H */

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

607
drivers/usb/host/isp116x.h Normal file
View File

@@ -0,0 +1,607 @@
/*
* ISP116x register declarations and HCD data structures
*
* Copyright (C) 2005 Olav Kongas <ok@artecdesign.ee>
* Portions:
* Copyright (C) 2004 Lothar Wassmann
* Copyright (C) 2004 Psion Teklogix
* Copyright (C) 2004 David Brownell
*/
/* us of 1ms frame */
#define MAX_LOAD_LIMIT 850
/* Full speed: max # of bytes to transfer for a single urb
at a time must be < 1024 && must be multiple of 64.
832 allows transfering 4kiB within 5 frames. */
#define MAX_TRANSFER_SIZE_FULLSPEED 832
/* Low speed: there is no reason to schedule in very big
chunks; often the requested long transfers are for
string descriptors containing short strings. */
#define MAX_TRANSFER_SIZE_LOWSPEED 64
/* Bytetime (us), a rough indication of how much time it
would take to transfer a byte of useful data over USB */
#define BYTE_TIME_FULLSPEED 1
#define BYTE_TIME_LOWSPEED 20
/* Buffer sizes */
#define ISP116x_BUF_SIZE 4096
#define ISP116x_ITL_BUFSIZE 0
#define ISP116x_ATL_BUFSIZE ((ISP116x_BUF_SIZE) - 2*(ISP116x_ITL_BUFSIZE))
#define ISP116x_WRITE_OFFSET 0x80
/*------------ ISP116x registers/bits ------------*/
#define HCREVISION 0x00
#define HCCONTROL 0x01
#define HCCONTROL_HCFS (3 << 6) /* host controller
functional state */
#define HCCONTROL_USB_RESET (0 << 6)
#define HCCONTROL_USB_RESUME (1 << 6)
#define HCCONTROL_USB_OPER (2 << 6)
#define HCCONTROL_USB_SUSPEND (3 << 6)
#define HCCONTROL_RWC (1 << 9) /* remote wakeup connected */
#define HCCONTROL_RWE (1 << 10) /* remote wakeup enable */
#define HCCMDSTAT 0x02
#define HCCMDSTAT_HCR (1 << 0) /* host controller reset */
#define HCCMDSTAT_SOC (3 << 16) /* scheduling overrun count */
#define HCINTSTAT 0x03
#define HCINT_SO (1 << 0) /* scheduling overrun */
#define HCINT_WDH (1 << 1) /* writeback of done_head */
#define HCINT_SF (1 << 2) /* start frame */
#define HCINT_RD (1 << 3) /* resume detect */
#define HCINT_UE (1 << 4) /* unrecoverable error */
#define HCINT_FNO (1 << 5) /* frame number overflow */
#define HCINT_RHSC (1 << 6) /* root hub status change */
#define HCINT_OC (1 << 30) /* ownership change */
#define HCINT_MIE (1 << 31) /* master interrupt enable */
#define HCINTENB 0x04
#define HCINTDIS 0x05
#define HCFMINTVL 0x0d
#define HCFMREM 0x0e
#define HCFMNUM 0x0f
#define HCLSTHRESH 0x11
#define HCRHDESCA 0x12
#define RH_A_NDP (0x3 << 0) /* # 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) /* overcurrent protection
mode */
#define RH_A_NOCP (1 << 12) /* no overcurrent protection */
#define RH_A_POTPGT (0xff << 24) /* power on -> power good
time */
#define HCRHDESCB 0x13
#define RH_B_DR (0xffff << 0) /* device removable flags */
#define RH_B_PPCM (0xffff << 16) /* port power control mask */
#define HCRHSTATUS 0x14
#define RH_HS_LPS (1 << 0) /* local power status */
#define RH_HS_OCI (1 << 1) /* over current indicator */
#define RH_HS_DRWE (1 << 15) /* device remote wakeup
enable */
#define RH_HS_LPSC (1 << 16) /* local power status change */
#define RH_HS_OCIC (1 << 17) /* over current indicator
change */
#define RH_HS_CRWE (1 << 31) /* clear remote wakeup
enable */
#define HCRHPORT1 0x15
#define RH_PS_CCS (1 << 0) /* current connect status */
#define RH_PS_PES (1 << 1) /* port enable status */
#define RH_PS_PSS (1 << 2) /* port suspend status */
#define RH_PS_POCI (1 << 3) /* port over current
indicator */
#define RH_PS_PRS (1 << 4) /* port reset status */
#define RH_PS_PPS (1 << 8) /* port power status */
#define RH_PS_LSDA (1 << 9) /* low speed device attached */
#define RH_PS_CSC (1 << 16) /* connect status change */
#define RH_PS_PESC (1 << 17) /* port enable status change */
#define RH_PS_PSSC (1 << 18) /* port suspend status
change */
#define RH_PS_OCIC (1 << 19) /* over current indicator
change */
#define RH_PS_PRSC (1 << 20) /* port reset status change */
#define HCRHPORT_CLRMASK (0x1f << 16)
#define HCRHPORT2 0x16
#define HCHWCFG 0x20
#define HCHWCFG_15KRSEL (1 << 12)
#define HCHWCFG_CLKNOTSTOP (1 << 11)
#define HCHWCFG_ANALOG_OC (1 << 10)
#define HCHWCFG_DACK_MODE (1 << 8)
#define HCHWCFG_EOT_POL (1 << 7)
#define HCHWCFG_DACK_POL (1 << 6)
#define HCHWCFG_DREQ_POL (1 << 5)
#define HCHWCFG_DBWIDTH_MASK (0x03 << 3)
#define HCHWCFG_DBWIDTH(n) (((n) << 3) & HCHWCFG_DBWIDTH_MASK)
#define HCHWCFG_INT_POL (1 << 2)
#define HCHWCFG_INT_TRIGGER (1 << 1)
#define HCHWCFG_INT_ENABLE (1 << 0)
#define HCDMACFG 0x21
#define HCDMACFG_BURST_LEN_MASK (0x03 << 5)
#define HCDMACFG_BURST_LEN(n) (((n) << 5) & HCDMACFG_BURST_LEN_MASK)
#define HCDMACFG_BURST_LEN_1 HCDMACFG_BURST_LEN(0)
#define HCDMACFG_BURST_LEN_4 HCDMACFG_BURST_LEN(1)
#define HCDMACFG_BURST_LEN_8 HCDMACFG_BURST_LEN(2)
#define HCDMACFG_DMA_ENABLE (1 << 4)
#define HCDMACFG_BUF_TYPE_MASK (0x07 << 1)
#define HCDMACFG_CTR_SEL (1 << 2)
#define HCDMACFG_ITLATL_SEL (1 << 1)
#define HCDMACFG_DMA_RW_SELECT (1 << 0)
#define HCXFERCTR 0x22
#define HCuPINT 0x24
#define HCuPINT_SOF (1 << 0)
#define HCuPINT_ATL (1 << 1)
#define HCuPINT_AIIEOT (1 << 2)
#define HCuPINT_OPR (1 << 4)
#define HCuPINT_SUSP (1 << 5)
#define HCuPINT_CLKRDY (1 << 6)
#define HCuPINTENB 0x25
#define HCCHIPID 0x27
#define HCCHIPID_MASK 0xff00
#define HCCHIPID_MAGIC 0x6100
#define HCSCRATCH 0x28
#define HCSWRES 0x29
#define HCSWRES_MAGIC 0x00f6
#define HCITLBUFLEN 0x2a
#define HCATLBUFLEN 0x2b
#define HCBUFSTAT 0x2c
#define HCBUFSTAT_ITL0_FULL (1 << 0)
#define HCBUFSTAT_ITL1_FULL (1 << 1)
#define HCBUFSTAT_ATL_FULL (1 << 2)
#define HCBUFSTAT_ITL0_DONE (1 << 3)
#define HCBUFSTAT_ITL1_DONE (1 << 4)
#define HCBUFSTAT_ATL_DONE (1 << 5)
#define HCRDITL0LEN 0x2d
#define HCRDITL1LEN 0x2e
#define HCITLPORT 0x40
#define HCATLPORT 0x41
/* Philips transfer descriptor */
struct ptd {
u16 count;
#define PTD_COUNT_MSK (0x3ff << 0)
#define PTD_TOGGLE_MSK (1 << 10)
#define PTD_ACTIVE_MSK (1 << 11)
#define PTD_CC_MSK (0xf << 12)
u16 mps;
#define PTD_MPS_MSK (0x3ff << 0)
#define PTD_SPD_MSK (1 << 10)
#define PTD_LAST_MSK (1 << 11)
#define PTD_EP_MSK (0xf << 12)
u16 len;
#define PTD_LEN_MSK (0x3ff << 0)
#define PTD_DIR_MSK (3 << 10)
#define PTD_DIR_SETUP (0)
#define PTD_DIR_OUT (1)
#define PTD_DIR_IN (2)
#define PTD_B5_5_MSK (1 << 13)
u16 faddr;
#define PTD_FA_MSK (0x7f << 0)
#define PTD_FMT_MSK (1 << 7)
} __attribute__ ((packed, aligned(2)));
/* PTD accessor macros. */
#define PTD_GET_COUNT(p) (((p)->count & PTD_COUNT_MSK) >> 0)
#define PTD_COUNT(v) (((v) << 0) & PTD_COUNT_MSK)
#define PTD_GET_TOGGLE(p) (((p)->count & PTD_TOGGLE_MSK) >> 10)
#define PTD_TOGGLE(v) (((v) << 10) & PTD_TOGGLE_MSK)
#define PTD_GET_ACTIVE(p) (((p)->count & PTD_ACTIVE_MSK) >> 11)
#define PTD_ACTIVE(v) (((v) << 11) & PTD_ACTIVE_MSK)
#define PTD_GET_CC(p) (((p)->count & PTD_CC_MSK) >> 12)
#define PTD_CC(v) (((v) << 12) & PTD_CC_MSK)
#define PTD_GET_MPS(p) (((p)->mps & PTD_MPS_MSK) >> 0)
#define PTD_MPS(v) (((v) << 0) & PTD_MPS_MSK)
#define PTD_GET_SPD(p) (((p)->mps & PTD_SPD_MSK) >> 10)
#define PTD_SPD(v) (((v) << 10) & PTD_SPD_MSK)
#define PTD_GET_LAST(p) (((p)->mps & PTD_LAST_MSK) >> 11)
#define PTD_LAST(v) (((v) << 11) & PTD_LAST_MSK)
#define PTD_GET_EP(p) (((p)->mps & PTD_EP_MSK) >> 12)
#define PTD_EP(v) (((v) << 12) & PTD_EP_MSK)
#define PTD_GET_LEN(p) (((p)->len & PTD_LEN_MSK) >> 0)
#define PTD_LEN(v) (((v) << 0) & PTD_LEN_MSK)
#define PTD_GET_DIR(p) (((p)->len & PTD_DIR_MSK) >> 10)
#define PTD_DIR(v) (((v) << 10) & PTD_DIR_MSK)
#define PTD_GET_B5_5(p) (((p)->len & PTD_B5_5_MSK) >> 13)
#define PTD_B5_5(v) (((v) << 13) & PTD_B5_5_MSK)
#define PTD_GET_FA(p) (((p)->faddr & PTD_FA_MSK) >> 0)
#define PTD_FA(v) (((v) << 0) & PTD_FA_MSK)
#define PTD_GET_FMT(p) (((p)->faddr & PTD_FMT_MSK) >> 7)
#define PTD_FMT(v) (((v) << 7) & PTD_FMT_MSK)
/* Hardware transfer status codes -- CC from ptd->count */
#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 PTD 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 */ -ETIME,
/* PIDCheck */ -EPROTO,
/* UnExpPID */ -EPROTO,
/* DataOver */ -EOVERFLOW,
/* DataUnder */ -EREMOTEIO,
/* (for hw) */ -EIO,
/* (for hw) */ -EIO,
/* BufferOver */ -ECOMM,
/* BuffUnder */ -ENOSR,
/* (for HCD) */ -EALREADY,
/* (for HCD) */ -EALREADY
};
/*--------------------------------------------------------------*/
#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */
#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE)
struct isp116x {
spinlock_t lock;
void __iomem *addr_reg;
void __iomem *data_reg;
struct isp116x_platform_data *board;
struct dentry *dentry;
unsigned long stat1, stat2, stat4, stat8, stat16;
/* HC registers */
u32 intenb; /* "OHCI" interrupts */
u16 irqenb; /* uP interrupts */
/* Root hub registers */
u32 rhdesca;
u32 rhdescb;
u32 rhstatus;
u32 rhport[2];
/* async schedule: control, bulk */
struct list_head async;
/* periodic schedule: int */
u16 load[PERIODIC_SIZE];
struct isp116x_ep *periodic[PERIODIC_SIZE];
unsigned periodic_count;
u16 fmindex;
/* Schedule for the current frame */
struct isp116x_ep *atl_active;
int atl_buflen;
int atl_bufshrt;
int atl_last_dir;
atomic_t atl_finishing;
};
static inline struct isp116x *hcd_to_isp116x(struct usb_hcd *hcd)
{
return (struct isp116x *)(hcd->hcd_priv);
}
static inline struct usb_hcd *isp116x_to_hcd(struct isp116x *isp116x)
{
return container_of((void *)isp116x, struct usb_hcd, hcd_priv);
}
struct isp116x_ep {
struct usb_host_endpoint *hep;
struct usb_device *udev;
struct ptd ptd;
u8 maxpacket;
u8 epnum;
u8 nextpid;
u16 error_count;
u16 length; /* of current packet */
unsigned char *data; /* to databuf */
/* queue of active EP's (the ones scheduled for the
current frame) */
struct isp116x_ep *active;
/* periodic schedule */
u16 period;
u16 branch;
u16 load;
struct isp116x_ep *next;
/* async schedule */
struct list_head schedule;
};
/*-------------------------------------------------------------------------*/
#ifdef DEBUG
#define DBG(stuff...) printk(KERN_DEBUG "116x: " 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 "116x: " stuff)
#define WARN(stuff...) printk(KERN_WARNING "116x: " stuff)
#define INFO(stuff...) printk(KERN_INFO "116x: " stuff)
/* ------------------------------------------------- */
#if defined(USE_PLATFORM_DELAY)
#if defined(USE_NDELAY)
#error USE_PLATFORM_DELAY and USE_NDELAY simultaneously defined.
#endif
#define isp116x_delay(h,d) (h)->board->delay( \
isp116x_to_hcd(h)->self.controller,d)
#define isp116x_check_platform_delay(h) ((h)->board->delay == NULL)
#elif defined(USE_NDELAY)
#define isp116x_delay(h,d) ndelay(d)
#define isp116x_check_platform_delay(h) 0
#else
#define isp116x_delay(h,d) do{}while(0)
#define isp116x_check_platform_delay(h) 0
#endif
#if defined(DEBUG)
#define IRQ_TEST() BUG_ON(!irqs_disabled())
#else
#define IRQ_TEST() do{}while(0)
#endif
static inline void isp116x_write_addr(struct isp116x *isp116x, unsigned reg)
{
IRQ_TEST();
writew(reg & 0xff, isp116x->addr_reg);
isp116x_delay(isp116x, 300);
}
static inline void isp116x_write_data16(struct isp116x *isp116x, u16 val)
{
writew(val, isp116x->data_reg);
isp116x_delay(isp116x, 150);
}
static inline void isp116x_raw_write_data16(struct isp116x *isp116x, u16 val)
{
__raw_writew(val, isp116x->data_reg);
isp116x_delay(isp116x, 150);
}
static inline u16 isp116x_read_data16(struct isp116x *isp116x)
{
u16 val;
val = readw(isp116x->data_reg);
isp116x_delay(isp116x, 150);
return val;
}
static inline u16 isp116x_raw_read_data16(struct isp116x *isp116x)
{
u16 val;
val = __raw_readw(isp116x->data_reg);
isp116x_delay(isp116x, 150);
return val;
}
static inline void isp116x_write_data32(struct isp116x *isp116x, u32 val)
{
writew(val & 0xffff, isp116x->data_reg);
isp116x_delay(isp116x, 150);
writew(val >> 16, isp116x->data_reg);
isp116x_delay(isp116x, 150);
}
static inline u32 isp116x_read_data32(struct isp116x *isp116x)
{
u32 val;
val = (u32) readw(isp116x->data_reg);
isp116x_delay(isp116x, 150);
val |= ((u32) readw(isp116x->data_reg)) << 16;
isp116x_delay(isp116x, 150);
return val;
}
/* Let's keep register access functions out of line. Hint:
we wait at least 150 ns at every access.
*/
static u16 isp116x_read_reg16(struct isp116x *isp116x, unsigned reg)
{
isp116x_write_addr(isp116x, reg);
return isp116x_read_data16(isp116x);
}
static u32 isp116x_read_reg32(struct isp116x *isp116x, unsigned reg)
{
isp116x_write_addr(isp116x, reg);
return isp116x_read_data32(isp116x);
}
static void isp116x_write_reg16(struct isp116x *isp116x, unsigned reg,
unsigned val)
{
isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET);
isp116x_write_data16(isp116x, (u16) (val & 0xffff));
}
static void isp116x_write_reg32(struct isp116x *isp116x, unsigned reg,
unsigned val)
{
isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET);
isp116x_write_data32(isp116x, (u32) val);
}
#define isp116x_show_reg_log(d,r,s) { \
if ((r) < 0x20) { \
DBG("%-12s[%02x]: %08x\n", #r, \
r, isp116x_read_reg32(d, r)); \
} else { \
DBG("%-12s[%02x]: %04x\n", #r, \
r, isp116x_read_reg16(d, r)); \
} \
}
#define isp116x_show_reg_seq(d,r,s) { \
if ((r) < 0x20) { \
seq_printf(s, "%-12s[%02x]: %08x\n", #r, \
r, isp116x_read_reg32(d, r)); \
} else { \
seq_printf(s, "%-12s[%02x]: %04x\n", #r, \
r, isp116x_read_reg16(d, r)); \
} \
}
#define isp116x_show_regs(d,type,s) { \
isp116x_show_reg_##type(d, HCREVISION, s); \
isp116x_show_reg_##type(d, HCCONTROL, s); \
isp116x_show_reg_##type(d, HCCMDSTAT, s); \
isp116x_show_reg_##type(d, HCINTSTAT, s); \
isp116x_show_reg_##type(d, HCINTENB, s); \
isp116x_show_reg_##type(d, HCFMINTVL, s); \
isp116x_show_reg_##type(d, HCFMREM, s); \
isp116x_show_reg_##type(d, HCFMNUM, s); \
isp116x_show_reg_##type(d, HCLSTHRESH, s); \
isp116x_show_reg_##type(d, HCRHDESCA, s); \
isp116x_show_reg_##type(d, HCRHDESCB, s); \
isp116x_show_reg_##type(d, HCRHSTATUS, s); \
isp116x_show_reg_##type(d, HCRHPORT1, s); \
isp116x_show_reg_##type(d, HCRHPORT2, s); \
isp116x_show_reg_##type(d, HCHWCFG, s); \
isp116x_show_reg_##type(d, HCDMACFG, s); \
isp116x_show_reg_##type(d, HCXFERCTR, s); \
isp116x_show_reg_##type(d, HCuPINT, s); \
isp116x_show_reg_##type(d, HCuPINTENB, s); \
isp116x_show_reg_##type(d, HCCHIPID, s); \
isp116x_show_reg_##type(d, HCSCRATCH, s); \
isp116x_show_reg_##type(d, HCITLBUFLEN, s); \
isp116x_show_reg_##type(d, HCATLBUFLEN, s); \
isp116x_show_reg_##type(d, HCBUFSTAT, s); \
isp116x_show_reg_##type(d, HCRDITL0LEN, s); \
isp116x_show_reg_##type(d, HCRDITL1LEN, s); \
}
/*
Dump registers for debugfs.
*/
static inline void isp116x_show_regs_seq(struct isp116x *isp116x,
struct seq_file *s)
{
isp116x_show_regs(isp116x, seq, s);
}
/*
Dump registers to syslog.
*/
static inline void isp116x_show_regs_log(struct isp116x *isp116x)
{
isp116x_show_regs(isp116x, log, NULL);
}
#if defined(URB_TRACE)
#define PIPETYPE(pipe) ({ char *__s; \
if (usb_pipecontrol(pipe)) __s = "ctrl"; \
else if (usb_pipeint(pipe)) __s = "int"; \
else if (usb_pipebulk(pipe)) __s = "bulk"; \
else __s = "iso"; \
__s;})
#define PIPEDIR(pipe) ({ usb_pipein(pipe) ? "in" : "out"; })
#define URB_NOTSHORT(urb) ({ (urb)->transfer_flags & URB_SHORT_NOT_OK ? \
"short_not_ok" : ""; })
/* print debug info about the URB */
static void urb_dbg(struct urb *urb, char *msg)
{
unsigned int pipe;
if (!urb) {
DBG("%s: zero urb\n", msg);
return;
}
pipe = urb->pipe;
DBG("%s: FA %d ep%d%s %s: len %d/%d %s\n", msg,
usb_pipedevice(pipe), usb_pipeendpoint(pipe),
PIPEDIR(pipe), PIPETYPE(pipe),
urb->transfer_buffer_length, urb->actual_length, URB_NOTSHORT(urb));
}
#else
#define urb_dbg(urb,msg) do{}while(0)
#endif /* ! defined(URB_TRACE) */
#if defined(PTD_TRACE)
#define PTD_DIR_STR(ptd) ({char __c; \
switch(PTD_GET_DIR(ptd)){ \
case 0: __c = 's'; break; \
case 1: __c = 'o'; break; \
default: __c = 'i'; break; \
}; __c;})
/*
Dump PTD info. The code documents the format
perfectly, right :)
*/
static inline void dump_ptd(struct ptd *ptd)
{
printk("td: %x %d%c%d %d,%d,%d %x %x%x%x\n",
PTD_GET_CC(ptd), PTD_GET_FA(ptd),
PTD_DIR_STR(ptd), PTD_GET_EP(ptd),
PTD_GET_COUNT(ptd), PTD_GET_LEN(ptd), PTD_GET_MPS(ptd),
PTD_GET_TOGGLE(ptd), PTD_GET_ACTIVE(ptd),
PTD_GET_SPD(ptd), PTD_GET_LAST(ptd));
}
static inline void dump_ptd_out_data(struct ptd *ptd, u8 * buf)
{
int k;
if (PTD_GET_DIR(ptd) != PTD_DIR_IN && PTD_GET_LEN(ptd)) {
printk("-> ");
for (k = 0; k < PTD_GET_LEN(ptd); ++k)
printk("%02x ", ((u8 *) buf)[k]);
printk("\n");
}
}
static inline void dump_ptd_in_data(struct ptd *ptd, u8 * buf)
{
int k;
if (PTD_GET_DIR(ptd) == PTD_DIR_IN && PTD_GET_COUNT(ptd)) {
printk("<- ");
for (k = 0; k < PTD_GET_COUNT(ptd); ++k)
printk("%02x ", ((u8 *) buf)[k]);
printk("\n");
}
if (PTD_GET_LAST(ptd))
printk("-\n");
}
#else
#define dump_ptd(ptd) do{}while(0)
#define dump_ptd_in_data(ptd,buf) do{}while(0)
#define dump_ptd_out_data(ptd,buf) do{}while(0)
#endif /* ! defined(PTD_TRACE) */

View File

@@ -0,0 +1,340 @@
/*
* OHCI HCD (Host Controller Driver) for USB.
*
* Copyright (C) 2004 SAN People (Pty) Ltd.
* Copyright (C) 2005 Thibaut VARENE <varenet@parisc-linux.org>
*
* AT91 Bus Glue
*
* Based on fragments of 2.4 driver by Rick Bronson.
* Based on ohci-omap.c
*
* This file is licenced under the GPL.
*/
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <asm/mach-types.h>
#include <asm/hardware.h>
#include <asm/arch/board.h>
#include <asm/arch/cpu.h>
#ifndef CONFIG_ARCH_AT91
#error "CONFIG_ARCH_AT91 must be defined."
#endif
/* interface and function clocks; sometimes also an AHB clock */
static struct clk *iclk, *fclk, *hclk;
static int clocked;
extern int usb_disabled(void);
/*-------------------------------------------------------------------------*/
static void at91_start_clock(void)
{
if (cpu_is_at91sam9261())
clk_enable(hclk);
clk_enable(iclk);
clk_enable(fclk);
clocked = 1;
}
static void at91_stop_clock(void)
{
clk_disable(fclk);
clk_disable(iclk);
if (cpu_is_at91sam9261())
clk_disable(hclk);
clocked = 0;
}
static void at91_start_hc(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_regs __iomem *regs = hcd->regs;
dev_dbg(&pdev->dev, "start\n");
/*
* Start the USB clocks.
*/
at91_start_clock();
/*
* The USB host controller must remain in reset.
*/
writel(0, &regs->control);
}
static void at91_stop_hc(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_regs __iomem *regs = hcd->regs;
dev_dbg(&pdev->dev, "stop\n");
/*
* Put the USB host controller into reset.
*/
writel(0, &regs->control);
/*
* Stop the USB clocks.
*/
at91_stop_clock();
}
/*-------------------------------------------------------------------------*/
static int usb_hcd_at91_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_at91_probe - initialize AT91-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.
*/
static int usb_hcd_at91_probe(const struct hc_driver *driver,
struct platform_device *pdev)
{
int retval;
struct usb_hcd *hcd = NULL;
if (pdev->num_resources != 2) {
pr_debug("hcd probe: invalid num_resources");
return -ENODEV;
}
if ((pdev->resource[0].flags != IORESOURCE_MEM)
|| (pdev->resource[1].flags != IORESOURCE_IRQ)) {
pr_debug("hcd probe: invalid resource type\n");
return -ENODEV;
}
hcd = usb_create_hcd(driver, &pdev->dev, "at91");
if (!hcd)
return -ENOMEM;
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)) {
pr_debug("request_mem_region failed\n");
retval = -EBUSY;
goto err1;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
pr_debug("ioremap failed\n");
retval = -EIO;
goto err2;
}
iclk = clk_get(&pdev->dev, "ohci_clk");
fclk = clk_get(&pdev->dev, "uhpck");
if (cpu_is_at91sam9261())
hclk = clk_get(&pdev->dev, "hck0");
at91_start_hc(pdev);
ohci_hcd_init(hcd_to_ohci(hcd));
retval = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED);
if (retval == 0)
return retval;
/* Error handling */
at91_stop_hc(pdev);
if (cpu_is_at91sam9261())
clk_put(hclk);
clk_put(fclk);
clk_put(iclk);
iounmap(hcd->regs);
err2:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err1:
usb_put_hcd(hcd);
return retval;
}
/* may be called with controller, bus, and devices active */
/**
* usb_hcd_at91_remove - shutdown processing for AT91-based HCDs
* @dev: USB Host Controller being removed
* Context: !in_interrupt()
*
* Reverses the effect of usb_hcd_at91_probe(), first invoking
* the HCD's stop() method. It is always called from a thread
* context, "rmmod" or something similar.
*
*/
static int usb_hcd_at91_remove(struct usb_hcd *hcd,
struct platform_device *pdev)
{
usb_remove_hcd(hcd);
at91_stop_hc(pdev);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
if (cpu_is_at91sam9261())
clk_put(hclk);
clk_put(fclk);
clk_put(iclk);
fclk = iclk = hclk = NULL;
dev_set_drvdata(&pdev->dev, NULL);
return 0;
}
/*-------------------------------------------------------------------------*/
static int __devinit
ohci_at91_start (struct usb_hcd *hcd)
{
struct at91_usbh_data *board = hcd->self.controller->platform_data;
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int ret;
if ((ret = ohci_init(ohci)) < 0)
return ret;
ohci->num_ports = board->ports;
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_at91_hc_driver = {
.description = hcd_name,
.product_desc = "AT91 OHCI",
.hcd_priv_size = sizeof(struct ohci_hcd),
/*
* generic hardware linkage
*/
.irq = ohci_irq,
.flags = HCD_USB11 | HCD_MEMORY,
/*
* basic lifecycle operations
*/
.start = ohci_at91_start,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
{
device_init_wakeup(&pdev->dev, 1);
return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev);
}
static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
{
device_init_wakeup(&pdev->dev, 0);
return usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev);
}
#ifdef CONFIG_PM
static int
ohci_hcd_at91_drv_suspend(struct platform_device *pdev, pm_message_t mesg)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
if (device_may_wakeup(&pdev->dev))
enable_irq_wake(hcd->irq);
/*
* The integrated transceivers seem unable to notice disconnect,
* reconnect, or wakeup without the 48 MHz clock active. so for
* correctness, always discard connection state (using reset).
*
* REVISIT: some boards will be able to turn VBUS off...
*/
if (at91_suspend_entering_slow_clock()) {
ohci_usb_reset (ohci);
at91_stop_clock();
}
return 0;
}
static int ohci_hcd_at91_drv_resume(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(hcd->irq);
if (!clocked)
at91_start_clock();
return 0;
}
#else
#define ohci_hcd_at91_drv_suspend NULL
#define ohci_hcd_at91_drv_resume NULL
#endif
MODULE_ALIAS("at91_ohci");
static struct platform_driver ohci_hcd_at91_driver = {
.probe = ohci_hcd_at91_drv_probe,
.remove = ohci_hcd_at91_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
.suspend = ohci_hcd_at91_drv_suspend,
.resume = ohci_hcd_at91_drv_resume,
.driver = {
.name = "at91_ohci",
.owner = THIS_MODULE,
},
};

View File

@@ -0,0 +1,347 @@
/*
* 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 <linux/platform_device.h>
#include <linux/signal.h>
#include <asm/mach-au1x00/au1000.h>
#ifndef CONFIG_SOC_AU1200
#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
#else /* Au1200 */
#define USB_HOST_CONFIG (USB_MSR_BASE + USB_MSR_MCFG)
#define USB_MCFG_PFEN (1<<31)
#define USB_MCFG_RDCOMB (1<<30)
#define USB_MCFG_SSDEN (1<<23)
#define USB_MCFG_OHCCLKEN (1<<16)
#define USB_MCFG_UCAM (1<<7)
#define USB_MCFG_OBMEN (1<<1)
#define USB_MCFG_OMEMEN (1<<0)
#define USBH_ENABLE_CE USB_MCFG_OHCCLKEN
#ifdef CONFIG_DMA_COHERENT
#define USBH_ENABLE_INIT (USB_MCFG_OHCCLKEN \
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
| USB_MCFG_SSDEN | USB_MCFG_UCAM \
| USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
#else
#define USBH_ENABLE_INIT (USB_MCFG_OHCCLKEN \
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
| USB_MCFG_SSDEN \
| USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
#endif
#define USBH_DISABLE (USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
#endif /* Au1200 */
extern int usb_disabled(void);
/*-------------------------------------------------------------------------*/
static void au1xxx_start_ohc(struct platform_device *dev)
{
printk(KERN_DEBUG __FILE__
": starting Au1xxx OHCI USB Controller\n");
/* enable host controller */
#ifndef CONFIG_SOC_AU1200
au_writel(USBH_ENABLE_CE, USB_HOST_CONFIG);
udelay(1000);
au_writel(USBH_ENABLE_INIT, USB_HOST_CONFIG);
udelay(1000);
#else /* Au1200 */
/* write HW defaults again in case Yamon cleared them */
if (au_readl(USB_HOST_CONFIG) == 0) {
au_writel(0x00d02000, USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
udelay(1000);
}
au_writel(USBH_ENABLE_CE | au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
udelay(1000);
au_writel(USBH_ENABLE_INIT | au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
udelay(1000);
#endif /* Au1200 */
#ifndef CONFIG_SOC_AU1200
/* wait for reset complete (read register twice; see au1500 errata) */
while (au_readl(USB_HOST_CONFIG),
!(au_readl(USB_HOST_CONFIG) & USBH_ENABLE_RD))
#endif
udelay(1000);
printk(KERN_DEBUG __FILE__
": Clock to USB host has been enabled \n");
}
static void au1xxx_stop_ohc(struct platform_device *dev)
{
printk(KERN_DEBUG __FILE__
": stopping Au1xxx OHCI USB Controller\n");
#ifndef CONFIG_SOC_AU1200
/* Disable clock */
au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_ENABLE_CE, USB_HOST_CONFIG);
#else /* Au1200 */
/* Disable mem */
au_writel(~USBH_DISABLE & au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
udelay(1000);
/* Disable clock */
au_writel(~USBH_ENABLE_CE & au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
au_readl(USB_HOST_CONFIG);
#endif /* Au1200 */
}
/*-------------------------------------------------------------------------*/
/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */
/**
* usb_ohci_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.
*
*/
static int usb_ohci_au1xxx_probe(const struct hc_driver *driver,
struct platform_device *dev)
{
int retval;
struct usb_hcd *hcd;
#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
/* Au1200 AB USB does not support coherent memory */
if (!(read_c0_prid() & 0xff)) {
pr_info("%s: this is chip revision AB !!\n",
dev->name);
pr_info("%s: update your board or re-configure the kernel\n",
dev->name);
return -ENODEV;
}
#endif
if (dev->resource[1].flags != IORESOURCE_IRQ) {
pr_debug("resource[1] is not IORESOURCE_IRQ\n");
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\n");
retval = -EBUSY;
goto err1;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
pr_debug("ioremap failed\n");
retval = -ENOMEM;
goto err2;
}
au1xxx_start_ohc(dev);
ohci_hcd_init(hcd_to_ohci(hcd));
retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED | IRQF_SHARED);
if (retval == 0)
return retval;
au1xxx_stop_ohc(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.
*
*/
static void usb_ohci_au1xxx_remove(struct usb_hcd *hcd, struct platform_device *dev)
{
usb_remove_hcd(hcd);
au1xxx_stop_ohc(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,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
static int ohci_hcd_au1xxx_drv_probe(struct platform_device *pdev)
{
int ret;
pr_debug ("In ohci_hcd_au1xxx_drv_probe");
if (usb_disabled())
return -ENODEV;
ret = usb_ohci_au1xxx_probe(&ohci_au1xxx_hc_driver, pdev);
return ret;
}
static int ohci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_ohci_au1xxx_remove(hcd, pdev);
return 0;
}
/*TBD*/
/*static int ohci_hcd_au1xxx_drv_suspend(struct platform_device *dev)
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
return 0;
}
static int ohci_hcd_au1xxx_drv_resume(struct platform_device *dev)
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
return 0;
}
*/
static struct platform_driver ohci_hcd_au1xxx_driver = {
.probe = ohci_hcd_au1xxx_drv_probe,
.remove = ohci_hcd_au1xxx_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
/*.suspend = ohci_hcd_au1xxx_drv_suspend, */
/*.resume = ohci_hcd_au1xxx_drv_resume, */
.driver = {
.name = "au1xxx-ohci",
.owner = THIS_MODULE,
},
};

708
drivers/usb/host/ohci-dbg.c Normal file
View File

@@ -0,0 +1,708 @@
/*
* 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, &regs->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, &regs->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, &regs->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, &regs->intrstatus),
next, size);
ohci_dump_intr_mask (controller, "intrenable",
ohci_readl (controller, &regs->intrenable),
next, size);
// intrdisable always same as intrenable
maybe_print_eds (controller, "ed_periodcurrent",
ohci_readl (controller, &regs->ed_periodcurrent),
next, size);
maybe_print_eds (controller, "ed_controlhead",
ohci_readl (controller, &regs->ed_controlhead),
next, size);
maybe_print_eds (controller, "ed_controlcurrent",
ohci_readl (controller, &regs->ed_controlcurrent),
next, size);
maybe_print_eds (controller, "ed_bulkhead",
ohci_readl (controller, &regs->ed_bulkhead),
next, size);
maybe_print_eds (controller, "ed_bulkcurrent",
ohci_readl (controller, &regs->ed_bulkcurrent),
next, size);
maybe_print_eds (controller, "donehead",
ohci_readl (controller, &regs->donehead), next, size);
}
#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, i;
temp = roothub_a (controller);
if (temp == ~(u32)0)
return;
if (verbose) {
ohci_dbg_sw (controller, next, size,
"roothub.a %08x POTPGT=%d%s%s%s%s%s NDP=%d(%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" : "",
(temp & RH_A_NDP), controller->num_ports
);
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 < controller->num_ports; 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 = class_get_devdata(class_dev);
hcd = bus_to_hcd(bus);
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, GFP_ATOMIC)))
return 0;
seen_count = 0;
bus = class_get_devdata(class_dev);
hcd = bus_to_hcd(bus);
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 = class_get_devdata(class_dev);
hcd = bus_to_hcd(bus);
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.event) {
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, &regs->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, &regs->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, &regs->periodicstart);
temp = scnprintf (next, size, "periodicstart 0x%04x\n",
rdata & 0x3fff);
size -= temp;
next += temp;
rdata = ohci_readl (ohci, &regs->lsthresh);
temp = scnprintf (next, size, "lsthresh 0x%04x\n",
rdata & 0x3fff);
size -= temp;
next += temp;
temp = scnprintf (next, size, "hub poll timer %s\n",
ohci_to_hcd(ohci)->poll_rh ? "ON" : "off");
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;
int retval;
retval = class_device_create_file(cldev, &class_device_attr_async);
retval = class_device_create_file(cldev, &class_device_attr_periodic);
retval = 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
/*-------------------------------------------------------------------------*/

View File

@@ -0,0 +1,216 @@
/*
* 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 ep93xx.
*
* 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
*
* Modified for ep93xx from ohci-pxa27x.c
* by Lennert Buytenhek <buytenh@wantstofly.org> 28-2-2006
* Based on an earlier driver by Ray Lehtiniemi
*
* This file is licenced under the GPL.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/signal.h>
#include <linux/platform_device.h>
#include <asm/mach-types.h>
#include <asm/hardware.h>
static struct clk *usb_host_clock;
static void ep93xx_start_hc(struct device *dev)
{
clk_enable(usb_host_clock);
}
static void ep93xx_stop_hc(struct device *dev)
{
clk_disable(usb_host_clock);
}
static int usb_hcd_ep93xx_probe(const struct hc_driver *driver,
struct platform_device *pdev)
{
int retval;
struct usb_hcd *hcd;
if (pdev->resource[1].flags != IORESOURCE_IRQ) {
pr_debug("resource[1] is not IORESOURCE_IRQ");
return -ENOMEM;
}
hcd = usb_create_hcd(driver, &pdev->dev, "ep93xx");
if (hcd == NULL)
return -ENOMEM;
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)) {
usb_put_hcd(hcd);
retval = -EBUSY;
goto err1;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (hcd->regs == NULL) {
pr_debug("ioremap failed");
retval = -ENOMEM;
goto err2;
}
usb_host_clock = clk_get(&pdev->dev, "usb_host");
ep93xx_start_hc(&pdev->dev);
ohci_hcd_init(hcd_to_ohci(hcd));
retval = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED);
if (retval == 0)
return retval;
ep93xx_stop_hc(&pdev->dev);
iounmap(hcd->regs);
err2:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err1:
usb_put_hcd(hcd);
return retval;
}
static void usb_hcd_ep93xx_remove(struct usb_hcd *hcd,
struct platform_device *pdev)
{
usb_remove_hcd(hcd);
ep93xx_stop_hc(&pdev->dev);
clk_put(usb_host_clock);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
}
static int __devinit ohci_ep93xx_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 struct hc_driver ohci_ep93xx_hc_driver = {
.description = hcd_name,
.product_desc = "EP93xx OHCI",
.hcd_priv_size = sizeof(struct ohci_hcd),
.irq = ohci_irq,
.flags = HCD_USB11 | HCD_MEMORY,
.start = ohci_ep93xx_start,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
.urb_enqueue = ohci_urb_enqueue,
.urb_dequeue = ohci_urb_dequeue,
.endpoint_disable = ohci_endpoint_disable,
.get_frame_number = ohci_get_frame,
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
extern int usb_disabled(void);
static int ohci_hcd_ep93xx_drv_probe(struct platform_device *pdev)
{
int ret;
ret = -ENODEV;
if (!usb_disabled())
ret = usb_hcd_ep93xx_probe(&ohci_ep93xx_hc_driver, pdev);
return ret;
}
static int ohci_hcd_ep93xx_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_ep93xx_remove(hcd, pdev);
return 0;
}
#ifdef CONFIG_PM
static int ohci_hcd_ep93xx_drv_suspend(struct platform_device *pdev, pm_message_t state)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
if (time_before(jiffies, ohci->next_statechange))
msleep(5);
ohci->next_statechange = jiffies;
ep93xx_stop_hc(&pdev->dev);
hcd->state = HC_STATE_SUSPENDED;
pdev->dev.power.power_state = PMSG_SUSPEND;
return 0;
}
static int ohci_hcd_ep93xx_drv_resume(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
int status;
if (time_before(jiffies, ohci->next_statechange))
msleep(5);
ohci->next_statechange = jiffies;
ep93xx_start_hc(&pdev->dev);
pdev->dev.power.power_state = PMSG_ON;
usb_hcd_resume_root_hub(hcd);
return 0;
}
#endif
static struct platform_driver ohci_hcd_ep93xx_driver = {
.probe = ohci_hcd_ep93xx_drv_probe,
.remove = ohci_hcd_ep93xx_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
#ifdef CONFIG_PM
.suspend = ohci_hcd_ep93xx_drv_suspend,
.resume = ohci_hcd_ep93xx_drv_resume,
#endif
.driver = {
.name = "ep93xx-ohci",
},
};

1029
drivers/usb/host/ohci-hcd.c Normal file

File diff suppressed because it is too large Load Diff

724
drivers/usb/host/ohci-hub.c Normal file
View File

@@ -0,0 +1,724 @@
/*
* 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" : "" \
);
/*-------------------------------------------------------------------------*/
/* hcd->hub_irq_enable() */
static void ohci_rhsc_enable (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
spin_lock_irq(&ohci->lock);
if (!ohci->autostop)
del_timer(&hcd->rh_timer); /* Prevent next poll */
ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
spin_unlock_irq(&ohci->lock);
}
#define OHCI_SCHED_ENABLES \
(OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
static void dl_done_list (struct ohci_hcd *);
static void finish_unlinks (struct ohci_hcd *, u16);
#ifdef CONFIG_PM
static int ohci_restart(struct ohci_hcd *ohci);
static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
__releases(ohci->lock)
__acquires(ohci->lock)
{
int status = 0;
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:
if (!ohci->autostop) {
ohci_dbg (ohci, "already suspended\n");
goto done;
}
}
ohci_dbg (ohci, "%s root hub\n",
autostop ? "auto-stop" : "suspend");
/* First stop any processing */
if (!autostop && (ohci->hc_control & OHCI_SCHED_ENABLES)) {
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");
ohci->autostop = 0;
spin_unlock_irq (&ohci->lock);
msleep (8);
spin_lock_irq (&ohci->lock);
}
dl_done_list (ohci);
finish_unlinks (ohci, ohci_frame_no(ohci));
/* maybe resume can wake root hub */
if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev) ||
autostop)
ohci->hc_control |= OHCI_CTRL_RWE;
else {
ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable);
ohci->hc_control &= ~OHCI_CTRL_RWE;
}
/* Suspend hub ... this is the "global (to this bus) suspend" mode,
* which doesn't imply ports will first be individually suspended.
*/
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 */
if (!autostop) {
ohci->next_statechange = jiffies + msecs_to_jiffies (5);
ohci->autostop = 0;
}
done:
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_rh_resume (struct ohci_hcd *ohci)
__releases(ohci->lock)
__acquires(ohci->lock)
{
struct usb_hcd *hcd = ohci_to_hcd (ohci);
u32 temp, enables;
int status = -EINPROGRESS;
int autostopped = ohci->autostop;
ohci->autostop = 0;
ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) {
/* this can happen after resuming a swsusp snapshot */
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, "%s root hub\n",
autostopped ? "auto-start" : "resume");
break;
case OHCI_USB_RESUME:
/* HCFS changes sometime after INTR_RD */
ohci_dbg(ohci, "%swakeup root hub\n",
autostopped ? "auto-" : "");
break;
case OHCI_USB_OPER:
/* this can happen after resuming a swsusp snapshot */
ohci_dbg (ohci, "snapshot resume? reinit\n");
status = -EBUSY;
break;
default: /* RESET, we lost power */
ohci_dbg (ohci, "lost power\n");
status = -EBUSY;
}
if (status == -EBUSY) {
if (!autostopped) {
spin_unlock_irq (&ohci->lock);
(void) ohci_init (ohci);
status = ohci_restart (ohci);
spin_lock_irq (&ohci->lock);
}
return status;
}
if (status != -EINPROGRESS)
return status;
if (autostopped)
goto skip_resume;
spin_unlock_irq (&ohci->lock);
/* Some controllers (lucent erratum) need extra-long delays */
msleep (20 /* usb 11.5.1.10 */ + 12 /* 32 msec counter */ + 1);
temp = ohci_readl (ohci, &ohci->regs->control);
temp &= OHCI_CTRL_HCFS;
if (temp != OHCI_USB_RESUME) {
ohci_err (ohci, "controller won't resume\n");
spin_lock_irq(&ohci->lock);
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);
/* the following code is executed with ohci->lock held and
* irqs disabled if and only if autostopped is true
*/
skip_resume:
/* 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);
/* Then re-enable operations */
ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control);
(void) ohci_readl (ohci, &ohci->regs->control);
if (!autostopped)
msleep (3);
temp = ohci->hc_control;
temp &= OHCI_CTRL_RWC;
temp |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
ohci->hc_control = temp;
ohci_writel (ohci, temp, &ohci->regs->control);
(void) ohci_readl (ohci, &ohci->regs->control);
/* TRSMRCY */
if (!autostopped) {
msleep (10);
spin_lock_irq (&ohci->lock);
}
/* now ohci->lock is always held and irqs are always disabled */
/* keep it alive for more than ~5x suspend + resume costs */
ohci->next_statechange = jiffies + STATECHANGE_DELAY;
/* 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);
}
return 0;
}
static int ohci_bus_suspend (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int rc;
spin_lock_irq (&ohci->lock);
if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
rc = -ESHUTDOWN;
else
rc = ohci_rh_suspend (ohci, 0);
spin_unlock_irq (&ohci->lock);
return rc;
}
static int ohci_bus_resume (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int rc;
if (time_before (jiffies, ohci->next_statechange))
msleep(5);
spin_lock_irq (&ohci->lock);
if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
rc = -ESHUTDOWN;
else
rc = ohci_rh_resume (ohci);
spin_unlock_irq (&ohci->lock);
/* poll until we know a device is connected or we autostop */
if (rc == 0)
usb_hcd_poll_rh_status(hcd);
return rc;
}
/* Carry out polling-, autostop-, and autoresume-related state changes */
static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
int any_connected)
{
int poll_rh = 1;
switch (ohci->hc_control & OHCI_CTRL_HCFS) {
case OHCI_USB_OPER:
/* keep on polling until we know a device is connected
* and RHSC is enabled */
if (!ohci->autostop) {
if (any_connected ||
!device_may_wakeup(&ohci_to_hcd(ohci)
->self.root_hub->dev)) {
if (ohci_readl(ohci, &ohci->regs->intrenable) &
OHCI_INTR_RHSC)
poll_rh = 0;
} else {
ohci->autostop = 1;
ohci->next_statechange = jiffies + HZ;
}
/* if no devices have been attached for one second, autostop */
} else {
if (changed || any_connected) {
ohci->autostop = 0;
ohci->next_statechange = jiffies +
STATECHANGE_DELAY;
} else if (time_after_eq(jiffies,
ohci->next_statechange)
&& !ohci->ed_rm_list
&& !(ohci->hc_control &
OHCI_SCHED_ENABLES)) {
ohci_rh_suspend(ohci, 1);
}
}
break;
/* if there is a port change, autostart or ask to be resumed */
case OHCI_USB_SUSPEND:
case OHCI_USB_RESUME:
if (changed) {
if (ohci->autostop)
ohci_rh_resume(ohci);
else
usb_hcd_resume_root_hub(ohci_to_hcd(ohci));
} else {
/* everything is idle, no need for polling */
poll_rh = 0;
}
break;
}
return poll_rh;
}
#else /* CONFIG_PM */
static inline int ohci_rh_resume(struct ohci_hcd *ohci)
{
return 0;
}
/* Carry out polling-related state changes.
* autostop isn't used when CONFIG_PM is turned off.
*/
static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
int any_connected)
{
int poll_rh = 1;
/* keep on polling until RHSC is enabled */
if (ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC)
poll_rh = 0;
return poll_rh;
}
#endif /* 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 i, changed = 0, length = 1;
int any_connected = 0;
unsigned long flags;
spin_lock_irqsave (&ohci->lock, flags);
/* undocumented erratum seen on at least rev D */
if ((ohci->flags & OHCI_QUIRK_AMD756)
&& (roothub_a (ohci) & RH_A_NDP) > MAX_ROOT_PORTS) {
ohci_warn (ohci, "bogus NDP, rereads as NDP=%d\n",
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 (ohci->num_ports > 7) {
buf [1] = 0;
length++;
}
/* look at each port */
for (i = 0; i < ohci->num_ports; i++) {
u32 status = roothub_portstatus (ohci, i);
/* can't autostop if ports are connected */
any_connected |= (status & RH_PS_CCS);
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);
}
}
hcd->poll_rh = ohci_root_hub_state_changes(ohci, changed,
any_connected);
done:
spin_unlock_irqrestore (&ohci->lock, flags);
return changed ? length : 0;
}
/*-------------------------------------------------------------------------*/
static void
ohci_hub_descriptor (
struct ohci_hcd *ohci,
struct usb_hub_descriptor *desc
) {
u32 rh = roothub_a (ohci);
u16 temp;
desc->bDescriptorType = 0x29;
desc->bPwrOn2PwrGood = (rh & RH_A_POTPGT) >> 24;
desc->bHubContrCurrent = 0;
desc->bNbrPorts = ohci->num_ports;
temp = 1 + (ohci->num_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);
memset(desc->bitmap, 0xff, sizeof(desc->bitmap));
desc->bitmap [0] = rh & RH_B_DR;
if (ohci->num_ports > 7) {
desc->bitmap [1] = (rh & RH_B_DR) >> 8;
desc->bitmap [2] = 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 int 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);
/* handle e.g. CardBus eject */
if (temp == ~(u32)0)
return -ESHUTDOWN;
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 */
return 0;
}
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;
if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
return -ESHUTDOWN;
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;
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);
put_unaligned(cpu_to_le32 (temp), (__le32 *) buf);
break;
case GetPortStatus:
if (!wIndex || wIndex > ports)
goto error;
wIndex--;
temp = roothub_portstatus (ohci, wIndex);
put_unaligned(cpu_to_le32 (temp), (__le32 *) buf);
#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:
retval = root_port_reset (ohci, wIndex);
break;
default:
goto error;
}
break;
default:
error:
/* "protocol stall" on error */
retval = -EPIPE;
}
return retval;
}

View File

@@ -0,0 +1,253 @@
/*
* 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 <linux/platform_device.h>
#include <linux/signal.h>
#include <asm/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, IRQF_DISABLED);
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,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
static int ohci_hcd_lh7a404_drv_probe(struct platform_device *pdev)
{
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 platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_lh7a404_remove(hcd, pdev);
return 0;
}
/*TBD*/
/*static int ohci_hcd_lh7a404_drv_suspend(struct platform_device *dev)
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
return 0;
}
static int ohci_hcd_lh7a404_drv_resume(struct platform_device *dev)
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
return 0;
}
*/
static struct platform_driver ohci_hcd_lh7a404_driver = {
.probe = ohci_hcd_lh7a404_drv_probe,
.remove = ohci_hcd_lh7a404_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
/*.suspend = ohci_hcd_lh7a404_drv_suspend, */
/*.resume = ohci_hcd_lh7a404_drv_resume, */
.driver = {
.name = "lh7a404-ohci",
.owner = THIS_MODULE,
},
};

138
drivers/usb/host/ohci-mem.c Normal file
View File

@@ -0,0 +1,138 @@
/*
* 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.
*/
/*-------------------------------------------------------------------------*/
/*
* OHCI deals with 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 "register" data, which is memory mapped.
* No memory seen by this driver (or any HCD) may be paged out.
*/
/*-------------------------------------------------------------------------*/
static void ohci_hcd_init (struct ohci_hcd *ohci)
{
ohci->next_statechange = jiffies;
spin_lock_init (&ohci->lock);
INIT_LIST_HEAD (&ohci->pending);
}
/*-------------------------------------------------------------------------*/
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, gfp_t 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, gfp_t 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);
}

View File

@@ -0,0 +1,546 @@
/*
* 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 <linux/signal.h> /* IRQF_DISABLED */
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/mach-types.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>
/* 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 struct clk *usb_dc_ck;
static int host_enabled;
static int host_initialized;
static void omap_ohci_clock_power(int on)
{
if (on) {
clk_enable(usb_dc_ck);
clk_enable(usb_host_ck);
/* guesstimate for T5 == 1x 32K clock + APLL lock time */
udelay(100);
} else {
clk_disable(usb_host_ck);
clk_disable(usb_dc_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;
}
#ifdef CONFIG_ARCH_OMAP15XX
/*
* 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;
}
#else
#define omap_1510_local_bus_power(x) {}
#define omap_1510_local_bus_init() {}
#endif
#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 ohci_omap_init(struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
struct omap_usb_config *config = hcd->self.controller->platform_data;
int need_transceiver = (config->otg != 0);
int ret;
dev_dbg(hcd->self.controller, "starting USB Controller\n");
if (config->otg) {
ohci_to_hcd(ohci)->self.otg_port = config->otg;
/* default/minimum OTG power budget: 8 mA */
ohci_to_hcd(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(hcd->self.controller, "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(hcd->self.controller, "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_to_hcd(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;
} else if (machine_is_nokia770()) {
/* We require a self-powered hub, which should have
* plenty of power. */
ohci_to_hcd(ohci)->power_budget = 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 ohci_omap_stop(struct usb_hcd *hcd)
{
dev_dbg(hcd->self.controller, "stopping USB Controller\n");
omap_ohci_clock_power(0);
}
/*-------------------------------------------------------------------------*/
/**
* 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.
*/
static int usb_hcd_omap_probe (const struct hc_driver *driver,
struct platform_device *pdev)
{
int retval, irq;
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);
if (!cpu_is_omap1510())
usb_dc_ck = clk_get(0, "usb_dc_ck");
else
usb_dc_ck = clk_get(0, "lb_ck");
if (IS_ERR(usb_dc_ck)) {
clk_put(usb_host_ck);
return PTR_ERR(usb_dc_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);
host_initialized = 0;
host_enabled = 1;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
retval = -ENXIO;
goto err2;
}
retval = usb_add_hcd(hcd, irq, IRQF_DISABLED);
if (retval)
goto err2;
host_initialized = 1;
if (!host_enabled)
omap_ohci_clock_power(0);
return 0;
err2:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err1:
usb_put_hcd(hcd);
err0:
clk_put(usb_dc_ck);
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.
*/
static inline void
usb_hcd_omap_remove (struct usb_hcd *hcd, struct platform_device *pdev)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
usb_remove_hcd(hcd);
if (ohci->transceiver) {
(void) otg_set_host(ohci->transceiver, 0);
put_device(ohci->transceiver->dev);
}
if (machine_is_omap_osk())
omap_free_gpio(9);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
clk_put(usb_dc_ck);
clk_put(usb_host_ck);
}
/*-------------------------------------------------------------------------*/
static int
ohci_omap_start (struct usb_hcd *hcd)
{
struct omap_usb_config *config;
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int ret;
if (!host_enabled)
return 0;
config = hcd->self.controller->platform_data;
if (config->otg || config->rwc) {
ohci->hc_control = OHCI_CTRL_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
*/
.reset = ohci_omap_init,
.start = ohci_omap_start,
.stop = ohci_omap_stop,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
static int ohci_hcd_omap_drv_probe(struct platform_device *dev)
{
return usb_hcd_omap_probe(&ohci_omap_hc_driver, dev);
}
static int ohci_hcd_omap_drv_remove(struct platform_device *dev)
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
usb_hcd_omap_remove(hcd, dev);
platform_set_drvdata(dev, NULL);
return 0;
}
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PM
static int ohci_omap_suspend(struct platform_device *dev, pm_message_t message)
{
struct ohci_hcd *ohci = hcd_to_ohci(platform_get_drvdata(dev));
if (time_before(jiffies, ohci->next_statechange))
msleep(5);
ohci->next_statechange = jiffies;
omap_ohci_clock_power(0);
ohci_to_hcd(ohci)->state = HC_STATE_SUSPENDED;
dev->dev.power.power_state = PMSG_SUSPEND;
return 0;
}
static int ohci_omap_resume(struct platform_device *dev)
{
struct ohci_hcd *ohci = hcd_to_ohci(platform_get_drvdata(dev));
if (time_before(jiffies, ohci->next_statechange))
msleep(5);
ohci->next_statechange = jiffies;
omap_ohci_clock_power(1);
dev->dev.power.power_state = PMSG_ON;
usb_hcd_resume_root_hub(platform_get_drvdata(dev));
return 0;
}
#endif
/*-------------------------------------------------------------------------*/
/*
* Driver definition to register with the OMAP bus
*/
static struct platform_driver ohci_hcd_omap_driver = {
.probe = ohci_hcd_omap_drv_probe,
.remove = ohci_hcd_omap_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
#ifdef CONFIG_PM
.suspend = ohci_omap_suspend,
.resume = ohci_omap_resume,
#endif
.driver = {
.owner = THIS_MODULE,
.name = "ohci",
},
};

313
drivers/usb/host/ohci-pci.c Normal file
View File

@@ -0,0 +1,313 @@
/*
* 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.
*/
#ifndef CONFIG_PCI
#error "This file is PCI bus glue. CONFIG_PCI must be defined."
#endif
/*-------------------------------------------------------------------------*/
/* AMD 756, for most chips (early revs), corrupts register
* values on read ... so enable the vendor workaround.
*/
static int __devinit ohci_quirk_amd756(struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci->flags = OHCI_QUIRK_AMD756;
ohci_dbg (ohci, "AMD756 erratum 4 workaround\n");
/* also erratum 10 (suspend/resume issues) */
device_init_wakeup(&hcd->self.root_hub->dev, 0);
return 0;
}
/* 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 ...)
*/
static int __devinit ohci_quirk_opti(struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci_dbg (ohci, "WARNING: OPTi workarounds unavailable\n");
return 0;
}
/* 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.
*/
static int __devinit ohci_quirk_ns(struct usb_hcd *hcd)
{
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
struct pci_dev *b;
b = pci_get_slot (pdev->bus, PCI_DEVFN (PCI_SLOT (pdev->devfn), 1));
if (b && b->device == PCI_DEVICE_ID_NS_87560_LIO
&& b->vendor == PCI_VENDOR_ID_NS) {
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci->flags |= OHCI_QUIRK_SUPERIO;
ohci_dbg (ohci, "Using NSC SuperIO setup\n");
}
pci_dev_put(b);
return 0;
}
/* Check for Compaq's ZFMicro chipset, which needs short
* delays before control or bulk queues get re-activated
* in finish_unlinks()
*/
static int __devinit ohci_quirk_zfmicro(struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci->flags |= OHCI_QUIRK_ZFMICRO;
ohci_dbg (ohci, "enabled Compaq ZFMicro chipset quirk\n");
return 0;
}
/* Check for Toshiba SCC OHCI which has big endian registers
* and little endian in memory data structures
*/
static int __devinit ohci_quirk_toshiba_scc(struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
/* That chip is only present in the southbridge of some
* cell based platforms which are supposed to select
* CONFIG_USB_OHCI_BIG_ENDIAN_MMIO. We verify here if
* that was the case though.
*/
#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO
ohci->flags |= OHCI_QUIRK_BE_MMIO;
ohci_dbg (ohci, "enabled big endian Toshiba quirk\n");
return 0;
#else
ohci_err (ohci, "unsupported big endian Toshiba quirk\n");
return -ENXIO;
#endif
}
/* List of quirks for OHCI */
static const struct pci_device_id ohci_pci_quirks[] = {
{
PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x740c),
.driver_data = (unsigned long)ohci_quirk_amd756,
},
{
PCI_DEVICE(PCI_VENDOR_ID_OPTI, 0xc861),
.driver_data = (unsigned long)ohci_quirk_opti,
},
{
PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_ANY_ID),
.driver_data = (unsigned long)ohci_quirk_ns,
},
{
PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xa0f8),
.driver_data = (unsigned long)ohci_quirk_zfmicro,
},
{
PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA_2, 0x01b6),
.driver_data = (unsigned long)ohci_quirk_toshiba_scc,
},
/* FIXME for some of the early AMD 760 southbridges, OHCI
* won't work at all. blacklist them.
*/
{},
};
static int ohci_pci_reset (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int ret = 0;
if (hcd->self.controller) {
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
const struct pci_device_id *quirk_id;
quirk_id = pci_match_id(ohci_pci_quirks, pdev);
if (quirk_id != NULL) {
int (*quirk)(struct usb_hcd *ohci);
quirk = (void *)quirk_id->driver_data;
ret = quirk(hcd);
}
}
if (ret == 0) {
ohci_hcd_init (ohci);
return ohci_init (ohci);
}
return ret;
}
static int __devinit ohci_pci_start (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int ret;
#ifdef CONFIG_PM /* avoid warnings about unused pdev */
if (hcd->self.controller) {
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
/* RWC may not be set for add-in PCI cards, since boot
* firmware probably ignored them. This transfers PCI
* PM wakeup capabilities (once the PCI layer is fixed).
*/
if (device_may_wakeup(&pdev->dev))
ohci->hc_control |= OHCI_CTRL_RWC;
}
#endif /* CONFIG_PM */
ret = ohci_run (ohci);
if (ret < 0) {
ohci_err (ohci, "can't start\n");
ohci_stop (hcd);
}
return ret;
}
#ifdef CONFIG_PM
static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
unsigned long flags;
int rc = 0;
/* Root hub was already suspended. Disable irq emission and
* mark HW unaccessible, bail out if RH has been resumed. Use
* the spinlock to properly synchronize with possible pending
* RH suspend or resume activity.
*
* This is still racy as hcd->state is manipulated outside of
* any locks =P But that will be a different fix.
*/
spin_lock_irqsave (&ohci->lock, flags);
if (hcd->state != HC_STATE_SUSPENDED) {
rc = -EINVAL;
goto bail;
}
ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
(void)ohci_readl(ohci, &ohci->regs->intrdisable);
/* make sure snapshot being resumed re-enumerates everything */
if (message.event == PM_EVENT_PRETHAW)
ohci_usb_reset(ohci);
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
bail:
spin_unlock_irqrestore (&ohci->lock, flags);
return rc;
}
static int ohci_pci_resume (struct usb_hcd *hcd)
{
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
usb_hcd_resume_root_hub(hcd);
return 0;
}
#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,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
#ifdef CONFIG_PM
/* these suspend/resume entries are for upstream PCI glue ONLY */
.suspend = ohci_pci_suspend,
.resume = ohci_pci_resume,
#endif
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_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_OHCI, ~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
.shutdown = usb_hcd_pci_shutdown,
};

View File

@@ -0,0 +1,467 @@
/*
* drivers/usb/host/ohci-pnx4008.c
*
* driver for Philips PNX4008 USB Host
*
* Authors: Dmitry Chigirev <source@mvista.com>
* Vitaly Wool <vitalywool@gmail.com>
*
* register initialization is based on code examples provided by Philips
* Copyright (c) 2005 Koninklijke Philips Electronics N.V.
*
* NOTE: This driver does not have suspend/resume functionality
* This driver is intended for engineering development purposes only
*
* 2005-2006 (c) MontaVista Software, Inc. 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.
*/
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/mach-types.h>
#include <asm/arch/platform.h>
#include <asm/arch/irqs.h>
#include <asm/arch/gpio.h>
#define USB_CTRL IO_ADDRESS(PNX4008_PWRMAN_BASE + 0x64)
/* USB_CTRL bit defines */
#define USB_SLAVE_HCLK_EN (1 << 24)
#define USB_HOST_NEED_CLK_EN (1 << 21)
#define USB_OTG_CLK_CTRL IO_ADDRESS(PNX4008_USB_CONFIG_BASE + 0xFF4)
#define USB_OTG_CLK_STAT IO_ADDRESS(PNX4008_USB_CONFIG_BASE + 0xFF8)
/* USB_OTG_CLK_CTRL bit defines */
#define AHB_M_CLOCK_ON (1 << 4)
#define OTG_CLOCK_ON (1 << 3)
#define I2C_CLOCK_ON (1 << 2)
#define DEV_CLOCK_ON (1 << 1)
#define HOST_CLOCK_ON (1 << 0)
#define USB_OTG_STAT_CONTROL IO_ADDRESS(PNX4008_USB_CONFIG_BASE + 0x110)
/* USB_OTG_STAT_CONTROL bit defines */
#define TRANSPARENT_I2C_EN (1 << 7)
#define HOST_EN (1 << 0)
/* ISP1301 USB transceiver I2C registers */
#define ISP1301_MODE_CONTROL_1 0x04 /* u8 read, set, +1 clear */
#define MC1_SPEED_REG (1 << 0)
#define MC1_SUSPEND_REG (1 << 1)
#define MC1_DAT_SE0 (1 << 2)
#define MC1_TRANSPARENT (1 << 3)
#define MC1_BDIS_ACON_EN (1 << 4)
#define MC1_OE_INT_EN (1 << 5)
#define MC1_UART_EN (1 << 6)
#define MC1_MASK 0x7f
#define ISP1301_MODE_CONTROL_2 0x12 /* u8 read, set, +1 clear */
#define MC2_GLOBAL_PWR_DN (1 << 0)
#define MC2_SPD_SUSP_CTRL (1 << 1)
#define MC2_BI_DI (1 << 2)
#define MC2_TRANSP_BDIR0 (1 << 3)
#define MC2_TRANSP_BDIR1 (1 << 4)
#define MC2_AUDIO_EN (1 << 5)
#define MC2_PSW_EN (1 << 6)
#define MC2_EN2V7 (1 << 7)
#define ISP1301_OTG_CONTROL_1 0x06 /* u8 read, set, +1 clear */
# define OTG1_DP_PULLUP (1 << 0)
# define OTG1_DM_PULLUP (1 << 1)
# define OTG1_DP_PULLDOWN (1 << 2)
# define OTG1_DM_PULLDOWN (1 << 3)
# define OTG1_ID_PULLDOWN (1 << 4)
# define OTG1_VBUS_DRV (1 << 5)
# define OTG1_VBUS_DISCHRG (1 << 6)
# define OTG1_VBUS_CHRG (1 << 7)
#define ISP1301_OTG_STATUS 0x10 /* u8 readonly */
# define OTG_B_SESS_END (1 << 6)
# define OTG_B_SESS_VLD (1 << 7)
#define ISP1301_I2C_ADDR 0x2C
#define ISP1301_I2C_MODE_CONTROL_1 0x4
#define ISP1301_I2C_MODE_CONTROL_2 0x12
#define ISP1301_I2C_OTG_CONTROL_1 0x6
#define ISP1301_I2C_OTG_CONTROL_2 0x10
#define ISP1301_I2C_INTERRUPT_SOURCE 0x8
#define ISP1301_I2C_INTERRUPT_LATCH 0xA
#define ISP1301_I2C_INTERRUPT_FALLING 0xC
#define ISP1301_I2C_INTERRUPT_RISING 0xE
#define ISP1301_I2C_REG_CLEAR_ADDR 1
struct i2c_driver isp1301_driver;
struct i2c_client *isp1301_i2c_client;
extern int usb_disabled(void);
extern int ocpi_enable(void);
static struct clk *usb_clk;
static int isp1301_probe(struct i2c_adapter *adap);
static int isp1301_detach(struct i2c_client *client);
static int isp1301_command(struct i2c_client *client, unsigned int cmd,
void *arg);
static unsigned short normal_i2c[] =
{ ISP1301_I2C_ADDR, ISP1301_I2C_ADDR + 1, I2C_CLIENT_END };
static unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END };
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_i2c,
.probe = dummy_i2c_addrlist,
.ignore = dummy_i2c_addrlist,
};
struct i2c_driver isp1301_driver = {
.id = I2C_DRIVERID_I2CDEV, /* Fake Id */
.class = I2C_CLASS_HWMON,
.attach_adapter = isp1301_probe,
.detach_client = isp1301_detach,
.command = isp1301_command
};
static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind)
{
struct i2c_client *c;
c = (struct i2c_client *)kzalloc(sizeof(*c), GFP_KERNEL);
if (!c)
return -ENOMEM;
strcpy(c->name, "isp1301");
c->flags = 0;
c->addr = addr;
c->adapter = adap;
c->driver = &isp1301_driver;
isp1301_i2c_client = c;
return i2c_attach_client(c);
}
static int isp1301_probe(struct i2c_adapter *adap)
{
return i2c_probe(adap, &addr_data, isp1301_attach);
}
static int isp1301_detach(struct i2c_client *client)
{
i2c_detach_client(client);
kfree(isp1301_i2c_client);
return 0;
}
/* No commands defined */
static int isp1301_command(struct i2c_client *client, unsigned int cmd,
void *arg)
{
return 0;
}
static void i2c_write(u8 buf, u8 subaddr)
{
char tmpbuf[2];
tmpbuf[0] = subaddr; /*register number */
tmpbuf[1] = buf; /*register data */
i2c_master_send(isp1301_i2c_client, &tmpbuf[0], 2);
}
static void isp1301_configure(void)
{
/* PNX4008 only supports DAT_SE0 USB mode */
/* PNX4008 R2A requires setting the MAX603 to output 3.6V */
/* Power up externel charge-pump */
i2c_write(MC1_DAT_SE0 | MC1_SPEED_REG, ISP1301_I2C_MODE_CONTROL_1);
i2c_write(~(MC1_DAT_SE0 | MC1_SPEED_REG),
ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR);
i2c_write(MC2_BI_DI | MC2_PSW_EN | MC2_SPD_SUSP_CTRL,
ISP1301_I2C_MODE_CONTROL_2);
i2c_write(~(MC2_BI_DI | MC2_PSW_EN | MC2_SPD_SUSP_CTRL),
ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR);
i2c_write(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN,
ISP1301_I2C_OTG_CONTROL_1);
i2c_write(~(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN),
ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR);
i2c_write(0xFF,
ISP1301_I2C_INTERRUPT_LATCH | ISP1301_I2C_REG_CLEAR_ADDR);
i2c_write(0xFF,
ISP1301_I2C_INTERRUPT_FALLING | ISP1301_I2C_REG_CLEAR_ADDR);
i2c_write(0xFF,
ISP1301_I2C_INTERRUPT_RISING | ISP1301_I2C_REG_CLEAR_ADDR);
}
static inline void isp1301_vbus_on(void)
{
i2c_write(OTG1_VBUS_DRV, ISP1301_I2C_OTG_CONTROL_1);
}
static inline void isp1301_vbus_off(void)
{
i2c_write(OTG1_VBUS_DRV,
ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR);
}
static void pnx4008_start_hc(void)
{
unsigned long tmp = __raw_readl(USB_OTG_STAT_CONTROL) | HOST_EN;
__raw_writel(tmp, USB_OTG_STAT_CONTROL);
isp1301_vbus_on();
}
static void pnx4008_stop_hc(void)
{
unsigned long tmp;
isp1301_vbus_off();
tmp = __raw_readl(USB_OTG_STAT_CONTROL) & ~HOST_EN;
__raw_writel(tmp, USB_OTG_STAT_CONTROL);
}
static int __devinit ohci_pnx4008_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) {
dev_err(hcd->self.controller, "can't start\n");
ohci_stop(hcd);
return ret;
}
return 0;
}
static const struct hc_driver ohci_pnx4008_hc_driver = {
.description = hcd_name,
.product_desc = "pnx4008 OHCI",
/*
* generic hardware linkage
*/
.irq = ohci_irq,
.flags = HCD_USB11 | HCD_MEMORY,
.hcd_priv_size = sizeof(struct ohci_hcd),
/*
* basic lifecycle operations
*/
.start = ohci_pnx4008_start,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
#define USB_CLOCK_MASK (AHB_M_CLOCK_ON| OTG_CLOCK_ON | HOST_CLOCK_ON | I2C_CLOCK_ON)
static void pnx4008_set_usb_bits(void)
{
start_int_set_falling_edge(SE_USB_OTG_ATX_INT_N);
start_int_ack(SE_USB_OTG_ATX_INT_N);
start_int_umask(SE_USB_OTG_ATX_INT_N);
start_int_set_rising_edge(SE_USB_OTG_TIMER_INT);
start_int_ack(SE_USB_OTG_TIMER_INT);
start_int_umask(SE_USB_OTG_TIMER_INT);
start_int_set_rising_edge(SE_USB_I2C_INT);
start_int_ack(SE_USB_I2C_INT);
start_int_umask(SE_USB_I2C_INT);
start_int_set_rising_edge(SE_USB_INT);
start_int_ack(SE_USB_INT);
start_int_umask(SE_USB_INT);
start_int_set_rising_edge(SE_USB_NEED_CLK_INT);
start_int_ack(SE_USB_NEED_CLK_INT);
start_int_umask(SE_USB_NEED_CLK_INT);
start_int_set_rising_edge(SE_USB_AHB_NEED_CLK_INT);
start_int_ack(SE_USB_AHB_NEED_CLK_INT);
start_int_umask(SE_USB_AHB_NEED_CLK_INT);
}
static void pnx4008_unset_usb_bits(void)
{
start_int_mask(SE_USB_OTG_ATX_INT_N);
start_int_mask(SE_USB_OTG_TIMER_INT);
start_int_mask(SE_USB_I2C_INT);
start_int_mask(SE_USB_INT);
start_int_mask(SE_USB_NEED_CLK_INT);
start_int_mask(SE_USB_AHB_NEED_CLK_INT);
}
static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev)
{
struct usb_hcd *hcd = 0;
struct ohci_hcd *ohci;
const struct hc_driver *driver = &ohci_pnx4008_hc_driver;
int ret = 0, irq;
dev_dbg(&pdev->dev, "%s: " DRIVER_INFO " (pnx4008)\n", hcd_name);
if (usb_disabled()) {
err("USB is disabled");
ret = -ENODEV;
goto out;
}
if (pdev->num_resources != 2
|| pdev->resource[0].flags != IORESOURCE_MEM
|| pdev->resource[1].flags != IORESOURCE_IRQ) {
err("Invalid resource configuration");
ret = -ENODEV;
goto out;
}
/* Enable AHB slave USB clock, needed for further USB clock control */
__raw_writel(USB_SLAVE_HCLK_EN | (1 << 19), USB_CTRL);
ret = i2c_add_driver(&isp1301_driver);
if (ret < 0) {
err("failed to connect I2C to ISP1301 USB Transceiver");
goto out;
}
isp1301_configure();
/* Enable USB PLL */
usb_clk = clk_get(&pdev->dev, "ck_pll5");
if (IS_ERR(usb_clk)) {
err("failed to acquire USB PLL");
ret = PTR_ERR(usb_clk);
goto out1;
}
ret = clk_enable(usb_clk);
if (ret < 0) {
err("failed to start USB PLL");
goto out2;
}
ret = clk_set_rate(usb_clk, 48000);
if (ret < 0) {
err("failed to set USB clock rate");
goto out3;
}
__raw_writel(__raw_readl(USB_CTRL) | USB_HOST_NEED_CLK_EN, USB_CTRL);
/* Set to enable all needed USB clocks */
__raw_writel(USB_CLOCK_MASK, USB_OTG_CLK_CTRL);
while ((__raw_readl(USB_OTG_CLK_STAT) & USB_CLOCK_MASK) !=
USB_CLOCK_MASK) ;
hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id);
if (!hcd) {
err("Failed to allocate HC buffer");
ret = -ENOMEM;
goto out3;
}
/* Set all USB bits in the Start Enable register */
pnx4008_set_usb_bits();
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");
ret = -ENOMEM;
goto out4;
}
hcd->regs = (void __iomem *)pdev->resource[0].start;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = -ENXIO;
goto out4;
}
pnx4008_start_hc();
platform_set_drvdata(pdev, hcd);
ohci = hcd_to_ohci(hcd);
ohci_hcd_init(ohci);
dev_info(&pdev->dev, "at 0x%p, irq %d\n", hcd->regs, hcd->irq);
ret = usb_add_hcd(hcd, irq, IRQF_DISABLED);
if (ret == 0)
return ret;
pnx4008_stop_hc();
out4:
pnx4008_unset_usb_bits();
usb_put_hcd(hcd);
out3:
clk_disable(usb_clk);
out2:
clk_put(usb_clk);
out1:
i2c_del_driver(&isp1301_driver);
out:
return ret;
}
static int usb_hcd_pnx4008_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_remove_hcd(hcd);
pnx4008_stop_hc();
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
pnx4008_unset_usb_bits();
clk_disable(usb_clk);
clk_put(usb_clk);
i2c_del_driver(&isp1301_driver);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver usb_hcd_pnx4008_driver = {
.driver = {
.name = "usb-ohci",
},
.probe = usb_hcd_pnx4008_probe,
.remove = usb_hcd_pnx4008_remove,
};

View File

@@ -0,0 +1,242 @@
/*
* 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 2005 Embedded Alley Solutions, Inc.
*
* Bus Glue for PNX8550
*
* 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 PNX8550 from ohci-sa1111.c and sa-omap.c
* by Vitaly Wool <vitalywool@gmail.com>
*
* This file is licenced under the GPL.
*/
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach-pnx8550/usb.h>
#include <asm/mach-pnx8550/int.h>
#include <asm/mach-pnx8550/pci.h>
#ifndef CONFIG_PNX8550
#error "This file is PNX8550 bus glue. CONFIG_PNX8550 must be defined."
#endif
extern int usb_disabled(void);
/*-------------------------------------------------------------------------*/
static void pnx8550_start_hc(struct platform_device *dev)
{
/*
* Set register CLK48CTL to enable and 48MHz
*/
outl(0x00000003, PCI_BASE | 0x0004770c);
/*
* Set register CLK12CTL to enable and 48MHz
*/
outl(0x00000003, PCI_BASE | 0x00047710);
udelay(100);
}
static void pnx8550_stop_hc(struct platform_device *dev)
{
udelay(10);
}
/*-------------------------------------------------------------------------*/
/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */
/**
* usb_hcd_pnx8550_probe - initialize pnx8550-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_pnx8550_probe (const struct hc_driver *driver,
struct platform_device *dev)
{
int retval;
struct usb_hcd *hcd;
if (dev->resource[0].flags != IORESOURCE_MEM ||
dev->resource[1].flags != IORESOURCE_IRQ) {
dev_err (&dev->dev,"invalid resource type\n");
return -ENOMEM;
}
hcd = usb_create_hcd (driver, &dev->dev, "pnx8550");
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)) {
dev_err(&dev->dev, "request_mem_region [0x%08llx, 0x%08llx] "
"failed\n", hcd->rsrc_start, hcd->rsrc_len);
retval = -EBUSY;
goto err1;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
dev_err(&dev->dev, "ioremap [[0x%08llx, 0x%08llx] failed\n",
hcd->rsrc_start, hcd->rsrc_len);
retval = -ENOMEM;
goto err2;
}
pnx8550_start_hc(dev);
ohci_hcd_init(hcd_to_ohci(hcd));
retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED);
if (retval == 0)
return retval;
pnx8550_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_pnx8550_remove - shutdown processing for pnx8550-based HCDs
* @dev: USB Host Controller being removed
* Context: !in_interrupt()
*
* Reverses the effect of usb_hcd_pnx8550_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_pnx8550_remove (struct usb_hcd *hcd, struct platform_device *dev)
{
usb_remove_hcd(hcd);
pnx8550_stop_hc(dev);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
}
/*-------------------------------------------------------------------------*/
static int __devinit
ohci_pnx8550_start (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int ret;
ohci_dbg (ohci, "ohci_pnx8550_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_pnx8550_hc_driver = {
.description = hcd_name,
.product_desc = "PNX8550 OHCI",
.hcd_priv_size = sizeof(struct ohci_hcd),
/*
* generic hardware linkage
*/
.irq = ohci_irq,
.flags = HCD_USB11 | HCD_MEMORY,
/*
* basic lifecycle operations
*/
.start = ohci_pnx8550_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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
static int ohci_hcd_pnx8550_drv_probe(struct platform_device *pdev)
{
int ret;
if (usb_disabled())
return -ENODEV;
ret = usb_hcd_pnx8550_probe(&ohci_pnx8550_hc_driver, pdev);
return ret;
}
static int ohci_hcd_pnx8550_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_pnx8550_remove(hcd, pdev);
return 0;
}
MODULE_ALIAS("pnx8550-ohci");
static struct platform_driver ohci_hcd_pnx8550_driver = {
.driver = {
.name = "pnx8550-ohci",
},
.probe = ohci_hcd_pnx8550_drv_probe,
.remove = ohci_hcd_pnx8550_drv_remove,
};

View File

@@ -0,0 +1,232 @@
/*
* 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 2006 Sylvain Munaut <tnt@246tNt.com>
*
* Bus glue for OHCI HC on the of_platform bus
*
* Modified for of_platform bus from ohci-sa1111.c
*
* This file is licenced under the GPL.
*/
#include <linux/signal.h>
#include <asm/of_platform.h>
#include <asm/prom.h>
static int __devinit
ohci_ppc_of_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_of_hc_driver = {
.description = hcd_name,
.product_desc = "OF OHCI",
.hcd_priv_size = sizeof(struct ohci_hcd),
/*
* generic hardware linkage
*/
.irq = ohci_irq,
.flags = HCD_USB11 | HCD_MEMORY,
/*
* basic lifecycle operations
*/
.start = ohci_ppc_of_start,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
static int __devinit
ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match)
{
struct device_node *dn = op->node;
struct usb_hcd *hcd;
struct ohci_hcd *ohci;
struct resource res;
int irq;
int rv;
int is_bigendian;
if (usb_disabled())
return -ENODEV;
is_bigendian =
device_is_compatible(dn, "ohci-bigendian") ||
device_is_compatible(dn, "ohci-be");
dev_dbg(&op->dev, "initializing PPC-OF USB Controller\n");
rv = of_address_to_resource(dn, 0, &res);
if (rv)
return rv;
hcd = usb_create_hcd(&ohci_ppc_of_hc_driver, &op->dev, "PPC-OF 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)) {
printk(KERN_ERR __FILE__ ": request_mem_region failed\n");
rv = -EBUSY;
goto err_rmr;
}
irq = irq_of_parse_and_map(dn, 0);
if (irq == NO_IRQ) {
printk(KERN_ERR __FILE__ ": irq_of_parse_and_map failed\n");
rv = -EBUSY;
goto err_irq;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
printk(KERN_ERR __FILE__ ": ioremap failed\n");
rv = -ENOMEM;
goto err_ioremap;
}
ohci = hcd_to_ohci(hcd);
if (is_bigendian)
ohci->flags |= OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC;
ohci_hcd_init(ohci);
rv = usb_add_hcd(hcd, irq, 0);
if (rv == 0)
return 0;
iounmap(hcd->regs);
err_ioremap:
irq_dispose_mapping(irq);
err_irq:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err_rmr:
usb_put_hcd(hcd);
return rv;
}
static int ohci_hcd_ppc_of_remove(struct of_device *op)
{
struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
dev_set_drvdata(&op->dev, NULL);
dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n");
usb_remove_hcd(hcd);
iounmap(hcd->regs);
irq_dispose_mapping(hcd->irq);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
return 0;
}
static int ohci_hcd_ppc_of_shutdown(struct of_device *op)
{
struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
if (hcd->driver->shutdown)
hcd->driver->shutdown(hcd);
return 0;
}
static struct of_device_id ohci_hcd_ppc_of_match[] = {
#ifdef CONFIG_USB_OHCI_HCD_PPC_OF_BE
{
.name = "usb",
.compatible = "ohci-bigendian",
},
{
.name = "usb",
.compatible = "ohci-be",
},
#endif
#ifdef CONFIG_USB_OHCI_HCD_PPC_OF_LE
{
.name = "usb",
.compatible = "ohci-littledian",
},
{
.name = "usb",
.compatible = "ohci-le",
},
#endif
{},
};
MODULE_DEVICE_TABLE(of, ohci_hcd_ppc_of_match);
#if !defined(CONFIG_USB_OHCI_HCD_PPC_OF_BE) && \
!defined(CONFIG_USB_OHCI_HCD_PPC_OF_LE)
#error "No endianess selected for ppc-of-ohci"
#endif
static struct of_platform_driver ohci_hcd_ppc_of_driver = {
.name = "ppc-of-ohci",
.match_table = ohci_hcd_ppc_of_match,
.probe = ohci_hcd_ppc_of_probe,
.remove = ohci_hcd_ppc_of_remove,
.shutdown = ohci_hcd_ppc_of_shutdown,
#ifdef CONFIG_PM
/*.suspend = ohci_hcd_ppc_soc_drv_suspend,*/
/*.resume = ohci_hcd_ppc_soc_drv_resume,*/
#endif
.driver = {
.name = "ppc-of-ohci",
.owner = THIS_MODULE,
},
};

View File

@@ -0,0 +1,210 @@
/*
* 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 <linux/platform_device.h>
#include <linux/signal.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.
*
* 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;
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;
}
ohci = hcd_to_ohci(hcd);
ohci->flags |= OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC;
ohci_hcd_init(ohci);
retval = usb_add_hcd(hcd, irq, IRQF_DISABLED);
if (retval == 0)
return retval;
pr_debug("Removing PPC-SOC USB Controller\n");
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().
* 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)
{
usb_remove_hcd(hcd);
pr_debug("stopping PPC-SOC USB Controller\n");
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(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,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
static int ohci_hcd_ppc_soc_drv_probe(struct platform_device *pdev)
{
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 platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_ppc_soc_remove(hcd, pdev);
return 0;
}
static struct platform_driver ohci_hcd_ppc_soc_driver = {
.probe = ohci_hcd_ppc_soc_drv_probe,
.remove = ohci_hcd_ppc_soc_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
#ifdef CONFIG_PM
/*.suspend = ohci_hcd_ppc_soc_drv_suspend,*/
/*.resume = ohci_hcd_ppc_soc_drv_resume,*/
#endif
.driver = {
.name = "ppc-soc-ohci",
.owner = THIS_MODULE,
},
};

196
drivers/usb/host/ohci-ps3.c Normal file
View File

@@ -0,0 +1,196 @@
/*
* PS3 OHCI Host Controller driver
*
* Copyright (C) 2006 Sony Computer Entertainment Inc.
* Copyright 2006 Sony Corp.
*
* 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; version 2 of the License.
*
* 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 <asm/ps3.h>
static int ps3_ohci_hc_reset(struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
ohci->flags |= OHCI_QUIRK_BE_MMIO;
ohci_hcd_init(ohci);
return ohci_init(ohci);
}
static int __devinit ps3_ohci_hc_start(struct usb_hcd *hcd)
{
int result;
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
/* Handle root hub init quirk in spider south bridge. */
/* Also set PwrOn2PwrGood to 0x7f (254ms). */
ohci_writel(ohci, 0x7f000000 | RH_A_PSM | RH_A_OCPM,
&ohci->regs->roothub.a);
ohci_writel(ohci, 0x00060000, &ohci->regs->roothub.b);
result = ohci_run(ohci);
if (result < 0) {
err("can't start %s", hcd->self.bus_name);
ohci_stop(hcd);
}
return result;
}
static const struct hc_driver ps3_ohci_hc_driver = {
.description = hcd_name,
.product_desc = "PS3 OHCI Host Controller",
.hcd_priv_size = sizeof(struct ohci_hcd),
.irq = ohci_irq,
.flags = HCD_MEMORY | HCD_USB11,
.reset = ps3_ohci_hc_reset,
.start = ps3_ohci_hc_start,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
.urb_enqueue = ohci_urb_enqueue,
.urb_dequeue = ohci_urb_dequeue,
.endpoint_disable = ohci_endpoint_disable,
.get_frame_number = ohci_get_frame,
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
.start_port_reset = ohci_start_port_reset,
#if defined(CONFIG_PM)
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
};
/* redefine dev_dbg to do a syntax check */
#if !defined(DEBUG)
#undef dev_dbg
static inline int __attribute__ ((format (printf, 2, 3))) dev_dbg(
const struct device *_dev, const char *fmt, ...) {return 0;}
#endif
static int ps3_ohci_sb_probe(struct ps3_system_bus_device *dev)
{
int result;
struct usb_hcd *hcd;
unsigned int virq;
static u64 dummy_mask = DMA_32BIT_MASK;
if (usb_disabled()) {
result = -ENODEV;
goto fail_start;
}
result = ps3_mmio_region_create(dev->m_region);
if (result) {
dev_dbg(&dev->core, "%s:%d: ps3_map_mmio_region failed\n",
__func__, __LINE__);
result = -EPERM;
goto fail_mmio;
}
dev_dbg(&dev->core, "%s:%d: mmio mapped_addr %lxh\n", __func__,
__LINE__, dev->m_region->lpar_addr);
result = ps3_alloc_io_irq(PS3_BINDING_CPU_ANY, dev->interrupt_id, &virq);
if (result) {
dev_dbg(&dev->core, "%s:%d: ps3_construct_io_irq(%d) failed.\n",
__func__, __LINE__, virq);
result = -EPERM;
goto fail_irq;
}
dev->core.power.power_state = PMSG_ON;
dev->core.dma_mask = &dummy_mask; /* FIXME: for improper usb code */
hcd = usb_create_hcd(&ps3_ohci_hc_driver, &dev->core, dev->core.bus_id);
if (!hcd) {
dev_dbg(&dev->core, "%s:%d: usb_create_hcd failed\n", __func__,
__LINE__);
result = -ENOMEM;
goto fail_create_hcd;
}
hcd->rsrc_start = dev->m_region->lpar_addr;
hcd->rsrc_len = dev->m_region->len;
hcd->regs = ioremap(dev->m_region->lpar_addr, dev->m_region->len);
if (!hcd->regs) {
dev_dbg(&dev->core, "%s:%d: ioremap failed\n", __func__,
__LINE__);
result = -EPERM;
goto fail_ioremap;
}
dev_dbg(&dev->core, "%s:%d: hcd->rsrc_start %lxh\n", __func__, __LINE__,
(unsigned long)hcd->rsrc_start);
dev_dbg(&dev->core, "%s:%d: hcd->rsrc_len %lxh\n", __func__, __LINE__,
(unsigned long)hcd->rsrc_len);
dev_dbg(&dev->core, "%s:%d: hcd->regs %lxh\n", __func__, __LINE__,
(unsigned long)hcd->regs);
dev_dbg(&dev->core, "%s:%d: virq %lu\n", __func__, __LINE__,
(unsigned long)virq);
ps3_system_bus_set_driver_data(dev, hcd);
result = usb_add_hcd(hcd, virq, IRQF_DISABLED);
if (result) {
dev_dbg(&dev->core, "%s:%d: usb_add_hcd failed (%d)\n",
__func__, __LINE__, result);
goto fail_add_hcd;
}
return result;
fail_add_hcd:
iounmap(hcd->regs);
fail_ioremap:
usb_put_hcd(hcd);
fail_create_hcd:
ps3_free_io_irq(virq);
fail_irq:
ps3_free_mmio_region(dev->m_region);
fail_mmio:
fail_start:
return result;
}
static int ps3_ohci_sb_remove (struct ps3_system_bus_device *dev)
{
struct usb_hcd *hcd =
(struct usb_hcd *)ps3_system_bus_get_driver_data(dev);
usb_put_hcd(hcd);
ps3_system_bus_set_driver_data(dev, NULL);
return 0;
}
MODULE_ALIAS("ps3-ohci");
static struct ps3_system_bus_driver ps3_ohci_sb_driver = {
.match_id = PS3_MATCH_ID_OHCI,
.core = {
.name = "ps3-ohci-driver",
},
.probe = ps3_ohci_sb_probe,
.remove = ps3_ohci_sb_remove,
};

View File

@@ -0,0 +1,371 @@
/*
* 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 <linux/signal.h>
#include <linux/platform_device.h>
#include <asm/mach-types.h>
#include <asm/hardware.h>
#include <asm/arch/pxa-regs.h>
#include <asm/arch/ohci.h>
#define PXA_UHC_MAX_PORTNUM 3
#define UHCRHPS(x) __REG2( 0x4C000050, (x)<<2 )
/*
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 )
{
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 );
UHCRHDA |= RH_A_NPS;
}
return 0;
}
extern int usb_disabled(void);
/*-------------------------------------------------------------------------*/
static int pxa27x_start_hc(struct device *dev)
{
int retval = 0;
struct pxaohci_platform_data *inf;
inf = dev->platform_data;
pxa_set_cken(CKEN10_USBHOST, 1);
UHCHR |= UHCHR_FHR;
udelay(11);
UHCHR &= ~UHCHR_FHR;
UHCHR |= UHCHR_FSBIR;
while (UHCHR & UHCHR_FSBIR)
cpu_relax();
if (inf->init)
retval = inf->init(dev);
if (retval < 0)
return retval;
UHCHR &= ~UHCHR_SSE;
UHCHIE = (UHCHIE_UPRIE | UHCHIE_RWIE);
/* Clear any OTG Pin Hold */
if (PSSR & PSSR_OTGPH)
PSSR |= PSSR_OTGPH;
return 0;
}
static void pxa27x_stop_hc(struct device *dev)
{
struct pxaohci_platform_data *inf;
inf = dev->platform_data;
if (inf->exit)
inf->exit(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 *pdev)
{
int retval;
struct usb_hcd *hcd;
struct pxaohci_platform_data *inf;
inf = pdev->dev.platform_data;
if (!inf)
return -ENODEV;
if (pdev->resource[1].flags != IORESOURCE_IRQ) {
pr_debug ("resource[1] is not IORESOURCE_IRQ");
return -ENOMEM;
}
hcd = usb_create_hcd (driver, &pdev->dev, "pxa27x");
if (!hcd)
return -ENOMEM;
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)) {
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;
}
if ((retval = pxa27x_start_hc(&pdev->dev)) < 0) {
pr_debug("pxa27x_start_hc failed");
goto err3;
}
/* Select Power Management Mode */
pxa27x_ohci_select_pmm(inf->port_mode);
if (inf->power_budget)
hcd->power_budget = inf->power_budget;
ohci_hcd_init(hcd_to_ohci(hcd));
retval = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED);
if (retval == 0)
return retval;
pxa27x_stop_hc(&pdev->dev);
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_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 *pdev)
{
usb_remove_hcd(hcd);
pxa27x_stop_hc(&pdev->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);
/* The value of NDP in roothub_a is incorrect on this hardware */
ohci->num_ports = 3;
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,
.shutdown = ohci_shutdown,
/*
* 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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
static int ohci_hcd_pxa27x_drv_probe(struct platform_device *pdev)
{
pr_debug ("In ohci_hcd_pxa27x_drv_probe");
if (usb_disabled())
return -ENODEV;
return usb_hcd_pxa27x_probe(&ohci_pxa27x_hc_driver, pdev);
}
static int ohci_hcd_pxa27x_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_pxa27x_remove(hcd, pdev);
platform_set_drvdata(pdev, NULL);
return 0;
}
#ifdef CONFIG_PM
static int ohci_hcd_pxa27x_drv_suspend(struct platform_device *pdev, pm_message_t state)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
if (time_before(jiffies, ohci->next_statechange))
msleep(5);
ohci->next_statechange = jiffies;
pxa27x_stop_hc(&pdev->dev);
hcd->state = HC_STATE_SUSPENDED;
pdev->dev.power.power_state = PMSG_SUSPEND;
return 0;
}
static int ohci_hcd_pxa27x_drv_resume(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
int status;
if (time_before(jiffies, ohci->next_statechange))
msleep(5);
ohci->next_statechange = jiffies;
if ((status = pxa27x_start_hc(&pdev->dev)) < 0)
return status;
pdev->dev.power.power_state = PMSG_ON;
usb_hcd_resume_root_hub(hcd);
return 0;
}
#endif
static struct platform_driver ohci_hcd_pxa27x_driver = {
.probe = ohci_hcd_pxa27x_drv_probe,
.remove = ohci_hcd_pxa27x_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
#ifdef CONFIG_PM
.suspend = ohci_hcd_pxa27x_drv_suspend,
.resume = ohci_hcd_pxa27x_drv_resume,
#endif
.driver = {
.name = "pxa27x-ohci",
},
};

1117
drivers/usb/host/ohci-q.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,935 @@
/*
* 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
*
* USB Bus Glue for Samsung S3C2410
*
* Written by Christopher Hoover <ch@hpl.hp.com>
* Based on fragments of previous driver by Rusell King et al.
*
* Modified for S3C2410 from ohci-sa1111.c, ohci-omap.c and ohci-lh7a40.c
* by Ben Dooks, <ben@simtec.co.uk>
* Copyright (C) 2004 Simtec Electronics
*
* Thanks to basprog@mail.ru for updates to newer kernels
*
* This file is licenced under the GPL.
*/
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/hardware.h>
#include <asm/arch/usb-control.h>
#include <asm/arch/regs-gpio.h>
#include <linux/major.h> //Qisda, Leo SJ Yang, 2009/08/31, for 3G power
#if defined(CONFIG_CPU_S3C6400)
#include <asm/arch/regs-s3c6400-clock.h>
#elif defined(CONFIG_CPU_S3C6410)
#include <asm/arch/regs-s3c6410-clock.h>
#endif
#define USB_HOST_PORT2_EN 0
/*Qisda,Leo SJ Yang,2009/09/09{*/
/*Fix 3G Module can not work after running mass storage*/
#ifdef CONFIG_USB_GADGET
extern char g_EMUSBTestIsOK;
#endif
/*}Qisda,Leo SJ Yang,2009/09/09*/
/*Fix 3G Module can not work after running mass storage*/
#if (USB_HOST_PORT2_EN == 1) && (CONFIG_PLAT_S3C64XX == 1)
#include <asm/arch/regs-usb-otg-hs.h>
#endif
#define valid_port(idx) ((idx) == 1 || (idx) == 2)
/* clock device associated with the hcd */
static struct clk *clk;
static struct clk *usb_clk;
#if (USB_HOST_PORT2_EN == 1) && (CONFIG_PLAT_S3C64XX == 1)
static struct clk *otg_clk;
#endif
/* forward definitions */
static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc);
/* conversion functions */
static struct s3c2410_hcd_info *to_s3c2410_info(struct usb_hcd *hcd)
{
return hcd->self.controller->platform_data;
}
static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd)
{
struct s3c2410_hcd_info *info = dev->dev.platform_data;
dev_dbg(&dev->dev, "s3c2410_start_hc:\n");
clk_enable(usb_clk);
mdelay(2); /* let the bus clock stabilise */
clk_enable(clk);
#if (USB_HOST_PORT2_EN == 1) && (CONFIG_PLAT_S3C64XX == 1)
clk_enable(otg_clk);
#endif
if (info != NULL) {
info->hcd = hcd;
info->report_oc = s3c2410_hcd_oc;
if (info->enable_oc != NULL) {
(info->enable_oc)(info, 1);
}
}
}
static void s3c2410_stop_hc(struct platform_device *dev)
{
struct s3c2410_hcd_info *info = dev->dev.platform_data;
dev_dbg(&dev->dev, "s3c2410_stop_hc:\n");
if (info != NULL) {
info->report_oc = NULL;
info->hcd = NULL;
if (info->enable_oc != NULL) {
(info->enable_oc)(info, 0);
}
}
clk_disable(clk);
clk_disable(usb_clk);
}
/* ohci_s3c2410_hub_status_data
*
* update the status data from the hub with anything that
* has been detected by our system
*/
static int
ohci_s3c2410_hub_status_data (struct usb_hcd *hcd, char *buf)
{
struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
struct s3c2410_hcd_port *port;
int orig;
int portno;
orig = ohci_hub_status_data (hcd, buf);
if (info == NULL)
return orig;
port = &info->port[0];
/* mark any changed port as changed */
for (portno = 0; portno < 2; port++, portno++) {
if (port->oc_changed == 1 &&
port->flags & S3C_HCDFLG_USED) {
dev_dbg(hcd->self.controller,
"oc change on port %d\n", portno);
if (orig < 1)
orig = 1;
buf[0] |= 1<<(portno+1);
}
}
return orig;
}
/* s3c2410_usb_set_power
*
* configure the power on a port, by calling the platform device
* routine registered with the platform device
*/
static void s3c2410_usb_set_power(struct s3c2410_hcd_info *info,
int port, int to)
{
if (info == NULL)
return;
if (info->power_control != NULL) {
info->port[port-1].power = to;
(info->power_control)(port-1, to);
}
}
/* ohci_s3c2410_hub_control
*
* look at control requests to the hub, and see if we need
* to take any action or over-ride the results from the
* request.
*/
static int ohci_s3c2410_hub_control (
struct usb_hcd *hcd,
u16 typeReq,
u16 wValue,
u16 wIndex,
char *buf,
u16 wLength)
{
struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
struct usb_hub_descriptor *desc;
int ret = -EINVAL;
u32 *data = (u32 *)buf;
dev_dbg(hcd->self.controller,
"s3c2410_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
hcd, typeReq, wValue, wIndex, buf, wLength);
/* if we are only an humble host without any special capabilities
* process the request straight away and exit */
if (info == NULL) {
ret = ohci_hub_control(hcd, typeReq, wValue,
wIndex, buf, wLength);
goto out;
}
/* check the request to see if it needs handling */
switch (typeReq) {
case SetPortFeature:
if (wValue == USB_PORT_FEAT_POWER) {
dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
s3c2410_usb_set_power(info, wIndex, 1);
goto out;
}
break;
case ClearPortFeature:
switch (wValue) {
case USB_PORT_FEAT_C_OVER_CURRENT:
dev_dbg(hcd->self.controller,
"ClearPortFeature: C_OVER_CURRENT\n");
if (valid_port(wIndex)) {
info->port[wIndex-1].oc_changed = 0;
info->port[wIndex-1].oc_status = 0;
}
goto out;
case USB_PORT_FEAT_OVER_CURRENT:
dev_dbg(hcd->self.controller,
"ClearPortFeature: OVER_CURRENT\n");
if (valid_port(wIndex)) {
info->port[wIndex-1].oc_status = 0;
}
goto out;
case USB_PORT_FEAT_POWER:
dev_dbg(hcd->self.controller,
"ClearPortFeature: POWER\n");
if (valid_port(wIndex)) {
s3c2410_usb_set_power(info, wIndex, 0);
return 0;
}
}
break;
}
ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
if (ret)
goto out;
switch (typeReq) {
case GetHubDescriptor:
/* update the hub's descriptor */
desc = (struct usb_hub_descriptor *)buf;
if (info->power_control == NULL)
return ret;
dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
desc->wHubCharacteristics);
/* remove the old configurations for power-switching, and
* over-current protection, and insert our new configuration
*/
desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
desc->wHubCharacteristics |= cpu_to_le16(0x0001);
if (info->enable_oc) {
desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM);
desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001);
}
dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
desc->wHubCharacteristics);
return ret;
case GetPortStatus:
/* check port status */
dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
if (valid_port(wIndex)) {
if (info->port[wIndex-1].oc_changed) {
*data |= cpu_to_le32(RH_PS_OCIC);
}
if (info->port[wIndex-1].oc_status) {
*data |= cpu_to_le32(RH_PS_POCI);
}
}
}
out:
return ret;
}
/* s3c2410_hcd_oc
*
* handle an over-current report
*/
static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc)
{
struct s3c2410_hcd_port *port;
struct usb_hcd *hcd;
unsigned long flags;
int portno;
if (info == NULL)
return;
port = &info->port[0];
hcd = info->hcd;
local_irq_save(flags);
for (portno = 0; portno < 2; port++, portno++) {
if (port_oc & (1<<portno) &&
port->flags & S3C_HCDFLG_USED) {
port->oc_status = 1;
port->oc_changed = 1;
/* ok, once over-current is detected,
the port needs to be powered down */
s3c2410_usb_set_power(info, portno+1, 0);
}
}
local_irq_restore(flags);
}
/* may be called without controller electrically present */
/* may be called with controller, bus, and devices active */
/*
* usb_hcd_s3c2410_remove - shutdown processing for HCD
* @dev: USB Host Controller being removed
* Context: !in_interrupt()
*
* Reverses the effect of usb_hcd_3c2410_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_s3c2410_remove (struct usb_hcd *hcd, struct platform_device *dev)
{
usb_remove_hcd(hcd);
s3c2410_stop_hc(dev);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
#ifdef CONFIG_QISDA_E600_EVT2
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT);
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);
//#elif CONFIG_QISDA_AS090B00_EVT1
#else
/*Qisda,Leo SJ Yang,2009/09/09{*/
/*Fix 3G Module can not work after running mass storage*/
#ifdef CONFIG_USB_GADGET
if( g_EMUSBTestIsOK!=1)
{
writel((readl(S3C2410_GPHDAT)&~(1<<14)),S3C2410_GPHDAT);
}
#endif
/*}Qisda,Leo SJ Yang,2009/09/09*/
/*Fix 3G Module can not work after running mass storage*/
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT);
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);
#endif
}
//Qisda, Leo SJ Yang, 2009/08/31, for 3G power {
#define S3C_3G_POWER_ON 1
#define S3C_3G_POWER_OFF 2
#define USB_3G_DEVICE_POWER_STATE 10
/*Qisda, 2009/10/9 Leo SJ Yang*/
/*fix the problem,the message
"show can not enable port" is printed
when there is no 3G module {*/
struct usb_hcd *g_phcd=NULL;
struct platform_device *g_pdev=NULL;
struct hc_driver *g_pdriver=NULL;
/*}Qisda, 2009/10/9 Leo SJ Yang*/
/*fix the problem,the message
"show can not enable port" is printed
when there is no 3G module */
static int s3c_3G_open(struct inode *inode, struct file *file)
{
return 0;
}
static int s3c_3G_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int retval;
static char s3c_3Gpower_off = 1;
static int USB_3G_POWER_SAVING = 0;
switch(cmd){
case S3C_3G_POWER_ON:
USB_3G_DEVICE_POWER_STATE_D0:
if(USB_3G_POWER_SAVING == 1){
//printk("\n== D1 -> D0\n");
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<8), S3C2410_GPHDAT);
USB_3G_POWER_SAVING = 0;
return 0;
}
#ifdef CONFIG_QISDA_E600_EVT2
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<14), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<8), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<23)) | 1<<22), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<11), S3C2410_GPHDAT);
s3c2410_gpio_cfgpin(S3C2410_GPD14, S3C2410_GPD14_OUTP);
s3c2410_gpio_setpin(S3C2410_GPD14,1); /* usb power enbale */
#endif //CONFIG_QISDA_E600_EVT2
#ifdef CONFIG_QISDA_AS090B00_EVT1
/*Qisda,Leo.SJ.Yang,2009/09/21{*/
/*Avoid 3G module comsumes more current during turn on 3G power*/
/*set 3G_DIABLE pin as high after set 3G_DISABLE pin as low delay 3 second*/
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<14), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) &~(1<<8), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<23)) | 1<<22), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<11), S3C2410_GPHDAT);
mdelay(5000);
writel(readl(S3C2410_GPHDAT)|(1<<8), S3C2410_GPHDAT);
/*}Qisda,Leo.SJ.Yang,2009/09/21*/
/*Avoid 3G module comsumes more int retval;current during turn on 3G power*/
/*set 3G_DIABLE pin as high after set 3G_DISABLE pin as low delay 3 second*/
#endif //CONFIG_QISDA_AS090B00_EVT1
#if defined(CONFIG_QISDA_AS090B00_EVT1_1)||defined(CONFIG_QISDA_QD090B00_EVT1)
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<14), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) &~(1<<8), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<23)) | 1<<22), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) &~(1<<11), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPGCON) & ~(1<<7)) | 1<<6), S3C2410_GPGCON);
writel(readl(S3C2410_GPGDAT) |(1<<3), S3C2410_GPGDAT);
//s3c2410_gpio_pullup(S3C2410_GPG3, 2); /* usb power enbale */
//mdelay(100);
/*2009/10/21 ,Qisda,Leo SJ Yang*/
/*3G will voltage over 3.6V if charging capcity for 100 msec*/
/*change the time of charging from 100 msec to 65 msec{*/
mdelay(65);
/*}2009/10/21 ,Qisda,Leo SJ Yang*/
writel(readl(S3C2410_GPHDAT) |(1<<11), S3C2410_GPHDAT);
s3c2410_gpio_pullup(S3C2410_GPH11, 2); /* usb power enbale */
mdelay(1);
writel(readl(S3C2410_GPGDAT) &~(1<<3), S3C2410_GPGDAT);
//s3c2410_gpio_pullup(S3C2410_GPG3, 1); /* usb power enbale */
mdelay(1);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<8), S3C2410_GPHDAT);
s3c2410_gpio_pullup(S3C2410_GPH8, 2); /* usb power enbale */
#endif //CONFIG_QISDA_AS090B00_EVT1_1
s3c_3Gpower_off = 0;
if(g_phcd==NULL)
{
s3c2410_usb_set_power(g_pdev->dev.platform_data, 1, 1);
s3c2410_usb_set_power(g_pdev->dev.platform_data, 2, 1);
g_phcd = usb_create_hcd(g_pdriver, &g_pdev->dev, "s3c24xx");
if (g_phcd == NULL)
return -ENOMEM;
g_phcd->rsrc_start = g_pdev->resource[0].start;
g_phcd->rsrc_len = g_pdev->resource[0].end - g_pdev->resource[0].start + 1;
if (!request_mem_region(g_phcd->rsrc_start,g_phcd->rsrc_len, hcd_name)) {
dev_err(&g_pdev->dev, "request_mem_region failed");
retval = -EBUSY;
goto err_put;
}
clk = clk_get(&g_pdev->dev, "usb-host");
if (IS_ERR(clk)) {
dev_err(&g_pdev->dev, "cannot get usb-host clock\n");
retval = -ENOENT;
goto err_mem;
}
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
usb_clk = clk_get(&g_pdev->dev, "usb-bus-host");
if (IS_ERR(usb_clk)) {
dev_err(&g_pdev->dev, "cannot get usb-host clock\n");
retval = -ENOENT;
goto err_clk;
}
#endif
s3c2410_start_hc(g_pdev,g_phcd);
g_phcd->regs = ioremap(g_phcd->rsrc_start,g_phcd->rsrc_len);
if (!g_phcd->regs) {
dev_err(&g_pdev->dev, "ioremap failed\n");
retval = -ENOMEM;
goto err_ioremap;
}
ohci_hcd_init(hcd_to_ohci(g_phcd));
retval = usb_add_hcd(g_phcd, g_pdev->resource[1].start, IRQF_DISABLED);
if (retval != 0)
goto err_ioremap;
return 0;
err_ioremap:
s3c2410_stop_hc(g_pdev);
iounmap(g_phcd->regs);
clk_put(usb_clk);
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
err_clk:
clk_put(clk);
#endif
err_mem:
release_mem_region(g_phcd->rsrc_start,g_phcd->rsrc_len);
err_put:
usb_put_hcd(g_phcd);
}
break;
case S3C_3G_POWER_OFF:
USB_3G_DEVICE_POWER_STATE_D3:
//printk("S3C_WIFI_POWER_OFF");
if(!s3c_3Gpower_off){
#ifdef CONFIG_QISDA_E600_EVT1_1
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT);
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);
#endif
#ifdef CONFIG_QISDA_AS090B00_EVT1
/*Qisda,Leo SJ Yang,2009/09/09{*/
/*Fix 3G Module can not work after running mass storage*/
#ifdef CONFIG_USB_GADGET
if( g_EMUSBTestIsOK!=1)
{
writel((readl(S3C2410_GPHDAT)&~(1<<14)),S3C2410_GPHDAT);
}
#endif
/*}Qisda,Leo SJ Yang,2009/09/09*/
/*Fix 3G Module can not work after running mass storage*/
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT);
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);
#endif
#if defined(CONFIG_QISDA_AS090B00_EVT1_1)|| defined(CONFIG_QISDA_QD090B00_EVT1)
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT);
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);
writel((readl(S3C2410_GPGDAT)&~(1<<3)),S3C2410_GPGDAT);
s3c2410_gpio_pullup(S3C2410_GPH11, 1); /* usb power enbale */
s3c2410_gpio_pullup(S3C2410_GPH8, 1); /* usb power enbale */
#endif
s3c_3Gpower_off = 1;
USB_3G_POWER_SAVING = 0;
}
break;
case USB_3G_DEVICE_POWER_STATE:
printk("\nUSB_3G_DEVICE_POWER_STATE: %d\n", arg);
#if defined(CONFIG_QISDA_AS090B00_EVT1_1)|| defined(CONFIG_QISDA_QD090B00_EVT1)
if(arg == 0){
goto USB_3G_DEVICE_POWER_STATE_D0;
}
else if(arg == 3){
goto USB_3G_DEVICE_POWER_STATE_D3;
}
else if((arg == 2) || (arg == 1)){
if(!s3c_3Gpower_off){
//printk("\nGPH8 low\n");
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) & ~(1<<8), S3C2410_GPHDAT);
s3c2410_gpio_pullup(S3C2410_GPH8, 1); /* usb power enbale */
USB_3G_POWER_SAVING = 1;
}
}
#endif
break;
default:
printk("no this command\n");
break;
}
return 0;
}
/* File operations struct for character device */
static const struct file_operations s3c_3G_fops = {
.owner = THIS_MODULE,
.ioctl = s3c_3G_ioctl,
.open = s3c_3G_open,
.release = NULL
};
//Qisda, Leo SJ Yang, 2009/08/31, for 3G power }
/**/
/**
* usb_hcd_s3c2410_probe - initialize S3C2410-based HCDs
* Context: !in_interrupt()
*
* Allocates basic resources for this USB host controller, and
* then invokes the start() methint retval;int retval;od for the HCD associated with it
* through the hotplug entry's driver_data.
*
*/
static int usb_hcd_s3c2410_probe (const struct hc_driver *driver,
struct platform_device *dev)
{
// struct usb_hcd *hcd = NULL;
// int retval;
g_pdev=dev;
g_pdriver=driver;
/*Qisda,Leo SJ Yang,2009/09/10{*/
/*Mark the code that is not used */
#if 0
#if defined(CONFIG_CPU_S3C2450) || defined(CONFIG_CPU_S3C2416)
/* USB host Power enable */
s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB4, 0);
s3c2410_gpio_setpin(S3C2410_GPB4, 1);
#endif
#endif
/*}Qisda,Leo SJ Yang,2009/09/10*/
/*Mark the code that is not used */
#if (USB_HOST_PORT2_EN == 1) && (CONFIG_PLAT_S3C64XX == 1)
/* set USB_SIG_MASK */
__raw_writel( __raw_readl(S3C_OTHERS)|(1<<16), S3C_OTHERS);
/* Initializes OTG PHY */
__raw_writel(0x0, S3C_USBOTG_PHYPWR);
raw_writel(0x60, S3C_USBOTG_PHYCLK);
__raw_writel(0x1, S3C_USBOTG_RSTCON);
udelay(20);
__raw_writel(0x0, S3C_USBOTG_RSTCON);
udelay(20);
#endif
#ifdef CONFIG_QISDA_E600_EVT0
/*Qisda,2009/7/29,Leo SJ Yang{*/
/*HW EVT0,Config C_MODE,USB_SEL,USB20_EN for USB Host*/
//GPH14
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<14), S3C2410_GPHDAT);
//GPK6
writel(((readl(S3C2416_GPKCON) & ~(1<<13)) | 1<<12), S3C2416_GPKCON);
writel(readl(S3C2416_GPKDAT)&~( 1<<6), S3C2416_GPKDAT);
//GPK12
writel(((readl(S3C2416_GPKCON) & ~(1<<23)) | 1<<22), S3C2416_GPKCON);
writel(readl(S3C2416_GPKDAT)|( 1<<11), S3C2416_GPKDAT);
/*}Qisda,2009/7/29,Leo SJ Yang*/
/*HW EVT0,Config C_MODE,USB_SEL,USB20_EN for USB Host*/
#endif
#ifdef CONFIG_QISDA_E600_EVT2
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<14), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<8), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<23)) | 1<<22), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<11), S3C410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<19)) | 1<<18), S3C2410_GPHCON);
s3c2410_gpio_cfgpin(S3C2410_GPD14, S3C2410_GPD14_OUTP);
s3c2410_gpio_setpin(S3C2410_GPD14,1); /* usb power enbale */
/*}Qisda,Leo SJ Yang,2009/08/31,add file operation for enable and disable 3G Module*/
#endif
#ifdef CONFIG_QISDA_AS090B00_EVT1
/*Qisda,Leo SJ Yang,2009/09/09{*/
/*Fix 3G Module can not work after running mass storage*/
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
if( g_EMUSBTestIsOK!=1)
writel(readl(S3C2410_GPHDAT)&~(1<<14), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(((readl(S3C2410_GPHCON) & ~(1<<23)) | 1<<22), S3C2410_GPHCON);
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT);
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);
/*}Qisda,Leo SJ Yang,2009/09/09*/
/*Fix 3G Module can not work after running mass storage*/
#endif
#if defined(CONFIG_QISDA_AS090B00_EVT1_1)||defined(CONFIG_QISDA_QD090B00_EVT1)
writel(((readl(S3C2410_GPHCON) & ~(1<<29)) | 1<<28), S3C2410_GPHCON);
writel(readl(S3C2410_GPHDAT) |(1<<14), S3C2410_GPHDAT);
writel(((readl(S3C2410_GPHCON) & ~(1<<17)) | 1<<16), S3C2410_GPHCON);
writel(((readl(S3C2410_GPHCON) & ~(1<<23)) | 1<<22), S3C2410_GPHCON);
writel(((readl(S3C2410_GPGCON) & ~(1<<7)) | 1<<6), S3C2410_GPGCON);
writel((readl(S3C2410_GPHDAT)&~(1<<11)),S3C2410_GPHDAT); //on/off
writel(readl(S3C2410_GPHDAT)&~(1<<8), S3C2410_GPHDAT);//disable
writel(readl(S3C2410_GPGDAT)&~(1<<3), S3C2410_GPGDAT);//audio reset
#endif
if (register_chrdev (S3C_3G_MAJOR, "s3c_3G_cmd", &s3c_3G_fops)) {
printk("unable to get major S3C_3G_MAJOR\n");
}
#if 0
s3c2410_usb_set_power(dev->dev.platform_data, 1, 1);
s3c2410_usb_set_power(dev->dev.platform_data, 2, 1);
hcd = usb_create_hcd(driver, &dev->dev, "s3c24xx");
if (hcd == NULL)
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)) {
dev_err(&dev->dev, "request_mem_region failed");
retval = -EBUSY;
goto err_put;
}
clk = clk_get(&dev->dev, "usb-host");
if (IS_ERR(clk)) {
dev_err(&dev->dev, "cannot get usb-host clock\n");
retval = -ENOENT;
goto err_mem;
}
#if (USB_HOST_PORT2_EN == 1) && (CONFIG_PLAT_S3C64XX == 1)
otg_clk = clk_get(&dev->dev, "otg");
if (IS_ERR(clk)) {
dev_err(&dev->dev, "cannot get otg clock\n");
retval = -ENOENT;
goto err_mem;
}
#endif
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
usb_clk = clk_get(&dev->dev, "usb-bus-host");
if (IS_ERR(usb_clk)) {
dev_err(&dev->dev, "cannot get usb-host clock\n");
retval = -ENOENT;
goto err_clk;
}
#endif
s3c2410_start_hc(dev, hcd);
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
dev_err(&dev->dev, "ioremap failed\n");
retval = -ENOMEM;
goto err_ioremap;
}
ohci_hcd_init(hcd_to_ohci(hcd));
retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED);
if (retval != 0)
goto err_ioremap;
return 0;
err_ioremap:
s3c2410_stop_hc(dev);
iounmap(hcd->regs);
clk_put(usb_clk);
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
err_clk:
clk_put(clk);
#endif
err_mem:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err_put:
usb_put_hcd(hcd);
return retval;
#endif
return 0;
}
/*-------------------------------------------------------------------------*/
static int
ohci_s3c2410_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_s3c2410_hc_driver = {
.description = hcd_name,
.product_desc = "S3C24XX OHCI",
.hcd_priv_size = sizeof(struct ohci_hcd),
/*
* generic hardware linkage
*/
.irq = ohci_irq,
.flags = HCD_USB11 | HCD_MEMORY,
/*
* basic lifecycle operations
*/
.start = ohci_s3c2410_start,
.stop = ohci_stop,
.shutdown = ohci_shutdown,
/*
* 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_s3c2410_hub_status_data,
.hub_control = ohci_s3c2410_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/* device driver */
static int ohci_hcd_s3c2410_drv_probe(struct platform_device *pdev)
{
return usb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, pdev);
}
static int ohci_hcd_s3c2410_drv_remove(struct platform_device *pdev)
{
struct usb_hcd *hcd = platform_get_drvdata(pdev);
usb_hcd_s3c2410_remove(hcd, pdev);
return 0;
}
static int ohci_hcd_s3c2410_drv_shutdown(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver ohci_hcd_s3c2410_driver = {
.probe = ohci_hcd_s3c2410_drv_probe,
.remove = ohci_hcd_s3c2410_drv_remove,
//.shutdown = usb_hcd_platform_shutdown,
/*2009/10/21 Qisda Leo SJ Yang*/
/*system crash after executing reboot{*/
.shutdown =ohci_hcd_s3c2410_drv_shutdown,
/*}2009/10/21 Qisda Leo SJ Yang*/
/*system crash after executing reboot*/
/*.suspend = ohci_hcd_s3c2410_drv_suspend, */
/*.resume = ohci_hcd_s3c2410_drv_resume, */
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-ohci",
},
};

View File

@@ -0,0 +1,271 @@
/*
* 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], IRQF_DISABLED);
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,
.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,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
#endif
.start_port_reset = ohci_start_port_reset,
};
/*-------------------------------------------------------------------------*/
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,
};

687
drivers/usb/host/ohci.h Normal file
View File

@@ -0,0 +1,687 @@
/*
* 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 */ -ETIME,
/* 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;
/*
* 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 num_ports;
int load [NUM_INTS];
u32 hc_control; /* copy of hc control reg */
unsigned long next_statechange; /* suspend/resume */
u32 fminterval; /* saved register */
unsigned autostop:1; /* rh auto stopping/stopped */
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_QUIRK_BE_DESC 0x08 /* BE descriptors */
#define OHCI_QUIRK_BE_MMIO 0x10 /* BE registers */
#define OHCI_QUIRK_ZFMICRO 0x20 /* Compaq ZFMicro chipset*/
// 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.
*
* In addition some more exotic implementations like the Toshiba
* Spider (aka SCC) cell southbridge are "mixed" endian, that is,
* they have a different endianness for registers vs. in-memory
* descriptors.
*
* 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.
*
* That leads to some tricky Kconfig rules howevber. There are
* different defaults based on some arch/ppc platforms, though
* the basic rules are:
*
* Controller type Kconfig options needed
* --------------- ----------------------
* little endian CONFIG_USB_OHCI_LITTLE_ENDIAN
*
* fully big endian CONFIG_USB_OHCI_BIG_ENDIAN_DESC _and_
* CONFIG_USB_OHCI_BIG_ENDIAN_MMIO
*
* mixed endian CONFIG_USB_OHCI_LITTLE_ENDIAN _and_
* CONFIG_USB_OHCI_BIG_ENDIAN_{MMIO,DESC}
*
* (If you have a mixed endian controller, you -must- also define
* CONFIG_USB_OHCI_LITTLE_ENDIAN or things will not work when building
* both your mixed endian and a fully big endian controller support in
* the same kernel image).
*/
#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_DESC
#ifdef CONFIG_USB_OHCI_LITTLE_ENDIAN
#define big_endian_desc(ohci) (ohci->flags & OHCI_QUIRK_BE_DESC)
#else
#define big_endian_desc(ohci) 1 /* only big endian */
#endif
#else
#define big_endian_desc(ohci) 0 /* only little endian */
#endif
#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO
#ifdef CONFIG_USB_OHCI_LITTLE_ENDIAN
#define big_endian_mmio(ohci) (ohci->flags & OHCI_QUIRK_BE_MMIO)
#else
#define big_endian_mmio(ohci) 1 /* only big endian */
#endif
#else
#define big_endian_mmio(ohci) 0 /* only little endian */
#endif
/*
* Big-endian read/write functions are arch-specific.
* Other arches can be added if/when they're needed.
*
* REVISIT: arch/powerpc now has readl/writel_be, so the
* definition below can die once the STB04xxx support is
* finally ported over.
*/
#if defined(CONFIG_PPC) && !defined(CONFIG_PPC_MERGE)
#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)
{
#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO
return big_endian_mmio(ohci) ?
readl_be (regs) :
readl (regs);
#else
return readl (regs);
#endif
}
static inline void _ohci_writel (const struct ohci_hcd *ohci,
const unsigned int val, __hc32 __iomem *regs)
{
#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO
big_endian_mmio(ohci) ?
writel_be (val, regs) :
writel (val, regs);
#else
writel (val, regs);
#endif
}
#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.
*/
#define ohci_readl(o,r) (_ohci_readl(o,r),_ohci_readl(o,r))
#define ohci_writel(o,v,r) _ohci_writel(o,v,r)
#else
#define ohci_readl(o,r) _ohci_readl(o,r)
#define ohci_writel(o,v,r) _ohci_writel(o,v,r)
#endif
/*-------------------------------------------------------------------------*/
/* cpu to ohci */
static inline __hc16 cpu_to_hc16 (const struct ohci_hcd *ohci, const u16 x)
{
return big_endian_desc(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_desc(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_desc(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_desc(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_desc(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_desc(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_desc(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_desc(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.
*
* FIXME: Deal with that as a runtime quirk when STB03xxx is ported over
* to arch/powerpc
*/
#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_desc(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_desc(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 inline 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 inline 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 inline u32 roothub_portstatus (struct ohci_hcd *hc, int i)
{ return read_roothub (hc, portstatus [i], 0xffe0fce0); }

View File

@@ -0,0 +1,352 @@
/*
* This file contains code to reset and initialize USB host controllers.
* Some of it includes work-arounds for PCI hardware and BIOS quirks.
* It may need to run early during booting -- before USB would normally
* initialize -- to ensure that Linux doesn't use any legacy modes.
*
* Copyright (c) 1999 Martin Mares <mj@ucw.cz>
* (and others)
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/acpi.h>
#include "pci-quirks.h"
#define UHCI_USBLEGSUP 0xc0 /* legacy support */
#define UHCI_USBCMD 0 /* command register */
#define UHCI_USBINTR 4 /* interrupt register */
#define UHCI_USBLEGSUP_RWC 0x8f00 /* the R/WC bits */
#define UHCI_USBLEGSUP_RO 0x5040 /* R/O and reserved bits */
#define UHCI_USBCMD_RUN 0x0001 /* RUN/STOP bit */
#define UHCI_USBCMD_HCRESET 0x0002 /* Host Controller reset */
#define UHCI_USBCMD_EGSM 0x0008 /* Global Suspend Mode */
#define UHCI_USBCMD_CONFIGURE 0x0040 /* Config Flag */
#define UHCI_USBINTR_RESUME 0x0002 /* Resume interrupt enable */
#define OHCI_CONTROL 0x04
#define OHCI_CMDSTATUS 0x08
#define OHCI_INTRSTATUS 0x0c
#define OHCI_INTRENABLE 0x10
#define OHCI_INTRDISABLE 0x14
#define OHCI_OCR (1 << 3) /* ownership change request */
#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */
#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */
#define OHCI_INTR_OC (1 << 30) /* ownership change */
#define EHCI_HCC_PARAMS 0x08 /* extended capabilities */
#define EHCI_USBCMD 0 /* command register */
#define EHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */
#define EHCI_USBSTS 4 /* status register */
#define EHCI_USBSTS_HALTED (1 << 12) /* HCHalted bit */
#define EHCI_USBINTR 8 /* interrupt register */
#define EHCI_USBLEGSUP 0 /* legacy support register */
#define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */
#define EHCI_USBLEGSUP_OS (1 << 24) /* OS semaphore */
#define EHCI_USBLEGCTLSTS 4 /* legacy control/status */
#define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */
/*
* Make sure the controller is completely inactive, unable to
* generate interrupts or do DMA.
*/
void uhci_reset_hc(struct pci_dev *pdev, unsigned long base)
{
/* Turn off PIRQ enable and SMI enable. (This also turns off the
* BIOS's USB Legacy Support.) Turn off all the R/WC bits too.
*/
pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);
/* 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(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD);
mb();
udelay(5);
if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)
dev_warn(&pdev->dev, "HCRESET not completed yet!\n");
/* Just to be safe, disable interrupt requests and
* make sure the controller is stopped.
*/
outw(0, base + UHCI_USBINTR);
outw(0, base + UHCI_USBCMD);
}
EXPORT_SYMBOL_GPL(uhci_reset_hc);
/*
* Initialize a controller that was newly discovered or has just been
* resumed. In either case we can't be sure of its previous state.
*
* Returns: 1 if the controller was reset, 0 otherwise.
*/
int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)
{
u16 legsup;
unsigned int cmd, intr;
/*
* When restarting a suspended controller, we expect all the
* settings to be the same as we left them:
*
* PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
* Controller is stopped and configured with EGSM set;
* No interrupts enabled except possibly Resume Detect.
*
* If any of these conditions are violated we do a complete reset.
*/
pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup);
if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) {
dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n",
__FUNCTION__, legsup);
goto reset_needed;
}
cmd = inw(base + UHCI_USBCMD);
if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) ||
!(cmd & UHCI_USBCMD_EGSM)) {
dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n",
__FUNCTION__, cmd);
goto reset_needed;
}
intr = inw(base + UHCI_USBINTR);
if (intr & (~UHCI_USBINTR_RESUME)) {
dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n",
__FUNCTION__, intr);
goto reset_needed;
}
return 0;
reset_needed:
dev_dbg(&pdev->dev, "Performing full reset\n");
uhci_reset_hc(pdev, base);
return 1;
}
EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
{
u16 cmd;
return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
}
#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev)
{
unsigned long base = 0;
int i;
if (!pio_enabled(pdev))
return;
for (i = 0; i < PCI_ROM_RESOURCE; i++)
if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) {
base = pci_resource_start(pdev, i);
break;
}
if (base)
uhci_check_and_reset_hc(pdev, base);
}
static int __devinit mmio_resource_enabled(struct pci_dev *pdev, int idx)
{
return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
}
static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev)
{
void __iomem *base;
if (!mmio_resource_enabled(pdev, 0))
return;
base = ioremap_nocache(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (base == NULL) return;
/* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */
#ifndef __hppa__
{
u32 control = readl(base + OHCI_CONTROL);
if (control & OHCI_CTRL_IR) {
int wait_time = 500; /* arbitrary; 5 seconds */
writel(OHCI_INTR_OC, base + OHCI_INTRENABLE);
writel(OHCI_OCR, base + OHCI_CMDSTATUS);
while (wait_time > 0 &&
readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) {
wait_time -= 10;
msleep(10);
}
if (wait_time <= 0)
printk(KERN_WARNING "%s %s: BIOS handoff "
"failed (BIOS bug ?) %08x\n",
pdev->dev.bus_id, "OHCI",
readl(base + OHCI_CONTROL));
/* reset controller, preserving RWC */
writel(control & OHCI_CTRL_RWC, base + OHCI_CONTROL);
}
}
#endif
/*
* disable interrupts
*/
writel(~(u32)0, base + OHCI_INTRDISABLE);
writel(~(u32)0, base + OHCI_INTRSTATUS);
iounmap(base);
}
static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev)
{
int wait_time, delta;
void __iomem *base, *op_reg_base;
u32 hcc_params, val;
u8 offset, cap_length;
int count = 256/4;
if (!mmio_resource_enabled(pdev, 0))
return;
base = ioremap_nocache(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (base == NULL) return;
cap_length = readb(base);
op_reg_base = base + cap_length;
/* EHCI 0.96 and later may have "extended capabilities"
* spec section 5.1 explains the bios handoff, e.g. for
* booting from USB disk or using a usb keyboard
*/
hcc_params = readl(base + EHCI_HCC_PARAMS);
offset = (hcc_params >> 8) & 0xff;
while (offset && count--) {
u32 cap;
int msec;
pci_read_config_dword(pdev, offset, &cap);
switch (cap & 0xff) {
case 1: /* BIOS/SMM/... handoff support */
if ((cap & EHCI_USBLEGSUP_BIOS)) {
pr_debug("%s %s: BIOS handoff\n",
pdev->dev.bus_id, "EHCI");
#if 0
/* aleksey_gorelov@phoenix.com reports that some systems need SMI forced on,
* but that seems dubious in general (the BIOS left it off intentionally)
* and is known to prevent some systems from booting. so we won't do this
* unless maybe we can determine when we're on a system that needs SMI forced.
*/
/* BIOS workaround (?): be sure the
* pre-Linux code receives the SMI
*/
pci_read_config_dword(pdev,
offset + EHCI_USBLEGCTLSTS,
&val);
pci_write_config_dword(pdev,
offset + EHCI_USBLEGCTLSTS,
val | EHCI_USBLEGCTLSTS_SOOE);
#endif
/* some systems get upset if this semaphore is
* set for any other reason than forcing a BIOS
* handoff..
*/
pci_write_config_byte(pdev, offset + 3, 1);
}
/* if boot firmware now owns EHCI, spin till
* it hands it over.
*/
msec = 5000;
while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) {
msleep(10);
msec -= 10;
pci_read_config_dword(pdev, offset, &cap);
}
if (cap & EHCI_USBLEGSUP_BIOS) {
/* well, possibly buggy BIOS... try to shut
* it down, and hope nothing goes too wrong
*/
printk(KERN_WARNING "%s %s: BIOS handoff "
"failed (BIOS bug ?) %08x\n",
pdev->dev.bus_id, "EHCI", cap);
pci_write_config_byte(pdev, offset + 2, 0);
}
/* just in case, always disable EHCI SMIs */
pci_write_config_dword(pdev,
offset + EHCI_USBLEGCTLSTS,
0);
break;
case 0: /* illegal reserved capability */
cap = 0;
/* FALLTHROUGH */
default:
printk(KERN_WARNING "%s %s: unrecognized "
"capability %02x\n",
pdev->dev.bus_id, "EHCI",
cap & 0xff);
break;
}
offset = (cap >> 8) & 0xff;
}
if (!count)
printk(KERN_DEBUG "%s %s: capability loop?\n",
pdev->dev.bus_id, "EHCI");
/*
* halt EHCI & disable its interrupts in any case
*/
val = readl(op_reg_base + EHCI_USBSTS);
if ((val & EHCI_USBSTS_HALTED) == 0) {
val = readl(op_reg_base + EHCI_USBCMD);
val &= ~EHCI_USBCMD_RUN;
writel(val, op_reg_base + EHCI_USBCMD);
wait_time = 2000;
delta = 100;
do {
writel(0x3f, op_reg_base + EHCI_USBSTS);
udelay(delta);
wait_time -= delta;
val = readl(op_reg_base + EHCI_USBSTS);
if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) {
break;
}
} while (wait_time > 0);
}
writel(0, op_reg_base + EHCI_USBINTR);
writel(0x3f, op_reg_base + EHCI_USBSTS);
iounmap(base);
return;
}
static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev)
{
if (pdev->class == PCI_CLASS_SERIAL_USB_UHCI)
quirk_usb_handoff_uhci(pdev);
else if (pdev->class == PCI_CLASS_SERIAL_USB_OHCI)
quirk_usb_handoff_ohci(pdev);
else if (pdev->class == PCI_CLASS_SERIAL_USB_EHCI)
quirk_usb_disable_ehci(pdev);
}
DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff);

View File

@@ -0,0 +1,7 @@
#ifndef __LINUX_USB_PCI_QUIRKS_H
#define __LINUX_USB_PCI_QUIRKS_H
void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
#endif /* __LINUX_USB_PCI_QUIRKS_H */

1858
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
View 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)

327
drivers/usb/host/sl811_cs.c Normal file
View File

@@ -0,0 +1,327 @@
/*
* PCMCIA driver for SL811HS (as found in REX-CFU1U)
* Filename: sl811_cs.c
* Author: Yukio Yamamoto
*
* Port to sl811-hcd and 2.6.x by
* Botond Botyanszki <boti@rocketmail.com>
* Simon Pickering
*
* Last update: 2005-05-12
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ds.h>
#include <linux/usb/sl811.h>
MODULE_AUTHOR("Botond Botyanszki");
MODULE_DESCRIPTION("REX-CFU1U PCMCIA driver for 2.6");
MODULE_LICENSE("GPL");
/*====================================================================*/
/* MACROS */
/*====================================================================*/
#if defined(DEBUG) || defined(PCMCIA_DEBUG)
static int pc_debug = 0;
module_param(pc_debug, int, 0644);
#define DBG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG "sl811_cs: " args)
#else
#define DBG(n, args...) do{}while(0)
#endif /* no debugging */
#define INFO(args...) printk(KERN_INFO "sl811_cs: " args)
#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444)
#define CS_CHECK(fn, ret) \
do { \
last_fn = (fn); \
if ((last_ret = (ret)) != 0) \
goto cs_failed; \
} while (0)
/*====================================================================*/
/* VARIABLES */
/*====================================================================*/
static const char driver_name[DEV_NAME_LEN] = "sl811_cs";
typedef struct local_info_t {
struct pcmcia_device *p_dev;
dev_node_t node;
} local_info_t;
static void sl811_cs_release(struct pcmcia_device * link);
/*====================================================================*/
static void release_platform_dev(struct device * dev)
{
DBG(0, "sl811_cs platform_dev release\n");
dev->parent = NULL;
}
static struct sl811_platform_data platform_data = {
.potpg = 100,
.power = 50, /* == 100mA */
// .reset = ... FIXME: invoke CF reset on the card
};
static struct resource resources[] = {
[0] = {
.flags = IORESOURCE_IRQ,
},
[1] = {
// .name = "address",
.flags = IORESOURCE_IO,
},
[2] = {
// .name = "data",
.flags = IORESOURCE_IO,
},
};
extern struct platform_driver sl811h_driver;
static struct platform_device platform_dev = {
.id = -1,
.dev = {
.platform_data = &platform_data,
.release = release_platform_dev,
},
.resource = resources,
.num_resources = ARRAY_SIZE(resources),
};
static int sl811_hc_init(struct device *parent, ioaddr_t base_addr, int irq)
{
if (platform_dev.dev.parent)
return -EBUSY;
platform_dev.dev.parent = parent;
/* finish seting up the platform device */
resources[0].start = irq;
resources[1].start = base_addr;
resources[1].end = base_addr;
resources[2].start = base_addr + 1;
resources[2].end = base_addr + 1;
/* The driver core will probe for us. We know sl811-hcd has been
* initialized already because of the link order dependency created
* by referencing "sl811h_driver".
*/
platform_dev.name = sl811h_driver.driver.name;
return platform_device_register(&platform_dev);
}
/*====================================================================*/
static void sl811_cs_detach(struct pcmcia_device *link)
{
DBG(0, "sl811_cs_detach(0x%p)\n", link);
sl811_cs_release(link);
/* This points to the parent local_info_t struct */
kfree(link->priv);
}
static void sl811_cs_release(struct pcmcia_device * link)
{
DBG(0, "sl811_cs_release(0x%p)\n", link);
pcmcia_disable_device(link);
platform_device_unregister(&platform_dev);
}
static int sl811_cs_config(struct pcmcia_device *link)
{
struct device *parent = &handle_to_dev(link);
local_info_t *dev = link->priv;
tuple_t tuple;
cisparse_t parse;
int last_fn, last_ret;
u_char buf[64];
config_info_t conf;
cistpl_cftable_entry_t dflt = { 0 };
DBG(0, "sl811_cs_config(0x%p)\n", link);
/* Look up the current Vcc */
CS_CHECK(GetConfigurationInfo,
pcmcia_get_configuration_info(link, &conf));
tuple.Attributes = 0;
tuple.TupleData = buf;
tuple.TupleDataMax = sizeof(buf);
tuple.TupleOffset = 0;
tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(link, &tuple));
while (1) {
cistpl_cftable_entry_t *cfg = &(parse.cftable_entry);
if (pcmcia_get_tuple_data(link, &tuple) != 0
|| pcmcia_parse_tuple(link, &tuple, &parse)
!= 0)
goto next_entry;
if (cfg->flags & CISTPL_CFTABLE_DEFAULT) {
dflt = *cfg;
}
if (cfg->index == 0)
goto next_entry;
link->conf.ConfigIndex = cfg->index;
/* Use power settings for Vcc and Vpp if present */
/* Note that the CIS values need to be rescaled */
if (cfg->vcc.present & (1<<CISTPL_POWER_VNOM)) {
if (cfg->vcc.param[CISTPL_POWER_VNOM]/10000
!= conf.Vcc)
goto next_entry;
} else if (dflt.vcc.present & (1<<CISTPL_POWER_VNOM)) {
if (dflt.vcc.param[CISTPL_POWER_VNOM]/10000
!= conf.Vcc)
goto next_entry;
}
if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM))
link->conf.Vpp =
cfg->vpp1.param[CISTPL_POWER_VNOM]/10000;
else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM))
link->conf.Vpp =
dflt.vpp1.param[CISTPL_POWER_VNOM]/10000;
/* we need an interrupt */
if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1)
link->conf.Attributes |= CONF_ENABLE_IRQ;
/* IO window settings */
link->io.NumPorts1 = link->io.NumPorts2 = 0;
if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) {
cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io;
link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK;
link->io.BasePort1 = io->win[0].base;
link->io.NumPorts1 = io->win[0].len;
if (pcmcia_request_io(link, &link->io) != 0)
goto next_entry;
}
break;
next_entry:
pcmcia_disable_device(link);
last_ret = pcmcia_get_next_tuple(link, &tuple);
}
/* require an IRQ and two registers */
if (!link->io.NumPorts1 || link->io.NumPorts1 < 2)
goto cs_failed;
if (link->conf.Attributes & CONF_ENABLE_IRQ)
CS_CHECK(RequestIRQ,
pcmcia_request_irq(link, &link->irq));
else
goto cs_failed;
CS_CHECK(RequestConfiguration,
pcmcia_request_configuration(link, &link->conf));
sprintf(dev->node.dev_name, driver_name);
dev->node.major = dev->node.minor = 0;
link->dev_node = &dev->node;
printk(KERN_INFO "%s: index 0x%02x: ",
dev->node.dev_name, link->conf.ConfigIndex);
if (link->conf.Vpp)
printk(", Vpp %d.%d", link->conf.Vpp/10, link->conf.Vpp%10);
printk(", irq %d", link->irq.AssignedIRQ);
printk(", io 0x%04x-0x%04x", link->io.BasePort1,
link->io.BasePort1+link->io.NumPorts1-1);
printk("\n");
if (sl811_hc_init(parent, link->io.BasePort1, link->irq.AssignedIRQ)
< 0) {
cs_failed:
printk("sl811_cs_config failed\n");
cs_error(link, last_fn, last_ret);
sl811_cs_release(link);
return -ENODEV;
}
return 0;
}
static int sl811_cs_probe(struct pcmcia_device *link)
{
local_info_t *local;
local = kmalloc(sizeof(local_info_t), GFP_KERNEL);
if (!local)
return -ENOMEM;
memset(local, 0, sizeof(local_info_t));
local->p_dev = link;
link->priv = local;
/* Initialize */
link->irq.Attributes = IRQ_TYPE_EXCLUSIVE;
link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID;
link->irq.Handler = NULL;
link->conf.Attributes = 0;
link->conf.IntType = INT_MEMORY_AND_IO;
return sl811_cs_config(link);
}
static struct pcmcia_device_id sl811_ids[] = {
PCMCIA_DEVICE_MANF_CARD(0xc015, 0x0001), /* RATOC USB HOST CF+ Card */
PCMCIA_DEVICE_NULL,
};
MODULE_DEVICE_TABLE(pcmcia, sl811_ids);
static struct pcmcia_driver sl811_cs_driver = {
.owner = THIS_MODULE,
.drv = {
.name = (char *)driver_name,
},
.probe = sl811_cs_probe,
.remove = sl811_cs_detach,
.id_table = sl811_ids,
};
/*====================================================================*/
static int __init init_sl811_cs(void)
{
return pcmcia_register_driver(&sl811_cs_driver);
}
module_init(init_sl811_cs);
static void __exit exit_sl811_cs(void)
{
pcmcia_unregister_driver(&sl811_cs_driver);
}
module_exit(exit_sl811_cs);

3248
drivers/usb/host/u132-hcd.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,592 @@
/*
* 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/kernel.h>
#include <linux/debugfs.h>
#include <linux/smp_lock.h>
#include <asm/io.h>
#include "uhci-hcd.h"
#define uhci_debug_operations (* (const struct file_operations *) NULL)
static struct dentry *uhci_debugfs_root;
#ifdef DEBUG
/* Handle REALLY large printks so we don't overflow buffers */
static 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_urbp(struct urb_priv *urbp, char *buf, int len, int space)
{
char *out = buf;
struct uhci_td *td;
int i, nactive, ninactive;
char *ptype;
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: ptype = "ISO"; break;
case PIPE_INTERRUPT: ptype = "INT"; break;
case PIPE_BULK: ptype = "BLK"; break;
default:
case PIPE_CONTROL: ptype = "CTL"; break;
}
out += sprintf(out, "%s%s", ptype, (urbp->fsbr ? " FSBR" : ""));
out += sprintf(out, " Actlen=%d", urbp->urb->actual_length);
if (urbp->urb->status != -EINPROGRESS)
out += sprintf(out, " Status=%d", urbp->urb->status);
out += sprintf(out, "\n");
i = nactive = ninactive = 0;
list_for_each_entry(td, &urbp->td_list, list) {
if (urbp->qh->type != USB_ENDPOINT_XFER_ISOC &&
(++i <= 10 || debug > 2)) {
out += sprintf(out, "%*s%d: ", space + 2, "", i);
out += uhci_show_td(td, out, len - (out - buf), 0);
} else {
if (td_status(td) & TD_CTRL_ACTIVE)
++nactive;
else
++ninactive;
}
}
if (nactive + ninactive > 0)
out += sprintf(out, "%*s[skipped %d inactive and %d active "
"TDs]\n",
space, "", ninactive, nactive);
return out - buf;
}
static int uhci_show_qh(struct uhci_hcd *uhci,
struct uhci_qh *qh, char *buf, int len, int space)
{
char *out = buf;
int i, nurbs;
__le32 element = qh_element(qh);
char *qtype;
/* Try to make sure there's enough memory */
if (len < 80 * 7)
return 0;
switch (qh->type) {
case USB_ENDPOINT_XFER_ISOC: qtype = "ISO"; break;
case USB_ENDPOINT_XFER_INT: qtype = "INT"; break;
case USB_ENDPOINT_XFER_BULK: qtype = "BLK"; break;
case USB_ENDPOINT_XFER_CONTROL: qtype = "CTL"; break;
default: qtype = "Skel" ; break;
}
out += sprintf(out, "%*s[%p] %s QH link (%08x) element (%08x)\n",
space, "", qh, qtype,
le32_to_cpu(qh->link), le32_to_cpu(element));
if (qh->type == USB_ENDPOINT_XFER_ISOC)
out += sprintf(out, "%*s period %d phase %d load %d us, "
"frame %x desc [%p]\n",
space, "", qh->period, qh->phase, qh->load,
qh->iso_frame, qh->iso_packet_desc);
else if (qh->type == USB_ENDPOINT_XFER_INT)
out += sprintf(out, "%*s period %d phase %d load %d us\n",
space, "", qh->period, qh->phase, qh->load);
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 (list_empty(&qh->queue)) {
out += sprintf(out, "%*s queue is empty\n", space, "");
if (qh == uhci->skel_async_qh)
out += uhci_show_td(uhci->term_td, out,
len - (out - buf), 0);
} else {
struct urb_priv *urbp = list_entry(qh->queue.next,
struct urb_priv, node);
struct uhci_td *td = list_entry(urbp->td_list.next,
struct uhci_td, list);
if (element != LINK_TO_TD(td))
out += sprintf(out, "%*s Element != First TD\n",
space, "");
i = nurbs = 0;
list_for_each_entry(urbp, &qh->queue, node) {
if (++i <= 10)
out += uhci_show_urbp(urbp, out,
len - (out - buf), space + 2);
else
++nurbs;
}
if (nurbs > 0)
out += sprintf(out, "%*s Skipped %d URBs\n",
space, "", nurbs);
}
if (qh->dummy_td) {
out += sprintf(out, "%*s Dummy TD\n", space, "");
out += uhci_show_td(qh->dummy_td, out, len - (out - buf), 0);
}
return out - buf;
}
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_root_hub_state(struct uhci_hcd *uhci, char *buf, int len)
{
char *out = buf;
char *rh_state;
/* Try to make sure there's enough memory */
if (len < 60)
return 0;
switch (uhci->rh_state) {
case UHCI_RH_RESET:
rh_state = "reset"; break;
case UHCI_RH_SUSPENDED:
rh_state = "suspended"; break;
case UHCI_RH_AUTO_STOPPED:
rh_state = "auto-stopped"; break;
case UHCI_RH_RESUMING:
rh_state = "resuming"; break;
case UHCI_RH_SUSPENDING:
rh_state = "suspending"; break;
case UHCI_RH_RUNNING:
rh_state = "running"; break;
case UHCI_RH_RUNNING_NODEVS:
rh_state = "running, no devs"; break;
default:
rh_state = "?"; break;
}
out += sprintf(out, "Root-hub state: %s FSBR: %d\n",
rh_state, uhci->fsbr_is_on);
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 * 9)
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));
out += sprintf(out, "Most recent frame: %x (%d) "
"Last ISO frame: %x (%d)\n",
uhci->frame_number, uhci->frame_number & 1023,
uhci->last_iso_frame, uhci->last_iso_frame & 1023);
return out - buf;
}
static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
{
char *out = buf;
int i, j;
struct uhci_qh *qh;
struct uhci_td *td;
struct list_head *tmp, *head;
int nframes, nerrs;
__le32 link;
__le32 fsbr_link;
static const char * const qh_names[] = {
"unlink", "iso", "int128", "int64", "int32", "int16",
"int8", "int4", "int2", "async", "term"
};
out += uhci_show_root_hub_state(uhci, out, len - (out - buf));
out += sprintf(out, "HC status\n");
out += uhci_show_status(uhci, out, len - (out - buf));
out += sprintf(out, "Periodic load table\n");
for (i = 0; i < MAX_PHASE; ++i) {
out += sprintf(out, "\t%d", uhci->load[i]);
if (i % 8 == 7)
*out++ = '\n';
}
out += sprintf(out, "Total: %d, #INT: %d, #ISO: %d\n",
uhci->total_load,
uhci_to_hcd(uhci)->self.bandwidth_int_reqs,
uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs);
if (debug <= 1)
return out - buf;
out += sprintf(out, "Frame List\n");
nframes = 10;
nerrs = 0;
for (i = 0; i < UHCI_NUMFRAMES; ++i) {
__le32 qh_dma;
j = 0;
td = uhci->frame_cpu[i];
link = uhci->frame[i];
if (!td)
goto check_link;
if (nframes > 0) {
out += sprintf(out, "- Frame %d -> (%08x)\n",
i, le32_to_cpu(link));
j = 1;
}
head = &td->fl_list;
tmp = head;
do {
td = list_entry(tmp, struct uhci_td, fl_list);
tmp = tmp->next;
if (link != LINK_TO_TD(td)) {
if (nframes > 0)
out += sprintf(out, " link does "
"not match list entry!\n");
else
++nerrs;
}
if (nframes > 0)
out += uhci_show_td(td, out,
len - (out - buf), 4);
link = td->link;
} while (tmp != head);
check_link:
qh_dma = uhci_frame_skel_link(uhci, i);
if (link != qh_dma) {
if (nframes > 0) {
if (!j) {
out += sprintf(out,
"- Frame %d -> (%08x)\n",
i, le32_to_cpu(link));
j = 1;
}
out += sprintf(out, " link does not match "
"QH (%08x)!\n", le32_to_cpu(qh_dma));
} else
++nerrs;
}
nframes -= j;
}
if (nerrs > 0)
out += sprintf(out, "Skipped %d bad links\n", nerrs);
out += sprintf(out, "Skeleton QHs\n");
fsbr_link = 0;
for (i = 0; i < UHCI_NUM_SKELQH; ++i) {
int cnt = 0;
qh = uhci->skelqh[i];
out += sprintf(out, "- skel_%s_qh\n", qh_names[i]); \
out += uhci_show_qh(uhci, qh, out, len - (out - buf), 4);
/* Last QH is the Terminating QH, it's different */
if (i == SKEL_TERM) {
if (qh_element(qh) != LINK_TO_TD(uhci->term_td))
out += sprintf(out, " skel_term_qh element is not set to term_td!\n");
link = fsbr_link;
if (!link)
link = LINK_TO_QH(uhci->skel_term_qh);
goto check_qh_link;
}
head = &qh->node;
tmp = head->next;
while (tmp != head) {
qh = list_entry(tmp, struct uhci_qh, node);
tmp = tmp->next;
if (++cnt <= 10)
out += uhci_show_qh(uhci, qh, out,
len - (out - buf), 4);
if (!fsbr_link && qh->skel >= SKEL_FSBR)
fsbr_link = LINK_TO_QH(qh);
}
if ((cnt -= 10) > 0)
out += sprintf(out, " Skipped %d QHs\n", cnt);
link = UHCI_PTR_TERM;
if (i <= SKEL_ISO)
;
else if (i < SKEL_ASYNC)
link = LINK_TO_QH(uhci->skel_async_qh);
else if (!uhci->fsbr_is_on)
;
else
link = LINK_TO_QH(uhci->skel_term_qh);
check_qh_link:
if (qh->link != link)
out += sprintf(out, " last QH not linked to next skeleton!\n");
}
return out - buf;
}
#ifdef CONFIG_DEBUG_FS
#define MAX_OUTPUT (64 * 1024)
struct uhci_debug {
int size;
char *data;
};
static int uhci_debug_open(struct inode *inode, struct file *file)
{
struct uhci_hcd *uhci = inode->i_private;
struct uhci_debug *up;
int ret = -ENOMEM;
unsigned long flags;
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 = 0;
spin_lock_irqsave(&uhci->lock, flags);
if (uhci->is_initialized)
up->size = uhci_sprint_schedule(uhci, up->data, MAX_OUTPUT);
spin_unlock_irqrestore(&uhci->lock, flags);
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;
}
#undef uhci_debug_operations
static const struct file_operations uhci_debug_operations = {
.owner = THIS_MODULE,
.open = uhci_debug_open,
.llseek = uhci_debug_lseek,
.read = uhci_debug_read,
.release = uhci_debug_release,
};
#endif /* CONFIG_DEBUG_FS */
#else /* DEBUG */
static inline void lprintk(char *buf)
{}
static inline int uhci_show_qh(struct uhci_hcd *uhci,
struct uhci_qh *qh, char *buf, int len, int space)
{
return 0;
}
static inline int uhci_sprint_schedule(struct uhci_hcd *uhci,
char *buf, int len)
{
return 0;
}
#endif

974
drivers/usb/host/uhci-hcd.c Normal file
View File

@@ -0,0 +1,974 @@
/*
* 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-2007 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.
*
*/
#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/slab.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 <linux/dmi.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"
#include "pci-quirks.h"
/*
* Version Information
*/
#define DRIVER_VERSION "v3.0"
#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"
/* for flakey hardware, ignore overcurrent indicators */
static int ignore_oc;
module_param(ignore_oc, bool, S_IRUGO);
MODULE_PARM_DESC(ignore_oc, "ignore hardware overcurrent indications");
/*
* debug = 0, no debugging messages
* debug = 1, dump failed URBs except for stalls
* debug = 2, dump all failed URBs (including stalls)
* show all queues in /debug/uhci/[pci_addr]
* debug = 3, show all TDs in URBs when dumping
*/
#ifdef DEBUG
#define DEBUG_CONFIGURED 1
static int debug = 1;
module_param(debug, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug level");
#else
#define DEBUG_CONFIGURED 0
#define debug 0
#endif
static char *errbuf;
#define ERRBUF_LEN (32 * 1024)
static struct kmem_cache *uhci_up_cachep; /* urb_priv */
static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state);
static void wakeup_rh(struct uhci_hcd *uhci);
static void uhci_get_current_frame_number(struct uhci_hcd *uhci);
/*
* Calculate the link pointer DMA value for the first Skeleton QH in a frame.
*/
static __le32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame)
{
int skelnum;
/*
* 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.
*
* ffs (Find First bit Set) does exactly what we need:
* 1,3,5,... => ffs = 0 => use period-2 QH = skelqh[8],
* 2,6,10,... => ffs = 1 => use period-4 QH = skelqh[7], etc.
* ffs >= 7 => not on any high-period queue, so use
* period-1 QH = skelqh[9].
* Add in UHCI_NUMFRAMES to insure at least one bit is set.
*/
skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES);
if (skelnum <= 1)
skelnum = 9;
return LINK_TO_QH(uhci->skelqh[skelnum]);
}
#include "uhci-debug.c"
#include "uhci-q.c"
#include "uhci-hub.c"
/*
* Finish up a host controller reset and update the recorded state.
*/
static void finish_reset(struct uhci_hcd *uhci)
{
int port;
/* HCRESET doesn't affect the Suspend, Reset, and Resume Detect
* bits in the port status and control registers.
* We have to clear them by hand.
*/
for (port = 0; port < uhci->rh_numports; ++port)
outw(0, uhci->io_addr + USBPORTSC1 + (port * 2));
uhci->port_c_suspend = uhci->resuming_ports = 0;
uhci->rh_state = UHCI_RH_RESET;
uhci->is_stopped = UHCI_IS_STOPPED;
uhci_to_hcd(uhci)->state = HC_STATE_HALT;
uhci_to_hcd(uhci)->poll_rh = 0;
uhci->dead = 0; /* Full reset resurrects the controller */
}
/*
* Last rites for a defunct/nonfunctional controller
* or one we don't want to use any more.
*/
static void uhci_hc_died(struct uhci_hcd *uhci)
{
uhci_get_current_frame_number(uhci);
uhci_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr);
finish_reset(uhci);
uhci->dead = 1;
/* The current frame may already be partway finished */
++uhci->frame_number;
}
/*
* Initialize a controller that was newly discovered or has lost power
* or otherwise been reset while it was suspended. In none of these cases
* can we be sure of its previous state.
*/
static void check_and_reset_hc(struct uhci_hcd *uhci)
{
if (uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr))
finish_reset(uhci);
}
/*
* Store the basic register settings needed by the controller.
*/
static void configure_hc(struct uhci_hcd *uhci)
{
/* Set the frame length to the default: 1 ms exactly */
outb(USBSOF_DEFAULT, uhci->io_addr + USBSOF);
/* Store the frame list base address */
outl(uhci->frame_dma_handle, uhci->io_addr + USBFLBASEADD);
/* Set the current frame number */
outw(uhci->frame_number & UHCI_MAX_SOF_NUMBER,
uhci->io_addr + USBFRNUM);
/* Mark controller as not halted before we enable interrupts */
uhci_to_hcd(uhci)->state = HC_STATE_SUSPENDED;
mb();
/* Enable PIRQ */
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,
USBLEGSUP_DEFAULT);
}
static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci)
{
int port;
/* If we have to ignore overcurrent events then almost by definition
* we can't depend on resume-detect interrupts. */
if (ignore_oc)
return 1;
switch (to_pci_dev(uhci_dev(uhci))->vendor) {
default:
break;
case PCI_VENDOR_ID_GENESYS:
/* Genesys Logic's GL880S controllers don't generate
* resume-detect interrupts.
*/
return 1;
case PCI_VENDOR_ID_INTEL:
/* Some of Intel's USB controllers have a bug that causes
* resume-detect interrupts if any port has an over-current
* condition. To make matters worse, some motherboards
* hardwire unused USB ports' over-current inputs active!
* To prevent problems, we will not enable resume-detect
* interrupts if any ports are OC.
*/
for (port = 0; port < uhci->rh_numports; ++port) {
if (inw(uhci->io_addr + USBPORTSC1 + port * 2) &
USBPORTSC_OC)
return 1;
}
break;
}
return 0;
}
static int remote_wakeup_is_broken(struct uhci_hcd *uhci)
{
int port;
char *sys_info;
static char bad_Asus_board[] = "A7V8X";
/* One of Asus's motherboards has a bug which causes it to
* wake up immediately from suspend-to-RAM if any of the ports
* are connected. In such cases we will not set EGSM.
*/
sys_info = dmi_get_system_info(DMI_BOARD_NAME);
if (sys_info && !strcmp(sys_info, bad_Asus_board)) {
for (port = 0; port < uhci->rh_numports; ++port) {
if (inw(uhci->io_addr + USBPORTSC1 + port * 2) &
USBPORTSC_CCS)
return 1;
}
}
return 0;
}
static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state)
__releases(uhci->lock)
__acquires(uhci->lock)
{
int auto_stop;
int int_enable, egsm_enable;
auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
"%s%s\n", __FUNCTION__,
(auto_stop ? " (auto-stop)" : ""));
/* If we get a suspend request when we're already auto-stopped
* then there's nothing to do.
*/
if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
uhci->rh_state = new_state;
return;
}
/* Enable resume-detect interrupts if they work.
* Then enter Global Suspend mode if _it_ works, still configured.
*/
egsm_enable = USBCMD_EGSM;
uhci->working_RD = 1;
int_enable = USBINTR_RESUME;
if (remote_wakeup_is_broken(uhci))
egsm_enable = 0;
if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
!device_may_wakeup(
&uhci_to_hcd(uhci)->self.root_hub->dev))
uhci->working_RD = int_enable = 0;
outw(int_enable, uhci->io_addr + USBINTR);
outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
mb();
udelay(5);
/* If we're auto-stopping then no devices have been attached
* for a while, so there shouldn't be any active URBs and the
* controller should stop after a few microseconds. Otherwise
* we will give the controller one frame to stop.
*/
if (!auto_stop && !(inw(uhci->io_addr + USBSTS) & USBSTS_HCH)) {
uhci->rh_state = UHCI_RH_SUSPENDING;
spin_unlock_irq(&uhci->lock);
msleep(1);
spin_lock_irq(&uhci->lock);
if (uhci->dead)
return;
}
if (!(inw(uhci->io_addr + USBSTS) & USBSTS_HCH))
dev_warn(&uhci_to_hcd(uhci)->self.root_hub->dev,
"Controller not stopped yet!\n");
uhci_get_current_frame_number(uhci);
uhci->rh_state = new_state;
uhci->is_stopped = UHCI_IS_STOPPED;
uhci_to_hcd(uhci)->poll_rh = !int_enable;
uhci_scan_schedule(uhci);
uhci_fsbr_off(uhci);
}
static void start_rh(struct uhci_hcd *uhci)
{
uhci_to_hcd(uhci)->state = HC_STATE_RUNNING;
uhci->is_stopped = 0;
/* Mark it configured and running with a 64-byte max packet.
* All interrupts are enabled, even though RESUME won't do anything.
*/
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, uhci->io_addr + USBCMD);
outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,
uhci->io_addr + USBINTR);
mb();
uhci->rh_state = UHCI_RH_RUNNING;
uhci_to_hcd(uhci)->poll_rh = 1;
}
static void wakeup_rh(struct uhci_hcd *uhci)
__releases(uhci->lock)
__acquires(uhci->lock)
{
dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
"%s%s\n", __FUNCTION__,
uhci->rh_state == UHCI_RH_AUTO_STOPPED ?
" (auto-start)" : "");
/* If we are auto-stopped then no devices are attached so there's
* no need for wakeup signals. Otherwise we send Global Resume
* for 20 ms.
*/
if (uhci->rh_state == UHCI_RH_SUSPENDED) {
uhci->rh_state = UHCI_RH_RESUMING;
outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,
uhci->io_addr + USBCMD);
spin_unlock_irq(&uhci->lock);
msleep(20);
spin_lock_irq(&uhci->lock);
if (uhci->dead)
return;
/* End Global Resume and wait for EOP to be sent */
outw(USBCMD_CF, uhci->io_addr + USBCMD);
mb();
udelay(4);
if (inw(uhci->io_addr + USBCMD) & USBCMD_FGR)
dev_warn(uhci_dev(uhci), "FGR not stopped yet!\n");
}
start_rh(uhci);
/* Restart root hub polling */
mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies);
}
static irqreturn_t uhci_irq(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned short status;
unsigned long flags;
/*
* 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(uhci->io_addr + USBSTS);
if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */
return IRQ_NONE;
outw(status, uhci->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) {
spin_lock_irqsave(&uhci->lock, flags);
if (uhci->rh_state >= UHCI_RH_RUNNING) {
dev_err(uhci_dev(uhci),
"host controller halted, "
"very bad!\n");
if (debug > 1 && errbuf) {
/* Print the schedule for debugging */
uhci_sprint_schedule(uhci,
errbuf, ERRBUF_LEN);
lprintk(errbuf);
}
uhci_hc_died(uhci);
/* Force a callback in case there are
* pending unlinks */
mod_timer(&hcd->rh_timer, jiffies);
}
spin_unlock_irqrestore(&uhci->lock, flags);
}
}
if (status & USBSTS_RD)
usb_hcd_poll_rh_status(hcd);
else {
spin_lock_irqsave(&uhci->lock, flags);
uhci_scan_schedule(uhci);
spin_unlock_irqrestore(&uhci->lock, flags);
}
return IRQ_HANDLED;
}
/*
* Store the current frame number in uhci->frame_number if the controller
* is runnning. Expand from 11 bits (of which we use only 10) to a
* full-sized integer.
*
* Like many other parts of the driver, this code relies on being polled
* more than once per second as long as the controller is running.
*/
static void uhci_get_current_frame_number(struct uhci_hcd *uhci)
{
if (!uhci->is_stopped) {
unsigned delta;
delta = (inw(uhci->io_addr + USBFRNUM) - uhci->frame_number) &
(UHCI_NUMFRAMES - 1);
uhci->frame_number += delta;
}
}
/*
* De-allocate all resources
*/
static void release_uhci(struct uhci_hcd *uhci)
{
int i;
if (DEBUG_CONFIGURED) {
spin_lock_irq(&uhci->lock);
uhci->is_initialized = 0;
spin_unlock_irq(&uhci->lock);
debugfs_remove(uhci->dentry);
}
for (i = 0; i < UHCI_NUM_SKELQH; i++)
uhci_free_qh(uhci, uhci->skelqh[i]);
uhci_free_td(uhci, uhci->term_td);
dma_pool_destroy(uhci->qh_pool);
dma_pool_destroy(uhci->td_pool);
kfree(uhci->frame_cpu);
dma_free_coherent(uhci_dev(uhci),
UHCI_NUMFRAMES * sizeof(*uhci->frame),
uhci->frame, uhci->frame_dma_handle);
}
static int uhci_init(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned io_size = (unsigned) hcd->rsrc_len;
int port;
uhci->io_addr = (unsigned long) hcd->rsrc_start;
/* The UHCI spec says devices must have 2 ports, and goes on to say
* they may have more but gives no way to determine how many there
* are. However according to the UHCI spec, Bit 7 of the port
* status and control register is always set to 1. So we try to
* use this to our advantage. Another common failure mode when
* a nonexistent register is addressed is to return all ones, so
* we test for that also.
*/
for (port = 0; port < (io_size - USBPORTSC1) / 2; port++) {
unsigned int portstatus;
portstatus = inw(uhci->io_addr + USBPORTSC1 + (port * 2));
if (!(portstatus & 0x0080) || portstatus == 0xffff)
break;
}
if (debug)
dev_info(uhci_dev(uhci), "detected %d ports\n", port);
/* Anything greater than 7 is weird so we'll ignore it. */
if (port > UHCI_RH_MAXCHILD) {
dev_info(uhci_dev(uhci), "port count misdetected? "
"forcing to 2 ports\n");
port = 2;
}
uhci->rh_numports = port;
/* Kick BIOS off this hardware and reset if the controller
* isn't already safely quiescent.
*/
check_and_reset_hc(uhci);
return 0;
}
/* Make sure the controller is quiescent and that we're not using it
* any more. This is mainly for the benefit of programs which, like kexec,
* expect the hardware to be idle: not doing DMA or generating IRQs.
*
* This routine may be called in a damaged or failing kernel. Hence we
* do not acquire the spinlock before shutting down the controller.
*/
static void uhci_shutdown(struct pci_dev *pdev)
{
struct usb_hcd *hcd = (struct usb_hcd *) pci_get_drvdata(pdev);
uhci_hc_died(hcd_to_uhci(hcd));
}
/*
* 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 in which the queues
* are encountered by the hardware is:
*
* - All isochronous events are 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 high-period interrupt queue.
* - The second queue is the period-1 interrupt and async
* (low-speed control, full-speed control, then bulk) queue.
* - The third queue is the terminating bandwidth reclamation queue,
* which contains no members, loops back to itself, and is present
* only when FSBR is on and there are no full-speed control or bulk QHs.
*/
static int uhci_start(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int retval = -EBUSY;
int i;
struct dentry *dentry;
hcd->uses_new_polling = 1;
spin_lock_init(&uhci->lock);
setup_timer(&uhci->fsbr_timer, uhci_fsbr_timeout,
(unsigned long) uhci);
INIT_LIST_HEAD(&uhci->idle_qh_list);
init_waitqueue_head(&uhci->waitqh);
if (DEBUG_CONFIGURED) {
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->frame = dma_alloc_coherent(uhci_dev(uhci),
UHCI_NUMFRAMES * sizeof(*uhci->frame),
&uhci->frame_dma_handle, 0);
if (!uhci->frame) {
dev_err(uhci_dev(uhci), "unable to allocate "
"consistent memory for frame list\n");
goto err_alloc_frame;
}
memset(uhci->frame, 0, UHCI_NUMFRAMES * sizeof(*uhci->frame));
uhci->frame_cpu = kcalloc(UHCI_NUMFRAMES, sizeof(*uhci->frame_cpu),
GFP_KERNEL);
if (!uhci->frame_cpu) {
dev_err(uhci_dev(uhci), "unable to allocate "
"memory for frame pointers\n");
goto err_alloc_frame_cpu;
}
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;
}
uhci->term_td = uhci_alloc_td(uhci);
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, NULL, NULL);
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 = async
*/
for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i)
uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh);
uhci->skel_async_qh->link = UHCI_PTR_TERM;
uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);
/* This dummy TD is to work around a bug in Intel PIIX controllers */
uhci_fill_td(uhci->term_td, 0, uhci_explen(0) |
(0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0);
uhci->term_td->link = UHCI_PTR_TERM;
uhci->skel_async_qh->element = uhci->skel_term_qh->element =
LINK_TO_TD(uhci->term_td);
/*
* Fill the frame list: make all entries point to the proper
* interrupt queue.
*/
for (i = 0; i < UHCI_NUMFRAMES; i++) {
/* Only place we don't use the frame list routines */
uhci->frame[i] = uhci_frame_skel_link(uhci, i);
}
/*
* Some architectures require a full mb() to enforce completion of
* the memory writes above before the I/O transfers in configure_hc().
*/
mb();
configure_hc(uhci);
uhci->is_initialized = 1;
start_rh(uhci);
return 0;
/*
* error exits:
*/
err_alloc_skelqh:
for (i = 0; i < UHCI_NUM_SKELQH; i++) {
if (uhci->skelqh[i])
uhci_free_qh(uhci, uhci->skelqh[i]);
}
uhci_free_td(uhci, uhci->term_td);
err_alloc_term_td:
dma_pool_destroy(uhci->qh_pool);
err_create_qh_pool:
dma_pool_destroy(uhci->td_pool);
err_create_td_pool:
kfree(uhci->frame_cpu);
err_alloc_frame_cpu:
dma_free_coherent(uhci_dev(uhci),
UHCI_NUMFRAMES * sizeof(*uhci->frame),
uhci->frame, uhci->frame_dma_handle);
err_alloc_frame:
debugfs_remove(uhci->dentry);
err_create_debug_entry:
return retval;
}
static void uhci_stop(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
spin_lock_irq(&uhci->lock);
if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) && !uhci->dead)
uhci_hc_died(uhci);
uhci_scan_schedule(uhci);
spin_unlock_irq(&uhci->lock);
del_timer_sync(&uhci->fsbr_timer);
release_uhci(uhci);
}
#ifdef CONFIG_PM
static int uhci_rh_suspend(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int rc = 0;
spin_lock_irq(&uhci->lock);
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
rc = -ESHUTDOWN;
else if (!uhci->dead)
suspend_rh(uhci, UHCI_RH_SUSPENDED);
spin_unlock_irq(&uhci->lock);
return rc;
}
static int uhci_rh_resume(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int rc = 0;
spin_lock_irq(&uhci->lock);
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
dev_warn(&hcd->self.root_hub->dev, "HC isn't running!\n");
rc = -ESHUTDOWN;
} else if (!uhci->dead)
wakeup_rh(uhci);
spin_unlock_irq(&uhci->lock);
return rc;
}
static int uhci_suspend(struct usb_hcd *hcd, pm_message_t message)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int rc = 0;
dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__);
spin_lock_irq(&uhci->lock);
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
goto done_okay; /* Already suspended or dead */
if (uhci->rh_state > UHCI_RH_SUSPENDED) {
dev_warn(uhci_dev(uhci), "Root hub isn't suspended!\n");
rc = -EBUSY;
goto done;
};
/* All PCI host controllers are required to disable IRQ generation
* at the source, so we must turn off PIRQ.
*/
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, 0);
mb();
hcd->poll_rh = 0;
/* FIXME: Enable non-PME# remote wakeup? */
/* make sure snapshot being resumed re-enumerates everything */
if (message.event == PM_EVENT_PRETHAW)
uhci_hc_died(uhci);
done_okay:
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
done:
spin_unlock_irq(&uhci->lock);
return rc;
}
static int uhci_resume(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__);
/* Since we aren't in D3 any more, it's safe to set this flag
* even if the controller was dead.
*/
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
mb();
spin_lock_irq(&uhci->lock);
/* FIXME: Disable non-PME# remote wakeup? */
/* The firmware or a boot kernel may have changed the controller
* settings during a system wakeup. Check it and reconfigure
* to avoid problems.
*/
check_and_reset_hc(uhci);
/* If the controller was dead before, it's back alive now */
configure_hc(uhci);
if (uhci->rh_state == UHCI_RH_RESET) {
/* The controller had to be reset */
usb_root_hub_lost_power(hcd->self.root_hub);
suspend_rh(uhci, UHCI_RH_SUSPENDED);
}
spin_unlock_irq(&uhci->lock);
if (!uhci->working_RD) {
/* Suspended root hub needs to be polled */
hcd->poll_rh = 1;
usb_hcd_poll_rh_status(hcd);
}
return 0;
}
#endif
/* Wait until a particular device/endpoint's QH is idle, and free it */
static void uhci_hcd_endpoint_disable(struct usb_hcd *hcd,
struct usb_host_endpoint *hep)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
struct uhci_qh *qh;
spin_lock_irq(&uhci->lock);
qh = (struct uhci_qh *) hep->hcpriv;
if (qh == NULL)
goto done;
while (qh->state != QH_STATE_IDLE) {
++uhci->num_waiting;
spin_unlock_irq(&uhci->lock);
wait_event_interruptible(uhci->waitqh,
qh->state == QH_STATE_IDLE);
spin_lock_irq(&uhci->lock);
--uhci->num_waiting;
}
uhci_free_qh(uhci, qh);
done:
spin_unlock_irq(&uhci->lock);
}
static int uhci_hcd_get_frame_number(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned frame_number;
unsigned delta;
/* Minimize latency by avoiding the spinlock */
frame_number = uhci->frame_number;
barrier();
delta = (inw(uhci->io_addr + USBFRNUM) - frame_number) &
(UHCI_NUMFRAMES - 1);
return frame_number + delta;
}
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_init,
.start = uhci_start,
#ifdef CONFIG_PM
.suspend = uhci_suspend,
.resume = uhci_resume,
.bus_suspend = uhci_rh_suspend,
.bus_resume = uhci_rh_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_UHCI, ~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,
.shutdown = uhci_shutdown,
#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 "%s\n",
ignore_oc ? ", overcurrent ignored" : "");
if (usb_disabled())
return -ENODEV;
if (DEBUG_CONFIGURED) {
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:
kmem_cache_destroy(uhci_up_cachep);
up_failed:
debugfs_remove(uhci_debugfs_root);
debug_failed:
kfree(errbuf);
errbuf_failed:
return retval;
}
static void __exit uhci_hcd_cleanup(void)
{
pci_unregister_driver(&uhci_pci_driver);
kmem_cache_destroy(uhci_up_cachep);
debugfs_remove(uhci_debugfs_root);
kfree(errbuf);
}
module_init(uhci_hcd_init);
module_exit(uhci_hcd_cleanup);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

480
drivers/usb/host/uhci-hcd.h Normal file
View File

@@ -0,0 +1,480 @@
#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: PCI problems */
#define USBSTS_HCPE 0x0010 /* Host Controller Process Error:
* the schedule is 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
#define USBSOF_DEFAULT 64 /* Frame length is exactly 1 ms */
/* 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 USBLEGSUP_RWC 0x8f00 /* the R/WC bits */
#define USBLEGSUP_RO 0x5040 /* R/O and reserved bits */
#define UHCI_PTR_BITS __constant_cpu_to_le32(0x000F)
#define UHCI_PTR_TERM __constant_cpu_to_le32(0x0001)
#define UHCI_PTR_QH __constant_cpu_to_le32(0x0002)
#define UHCI_PTR_DEPTH __constant_cpu_to_le32(0x0004)
#define UHCI_PTR_BREADTH __constant_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 in the future frames
* can be scheduled */
#define MAX_PHASE 32 /* Periodic scheduling length */
/* When no queues need Full-Speed Bandwidth Reclamation,
* delay this long before turning FSBR off */
#define FSBR_OFF_DELAY msecs_to_jiffies(10)
/* If a queue hasn't advanced after this much time, assume it is stuck */
#define QH_WAIT_TIMEOUT msecs_to_jiffies(200)
/*
* Queue Headers
*/
/*
* One role of a QH is to hold a queue of TDs for some endpoint. One QH goes
* with each endpoint, and qh->element (updated by the HC) is either:
* - the next unprocessed TD in the endpoint's queue, or
* - UHCI_PTR_TERM (when there's no more traffic for this 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 schedule, qh->link maintains a list of QHs seen by the HC:
* skel1 --> ep1-qh --> ep2-qh --> ... --> skel2 --> ...
*
* qh->node is the software equivalent of qh->link. The differences
* are that the software list is doubly-linked and QHs in the UNLINKING
* state are on the software list but not the hardware schedule.
*
* For bookkeeping purposes we maintain QHs even for Isochronous endpoints,
* but they never get added to the hardware schedule.
*/
#define QH_STATE_IDLE 1 /* QH is not being used */
#define QH_STATE_UNLINKING 2 /* QH has been removed from the
* schedule but the hardware may
* still be using it */
#define QH_STATE_ACTIVE 3 /* QH is on the schedule */
struct uhci_qh {
/* Hardware fields */
__le32 link; /* Next QH in the schedule */
__le32 element; /* Queue element (TD) pointer */
/* Software fields */
dma_addr_t dma_handle;
struct list_head node; /* Node in the list of QHs */
struct usb_host_endpoint *hep; /* Endpoint information */
struct usb_device *udev;
struct list_head queue; /* Queue of urbps for this QH */
struct uhci_td *dummy_td; /* Dummy TD to end the queue */
struct uhci_td *post_td; /* Last TD completed */
struct usb_iso_packet_descriptor *iso_packet_desc;
/* Next urb->iso_frame_desc entry */
unsigned long advance_jiffies; /* Time of last queue advance */
unsigned int unlink_frame; /* When the QH was unlinked */
unsigned int period; /* For Interrupt and Isochronous QHs */
short phase; /* Between 0 and period-1 */
short load; /* Periodic time requirement, in us */
unsigned int iso_frame; /* Frame # for iso_packet_desc */
int iso_status; /* Status for Isochronous URBs */
int state; /* QH_STATE_xxx; see above */
int type; /* Queue type (control, bulk, etc) */
int skel; /* Skeleton queue number */
unsigned int initial_toggle:1; /* Endpoint's current toggle value */
unsigned int needs_fixup:1; /* Must fix the TD toggle values */
unsigned int is_stopped:1; /* Queue was stopped by error/unlink */
unsigned int wait_expired:1; /* QH_WAIT_TIMEOUT has expired */
unsigned int bandwidth_reserved:1; /* Periodic bandwidth has
* been allocated */
} __attribute__((aligned(16)));
/*
* We need a special accessor for the element pointer because it is
* subject to asynchronous updates by the controller.
*/
static inline __le32 qh_element(struct uhci_qh *qh) {
__le32 element = qh->element;
barrier();
return element;
}
#define LINK_TO_QH(qh) (UHCI_PTR_QH | cpu_to_le32((qh)->dma_handle))
/*
* Transfer Descriptors
*/
/*
* 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) - 1) & TD_TOKEN_EXPLEN_MASK) << \
TD_TOKEN_EXPLEN_SHIFT)
#define uhci_expected_length(token) ((((token) >> TD_TOKEN_EXPLEN_SHIFT) + \
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.
*
* 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.
*/
struct uhci_td {
/* Hardware fields */
__le32 link;
__le32 status;
__le32 token;
__le32 buffer;
/* Software fields */
dma_addr_t dma_handle;
struct list_head list;
int frame; /* for iso: what frame? */
struct list_head fl_list;
} __attribute__((aligned(16)));
/*
* We need a special accessor for the control/status word because it is
* subject to asynchronous updates by the controller.
*/
static inline u32 td_status(struct uhci_td *td) {
__le32 status = td->status;
barrier();
return le32_to_cpu(status);
}
#define LINK_TO_TD(td) (cpu_to_le32((td)->dma_handle))
/*
* Skeleton Queue Headers
*/
/*
* The UHCI driver uses QHs with Interrupt, Control and Bulk URBs for
* automatic queuing. To make it easy to insert entries into the schedule,
* we have a skeleton of QHs for each predefined Interrupt latency.
* Asynchronous QHs (low-speed control, full-speed control, and bulk)
* go onto the period-1 interrupt list, since they all get accessed on
* every frame.
*
* When we want to add a new QH, we add it to the list starting from the
* appropriate skeleton QH. For instance, the schedule can look like this:
*
* skel int128 QH
* dev 1 interrupt QH
* dev 5 interrupt QH
* skel int64 QH
* skel int32 QH
* ...
* skel int1 + async QH
* dev 5 low-speed control QH
* dev 1 bulk QH
* dev 2 bulk QH
*
* There is a special terminating QH used to keep full-speed bandwidth
* reclamation active when no full-speed control or bulk QHs are linked
* into the schedule. It has an inactive TD (to work around a PIIX bug,
* see the Intel errata) and it points back to itself.
*
* There's a special skeleton QH for Isochronous QHs which never appears
* on the schedule. Isochronous TDs go on the schedule before the
* the skeleton QHs. The hardware accesses them directly rather than
* through their QH, which is used only for bookkeeping purposes.
* While the UHCI spec doesn't forbid the use of QHs for Isochronous,
* it doesn't use them either. And the spec says that queues never
* advance on an error completion status, which makes them totally
* unsuitable for Isochronous transfers.
*
* There's also a special skeleton QH used for QHs which are in the process
* of unlinking and so may still be in use by the hardware. It too never
* appears on the schedule.
*/
#define UHCI_NUM_SKELQH 11
#define SKEL_UNLINK 0
#define skel_unlink_qh skelqh[SKEL_UNLINK]
#define SKEL_ISO 1
#define skel_iso_qh skelqh[SKEL_ISO]
/* int128, int64, ..., int1 = 2, 3, ..., 9 */
#define SKEL_INDEX(exponent) (9 - exponent)
#define SKEL_ASYNC 9
#define skel_async_qh skelqh[SKEL_ASYNC]
#define SKEL_TERM 10
#define skel_term_qh skelqh[SKEL_TERM]
/* The following entries refer to sublists of skel_async_qh */
#define SKEL_LS_CONTROL 20
#define SKEL_FS_CONTROL 21
#define SKEL_FSBR SKEL_FS_CONTROL
#define SKEL_BULK 22
/*
* The UHCI controller and root hub
*/
/*
* States for the root hub:
*
* To prevent "bouncing" in the presence of electrical noise,
* when there are no devices attached we delay for 1 second in the
* RUNNING_NODEVS state before switching to the AUTO_STOPPED state.
*
* (Note that the AUTO_STOPPED state won't be necessary once the hub
* driver learns to autosuspend.)
*/
enum uhci_rh_state {
/* In the following states the HC must be halted.
* These two must come first. */
UHCI_RH_RESET,
UHCI_RH_SUSPENDED,
UHCI_RH_AUTO_STOPPED,
UHCI_RH_RESUMING,
/* In this state the HC changes from running to halted,
* so it can legally appear either way. */
UHCI_RH_SUSPENDING,
/* In the following states it's an error if the HC is halted.
* These two must come last. */
UHCI_RH_RUNNING, /* The normal state */
UHCI_RH_RUNNING_NODEVS, /* Running with no devices attached */
};
/*
* The full UHCI controller information:
*/
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 uhci_td *term_td; /* Terminating TD, see UHCI bug */
struct uhci_qh *skelqh[UHCI_NUM_SKELQH]; /* Skeleton QHs */
struct uhci_qh *next_qh; /* Next QH to scan */
spinlock_t lock;
dma_addr_t frame_dma_handle; /* Hardware frame list */
__le32 *frame;
void **frame_cpu; /* CPU's frame list */
enum uhci_rh_state rh_state;
unsigned long auto_stop_time; /* When to AUTO_STOP */
unsigned int frame_number; /* As of last check */
unsigned int is_stopped;
#define UHCI_IS_STOPPED 9999 /* Larger than a frame # */
unsigned int last_iso_frame; /* Frame of last scan */
unsigned int cur_iso_frame; /* Frame for current scan */
unsigned int scan_in_progress:1; /* Schedule scan is running */
unsigned int need_rescan:1; /* Redo the schedule scan */
unsigned int dead:1; /* Controller has died */
unsigned int working_RD:1; /* Suspended root hub doesn't
need to be polled */
unsigned int is_initialized:1; /* Data structure is usable */
unsigned int fsbr_is_on:1; /* FSBR is turned on */
unsigned int fsbr_is_wanted:1; /* Does any URB want FSBR? */
unsigned int fsbr_expiring:1; /* FSBR is timing out */
struct timer_list fsbr_timer; /* For turning off FBSR */
/* Support for port suspend/resume/reset */
unsigned long port_c_suspend; /* Bit-arrays of ports */
unsigned long resuming_ports;
unsigned long ports_timeout; /* Time to stop signalling */
struct list_head idle_qh_list; /* Where the idle QHs live */
int rh_numports; /* Number of root-hub ports */
wait_queue_head_t waitqh; /* endpoint_disable waiters */
int num_waiting; /* Number of waiters */
int total_load; /* Sum of array values */
short load[MAX_PHASE]; /* Periodic allocations */
};
/* 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)
/* Utility macro for comparing frame numbers */
#define uhci_frame_before_eq(f1, f2) (0 <= (int) ((f2) - (f1)))
/*
* Private per-URB data
*/
struct urb_priv {
struct list_head node; /* Node in the QH's urbp list */
struct urb *urb;
struct uhci_qh *qh; /* QH for this URB */
struct list_head td_list;
unsigned fsbr:1; /* URB wants FSBR */
};
/*
* 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
*/
/* Some special IDs */
#define PCI_VENDOR_ID_GENESYS 0x17a0
#define PCI_DEVICE_ID_GL880S_UHCI 0x8083
#endif

414
drivers/usb/host/uhci-hub.c Normal file
View File

@@ -0,0 +1,414 @@
/*
* 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)
/* suspend/resume bits: port suspended or port resuming */
#define SUSPEND_BITS (USBPORTSC_SUSP | USBPORTSC_RD)
/* A port that either is connected or has a changed-bit set will prevent
* us from AUTO_STOPPING.
*/
static int any_ports_active(struct uhci_hcd *uhci)
{
int port;
for (port = 0; port < uhci->rh_numports; ++port) {
if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) &
(USBPORTSC_CCS | RWC_BITS)) ||
test_bit(port, &uhci->port_c_suspend))
return 1;
}
return 0;
}
static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)
{
int port;
int mask = RWC_BITS;
/* Some boards (both VIA and Intel apparently) report bogus
* overcurrent indications, causing massive log spam unless
* we completely ignore them. This doesn't seem to be a problem
* with the chipset so much as with the way it is connected on
* the motherboard; if the overcurrent input is left to float
* then it may constantly register false positives. */
if (ignore_oc)
mask &= ~USBPORTSC_OCC;
*buf = 0;
for (port = 0; port < uhci->rh_numports; ++port) {
if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) ||
test_bit(port, &uhci->port_c_suspend))
*buf |= (1 << (port + 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;
int i;
if (inw(port_addr) & SUSPEND_BITS) {
CLR_RH_PORTSTAT(SUSPEND_BITS);
if (test_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 is supposed to take 3 bit times (= 2 microseconds).
* Experiments show that some controllers take longer, so
* we'll poll for completion. */
for (i = 0; i < 10; ++i) {
if (!(inw(port_addr) & SUSPEND_BITS))
break;
udelay(1);
}
}
clear_bit(port, &uhci->resuming_ports);
}
/* Wait for the UHCI controller in HP's iLO2 server management chip.
* It can take up to 250 us to finish a reset and set the CSC bit.
*/
static void wait_for_HP(unsigned long port_addr)
{
int i;
for (i = 10; i < 250; i += 10) {
if (inw(port_addr) & USBPORTSC_CSC)
return;
udelay(10);
}
/* Log a warning? */
}
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);
/* HP's server management chip requires
* a longer delay. */
if (to_pci_dev(uhci_dev(uhci))->vendor ==
PCI_VENDOR_ID_HP)
wait_for_HP(port_addr);
/* 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);
/* Make sure we see the port again
* after the resuming period is over. */
mod_timer(&uhci_to_hcd(uhci)->rh_timer,
uhci->ports_timeout);
} else if (time_after_eq(jiffies,
uhci->ports_timeout)) {
uhci_finish_suspend(uhci, port, port_addr);
}
}
}
}
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned long flags;
int status = 0;
spin_lock_irqsave(&uhci->lock, flags);
uhci_scan_schedule(uhci);
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
goto done;
uhci_check_ports(uhci);
status = get_hub_status_data(uhci, buf);
switch (uhci->rh_state) {
case UHCI_RH_SUSPENDING:
case UHCI_RH_SUSPENDED:
/* if port change, ask to be resumed */
if (status)
usb_hcd_resume_root_hub(hcd);
break;
case UHCI_RH_AUTO_STOPPED:
/* if port change, auto start */
if (status)
wakeup_rh(uhci);
break;
case UHCI_RH_RUNNING:
/* are any devices attached? */
if (!any_ports_active(uhci)) {
uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
uhci->auto_stop_time = jiffies + HZ;
}
break;
case UHCI_RH_RUNNING_NODEVS:
/* auto-stop if nothing connected for 1 second */
if (any_ports_active(uhci))
uhci->rh_state = UHCI_RH_RUNNING;
else if (time_after_eq(jiffies, uhci->auto_stop_time))
suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
break;
default:
break;
}
done:
spin_unlock_irqrestore(&uhci->lock, flags);
return status;
}
/* 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;
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
return -ETIMEDOUT;
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) && !ignore_oc)
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->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 & SUSPEND_BITS)
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_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 (!(inw(port_addr) & USBPORTSC_SUSP)) {
/* Make certain the port isn't suspended */
uhci_finish_suspend(uhci, port, port_addr);
} else if (!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;
}

1752
drivers/usb/host/uhci-q.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
#
# Makefile for DWC_otg Highspeed USB controller driver
#
CPPFLAGS += -DDWC_HOST_ONLY
#CPPFLAGS += -DDWC_DEVICE_ONLY
#CPPFLAGS += -Dlinux -DDWC_HS_ELECT_TST
obj-$(CONFIG_USB_OTG_HOST) += dwc_otg.o
dwc_otg-objs := dwc_otg_driver.o #dwc_otg_attr.o
dwc_otg-objs += dwc_otg_cil.o dwc_otg_cil_intr.o
dwc_otg-objs += dwc_otg_pcd.o dwc_otg_pcd_intr.o
dwc_otg-objs += dwc_otg_hcd.o dwc_otg_hcd_intr.o dwc_otg_hcd_queue.o

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
/* ==========================================================================
* $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_attr.h $
* $Revision: 1.1 $
* $Date: 2008/03/31 00:20:10 $
* $Change: 510275 $
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#if !defined(__DWC_OTG_ATTR_H__)
#define __DWC_OTG_ATTR_H__
/** @file
* This file contains the interface to the Linux device attributes.
*/
extern struct device_attribute dev_attr_regoffset;
extern struct device_attribute dev_attr_regvalue;
extern struct device_attribute dev_attr_mode;
extern struct device_attribute dev_attr_hnpcapable;
extern struct device_attribute dev_attr_srpcapable;
extern struct device_attribute dev_attr_hnp;
extern struct device_attribute dev_attr_srp;
extern struct device_attribute dev_attr_buspower;
extern struct device_attribute dev_attr_bussuspend;
extern struct device_attribute dev_attr_busconnected;
extern struct device_attribute dev_attr_gotgctl;
extern struct device_attribute dev_attr_gusbcfg;
extern struct device_attribute dev_attr_grxfsiz;
extern struct device_attribute dev_attr_gnptxfsiz;
extern struct device_attribute dev_attr_gpvndctl;
extern struct device_attribute dev_attr_ggpio;
extern struct device_attribute dev_attr_guid;
extern struct device_attribute dev_attr_gsnpsid;
extern struct device_attribute dev_attr_devspeed;
extern struct device_attribute dev_attr_enumspeed;
extern struct device_attribute dev_attr_hptxfsiz;
extern struct device_attribute dev_attr_hprt0;
//void dwc_otg_attr_create (struct lm_device *lmdev);
//void dwc_otg_attr_remove (struct lm_device *lmdev);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,960 @@
/* ==========================================================================
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#if !defined(__DWC_CIL_H__)
#define __DWC_CIL_H__
#include "dwc_otg_plat.h"
#include "dwc_otg_regs.h"
#ifdef DEBUG
#include "linux/timer.h"
#endif
/**
* @file
* This file contains the interface to the Core Interface Layer.
*/
/**
* The <code>dwc_ep</code> structure represents the state of a single
* endpoint when acting in device mode. It contains the data items
* needed for an endpoint to be activated and transfer packets.
*/
typedef struct dwc_ep
{
/** EP number used for register address lookup */
uint8_t num;
/** EP direction 0 = OUT */
unsigned is_in : 1;
/** EP active. */
unsigned active : 1;
/** Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use non-periodic Tx FIFO
If dedicated Tx FIFOs are enabled for all IN Eps - Tx FIFO # FOR IN EPs*/
unsigned tx_fifo_num : 4;
/** EP type: 0 - Control, 1 - ISOC, 2 - BULK, 3 - INTR */
unsigned type : 2;
#define DWC_OTG_EP_TYPE_CONTROL 0
#define DWC_OTG_EP_TYPE_ISOC 1
#define DWC_OTG_EP_TYPE_BULK 2
#define DWC_OTG_EP_TYPE_INTR 3
/** DATA start PID for INTR and BULK EP */
unsigned data_pid_start : 1;
/** Frame (even/odd) for ISOC EP */
unsigned even_odd_frame : 1;
/** Max Packet bytes */
unsigned maxpacket : 11;
/** @name Transfer state */
/** @{ */
/**
* Pointer to the beginning of the transfer buffer -- do not modify
* during transfer.
*/
uint32_t dma_addr;
uint32_t dma_desc_addr;
dwc_otg_dma_desc_t* desc_addr;
uint8_t *start_xfer_buff;
/** pointer to the transfer buffer */
uint8_t *xfer_buff;
/** Number of bytes to transfer */
unsigned xfer_len : 19;
/** Number of bytes transferred. */
unsigned xfer_count : 19;
/** Sent ZLP */
unsigned sent_zlp : 1;
/** Total len for control transfer */
unsigned total_len : 19;
/** stall clear flag */
unsigned stall_clear_flag : 1;
#ifdef _EN_ISOC_
/**
* Variables specific for ISOC EPs
*
*/
/** DMA addresses of ISOC buffers */
uint32_t dma_addr0;
uint32_t dma_addr1;
uint32_t iso_dma_desc_addr;
dwc_otg_iso_dma_desc_t* iso_desc_addr;
/** pointer to the transfer buffers */
uint8_t *xfer_buff0;
uint8_t *xfer_buff1;
/** number of ISOC Buffer is processing */
uint32_t proc_buf_num;
/** Interval of ISOC Buffer processing */
uint32_t buf_proc_intrvl;
/** Data size for regular frame */
uint32_t data_per_frame;
/* todo - pattern data support is to be implemented in the future */
/** Data size for pattern frame */
uint32_t data_pattern_frame;
/** Frame number of pattern data */
uint32_t sync_frame;
/** bInterval */
uint32_t bInterval;
/** DMA Desc count for buffers */
uint32_t desc_cnt;
/** ISO Packet number per frame */
uint32_t pkt_per_frm;
/** Next frame num for which will be setup DMA Desc */
uint32_t next_frame;
#endif //_EN_ISOC_
/** @} */
} dwc_ep_t;
/*
* Reasons for halting a host channel.
*/
typedef enum dwc_otg_halt_status
{
DWC_OTG_HC_XFER_NO_HALT_STATUS,
DWC_OTG_HC_XFER_COMPLETE,
DWC_OTG_HC_XFER_URB_COMPLETE,
DWC_OTG_HC_XFER_ACK,
DWC_OTG_HC_XFER_NAK,
DWC_OTG_HC_XFER_NYET,
DWC_OTG_HC_XFER_STALL,
DWC_OTG_HC_XFER_XACT_ERR,
DWC_OTG_HC_XFER_FRAME_OVERRUN,
DWC_OTG_HC_XFER_BABBLE_ERR,
DWC_OTG_HC_XFER_DATA_TOGGLE_ERR,
DWC_OTG_HC_XFER_AHB_ERR,
DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE,
DWC_OTG_HC_XFER_URB_DEQUEUE
} dwc_otg_halt_status_e;
/**
* Host channel descriptor. This structure represents the state of a single
* host channel when acting in host mode. It contains the data items needed to
* transfer packets to an endpoint via a host channel.
*/
typedef struct dwc_hc
{
/** Host channel number used for register address lookup */
uint8_t hc_num;
/** Device to access */
unsigned dev_addr : 7;
/** EP to access */
unsigned ep_num : 4;
/** EP direction. 0: OUT, 1: IN */
unsigned ep_is_in : 1;
/**
* EP speed.
* One of the following values:
* - DWC_OTG_EP_SPEED_LOW
* - DWC_OTG_EP_SPEED_FULL
* - DWC_OTG_EP_SPEED_HIGH
*/
unsigned speed : 2;
#define DWC_OTG_EP_SPEED_LOW 0
#define DWC_OTG_EP_SPEED_FULL 1
#define DWC_OTG_EP_SPEED_HIGH 2
/**
* Endpoint type.
* One of the following values:
* - DWC_OTG_EP_TYPE_CONTROL: 0
* - DWC_OTG_EP_TYPE_ISOC: 1
* - DWC_OTG_EP_TYPE_BULK: 2
* - DWC_OTG_EP_TYPE_INTR: 3
*/
unsigned ep_type : 2;
/** Max packet size in bytes */
unsigned max_packet : 11;
/**
* PID for initial transaction.
* 0: DATA0,<br>
* 1: DATA2,<br>
* 2: DATA1,<br>
* 3: MDATA (non-Control EP),
* SETUP (Control EP)
*/
unsigned data_pid_start : 2;
#define DWC_OTG_HC_PID_DATA0 0
#define DWC_OTG_HC_PID_DATA2 1
#define DWC_OTG_HC_PID_DATA1 2
#define DWC_OTG_HC_PID_MDATA 3
#define DWC_OTG_HC_PID_SETUP 3
/** Number of periodic transactions per (micro)frame */
unsigned multi_count: 2;
/** @name Transfer State */
/** @{ */
/** Pointer to the current transfer buffer position. */
uint8_t *xfer_buff;
/** Total number of bytes to transfer. */
uint32_t xfer_len;
/** Number of bytes transferred so far. */
uint32_t xfer_count;
/** Packet count at start of transfer.*/
uint16_t start_pkt_count;
/**
* Flag to indicate whether the transfer has been started. Set to 1 if
* it has been started, 0 otherwise.
*/
uint8_t xfer_started;
/**
* Set to 1 to indicate that a PING request should be issued on this
* channel. If 0, process normally.
*/
uint8_t do_ping;
/**
* Set to 1 to indicate that the error count for this transaction is
* non-zero. Set to 0 if the error count is 0.
*/
uint8_t error_state;
/**
* Set to 1 to indicate that this channel should be halted the next
* time a request is queued for the channel. This is necessary in
* slave mode if no request queue space is available when an attempt
* is made to halt the channel.
*/
uint8_t halt_on_queue;
/**
* Set to 1 if the host channel has been halted, but the core is not
* finished flushing queued requests. Otherwise 0.
*/
uint8_t halt_pending;
/**
* Reason for halting the host channel.
*/
dwc_otg_halt_status_e halt_status;
/*
* Split settings for the host channel
*/
uint8_t do_split; /**< Enable split for the channel */
uint8_t complete_split; /**< Enable complete split */
uint8_t hub_addr; /**< Address of high speed hub */
uint8_t port_addr; /**< Port of the low/full speed device */
/** Split transaction position
* One of the following values:
* - DWC_HCSPLIT_XACTPOS_MID
* - DWC_HCSPLIT_XACTPOS_BEGIN
* - DWC_HCSPLIT_XACTPOS_END
* - DWC_HCSPLIT_XACTPOS_ALL */
uint8_t xact_pos;
/** Set when the host channel does a short read. */
uint8_t short_read;
/**
* Number of requests issued for this channel since it was assigned to
* the current transfer (not counting PINGs).
*/
uint8_t requests;
/**
* Queue Head for the transfer being processed by this channel.
*/
struct dwc_otg_qh *qh;
/** @} */
/** Entry in list of host channels. */
struct list_head hc_list_entry;
} dwc_hc_t;
/**
* The following parameters may be specified when starting the module. These
* parameters define how the DWC_otg controller should be configured.
* Parameter values are passed to the CIL initialization function
* dwc_otg_cil_init.
*/
typedef struct dwc_otg_core_params
{
int32_t opt;
#define dwc_param_opt_default 1
/**
* Specifies the OTG capabilities. The driver will automatically
* detect the value for this parameter if none is specified.
* 0 - HNP and SRP capable (default)
* 1 - SRP Only capable
* 2 - No HNP/SRP capable
*/
int32_t otg_cap;
#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0
#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1
#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2
#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE
/**
* Specifies whether to use slave or DMA mode for accessing the data
* FIFOs. The driver will automatically detect the value for this
* parameter if none is specified.
* 0 - Slave
* 1 - DMA (default, if available)
*/
int32_t dma_enable;
#define dwc_param_dma_enable_default 1
/**
* When DMA mode is enabled specifies whether to use address DMA or DMA Descritor mode for accessing the data
* FIFOs in device mode. The driver will automatically detect the value for this
* parameter if none is specified.
* 0 - address DMA
* 1 - DMA Descriptor(default, if available)
*/
int32_t dma_desc_enable;
#define dwc_param_dma_desc_enable_default 0
/** The DMA Burst size (applicable only for External DMA
* Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32)
*/
int32_t dma_burst_size; /* Translate this to GAHBCFG values */
#define dwc_param_dma_burst_size_default 32
/**
* Specifies the maximum speed of operation in host and device mode.
* The actual speed depends on the speed of the attached device and
* the value of phy_type. The actual speed depends on the speed of the
* attached device.
* 0 - High Speed (default)
* 1 - Full Speed
*/
int32_t speed;
#define dwc_param_speed_default 0
#define DWC_SPEED_PARAM_HIGH 0
#define DWC_SPEED_PARAM_FULL 1
/** Specifies whether low power mode is supported when attached
* to a Full Speed or Low Speed device in host mode.
* 0 - Don't support low power mode (default)
* 1 - Support low power mode
*/
int32_t host_support_fs_ls_low_power;
#define dwc_param_host_support_fs_ls_low_power_default 0
/** Specifies the PHY clock rate in low power mode when connected to a
* Low Speed device in host mode. This parameter is applicable only if
* HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS
* then defaults to 6 MHZ otherwise 48 MHZ.
*
* 0 - 48 MHz
* 1 - 6 MHz
*/
int32_t host_ls_low_power_phy_clk;
#define dwc_param_host_ls_low_power_phy_clk_default 0
#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0
#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1
/**
* 0 - Use cC FIFO size parameters
* 1 - Allow dynamic FIFO sizing (default)
*/
int32_t enable_dynamic_fifo;
#define dwc_param_enable_dynamic_fifo_default 1
/** Total number of 4-byte words in the data FIFO memory. This
* memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic
* Tx FIFOs.
* 32 to 32768 (default 8192)
* Note: The total FIFO memory depth in the FPGA configuration is 8192.
*/
int32_t data_fifo_size;
#define dwc_param_data_fifo_size_default 8192
/** Number of 4-byte words in the Rx FIFO in device mode when dynamic
* FIFO sizing is enabled.
* 16 to 32768 (default 1064)
*/
int32_t dev_rx_fifo_size;
#define dwc_param_dev_rx_fifo_size_default 1064
/** Number of 4-byte words in the non-periodic Tx FIFO in device mode
* when dynamic FIFO sizing is enabled.
* 16 to 32768 (default 1024)
*/
int32_t dev_nperio_tx_fifo_size;
#define dwc_param_dev_nperio_tx_fifo_size_default 1024
/** Number of 4-byte words in each of the periodic Tx FIFOs in device
* mode when dynamic FIFO sizing is enabled.
* 4 to 768 (default 256)
*/
uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS];
#define dwc_param_dev_perio_tx_fifo_size_default 256
/** Number of 4-byte words in the Rx FIFO in host mode when dynamic
* FIFO sizing is enabled.
* 16 to 32768 (default 1024)
*/
int32_t host_rx_fifo_size;
#define dwc_param_host_rx_fifo_size_default 1024
/** Number of 4-byte words in the non-periodic Tx FIFO in host mode
* when Dynamic FIFO sizing is enabled in the core.
* 16 to 32768 (default 1024)
*/
int32_t host_nperio_tx_fifo_size;
#define dwc_param_host_nperio_tx_fifo_size_default 1024
/** Number of 4-byte words in the host periodic Tx FIFO when dynamic
* FIFO sizing is enabled.
* 16 to 32768 (default 1024)
*/
int32_t host_perio_tx_fifo_size;
#define dwc_param_host_perio_tx_fifo_size_default 1024
/** The maximum transfer size supported in bytes.
* 2047 to 65,535 (default 65,535)
*/
int32_t max_transfer_size;
#define dwc_param_max_transfer_size_default 65535
/** The maximum number of packets in a transfer.
* 15 to 511 (default 511)
*/
int32_t max_packet_count;
#define dwc_param_max_packet_count_default 511
/** The number of host channel registers to use.
* 1 to 16 (default 12)
* Note: The FPGA configuration supports a maximum of 12 host channels.
*/
int32_t host_channels;
#define dwc_param_host_channels_default 12
/** The number of endpoints in addition to EP0 available for device
* mode operations.
* 1 to 15 (default 6 IN and OUT)
* Note: The FPGA configuration supports a maximum of 6 IN and OUT
* endpoints in addition to EP0.
*/
int32_t dev_endpoints;
#define dwc_param_dev_endpoints_default 6
/**
* Specifies the type of PHY interface to use. By default, the driver
* will automatically detect the phy_type.
*
* 0 - Full Speed PHY
* 1 - UTMI+ (default)
* 2 - ULPI
*/
int32_t phy_type;
#define DWC_PHY_TYPE_PARAM_FS 0
#define DWC_PHY_TYPE_PARAM_UTMI 1
#define DWC_PHY_TYPE_PARAM_ULPI 2
#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI
/**
* Specifies the UTMI+ Data Width. This parameter is
* applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI
* PHY_TYPE, this parameter indicates the data width between
* the MAC and the ULPI Wrapper.) Also, this parameter is
* applicable only if the OTG_HSPHY_WIDTH cC parameter was set
* to "8 and 16 bits", meaning that the core has been
* configured to work at either data path width.
*
* 8 or 16 bits (default 16)
*/
int32_t phy_utmi_width;
#define dwc_param_phy_utmi_width_default 16
/**
* Specifies whether the ULPI operates at double or single
* data rate. This parameter is only applicable if PHY_TYPE is
* ULPI.
*
* 0 - single data rate ULPI interface with 8 bit wide data
* bus (default)
* 1 - double data rate ULPI interface with 4 bit wide data
* bus
*/
int32_t phy_ulpi_ddr;
#define dwc_param_phy_ulpi_ddr_default 0
/**
* Specifies whether to use the internal or external supply to
* drive the vbus with a ULPI phy.
*/
int32_t phy_ulpi_ext_vbus;
#define DWC_PHY_ULPI_INTERNAL_VBUS 0
#define DWC_PHY_ULPI_EXTERNAL_VBUS 1
#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS
/**
* Specifies whether to use the I2Cinterface for full speed PHY. This
* parameter is only applicable if PHY_TYPE is FS.
* 0 - No (default)
* 1 - Yes
*/
int32_t i2c_enable;
#define dwc_param_i2c_enable_default 0
int32_t ulpi_fs_ls;
#define dwc_param_ulpi_fs_ls_default 0
int32_t ts_dline;
#define dwc_param_ts_dline_default 0
/**
* Specifies whether dedicated transmit FIFOs are
* enabled for non periodic IN endpoints in device mode
* 0 - No
* 1 - Yes
*/
int32_t en_multiple_tx_fifo;
#define dwc_param_en_multiple_tx_fifo_default 1
/** Number of 4-byte words in each of the Tx FIFOs in device
* mode when dynamic FIFO sizing is enabled.
* 4 to 768 (default 256)
*/
uint32_t dev_tx_fifo_size[MAX_TX_FIFOS];
#define dwc_param_dev_tx_fifo_size_default 256
/** Thresholding enable flag-
* bit 0 - enable non-ISO Tx thresholding
* bit 1 - enable ISO Tx thresholding
* bit 2 - enable Rx thresholding
*/
uint32_t thr_ctl;
#define dwc_param_thr_ctl_default 0
/** Thresholding length for Tx
* FIFOs in 32 bit DWORDs
*/
uint32_t tx_thr_length;
#define dwc_param_tx_thr_length_default 64
/** Thresholding length for Rx
* FIFOs in 32 bit DWORDs
*/
uint32_t rx_thr_length;
#define dwc_param_rx_thr_length_default 64
} dwc_otg_core_params_t;
#ifdef DEBUG
struct dwc_otg_core_if;
typedef struct hc_xfer_info
{
struct dwc_otg_core_if *core_if;
dwc_hc_t *hc;
} hc_xfer_info_t;
#endif
/**
* The <code>dwc_otg_core_if</code> structure contains information needed to manage
* the DWC_otg controller acting in either host or device mode. It
* represents the programming view of the controller as a whole.
*/
typedef struct dwc_otg_core_if
{
/** Parameters that define how the core should be configured.*/
dwc_otg_core_params_t *core_params;
/** Core Global registers starting at offset 000h. */
dwc_otg_core_global_regs_t *core_global_regs;
/** Device-specific information */
dwc_otg_dev_if_t *dev_if;
/** Host-specific information */
dwc_otg_host_if_t *host_if;
/*
* Set to 1 if the core PHY interface bits in USBCFG have been
* initialized.
*/
uint8_t phy_init_done;
/*
* SRP Success flag, set by srp success interrupt in FS I2C mode
*/
uint8_t srp_success;
uint8_t srp_timer_started;
/* Common configuration information */
/** Power and Clock Gating Control Register */
volatile uint32_t *pcgcctl;
#define DWC_OTG_PCGCCTL_OFFSET 0xE00
/** Push/pop addresses for endpoints or host channels.*/
uint32_t *data_fifo[MAX_EPS_CHANNELS];
#define DWC_OTG_DATA_FIFO_OFFSET 0x1000
#define DWC_OTG_DATA_FIFO_SIZE 0x1000
/** Total RAM for FIFOs (Bytes) */
uint16_t total_fifo_size;
/** Size of Rx FIFO (Bytes) */
uint16_t rx_fifo_size;
/** Size of Non-periodic Tx FIFO (Bytes) */
uint16_t nperio_tx_fifo_size;
/** 1 if DMA is enabled, 0 otherwise. */
uint8_t dma_enable;
/** 1 if DMA descriptor is enabled, 0 otherwise. */
uint8_t dma_desc_enable;
/** 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */
uint8_t en_multiple_tx_fifo;
/** Set to 1 if multiple packets of a high-bandwidth transfer is in
* process of being queued */
uint8_t queuing_high_bandwidth;
/** Hardware Configuration -- stored here for convenience.*/
hwcfg1_data_t hwcfg1;
hwcfg2_data_t hwcfg2;
hwcfg3_data_t hwcfg3;
hwcfg4_data_t hwcfg4;
/** Host and Device Configuration -- stored here for convenience.*/
hcfg_data_t hcfg;
dcfg_data_t dcfg;
/** The operational State, during transations
* (a_host>>a_peripherial and b_device=>b_host) this may not
* match the core but allows the software to determine
* transitions.
*/
uint8_t op_state;
/**
* Set to 1 if the HCD needs to be restarted on a session request
* interrupt. This is required if no connector ID status change has
* occurred since the HCD was last disconnected.
*/
uint8_t restart_hcd_on_session_req;
/** HCD callbacks */
/** A-Device is a_host */
#define A_HOST (1)
/** A-Device is a_suspend */
#define A_SUSPEND (2)
/** A-Device is a_peripherial */
#define A_PERIPHERAL (3)
/** B-Device is operating as a Peripheral. */
#define B_PERIPHERAL (4)
/** B-Device is operating as a Host. */
#define B_HOST (5)
/** HCD callbacks */
struct dwc_otg_cil_callbacks *hcd_cb;
/** PCD callbacks */
struct dwc_otg_cil_callbacks *pcd_cb;
/** Device mode Periodic Tx FIFO Mask */
uint32_t p_tx_msk;
/** Device mode Periodic Tx FIFO Mask */
uint32_t tx_msk;
#ifdef DEBUG
uint32_t start_hcchar_val[MAX_EPS_CHANNELS];
hc_xfer_info_t hc_xfer_info[MAX_EPS_CHANNELS];
struct timer_list hc_xfer_timer[MAX_EPS_CHANNELS];
uint32_t hfnum_7_samples;
uint64_t hfnum_7_frrem_accum;
uint32_t hfnum_0_samples;
uint64_t hfnum_0_frrem_accum;
uint32_t hfnum_other_samples;
uint64_t hfnum_other_frrem_accum;
#endif
uint irq_pat[10];
uint irq_hcd[12];
uint irq_hlt[12];
uint xfr_done[12];
uint split[2];
} dwc_otg_core_if_t;
/*
* The following functions support initialization of the CIL driver component
* and the DWC_otg controller.
*/
extern dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t *_reg_base_addr,
dwc_otg_core_params_t *_core_params);
extern void dwc_otg_cil_remove(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_core_init(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_core_host_init(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_core_dev_init(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_enable_global_interrupts( dwc_otg_core_if_t *_core_if );
extern void dwc_otg_disable_global_interrupts( dwc_otg_core_if_t *_core_if );
/** @name Device CIL Functions
* The following functions support managing the DWC_otg controller in device
* mode.
*/
/**@{*/
extern void dwc_otg_wakeup(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_read_setup_packet (dwc_otg_core_if_t *_core_if, uint32_t *_dest);
extern uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_ep0_activate(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep_activate(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep_deactivate(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep_start_transfer(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep_write_packet(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep, int _dma);
extern void dwc_otg_ep_set_stall(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_ep_clear_stall(dwc_otg_core_if_t *_core_if, dwc_ep_t *_ep);
extern void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_dump_dev_registers(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_dump_spram(dwc_otg_core_if_t *_core_if);
void dwc_otg_dump_ep0desc(dwc_otg_core_if_t* _core_if);
/**@}*/
/** @name Host CIL Functions
* The following functions support managing the DWC_otg controller in host
* mode.
*/
/**@{*/
extern void dwc_otg_hc_init(dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc);
extern void dwc_otg_hc_halt(dwc_otg_core_if_t *_core_if,
dwc_hc_t *_hc,
dwc_otg_halt_status_e _halt_status);
extern void dwc_otg_hc_cleanup(dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc);
extern void dwc_otg_hc_start_transfer(dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc);
extern int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc);
extern void dwc_otg_hc_do_ping(dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc);
extern void dwc_otg_hc_write_packet(dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc);
extern void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t *_core_if);
/**
* This function Reads HPRT0 in preparation to modify. It keeps the
* WC bits 0 so that if they are read as 1, they won't clear when you
* write it back
*/
static inline uint32_t dwc_otg_read_hprt0(dwc_otg_core_if_t *_core_if)
{
hprt0_data_t hprt0;
hprt0.d32 = dwc_read_reg32(_core_if->host_if->hprt0);
hprt0.b.prtena = 0;
hprt0.b.prtconndet = 0;
hprt0.b.prtenchng = 0;
hprt0.b.prtovrcurrchng = 0;
return hprt0.d32;
}
extern void dwc_otg_dump_host_registers(dwc_otg_core_if_t *_core_if);
/**@}*/
/** @name Common CIL Functions
* The following functions support managing the DWC_otg controller in either
* device or host mode.
*/
/**@{*/
extern void dwc_otg_read_packet(dwc_otg_core_if_t *core_if,
uint8_t *dest,
uint16_t bytes);
extern void dwc_otg_dump_global_registers(dwc_otg_core_if_t *_core_if);
extern void dwc_otg_flush_tx_fifo( dwc_otg_core_if_t *_core_if,
const int _num );
extern void dwc_otg_flush_rx_fifo( dwc_otg_core_if_t *_core_if );
extern void dwc_otg_core_reset( dwc_otg_core_if_t *_core_if );
/**
* This function returns the Core Interrupt register.
*/
static inline uint32_t dwc_otg_read_core_intr(dwc_otg_core_if_t *_core_if)
{
#if 0
return (dwc_read_reg32(&_core_if->core_global_regs->gintsts) &
dwc_read_reg32(&_core_if->core_global_regs->gintmsk));
#else
return (readl(S3C_UDC_OTG_GINTSTS) & readl(S3C_UDC_OTG_GINTMSK));
#endif
}
/**
* This function returns the OTG Interrupt register.
*/
static inline uint32_t dwc_otg_read_otg_intr (dwc_otg_core_if_t *_core_if)
{
return (dwc_read_reg32 (&_core_if->core_global_regs->gotgint));
}
/**
* This function reads the Device All Endpoints Interrupt register and
* returns the IN endpoint interrupt bits.
*/
static inline uint32_t dwc_otg_read_dev_all_in_ep_intr(dwc_otg_core_if_t *_core_if)
{
uint32_t v;
v = dwc_read_reg32(&_core_if->dev_if->dev_global_regs->daint) &
dwc_read_reg32(&_core_if->dev_if->dev_global_regs->daintmsk);
return (v & 0xffff);
}
/**
* This function reads the Device All Endpoints Interrupt register and
* returns the OUT endpoint interrupt bits.
*/
static inline uint32_t dwc_otg_read_dev_all_out_ep_intr(dwc_otg_core_if_t *_core_if)
{
uint32_t v;
v = dwc_read_reg32(&_core_if->dev_if->dev_global_regs->daint) &
dwc_read_reg32(&_core_if->dev_if->dev_global_regs->daintmsk);
return ((v & 0xffff0000) >> 16);
}
/**
* This function returns the Device IN EP Interrupt register
*/
static inline uint32_t dwc_otg_read_dev_in_ep_intr(dwc_otg_core_if_t *_core_if,
dwc_ep_t *_ep)
{
dwc_otg_dev_if_t *dev_if = _core_if->dev_if;
uint32_t v, msk, emp;
msk = dwc_read_reg32(&dev_if->dev_global_regs->diepmsk);
emp = dwc_read_reg32(&dev_if->dev_global_regs->dtknqr4_fifoemptymsk);
msk |= ((emp >> _ep->num) & 0x1) << 7;
v = dwc_read_reg32(&dev_if->in_ep_regs[_ep->num]->diepint) & msk;
/*
dwc_otg_dev_if_t *dev_if = _core_if->dev_if;
uint32_t v;
v = dwc_read_reg32(&dev_if->in_ep_regs[_ep->num]->diepint) &
dwc_read_reg32(&dev_if->dev_global_regs->diepmsk);
*/
return v;
}
/**
* This function returns the Device OUT EP Interrupt register
*/
static inline uint32_t dwc_otg_read_dev_out_ep_intr(dwc_otg_core_if_t *_core_if,
dwc_ep_t *_ep)
{
dwc_otg_dev_if_t *dev_if = _core_if->dev_if;
uint32_t v;
v = dwc_read_reg32( &dev_if->out_ep_regs[_ep->num]->doepint) &
dwc_read_reg32(&dev_if->dev_global_regs->doepmsk);
return v;
}
/**
* This function returns the Host All Channel Interrupt register
*/
static inline uint32_t dwc_otg_read_host_all_channels_intr (dwc_otg_core_if_t *_core_if)
{
return (dwc_read_reg32 (&_core_if->host_if->host_global_regs->haint));
}
static inline uint32_t dwc_otg_read_host_channel_intr (dwc_otg_core_if_t *_core_if, dwc_hc_t *_hc)
{
return (dwc_read_reg32 (&_core_if->host_if->hc_regs[_hc->hc_num]->hcint));
}
/**
* This function returns the mode of the operation, host or device.
*
* @return 0 - Device Mode, 1 - Host Mode
*/
static inline uint32_t dwc_otg_mode(dwc_otg_core_if_t *_core_if)
{
return (dwc_read_reg32( &_core_if->core_global_regs->gintsts ) & 0x1);
}
static inline uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t *_core_if)
{
return (dwc_otg_mode(_core_if) != DWC_HOST_MODE);
}
static inline uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t *_core_if)
{
return (dwc_otg_mode(_core_if) == DWC_HOST_MODE);
}
extern int32_t dwc_otg_handle_common_intr( dwc_otg_core_if_t *_core_if, uint sts);
/**@}*/
/**
* DWC_otg CIL callback structure. This structure allows the HCD and
* PCD to register functions used for starting and stopping the PCD
* and HCD for role change on for a DRD.
*/
typedef struct dwc_otg_cil_callbacks
{
/** Start function for role change */
int (*start) (void *_p);
/** Stop Function for role change */
int (*stop) (void *_p);
/** Disconnect Function for role change */
int (*disconnect) (void *_p);
/** Resume/Remote wakeup Function */
int (*resume_wakeup) (void *_p);
/** Suspend function */
int (*suspend) (void *_p);
/** Session Start (SRP) */
int (*session_start) (void *_p);
/** Pointer passed to start() and stop() */
void *p;
} dwc_otg_cil_callbacks_t;
extern void dwc_otg_cil_register_pcd_callbacks( dwc_otg_core_if_t *_core_if,
dwc_otg_cil_callbacks_t *_cb,
void *_p);
extern void dwc_otg_cil_register_hcd_callbacks( dwc_otg_core_if_t *_core_if,
dwc_otg_cil_callbacks_t *_cb,
void *_p);
#endif

View File

@@ -0,0 +1,721 @@
/* ==========================================================================
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
/** @file
*
* The Core Interface Layer provides basic services for accessing and
* managing the DWC_otg hardware. These services are used by both the
* Host Controller Driver and the Peripheral Controller Driver.
*
* This file contains the Common Interrupt handlers.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/stat.h> /* permission constants */
#include <linux/version.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include "dwc_otg_plat.h"
#include "dwc_otg_regs.h"
#include "dwc_otg_cil.h"
#ifdef DEBUG
inline const char *op_state_str(dwc_otg_core_if_t * _core_if)
{
return (_core_if->op_state == A_HOST ? "a_host" :
(_core_if->op_state == A_SUSPEND ? "a_suspend" :
(_core_if->op_state == A_PERIPHERAL ? "a_peripheral" :
(_core_if->op_state == B_PERIPHERAL ? "b_peripheral" :
(_core_if->op_state == B_HOST ? "b_host" : "unknown")))));
}
#endif
/** This function will log a debug message
*
* @param _core_if Programming view of DWC_otg controller.
*/
int32_t dwc_otg_handle_mode_mismatch_intr(dwc_otg_core_if_t * _core_if)
{
gintsts_data_t gintsts;
printk("Mode Mismatch Interrupt: currently in %s mode\n",
dwc_otg_mode(_core_if) ? "Host" : "Device");
/* Clear interrupt */
gintsts.d32 = 0;
gintsts.b.modemismatch = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
return 1;
}
/** Start the HCD. Helper function for using the HCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void hcd_start(dwc_otg_core_if_t * _core_if)
{
if (_core_if->hcd_cb && _core_if->hcd_cb->start) {
_core_if->hcd_cb->start(_core_if->hcd_cb->p);
}
}
/** Stop the HCD. Helper function for using the HCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void hcd_stop(dwc_otg_core_if_t * _core_if)
{
if (_core_if->hcd_cb && _core_if->hcd_cb->stop) {
_core_if->hcd_cb->stop(_core_if->hcd_cb->p);
}
}
/** Disconnect the HCD. Helper function for using the HCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void hcd_disconnect(dwc_otg_core_if_t * _core_if)
{
if (_core_if->hcd_cb && _core_if->hcd_cb->disconnect) {
_core_if->hcd_cb->disconnect(_core_if->hcd_cb->p);
}
}
/** Inform the HCD the a New Session has begun. Helper function for
* using the HCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void hcd_session_start(dwc_otg_core_if_t * _core_if)
{
if (_core_if->hcd_cb && _core_if->hcd_cb->session_start) {
_core_if->hcd_cb->session_start(_core_if->hcd_cb->p);
}
}
/** Start the PCD. Helper function for using the PCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void pcd_start(dwc_otg_core_if_t * _core_if)
{
if (_core_if->pcd_cb && _core_if->pcd_cb->start) {
_core_if->pcd_cb->start(_core_if->pcd_cb->p);
}
}
/** Stop the PCD. Helper function for using the PCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void pcd_stop(dwc_otg_core_if_t * _core_if)
{
if (_core_if->pcd_cb && _core_if->pcd_cb->stop) {
_core_if->pcd_cb->stop(_core_if->pcd_cb->p);
}
}
/** Suspend the PCD. Helper function for using the PCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void pcd_suspend(dwc_otg_core_if_t * _core_if)
{
if (_core_if->pcd_cb && _core_if->pcd_cb->suspend) {
_core_if->pcd_cb->suspend(_core_if->pcd_cb->p);
}
}
/** Resume the PCD. Helper function for using the PCD callbacks.
*
* @param _core_if Programming view of DWC_otg controller.
*/
static inline void pcd_resume(dwc_otg_core_if_t * _core_if)
{
if (_core_if->pcd_cb && _core_if->pcd_cb->resume_wakeup) {
_core_if->pcd_cb->resume_wakeup(_core_if->pcd_cb->p);
}
}
/**
* This function handles the OTG Interrupts. It reads the OTG
* Interrupt Register (GOTGINT) to determine what interrupt has
* occurred.
*
* @param _core_if Programming view of DWC_otg controller.
*/
int32_t dwc_otg_handle_otg_intr(dwc_otg_core_if_t * _core_if)
{
dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs;
gotgint_data_t gotgint;
gotgctl_data_t gotgctl;
gintmsk_data_t gintmsk;
gotgint.d32 = dwc_read_reg32(&global_regs->gotgint);
gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl);
DWC_DEBUGPL(DBG_CIL, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint.d32,
op_state_str(_core_if));
//DWC_DEBUGPL(DBG_CIL, "gotgctl=%08x\n", gotgctl.d32 );
if (gotgint.b.sesenddet) {
DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
"Session End Detected++ (%s)\n", op_state_str(_core_if));
gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl);
if (_core_if->op_state == B_HOST) {
pcd_start(_core_if);
_core_if->op_state = B_PERIPHERAL;
} else {
/* If not B_HOST and Device HNP still set. HNP
* Did not succeed!*/
if (gotgctl.b.devhnpen) {
DWC_DEBUGPL(DBG_ANY, "Session End Detected\n");
DWC_ERROR("Device Not Connected/Responding!\n");
}
/* If Session End Detected the B-Cable has
* been disconnected. */
/* Reset PCD and Gadget driver to a
* clean state. */
pcd_stop(_core_if);
}
gotgctl.d32 = 0;
gotgctl.b.devhnpen = 1;
dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 0);
}
if (gotgint.b.sesreqsucstschng) {
DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
"Session Reqeust Success Status Change++\n");
gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl);
if (gotgctl.b.sesreqscs) {
if ((_core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) &&
(_core_if->core_params->i2c_enable)) {
_core_if->srp_success = 1;
} else {
pcd_resume(_core_if);
/* Clear Session Request */
gotgctl.d32 = 0;
gotgctl.b.sesreq = 1;
dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 0);
}
}
}
if (gotgint.b.hstnegsucstschng) {
/* Print statements during the HNP interrupt handling
* can cause it to fail.*/
gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl);
if (gotgctl.b.hstnegscs) {
if (dwc_otg_is_host_mode(_core_if)) {
_core_if->op_state = B_HOST;
/*
* Need to disable SOF interrupt immediately.
* When switching from device to host, the PCD
* interrupt handler won't handle the
* interrupt if host mode is already set. The
* HCD interrupt handler won't get called if
* the HCD state is HALT. This means that the
* interrupt does not get handled and Linux
* complains loudly.
*/
gintmsk.d32 = 0;
gintmsk.b.sofintr = 1;
dwc_modify_reg32(&global_regs->gintmsk, gintmsk.d32, 0);
pcd_stop(_core_if);
/*
* Initialize the Core for Host mode.
*/
hcd_start(_core_if);
_core_if->op_state = B_HOST;
}
} else {
gotgctl.d32 = 0;
gotgctl.b.hnpreq = 1;
gotgctl.b.devhnpen = 1;
dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 0);
DWC_DEBUGPL(DBG_ANY, "HNP Failed\n");
DWC_ERROR("Device Not Connected/Responding\n");
}
}
if (gotgint.b.hstnegdet) {
/* The disconnect interrupt is set at the same time as
* Host Negotiation Detected. During the mode
* switch all interrupts are cleared so the disconnect
* interrupt handler will not get executed.
*/
DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
"Host Negotiation Detected++ (%s)\n",
(dwc_otg_is_host_mode(_core_if) ? "Host" : "Device"));
if (dwc_otg_is_device_mode(_core_if)) {
DWC_DEBUGPL(DBG_ANY, "a_suspend->a_peripheral (%d)\n", _core_if->op_state);
hcd_disconnect(_core_if);
pcd_start(_core_if);
_core_if->op_state = A_PERIPHERAL;
} else {
/*
* Need to disable SOF interrupt immediately. When
* switching from device to host, the PCD interrupt
* handler won't handle the interrupt if host mode is
* already set. The HCD interrupt handler won't get
* called if the HCD state is HALT. This means that
* the interrupt does not get handled and Linux
* complains loudly.
*/
gintmsk.d32 = 0;
gintmsk.b.sofintr = 1;
dwc_modify_reg32(&global_regs->gintmsk, gintmsk.d32, 0);
pcd_stop(_core_if);
hcd_start(_core_if);
_core_if->op_state = A_HOST;
}
}
if (gotgint.b.adevtoutchng) {
DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " "A-Device Timeout Change++\n");
}
if (gotgint.b.debdone) {
DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " "Debounce Done++\n");
}
/* Clear GOTGINT */
dwc_write_reg32(&_core_if->core_global_regs->gotgint, gotgint.d32);
return 1;
}
/**
* This function handles the Connector ID Status Change Interrupt. It
* reads the OTG Interrupt Register (GOTCTL) to determine whether this
* is a Device to Host Mode transition or a Host Mode to Device
* Transition.
*
* This only occurs when the cable is connected/removed from the PHY
* connector.
*
* @param _core_if Programming view of DWC_otg controller.
*/
int32_t dwc_otg_handle_conn_id_status_change_intr(dwc_otg_core_if_t * _core_if)
{
uint32_t count = 0;
gintsts_data_t gintsts = {.d32 = 0 };
gintmsk_data_t gintmsk = {.d32 = 0 };
gotgctl_data_t gotgctl = {.d32 = 0 };
/*
* Need to disable SOF interrupt immediately. If switching from device
* to host, the PCD interrupt handler won't handle the interrupt if
* host mode is already set. The HCD interrupt handler won't get
* called if the HCD state is HALT. This means that the interrupt does
* not get handled and Linux complains loudly.
*/
gintmsk.b.sofintr = 1;
dwc_modify_reg32(&_core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
DWC_DEBUGPL(DBG_CIL, " ++Connector ID Status Change Interrupt++ (%s)\n",
(dwc_otg_is_host_mode(_core_if) ? "Host" : "Device"));
gotgctl.d32 = dwc_read_reg32(&_core_if->core_global_regs->gotgctl);
DWC_DEBUGPL(DBG_CIL, "gotgctl=%0x\n", gotgctl.d32);
DWC_DEBUGPL(DBG_CIL, "gotgctl.b.conidsts=%d\n", gotgctl.b.conidsts);
/* B-Device connector (Device Mode) */
if (gotgctl.b.conidsts) {
/* Wait for switch to device mode. */
while (!dwc_otg_is_device_mode(_core_if)) {
printk("Waiting for Peripheral Mode, Mode=%s\n",
(dwc_otg_is_host_mode(_core_if) ? "Host" : "Peripheral"));
mdelay(100);
if (++count > 10000)
*(uint32_t *) NULL = 0;
}
_core_if->op_state = B_PERIPHERAL;
dwc_otg_core_init(_core_if);
dwc_otg_enable_global_interrupts(_core_if);
pcd_start(_core_if);
} else {
/* A-Device connector (Host Mode) */
while (!dwc_otg_is_host_mode(_core_if)) {
printk("Waiting for Host Mode, Mode=%s\n",
(dwc_otg_is_host_mode(_core_if) ? "Host" : "Peripheral"));
mdelay(100);
if (++count > 10000)
*(uint32_t *) NULL = 0;
}
_core_if->op_state = A_HOST;
/*
* Initialize the Core for Host mode.
*/
dwc_otg_core_init(_core_if);
dwc_otg_enable_global_interrupts(_core_if);
hcd_start(_core_if);
}
/* Set flag and clear interrupt */
gintsts.b.conidstschng = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
return 1;
}
/**
* This interrupt indicates that a device is initiating the Session
* Request Protocol to request the host to turn on bus power so a new
* session can begin. The handler responds by turning on bus power. If
* the DWC_otg controller is in low power mode, the handler brings the
* controller out of low power mode before turning on bus power.
*
* @param _core_if Programming view of DWC_otg controller.
*/
int32_t dwc_otg_handle_session_req_intr (dwc_otg_core_if_t * _core_if)
{
#ifndef DWC_HOST_ONLY
hprt0_data_t hprt0;
#endif
gintsts_data_t gintsts;
#ifndef DWC_HOST_ONLY
DWC_DEBUGPL(DBG_ANY, "++Session Request Interrupt++\n");
if (dwc_otg_is_device_mode(_core_if)) {
DWC_PRINT("SRP: Device mode\n");
} else {
DWC_PRINT("SRP: Host mode\n");
/* Turn on the port power bit. */
hprt0.d32 = dwc_otg_read_hprt0(_core_if);
hprt0.b.prtpwr = 1;
dwc_write_reg32(_core_if->host_if->hprt0, hprt0.d32);
/* Start the Connection timer. So a message can be displayed
* if connect does not occur within 10 seconds. */
hcd_session_start(_core_if);
}
#endif
/* Clear interrupt */
gintsts.d32 = 0;
gintsts.b.sessreqintr = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
return 1;
}
/**
* This interrupt indicates that the DWC_otg controller has detected a
* resume or remote wakeup sequence. If the DWC_otg controller is in
* low power mode, the handler must brings the controller out of low
* power mode. The controller automatically begins resume
* signaling. The handler schedules a time to stop resume signaling.
*/
int32_t dwc_otg_handle_wakeup_detected_intr(dwc_otg_core_if_t * _core_if)
{
gintsts_data_t gintsts;
DWC_DEBUGPL(DBG_ANY, "++Resume and Remote Wakeup Detected Interrupt++\n");
if (dwc_otg_is_device_mode(_core_if)) {
dctl_data_t dctl = {.d32 = 0 };
DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n",
dwc_read_reg32(&_core_if->dev_if->dev_global_regs->dsts));
#ifdef PARTIAL_POWER_DOWN
if (_core_if->hwcfg4.b.power_optimiz) {
pcgcctl_data_t power = {.d32 = 0 };
power.d32 = dwc_read_reg32(_core_if->pcgcctl);
DWC_DEBUGPL(DBG_CIL, "PCGCCTL=%0x\n", power.d32);
power.b.stoppclk = 0;
dwc_write_reg32(_core_if->pcgcctl, power.d32);
power.b.pwrclmp = 0;
dwc_write_reg32(_core_if->pcgcctl, power.d32);
power.b.rstpdwnmodule = 0;
dwc_write_reg32(_core_if->pcgcctl, power.d32);
}
#endif
/* Clear the Remote Wakeup Signalling */
dctl.b.rmtwkupsig = 1;
dwc_modify_reg32(&_core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0);
if (_core_if->pcd_cb && _core_if->pcd_cb->resume_wakeup) {
_core_if->pcd_cb->resume_wakeup(_core_if->pcd_cb->p);
}
} else {
/*
* Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms
* so that OPT tests pass with all PHYs).
*/
hprt0_data_t hprt0 = {.d32 = 0 };
pcgcctl_data_t pcgcctl = {.d32 = 0 };
/* Restart the Phy Clock */
pcgcctl.b.stoppclk = 1;
dwc_modify_reg32(_core_if->pcgcctl, pcgcctl.d32, 0);
udelay(10);
/* Now wait for 70 ms. */
hprt0.d32 = dwc_otg_read_hprt0(_core_if);
printk("Resume: HPRT0=%0x\n", hprt0.d32);
mdelay(70);
hprt0.b.prtres = 0; /* Resume */
dwc_write_reg32(_core_if->host_if->hprt0, hprt0.d32);
DWC_DEBUGPL(DBG_ANY, "Clear Resume: HPRT0=%0x\n",
dwc_read_reg32(_core_if->host_if->hprt0));
}
/* Clear interrupt */
gintsts.d32 = 0;
gintsts.b.wkupintr = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
return 1;
}
/**
* This interrupt indicates that a device has been disconnected from
* the root port.
*/
int32_t dwc_otg_handle_disconnect_intr(dwc_otg_core_if_t * _core_if)
{
gintsts_data_t gintsts;
DWC_DEBUGPL(DBG_ANY, "++Disconnect Detected Interrupt++ (%s) %s\n",
(dwc_otg_is_host_mode(_core_if) ? "Host" : "Device"), op_state_str(_core_if));
/** @todo Consolidate this if statement. */
#ifndef DWC_HOST_ONLY
if (_core_if->op_state == B_HOST) {
/* If in device mode Disconnect and stop the HCD, then
* start the PCD. */
hcd_disconnect(_core_if);
pcd_start(_core_if);
_core_if->op_state = B_PERIPHERAL;
} else if (dwc_otg_is_device_mode(_core_if)) {
gotgctl_data_t gotgctl = {.d32 = 0 };
gotgctl.d32 = dwc_read_reg32(&_core_if->core_global_regs->gotgctl);
if (gotgctl.b.hstsethnpen == 1) {
/* Do nothing, if HNP in process the OTG
* interrupt "Host Negotiation Detected"
* interrupt will do the mode switch.
*/
} else if (gotgctl.b.devhnpen == 0) {
/* If in device mode Disconnect and stop the HCD, then
* start the PCD. */
hcd_disconnect(_core_if);
pcd_start(_core_if);
_core_if->op_state = B_PERIPHERAL;
} else {
DWC_DEBUGPL(DBG_ANY, "!a_peripheral && !devhnpen\n");
}
} else {
if (_core_if->op_state == A_HOST) {
/* A-Cable still connected but device disconnected. */
hcd_disconnect(_core_if);
}
}
#endif
gintsts.d32 = 0;
gintsts.b.disconnect = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
return 1;
}
/**
* This interrupt indicates that SUSPEND state has been detected on
* the USB.
*
* For HNP the USB Suspend interrupt signals the change from
* "a_peripheral" to "a_host".
*
* When power management is enabled the core will be put in low power
* mode.
*/
int32_t dwc_otg_handle_usb_suspend_intr(dwc_otg_core_if_t * _core_if)
{
dsts_data_t dsts;
gintsts_data_t gintsts;
DWC_DEBUGPL(DBG_ANY, "USB SUSPEND\n");
if (dwc_otg_is_device_mode(_core_if)) {
/* Check the Device status register to determine if the Suspend
* state is active. */
dsts.d32 = dwc_read_reg32(&_core_if->dev_if->dev_global_regs->dsts);
DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n", dsts.d32);
DWC_DEBUGPL(DBG_PCD, "DSTS.Suspend Status=%d "
"HWCFG4.power Optimize=%d\n",
dsts.b.suspsts, _core_if->hwcfg4.b.power_optimiz);
#ifdef PARTIAL_POWER_DOWN
/** @todo Add a module parameter for power management. */
if (dsts.b.suspsts && _core_if->hwcfg4.b.power_optimiz) {
pcgcctl_data_t power = {.d32 = 0 };
DWC_DEBUGPL(DBG_CIL, "suspend\n");
power.b.pwrclmp = 1;
dwc_write_reg32(_core_if->pcgcctl, power.d32);
power.b.rstpdwnmodule = 1;
dwc_modify_reg32(_core_if->pcgcctl, 0, power.d32);
power.b.stoppclk = 1;
dwc_modify_reg32(_core_if->pcgcctl, 0, power.d32);
} else {
DWC_DEBUGPL(DBG_ANY, "disconnect?\n");
}
#endif
/* PCD callback for suspend. */
pcd_suspend(_core_if);
} else {
if (_core_if->op_state == A_PERIPHERAL) {
DWC_DEBUGPL(DBG_ANY, "a_peripheral->a_host\n");
/* Clear the a_peripheral flag, back to a_host. */
pcd_stop(_core_if);
hcd_start(_core_if);
_core_if->op_state = A_HOST;
}
}
/* Clear interrupt */
gintsts.d32 = 0;
gintsts.b.usbsuspend = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
return 1;
}
#define DWC_INT_MSK_COMMON 0xf1000806
/**
* This function returns the Core Interrupt register.
*/
static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * _core_if)
{
#if 0
gintsts_data_t gintsts;
gintmsk_data_t gintmsk;
gintmsk_data_t gintmsk_common = {.d32 = 0 };
gintmsk_common.b.wkupintr = 1;
gintmsk_common.b.sessreqintr = 1;
gintmsk_common.b.conidstschng = 1;
gintmsk_common.b.otgintr = 1;
gintmsk_common.b.modemismatch = 1;
gintmsk_common.b.disconnect = 1;
gintmsk_common.b.usbsuspend = 1;
/** @todo: The port interrupt occurs while in device
* mode. Added code to CIL to clear the interrupt for now!
*/
gintmsk_common.b.portintr = 1;
printk("gintmsk_common.d32: %08x\n", gintmsk_common.d32);
gintsts.d32 = dwc_read_reg32(&_core_if->core_global_regs->gintsts);
gintmsk.d32 = dwc_read_reg32(&_core_if->core_global_regs->gintmsk);
#ifdef DEBUG
/* if any common interrupts set */
if (gintsts.d32 & gintmsk_common.d32) {
DWC_DEBUGPL(DBG_ANY, "gintsts=%08x gintmsk=%08x\n", gintsts.d32, gintmsk.d32);
}
#endif
return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32);
#else
u32 sts, msk;
sts = readl(S3C_UDC_OTG_GINTSTS);
msk = readl(S3C_UDC_OTG_GINTMSK);
return ((sts & msk) & DWC_INT_MSK_COMMON);
#endif
}
/**
* Common interrupt handler.
*
* The common interrupts are those that occur in both Host and Device mode.
* This handler handles the following interrupts:
* - Mode Mismatch Interrupt
* - Disconnect Interrupt
* - OTG Interrupt
* - Connector ID Status Change Interrupt
* - Session Request Interrupt.
* - Resume / Remote Wakeup Detected Interrupt.
*
*/
extern int32_t dwc_otg_handle_common_intr(dwc_otg_core_if_t * _core_if, uint sts)
{
int retval = IRQ_NONE;
gintsts_data_t gintsts;
gintsts.d32 = sts;
if (gintsts.b.modemismatch) {
retval |= dwc_otg_handle_mode_mismatch_intr(_core_if);
}
if (gintsts.b.otgintr) {
retval |= dwc_otg_handle_otg_intr(_core_if);
}
if (gintsts.b.conidstschng) {
retval |= dwc_otg_handle_conn_id_status_change_intr(_core_if);
}
if (gintsts.b.disconnect) {
retval |= dwc_otg_handle_disconnect_intr(_core_if);
}
if (gintsts.b.sessreqintr) {
retval |= dwc_otg_handle_session_req_intr(_core_if);
}
if (gintsts.b.wkupintr) {
retval |= dwc_otg_handle_wakeup_detected_intr(_core_if);
}
if (gintsts.b.usbsuspend) {
retval |= dwc_otg_handle_usb_suspend_intr(_core_if);
}
if (gintsts.b.portintr && dwc_otg_is_device_mode(_core_if)) {
/* The port interrupt occurs while in device mode with HPRT0
* Port Enable/Disable.
*/
gintsts.d32 = 0;
gintsts.b.portintr = 1;
dwc_write_reg32(&_core_if->core_global_regs->gintsts, gintsts.d32);
retval |= 1;
}
return retval;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/* ==========================================================================
* $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_driver.h $
* $Revision: 1.1 $
* $Date: 2008/03/31 00:20:10 $
* $Change: 791271 $
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#if !defined(__DWC_OTG_DRIVER_H__)
#define __DWC_OTG_DRIVER_H__
/** @file
* This file contains the interface to the Linux driver.
*/
#include "dwc_otg_cil.h"
/* Type declarations */
struct dwc_otg_pcd;
struct dwc_otg_hcd;
/**
* This structure is a wrapper that encapsulates the driver components used to
* manage a single DWC_otg controller.
*/
typedef struct dwc_otg_device
{
/** Base address returned from ioremap() */
void *base;
/** Pointer to the core interface structure. */
dwc_otg_core_if_t *core_if;
/** Register offset for Diagnostic API.*/
uint32_t reg_offset;
/** Pointer to the PCD structure. */
struct dwc_otg_pcd *pcd;
/** Pointer to the HCD structure. */
struct dwc_otg_hcd *hcd;
/** Flag to indicate whether the common IRQ handler is installed. */
uint8_t common_irq_installed;
} dwc_otg_device_t;
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,645 @@
/* ==========================================================================
* $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_hcd.h $
* $Revision: 1.1 $
* $Date: 2008/03/31 00:20:10 $
* $Change: 993572 $
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#ifndef DWC_DEVICE_ONLY
#if !defined(__DWC_HCD_H__)
#define __DWC_HCD_H__
#include <linux/list.h>
#include <linux/usb.h>
#include <../drivers/usb/core/hcd.h>
struct dwc_otg_device;
#include "dwc_otg_cil.h"
/**
* @file
*
* This file contains the structures, constants, and interfaces for
* the Host Contoller Driver (HCD).
*
* The Host Controller Driver (HCD) is responsible for translating requests
* from the USB Driver into the appropriate actions on the DWC_otg controller.
* It isolates the USBD from the specifics of the controller by providing an
* API to the USBD.
*/
/**
* Phases for control transfers.
*/
typedef enum dwc_otg_control_phase {
DWC_OTG_CONTROL_SETUP,
DWC_OTG_CONTROL_DATA,
DWC_OTG_CONTROL_STATUS
} dwc_otg_control_phase_e;
/** Transaction types. */
typedef enum dwc_otg_transaction_type {
DWC_OTG_TRANSACTION_NONE,
DWC_OTG_TRANSACTION_PERIODIC,
DWC_OTG_TRANSACTION_NON_PERIODIC,
DWC_OTG_TRANSACTION_ALL
} dwc_otg_transaction_type_e;
/**
* A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
* interrupt, or isochronous transfer. A single QTD is created for each URB
* (of one of these types) submitted to the HCD. The transfer associated with
* a QTD may require one or multiple transactions.
*
* A QTD is linked to a Queue Head, which is entered in either the
* non-periodic or periodic schedule for execution. When a QTD is chosen for
* execution, some or all of its transactions may be executed. After
* execution, the state of the QTD is updated. The QTD may be retired if all
* its transactions are complete or if an error occurred. Otherwise, it
* remains in the schedule so more transactions can be executed later.
*/
typedef struct dwc_otg_qtd {
/**
* Determines the PID of the next data packet for the data phase of
* control transfers. Ignored for other transfer types.<br>
* One of the following values:
* - DWC_OTG_HC_PID_DATA0
* - DWC_OTG_HC_PID_DATA1
*/
uint8_t data_toggle;
/** Current phase for control transfers (Setup, Data, or Status). */
dwc_otg_control_phase_e control_phase;
/** Keep track of the current split type
* for FS/LS endpoints on a HS Hub */
uint8_t complete_split;
/** How many bytes transferred during SSPLIT OUT */
uint32_t ssplit_out_xfer_count;
/**
* Holds the number of bus errors that have occurred for a transaction
* within this transfer.
*/
uint8_t error_count;
/**
* Index of the next frame descriptor for an isochronous transfer. A
* frame descriptor describes the buffer position and length of the
* data to be transferred in the next scheduled (micro)frame of an
* isochronous transfer. It also holds status for that transaction.
* The frame index starts at 0.
*/
int isoc_frame_index;
/** Position of the ISOC split on full/low speed */
uint8_t isoc_split_pos;
/** Position of the ISOC split in the buffer for the current frame */
uint16_t isoc_split_offset;
/** URB for this transfer */
struct urb *urb;
/** This list of QTDs */
struct list_head qtd_list_entry;
} dwc_otg_qtd_t;
/**
* A Queue Head (QH) holds the static characteristics of an endpoint and
* maintains a list of transfers (QTDs) for that endpoint. A QH structure may
* be entered in either the non-periodic or periodic schedule.
*/
typedef struct dwc_otg_qh {
/**
* Endpoint type.
* One of the following values:
* - USB_ENDPOINT_XFER_CONTROL
* - USB_ENDPOINT_XFER_ISOC
* - USB_ENDPOINT_XFER_BULK
* - USB_ENDPOINT_XFER_INT
*/
uint8_t ep_type;
uint8_t ep_is_in;
/** wMaxPacketSize Field of Endpoint Descriptor. */
uint16_t maxp;
/**
* Determines the PID of the next data packet for non-control
* transfers. Ignored for control transfers.<br>
* One of the following values:
* - DWC_OTG_HC_PID_DATA0
* - DWC_OTG_HC_PID_DATA1
*/
uint8_t data_toggle;
/** Ping state if 1. */
uint8_t ping_state;
/**
* List of QTDs for this QH.
*/
struct list_head qtd_list;
/** Host channel currently processing transfers for this QH. */
dwc_hc_t *channel;
/** QTD currently assigned to a host channel for this QH. */
dwc_otg_qtd_t *qtd_in_process;
/** Full/low speed endpoint on high-speed hub requires split. */
uint8_t do_split;
/** @name Periodic schedule information */
/** @{ */
/** Bandwidth in microseconds per (micro)frame. */
uint8_t usecs;
/** Interval between transfers in (micro)frames. */
uint16_t interval;
/**
* (micro)frame to initialize a periodic transfer. The transfer
* executes in the following (micro)frame.
*/
uint16_t sched_frame;
/** (micro)frame at which last start split was initialized. */
uint16_t start_split_frame;
/** @} */
/** Entry for QH in either the periodic or non-periodic schedule. */
struct list_head qh_list_entry;
} dwc_otg_qh_t;
/**
* This structure holds the state of the HCD, including the non-periodic and
* periodic schedules.
*/
typedef struct dwc_otg_hcd {
/** DWC OTG Core Interface Layer */
dwc_otg_core_if_t *core_if;
/** Internal DWC HCD Flags */
volatile union dwc_otg_hcd_internal_flags {
uint32_t d32;
struct {
unsigned port_connect_status_change : 1;
unsigned port_connect_status : 1;
unsigned port_reset_change : 1;
unsigned port_enable_change : 1;
unsigned port_suspend_change : 1;
unsigned port_over_current_change : 1;
unsigned reserved : 27;
} b;
} flags;
/**
* Inactive items in the non-periodic schedule. This is a list of
* Queue Heads. Transfers associated with these Queue Heads are not
* currently assigned to a host channel.
*/
struct list_head non_periodic_sched_inactive;
/**
* Active items in the non-periodic schedule. This is a list of
* Queue Heads. Transfers associated with these Queue Heads are
* currently assigned to a host channel.
*/
struct list_head non_periodic_sched_active;
/**
* Pointer to the next Queue Head to process in the active
* non-periodic schedule.
*/
struct list_head *non_periodic_qh_ptr;
/**
* Inactive items in the periodic schedule. This is a list of QHs for
* periodic transfers that are _not_ scheduled for the next frame.
* Each QH in the list has an interval counter that determines when it
* needs to be scheduled for execution. This scheduling mechanism
* allows only a simple calculation for periodic bandwidth used (i.e.
* must assume that all periodic transfers may need to execute in the
* same frame). However, it greatly simplifies scheduling and should
* be sufficient for the vast majority of OTG hosts, which need to
* connect to a small number of peripherals at one time.
*
* Items move from this list to periodic_sched_ready when the QH
* interval counter is 0 at SOF.
*/
struct list_head periodic_sched_inactive;
/**
* List of periodic QHs that are ready for execution in the next
* frame, but have not yet been assigned to host channels.
*
* Items move from this list to periodic_sched_assigned as host
* channels become available during the current frame.
*/
struct list_head periodic_sched_ready;
/**
* List of periodic QHs to be executed in the next frame that are
* assigned to host channels.
*
* Items move from this list to periodic_sched_queued as the
* transactions for the QH are queued to the DWC_otg controller.
*/
struct list_head periodic_sched_assigned;
/**
* List of periodic QHs that have been queued for execution.
*
* Items move from this list to either periodic_sched_inactive or
* periodic_sched_ready when the channel associated with the transfer
* is released. If the interval for the QH is 1, the item moves to
* periodic_sched_ready because it must be rescheduled for the next
* frame. Otherwise, the item moves to periodic_sched_inactive.
*/
struct list_head periodic_sched_queued;
/**
* Total bandwidth claimed so far for periodic transfers. This value
* is in microseconds per (micro)frame. The assumption is that all
* periodic transfers may occur in the same (micro)frame.
*/
uint16_t periodic_usecs;
/**
* Frame number read from the core at SOF. The value ranges from 0 to
* DWC_HFNUM_MAX_FRNUM.
*/
uint16_t frame_number;
/**
* Free host channels in the controller. This is a list of
* dwc_hc_t items.
*/
struct list_head free_hc_list;
/**
* Number of host channels assigned to periodic transfers. Currently
* assuming that there is a dedicated host channel for each periodic
* transaction and at least one host channel available for
* non-periodic transactions.
*/
int periodic_channels;
/**
* Number of host channels assigned to non-periodic transfers.
*/
int non_periodic_channels;
/**
* Array of pointers to the host channel descriptors. Allows accessing
* a host channel descriptor given the host channel number. This is
* useful in interrupt handlers.
*/
dwc_hc_t *hc_ptr_array[MAX_EPS_CHANNELS];
/**
* Buffer to use for any data received during the status phase of a
* control transfer. Normally no data is transferred during the status
* phase. This buffer is used as a bit bucket.
*/
uint8_t *status_buf;
/**
* DMA address for status_buf.
*/
dma_addr_t status_buf_dma;
#define DWC_OTG_HCD_STATUS_BUF_SIZE 64
/**
* Structure to allow starting the HCD in a non-interrupt context
* during an OTG role change.
*/
struct work_struct start_work;
/**
* Connection timer. An OTG host must display a message if the device
* does not connect. Started when the VBus power is turned on via
* sysfs attribute "buspower".
*/
struct timer_list conn_timer;
/* Tasket to do a reset */
struct tasklet_struct *reset_tasklet;
#ifdef DEBUG
uint32_t frrem_samples;
uint64_t frrem_accum;
uint32_t hfnum_7_samples_a;
uint64_t hfnum_7_frrem_accum_a;
uint32_t hfnum_0_samples_a;
uint64_t hfnum_0_frrem_accum_a;
uint32_t hfnum_other_samples_a;
uint64_t hfnum_other_frrem_accum_a;
uint32_t hfnum_7_samples_b;
uint64_t hfnum_7_frrem_accum_b;
uint32_t hfnum_0_samples_b;
uint64_t hfnum_0_frrem_accum_b;
uint32_t hfnum_other_samples_b;
uint64_t hfnum_other_frrem_accum_b;
#endif
} dwc_otg_hcd_t;
/** Gets the dwc_otg_hcd from a struct usb_hcd */
static inline dwc_otg_hcd_t *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd)
{
return (dwc_otg_hcd_t *)(hcd->hcd_priv);
}
/** Gets the struct usb_hcd that contains a dwc_otg_hcd_t. */
static inline struct usb_hcd *dwc_otg_hcd_to_hcd(dwc_otg_hcd_t *dwc_otg_hcd)
{
return container_of((void *)dwc_otg_hcd, struct usb_hcd, hcd_priv);
}
/** @name HCD Create/Destroy Functions */
/** @{ */
extern int dwc_otg_hcd_init(struct platform_device *_lmdev);
extern void dwc_otg_hcd_remove(struct platform_device *_lmdev);
/** @} */
/** @name Linux HC Driver API Functions */
/** @{ */
extern int dwc_otg_hcd_start(struct usb_hcd *hcd);
extern void dwc_otg_hcd_stop(struct usb_hcd *hcd);
extern int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd);
extern void dwc_otg_hcd_free(struct usb_hcd *hcd);
extern int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd,
struct usb_host_endpoint *ep,
struct urb *urb,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
int mem_flags
#else
gfp_t mem_flags
#endif
);
extern int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
struct usb_host_endpoint *ep,
#endif
struct urb *urb);
extern void dwc_otg_hcd_endpoint_disable(struct usb_hcd *hcd,
struct usb_host_endpoint *ep);
extern irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
, struct pt_regs *regs
#endif
);
extern int dwc_otg_hcd_hub_status_data(struct usb_hcd *hcd,
char *buf);
extern int dwc_otg_hcd_hub_control(struct usb_hcd *hcd,
u16 typeReq,
u16 wValue,
u16 wIndex,
char *buf,
u16 wLength);
/** @} */
/** @name Transaction Execution Functions */
/** @{ */
extern dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t *_hcd);
extern void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t *_hcd,
dwc_otg_transaction_type_e _tr_type);
extern void dwc_otg_hcd_complete_urb(dwc_otg_hcd_t *_hcd, struct urb *_urb,
int _status);
/** @} */
/** @name Interrupt Handler Functions */
/** @{ */
extern int32_t dwc_otg_hcd_handle_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_sof_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_rx_status_q_level_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_incomplete_periodic_intr(dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_port_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_conn_id_status_change_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_disconnect_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_hc_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_hc_n_intr (dwc_otg_hcd_t *_dwc_otg_hcd, uint32_t _num);
extern int32_t dwc_otg_hcd_handle_session_req_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
extern int32_t dwc_otg_hcd_handle_wakeup_detected_intr (dwc_otg_hcd_t *_dwc_otg_hcd);
/** @} */
/** @name Schedule Queue Functions */
/** @{ */
/* Implemented in dwc_otg_hcd_queue.c */
extern dwc_otg_qh_t *dwc_otg_hcd_qh_create (dwc_otg_hcd_t *_hcd, struct urb *_urb);
extern void dwc_otg_hcd_qh_init (dwc_otg_hcd_t *_hcd, dwc_otg_qh_t *_qh, struct urb *_urb);
extern void dwc_otg_hcd_qh_free (dwc_otg_qh_t *_qh);
extern int dwc_otg_hcd_qh_add (dwc_otg_hcd_t *_hcd, dwc_otg_qh_t *_qh);
extern void dwc_otg_hcd_qh_remove (dwc_otg_hcd_t *_hcd, dwc_otg_qh_t *_qh);
extern void dwc_otg_hcd_qh_deactivate (dwc_otg_hcd_t *_hcd, dwc_otg_qh_t *_qh, int sched_csplit);
/** Remove and free a QH */
static inline void dwc_otg_hcd_qh_remove_and_free (dwc_otg_hcd_t *_hcd,
dwc_otg_qh_t *_qh)
{
dwc_otg_hcd_qh_remove (_hcd, _qh);
dwc_otg_hcd_qh_free (_qh);
}
/** Allocates memory for a QH structure.
* @return Returns the memory allocate or NULL on error. */
static inline dwc_otg_qh_t *dwc_otg_hcd_qh_alloc (void)
{
return (dwc_otg_qh_t *) kmalloc (sizeof(dwc_otg_qh_t), GFP_KERNEL);
}
extern dwc_otg_qtd_t *dwc_otg_hcd_qtd_create (struct urb *urb);
extern int dwc_otg_hcd_qtd_add (dwc_otg_qtd_t *qtd, dwc_otg_hcd_t *dwc_otg_hcd);
/** Frees the memory for a QTD structure. QTD should already be removed from
* list.
* @param[in] _qtd QTD to free.*/
static inline void dwc_otg_hcd_qtd_free (dwc_otg_qtd_t *_qtd)
{
kfree (_qtd);
}
/** Removes a QTD from list.
* @param[in] _qtd QTD to remove from list. */
static inline void dwc_otg_hcd_qtd_remove (dwc_otg_qtd_t *_qtd)
{
unsigned long flags;
local_irq_save (flags);
list_del (&_qtd->qtd_list_entry);
local_irq_restore (flags);
}
/** Remove and free a QTD */
static inline void dwc_otg_hcd_qtd_remove_and_free (dwc_otg_qtd_t *_qtd)
{
dwc_otg_hcd_qtd_remove (_qtd);
dwc_otg_hcd_qtd_free (_qtd);
}
/** @} */
/** @name Internal Functions */
/** @{ */
dwc_otg_qh_t *dwc_urb_to_qh(struct urb *_urb);
void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t *_hcd);
void dwc_otg_hcd_dump_state(dwc_otg_hcd_t *_hcd);
/** @} */
/** Gets the usb_host_endpoint associated with an URB. */
static inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *_urb)
{
struct usb_device *dev = _urb->dev;
int ep_num = usb_pipeendpoint(_urb->pipe);
if (usb_pipein(_urb->pipe))
return dev->ep_in[ep_num];
else
return dev->ep_out[ep_num];
}
/**
* Gets the endpoint number from a _bEndpointAddress argument. The endpoint is
* qualified with its direction (possible 32 endpoints per device).
*/
#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \
((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4)
/** Gets the QH that contains the list_head */
#define dwc_list_to_qh(_list_head_ptr_) (container_of(_list_head_ptr_,dwc_otg_qh_t,qh_list_entry))
/** Gets the QTD that contains the list_head */
#define dwc_list_to_qtd(_list_head_ptr_) (container_of(_list_head_ptr_,dwc_otg_qtd_t,qtd_list_entry))
/** Check if QH is non-periodic */
#define dwc_qh_is_non_per(_qh_ptr_) ((_qh_ptr_->ep_type == USB_ENDPOINT_XFER_BULK) || \
(_qh_ptr_->ep_type == USB_ENDPOINT_XFER_CONTROL))
/** High bandwidth multiplier as encoded in highspeed endpoint descriptors */
#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
/** Packet size for any kind of endpoint descriptor */
#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
/**
* Returns true if _frame1 is less than or equal to _frame2. The comparison is
* done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the
* frame number when the max frame number is reached.
*/
static inline int dwc_frame_num_le(uint16_t _frame1, uint16_t _frame2)
{
return ((_frame2 - _frame1) & DWC_HFNUM_MAX_FRNUM) <=
(DWC_HFNUM_MAX_FRNUM >> 1);
}
/**
* Returns true if _frame1 is greater than _frame2. The comparison is done
* modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame
* number when the max frame number is reached.
*/
static inline int dwc_frame_num_gt(uint16_t _frame1, uint16_t _frame2)
{
return (_frame1 != _frame2) &&
(((_frame1 - _frame2) & DWC_HFNUM_MAX_FRNUM) <
(DWC_HFNUM_MAX_FRNUM >> 1));
}
/**
* Increments _frame by the amount specified by _inc. The addition is done
* modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value.
*/
static inline uint16_t dwc_frame_num_inc(uint16_t _frame, uint16_t _inc)
{
return (_frame + _inc) & DWC_HFNUM_MAX_FRNUM;
}
static inline uint16_t dwc_full_frame_num (uint16_t _frame)
{
return ((_frame) & DWC_HFNUM_MAX_FRNUM) >> 3;
}
static inline uint16_t dwc_micro_frame_num (uint16_t _frame)
{
return (_frame) & 0x7;
}
#ifdef DEBUG
/**
* Macro to sample the remaining PHY clocks left in the current frame. This
* may be used during debugging to determine the average time it takes to
* execute sections of code. There are two possible sample points, "a" and
* "b", so the _letter argument must be one of these values.
*
* To dump the average sample times, read the "hcd_frrem" sysfs attribute. For
* example, "cat /sys/devices/lm0/hcd_frrem".
*/
#define dwc_sample_frrem(_hcd, _qh, _letter) \
{ \
hfnum_data_t hfnum; \
dwc_otg_qtd_t *qtd; \
qtd = list_entry(_qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); \
if (usb_pipeint(qtd->urb->pipe) && _qh->start_split_frame != 0 && !qtd->complete_split) { \
hfnum.d32 = dwc_read_reg32(&_hcd->core_if->host_if->host_global_regs->hfnum); \
switch (hfnum.b.frnum & 0x7) { \
case 7: \
_hcd->hfnum_7_samples_##_letter++; \
_hcd->hfnum_7_frrem_accum_##_letter += hfnum.b.frrem; \
break; \
case 0: \
_hcd->hfnum_0_samples_##_letter++; \
_hcd->hfnum_0_frrem_accum_##_letter += hfnum.b.frrem; \
break; \
default: \
_hcd->hfnum_other_samples_##_letter++; \
_hcd->hfnum_other_frrem_accum_##_letter += hfnum.b.frrem; \
break; \
} \
} \
}
#else
#define dwc_sample_frrem(_hcd, _qh, _letter)
#endif
#endif
#endif /* DWC_DEVICE_ONLY */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,660 @@
/* ==========================================================================
* $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_hcd_queue.c $
* $Revision: 1.1 $
* $Date: 2008/03/31 00:20:10 $
* $Change: 993572 $
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#ifndef DWC_DEVICE_ONLY
/**
* @file
*
* This file contains the functions to manage Queue Heads and Queue
* Transfer Descriptors.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/string.h>
#include <linux/version.h>
#include <asm/arch/irqs.h>
#include "dwc_otg_driver.h"
#include "dwc_otg_hcd.h"
#include "dwc_otg_regs.h"
/**
* This function allocates and initializes a QH.
*
* @param _hcd The HCD state structure for the DWC OTG controller.
* @param[in] _urb Holds the information about the device/endpoint that we need
* to initialize the QH.
*
* @return Returns pointer to the newly allocated QH, or NULL on error. */
dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * _hcd, struct urb *_urb)
{
dwc_otg_qh_t *qh;
/* Allocate memory */
/** @todo add memflags argument */
qh = dwc_otg_hcd_qh_alloc();
if (qh == NULL) {
return NULL;
}
dwc_otg_hcd_qh_init(_hcd, qh, _urb);
return qh;
}
/** Free each QTD in the QH's QTD-list then free the QH. QH should already be
* removed from a list. QTD list should already be empty if called from URB
* Dequeue.
*
* @param[in] _qh The QH to free.
*/
void dwc_otg_hcd_qh_free(dwc_otg_qh_t * _qh)
{
dwc_otg_qtd_t *qtd;
struct list_head *pos;
unsigned long flags;
/* Free each QTD in the QTD list */
local_irq_save(flags);
for (pos = _qh->qtd_list.next; pos != &_qh->qtd_list; pos = _qh->qtd_list.next) {
list_del(pos);
qtd = dwc_list_to_qtd(pos);
dwc_otg_hcd_qtd_free(qtd);
}
local_irq_restore(flags);
kfree(_qh);
return;
}
/** Initializes a QH structure.
*
* @param[in] _hcd The HCD state structure for the DWC OTG controller.
* @param[in] _qh The QH to init.
* @param[in] _urb Holds the information about the device/endpoint that we need
* to initialize the QH. */
#define SCHEDULE_SLOP 10
void dwc_otg_hcd_qh_init(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh, struct urb *_urb)
{
memset(_qh, 0, sizeof(dwc_otg_qh_t));
/* Initialize QH */
switch (usb_pipetype(_urb->pipe)) {
case PIPE_CONTROL:
_qh->ep_type = USB_ENDPOINT_XFER_CONTROL;
break;
case PIPE_BULK:
_qh->ep_type = USB_ENDPOINT_XFER_BULK;
break;
case PIPE_ISOCHRONOUS:
_qh->ep_type = USB_ENDPOINT_XFER_ISOC;
break;
case PIPE_INTERRUPT:
_qh->ep_type = USB_ENDPOINT_XFER_INT;
break;
}
_qh->ep_is_in = usb_pipein(_urb->pipe) ? 1 : 0;
_qh->data_toggle = DWC_OTG_HC_PID_DATA0;
_qh->maxp = usb_maxpacket(_urb->dev, _urb->pipe, !(usb_pipein(_urb->pipe)));
INIT_LIST_HEAD(&_qh->qtd_list);
INIT_LIST_HEAD(&_qh->qh_list_entry);
_qh->channel = NULL;
/* FS/LS Enpoint on HS Hub
* NOT virtual root hub */
_qh->do_split = 0;
if (((_urb->dev->speed == USB_SPEED_LOW) ||
(_urb->dev->speed == USB_SPEED_FULL)) &&
(_urb->dev->tt) && (_urb->dev->tt->hub->devnum != 1)) {
DWC_DEBUGPL(DBG_HCD, "QH init: EP %d: TT found at hub addr %d, for port %d\n",
usb_pipeendpoint(_urb->pipe), _urb->dev->tt->hub->devnum,
_urb->dev->ttport);
_qh->do_split = 1;
}
if (_qh->ep_type == USB_ENDPOINT_XFER_INT || _qh->ep_type == USB_ENDPOINT_XFER_ISOC) {
/* Compute scheduling parameters once and save them. */
hprt0_data_t hprt;
/** @todo Account for split transfers in the bus time. */
int bytecount = dwc_hb_mult(_qh->maxp) * dwc_max_packet(_qh->maxp);
_qh->usecs = usb_calc_bus_time(_urb->dev->speed,
usb_pipein(_urb->pipe),
(_qh->ep_type == USB_ENDPOINT_XFER_ISOC), bytecount);
/* Start in a slightly future (micro)frame. */
_qh->sched_frame = dwc_frame_num_inc(_hcd->frame_number, SCHEDULE_SLOP);
_qh->interval = _urb->interval;
#if 0
/* Increase interrupt polling rate for debugging. */
if (_qh->ep_type == USB_ENDPOINT_XFER_INT) {
_qh->interval = 8;
}
#endif
hprt.d32 = dwc_read_reg32(_hcd->core_if->host_if->hprt0);
if ((hprt.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) &&
((_urb->dev->speed == USB_SPEED_LOW) || (_urb->dev->speed == USB_SPEED_FULL))) {
_qh->interval *= 8;
_qh->sched_frame |= 0x7;
_qh->start_split_frame = _qh->sched_frame;
}
}
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD QH Initialized\n");
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - qh = %p\n", _qh);
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Device Address = %d\n", _urb->dev->devnum);
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Endpoint %d, %s\n",
usb_pipeendpoint(_urb->pipe),
usb_pipein(_urb->pipe) == USB_DIR_IN ? "IN" : "OUT");
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Speed = %s\n", ( {
char *speed;
switch (_urb->dev->speed) {
case USB_SPEED_LOW:
speed = "low"; break; case USB_SPEED_FULL:
speed = "full"; break; case USB_SPEED_HIGH:
speed = "high"; break; default:
speed = "?"; break;};
speed;}
));
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Type = %s\n", ( {
char *type;
switch (_qh->ep_type) {
case USB_ENDPOINT_XFER_ISOC:
type = "isochronous"; break; case USB_ENDPOINT_XFER_INT:
type = "interrupt"; break; case USB_ENDPOINT_XFER_CONTROL:
type = "control"; break; case USB_ENDPOINT_XFER_BULK:
type = "bulk"; break; default:
type = "?"; break;};
type;}
));
#ifdef DEBUG
if (_qh->ep_type == USB_ENDPOINT_XFER_INT) {
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - usecs = %d\n", _qh->usecs);
DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - interval = %d\n", _qh->interval);
}
#endif
return;
}
/**
* Checks that a channel is available for a periodic transfer.
*
* @return 0 if successful, negative error code otherise.
*/
static int periodic_channel_available(dwc_otg_hcd_t * _hcd)
{
/*
* Currently assuming that there is a dedicated host channnel for each
* periodic transaction plus at least one host channel for
* non-periodic transactions.
*/
int status;
int num_channels;
num_channels = _hcd->core_if->core_params->host_channels;
if ((_hcd->periodic_channels + _hcd->non_periodic_channels < num_channels) &&
(_hcd->periodic_channels < num_channels - 1)) {
status = 0;
} else {
DWC_NOTICE("%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n",
__func__, num_channels, _hcd->periodic_channels,
_hcd->non_periodic_channels);
status = -ENOSPC;
}
return status;
}
/**
* Checks that there is sufficient bandwidth for the specified QH in the
* periodic schedule. For simplicity, this calculation assumes that all the
* transfers in the periodic schedule may occur in the same (micro)frame.
*
* @param _hcd The HCD state structure for the DWC OTG controller.
* @param _qh QH containing periodic bandwidth required.
*
* @return 0 if successful, negative error code otherwise.
*/
static int check_periodic_bandwidth(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
{
int status;
uint16_t max_claimed_usecs;
status = 0;
if (_hcd->core_if->core_params->speed == DWC_SPEED_PARAM_HIGH) {
/*
* High speed mode.
* Max periodic usecs is 80% x 125 usec = 100 usec.
*/
max_claimed_usecs = 100 - _qh->usecs;
} else {
/*
* Full speed mode.
* Max periodic usecs is 90% x 1000 usec = 900 usec.
*/
max_claimed_usecs = 900 - _qh->usecs;
}
if (_hcd->periodic_usecs > max_claimed_usecs) {
DWC_NOTICE("%s: already claimed usecs %d, required usecs %d\n",
__func__, _hcd->periodic_usecs, _qh->usecs);
status = -ENOSPC;
}
return status;
}
/**
* Checks that the max transfer size allowed in a host channel is large enough
* to handle the maximum data transfer in a single (micro)frame for a periodic
* transfer.
*
* @param _hcd The HCD state structure for the DWC OTG controller.
* @param _qh QH for a periodic endpoint.
*
* @return 0 if successful, negative error code otherwise.
*/
static int check_max_xfer_size(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
{
int status;
uint32_t max_xfer_size;
uint32_t max_channel_xfer_size;
status = 0;
max_xfer_size = dwc_max_packet(_qh->maxp) * dwc_hb_mult(_qh->maxp);
max_channel_xfer_size = _hcd->core_if->core_params->max_transfer_size;
if (max_xfer_size > max_channel_xfer_size) {
DWC_NOTICE("%s: Periodic xfer length %d > "
"max xfer length for channel %d\n",
__func__, max_xfer_size, max_channel_xfer_size);
status = -ENOSPC;
}
return status;
}
/**
* Schedules an interrupt or isochronous transfer in the periodic schedule.
*
* @param _hcd The HCD state structure for the DWC OTG controller.
* @param _qh QH for the periodic transfer. The QH should already contain the
* scheduling information.
*
* @return 0 if successful, negative error code otherwise.
*/
static int schedule_periodic(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
{
int status = 0;
status = periodic_channel_available(_hcd);
if (status) {
DWC_NOTICE("%s: No host channel available for periodic " "transfer.\n", __func__);
return status;
}
status = check_periodic_bandwidth(_hcd, _qh);
if (status) {
DWC_NOTICE("%s: Insufficient periodic bandwidth for "
"periodic transfer.\n", __func__);
return status;
}
status = check_max_xfer_size(_hcd, _qh);
if (status) {
DWC_NOTICE("%s: Channel max transfer size too small "
"for periodic transfer.\n", __func__);
return status;
}
/* Always start in the inactive schedule. */
list_add_tail(&_qh->qh_list_entry, &_hcd->periodic_sched_inactive);
/* Reserve the periodic channel. */
_hcd->periodic_channels++;
/* Update claimed usecs per (micro)frame. */
_hcd->periodic_usecs += _qh->usecs;
/* Update average periodic bandwidth claimed and # periodic reqs for usbfs. */
hcd_to_bus(dwc_otg_hcd_to_hcd(_hcd))->bandwidth_allocated += _qh->usecs / _qh->interval;
if (_qh->ep_type == USB_ENDPOINT_XFER_INT) {
hcd_to_bus(dwc_otg_hcd_to_hcd(_hcd))->bandwidth_int_reqs++;
DWC_DEBUGPL(DBG_HCD, "Scheduled intr: qh %p, usecs %d, period %d\n",
_qh, _qh->usecs, _qh->interval);
} else {
hcd_to_bus(dwc_otg_hcd_to_hcd(_hcd))->bandwidth_isoc_reqs++;
DWC_DEBUGPL(DBG_HCD, "Scheduled isoc: qh %p, usecs %d, period %d\n",
_qh, _qh->usecs, _qh->interval);
}
return status;
}
/**
* This function adds a QH to either the non periodic or periodic schedule if
* it is not already in the schedule. If the QH is already in the schedule, no
* action is taken.
*
* @return 0 if successful, negative error code otherwise.
*/
int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
{
unsigned long flags;
int status = 0;
local_irq_save(flags);
if (!list_empty(&_qh->qh_list_entry)) {
/* QH already in a schedule. */
goto done;
}
/* Add the new QH to the appropriate schedule */
if (dwc_qh_is_non_per(_qh)) {
/* Always start in the inactive schedule. */
list_add_tail(&_qh->qh_list_entry, &_hcd->non_periodic_sched_inactive);
} else {
status = schedule_periodic(_hcd, _qh);
}
done:
local_irq_restore(flags);
return status;
}
/**
* Removes an interrupt or isochronous transfer from the periodic schedule.
*
* @param _hcd The HCD state structure for the DWC OTG controller.
* @param _qh QH for the periodic transfer.
*/
static void deschedule_periodic(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
{
list_del_init(&_qh->qh_list_entry);
/* Release the periodic channel reservation. */
_hcd->periodic_channels--;
/* Update claimed usecs per (micro)frame. */
_hcd->periodic_usecs -= _qh->usecs;
/* Update average periodic bandwidth claimed and # periodic reqs for usbfs. */
hcd_to_bus(dwc_otg_hcd_to_hcd(_hcd))->bandwidth_allocated -= _qh->usecs / _qh->interval;
if (_qh->ep_type == USB_ENDPOINT_XFER_INT) {
hcd_to_bus(dwc_otg_hcd_to_hcd(_hcd))->bandwidth_int_reqs--;
DWC_DEBUGPL(DBG_HCD, "Descheduled intr: qh %p, usecs %d, period %d\n",
_qh, _qh->usecs, _qh->interval);
} else {
hcd_to_bus(dwc_otg_hcd_to_hcd(_hcd))->bandwidth_isoc_reqs--;
DWC_DEBUGPL(DBG_HCD, "Descheduled isoc: qh %p, usecs %d, period %d\n",
_qh, _qh->usecs, _qh->interval);
}
}
/**
* Removes a QH from either the non-periodic or periodic schedule. Memory is
* not freed.
*
* @param[in] _hcd The HCD state structure.
* @param[in] _qh QH to remove from schedule. */
void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
{
unsigned long flags;
local_irq_save(flags);
if (list_empty(&_qh->qh_list_entry)) {
/* QH is not in a schedule. */
goto done;
}
if (dwc_qh_is_non_per(_qh)) {
if (_hcd->non_periodic_qh_ptr == &_qh->qh_list_entry) {
_hcd->non_periodic_qh_ptr = _hcd->non_periodic_qh_ptr->next;
}
list_del_init(&_qh->qh_list_entry);
} else {
deschedule_periodic(_hcd, _qh);
}
done:
local_irq_restore(flags);
}
/**
* Deactivates a QH. For non-periodic QHs, removes the QH from the active
* non-periodic schedule. The QH is added to the inactive non-periodic
* schedule if any QTDs are still attached to the QH.
*
* For periodic QHs, the QH is removed from the periodic queued schedule. If
* there are any QTDs still attached to the QH, the QH is added to either the
* periodic inactive schedule or the periodic ready schedule and its next
* scheduled frame is calculated. The QH is placed in the ready schedule if
* the scheduled frame has been reached already. Otherwise it's placed in the
* inactive schedule. If there are no QTDs attached to the QH, the QH is
* completely removed from the periodic schedule.
*/
void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh,
int sched_next_periodic_split)
{
unsigned long flags;
local_irq_save(flags);
if (dwc_qh_is_non_per(_qh)) {
dwc_otg_hcd_qh_remove(_hcd, _qh);
if (!list_empty(&_qh->qtd_list)) {
/* Add back to inactive non-periodic schedule. */
dwc_otg_hcd_qh_add(_hcd, _qh);
}
} else {
uint16_t frame_number = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(_hcd));
if (_qh->do_split) {
/* Schedule the next continuing periodic split transfer */
if (sched_next_periodic_split) {
_qh->sched_frame = frame_number;
if (dwc_frame_num_le(frame_number,
dwc_frame_num_inc(_qh->start_split_frame,
1))) {
/*
* Allow one frame to elapse after start
* split microframe before scheduling
* complete split, but DONT if we are
* doing the next start split in the
* same frame for an ISOC out.
*/
if ((_qh->ep_type != USB_ENDPOINT_XFER_ISOC)
|| (_qh->ep_is_in != 0)) {
_qh->sched_frame =
dwc_frame_num_inc(_qh->sched_frame, 1);
}
}
} else {
_qh->sched_frame = dwc_frame_num_inc(_qh->start_split_frame,
_qh->interval);
if (dwc_frame_num_le(_qh->sched_frame, frame_number)) {
_qh->sched_frame = frame_number;
}
_qh->sched_frame |= 0x7;
_qh->start_split_frame = _qh->sched_frame;
}
} else {
_qh->sched_frame = dwc_frame_num_inc(_qh->sched_frame, _qh->interval);
if (dwc_frame_num_le(_qh->sched_frame, frame_number)) {
_qh->sched_frame = frame_number;
}
}
if (list_empty(&_qh->qtd_list)) {
dwc_otg_hcd_qh_remove(_hcd, _qh);
} else {
/*
* Remove from periodic_sched_queued and move to
* appropriate queue.
*/
if (_qh->sched_frame == frame_number) {
list_move(&_qh->qh_list_entry, &_hcd->periodic_sched_ready);
} else {
list_move(&_qh->qh_list_entry, &_hcd->periodic_sched_inactive);
}
}
}
local_irq_restore(flags);
}
#if 0
/** Allocates memory for a QTD structure.
* @return Returns the memory allocate or NULL on error. */
static inline dwc_otg_qtd_t *dwc_otg_hcd_qtd_alloc (void)
{
return (dwc_otg_qtd_t *) kmalloc (sizeof(dwc_otg_qtd_t), GFP_KERNEL);
}
#endif
/**
* Initializes a QTD structure.
*
* @param[in] _qtd The QTD to initialize.
* @param[in] _urb The URB to use for initialization. */
static void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * _qtd, struct urb *_urb)
{
memset(_qtd, 0, sizeof(dwc_otg_qtd_t));
_qtd->urb = _urb;
if (usb_pipecontrol(_urb->pipe)) {
/*
* The only time the QTD data toggle is used is on the data
* phase of control transfers. This phase always starts with
* DATA1.
*/
_qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
_qtd->control_phase = DWC_OTG_CONTROL_SETUP;
}
/* start split */
_qtd->complete_split = 0;
_qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
_qtd->isoc_split_offset = 0;
/* Store the qtd ptr in the urb to reference what QTD. */
_urb->hcpriv = _qtd;
return;
}
/**
* This function allocates and initializes a QTD.
*
* @param[in] _urb The URB to create a QTD from. Each URB-QTD pair will end up
* pointing to each other so each pair should have a unique correlation.
*
* @return Returns pointer to the newly allocated QTD, or NULL on error. */
dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(struct urb *_urb)
{
dwc_otg_qtd_t *qtd;
#if 0
qtd = dwc_otg_hcd_qtd_alloc();
#else
qtd = (dwc_otg_qtd_t *) kmalloc (sizeof(dwc_otg_qtd_t), GFP_KERNEL);
#endif
if (qtd == NULL) {
return NULL;
}
dwc_otg_hcd_qtd_init(qtd, _urb);
return qtd;
}
/**
* This function adds a QTD to the QTD-list of a QH. It will find the correct
* QH to place the QTD into. If it does not find a QH, then it will create a
* new QH. If the QH to which the QTD is added is not currently scheduled, it
* is placed into the proper schedule based on its EP type.
*
* @param[in] _qtd The QTD to add
* @param[in] _dwc_otg_hcd The DWC HCD structure
*
* @return 0 if successful, negative error code otherwise.
*/
int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * _qtd, dwc_otg_hcd_t * _dwc_otg_hcd)
{
struct usb_host_endpoint *ep;
dwc_otg_qh_t *qh;
unsigned long flags;
int retval = 0;
struct urb *urb = _qtd->urb;
local_irq_save(flags);
/*
* Get the QH which holds the QTD-list to insert to. Create QH if it
* doesn't exist.
*/
ep = dwc_urb_to_endpoint(urb);
qh = (dwc_otg_qh_t *) ep->hcpriv;
if (qh == NULL) {
qh = dwc_otg_hcd_qh_create(_dwc_otg_hcd, urb);
if (qh == NULL) {
goto done;
}
ep->hcpriv = qh;
}
retval = dwc_otg_hcd_qh_add(_dwc_otg_hcd, qh);
if (retval == 0) {
list_add_tail(&_qtd->qtd_list_entry, &qh->qtd_list);
}
done:
local_irq_restore(flags);
return retval;
}
#endif /* DWC_DEVICE_ONLY */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,230 @@
/* ==========================================================================
* $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_pcd.h $
* $Revision: 1.1 $
* $Date: 2008/03/31 00:20:10 $
* $Change: 1000263 $
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#ifndef DWC_HOST_ONLY
#if !defined(__DWC_PCD_H__)
#define __DWC_PCD_H__
#include <linux/types.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/device.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
# include <linux/usb/ch9.h>
#else
# include <linux/usb_ch9.h>
#endif
#include <linux/usb_gadget.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
struct lm_device;
struct dwc_otg_device;
#include "dwc_otg_cil.h"
/**
* @file
*
* This file contains the structures, constants, and interfaces for
* the Perpherial Contoller Driver (PCD).
*
* The Peripheral Controller Driver (PCD) for Linux will implement the
* Gadget API, so that the existing Gadget drivers can be used. For
* the Mass Storage Function driver the File-backed USB Storage Gadget
* (FBS) driver will be used. The FBS driver supports the
* Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only
* transports.
*
*/
/** Invalid DMA Address */
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
/** Maxpacket size for EP0 */
#define MAX_EP0_SIZE 64
/** Maxpacket size for any EP */
#define MAX_PACKET_SIZE 1024
/**
* Get the pointer to the core_if from the pcd pointer.
*/
#define GET_CORE_IF( _pcd ) (_pcd->otg_dev->core_if)
/**
* States of EP0.
*/
typedef enum ep0_state
{
EP0_DISCONNECT, /* no host */
EP0_IDLE,
EP0_IN_DATA_PHASE,
EP0_OUT_DATA_PHASE,
EP0_IN_STATUS_PHASE,
EP0_OUT_STATUS_PHASE,
EP0_STALL,
} ep0state_e;
/** Fordward declaration.*/
struct dwc_otg_pcd;
/** DWC_otg iso request structure.
*
*/
typedef struct usb_iso_request dwc_otg_pcd_iso_request_t;
/** PCD EP structure.
* This structure describes an EP, there is an array of EPs in the PCD
* structure.
*/
typedef struct dwc_otg_pcd_ep
{
/** USB EP data */
struct usb_ep ep;
/** USB EP Descriptor */
const struct usb_endpoint_descriptor *desc;
/** queue of dwc_otg_pcd_requests. */
struct list_head queue;
unsigned stopped : 1;
unsigned disabling : 1;
unsigned dma : 1;
unsigned queue_sof : 1;
/** DWC_otg Isochronous Transfer */
// dwc_otg_pcd_iso_request_t* iso_req;
struct usb_iso_request* iso_req;
/** DWC_otg ep data. */
dwc_ep_t dwc_ep;
/** Pointer to PCD */
struct dwc_otg_pcd *pcd;
}dwc_otg_pcd_ep_t;
/** DWC_otg PCD Structure.
* This structure encapsulates the data for the dwc_otg PCD.
*/
typedef struct dwc_otg_pcd
{
/** USB gadget */
struct usb_gadget gadget;
/** USB gadget driver pointer*/
struct usb_gadget_driver *driver;
/** The DWC otg device pointer. */
struct dwc_otg_device *otg_dev;
/** State of EP0 */
ep0state_e ep0state;
/** EP0 Request is pending */
unsigned ep0_pending : 1;
/** Indicates when SET CONFIGURATION Request is in process */
unsigned request_config : 1;
/** The state of the Remote Wakeup Enable. */
unsigned remote_wakeup_enable : 1;
/** The state of the B-Device HNP Enable. */
unsigned b_hnp_enable : 1;
/** The state of A-Device HNP Support. */
unsigned a_hnp_support : 1;
/** The state of the A-Device Alt HNP support. */
unsigned a_alt_hnp_support : 1;
/** Count of pending Requests */
unsigned request_pending;
/** SETUP packet for EP0
* This structure is allocated as a DMA buffer on PCD initialization
* with enough space for up to 3 setup packets.
*/
union
{
struct usb_ctrlrequest req;
uint32_t d32[2];
} *setup_pkt;
dma_addr_t setup_pkt_dma_handle;
/** 2-byte dma buffer used to return status from GET_STATUS */
uint16_t *status_buf;
dma_addr_t status_buf_dma_handle;
/** EP0 */
dwc_otg_pcd_ep_t ep0;
/** Array of IN EPs. */
dwc_otg_pcd_ep_t in_ep[ MAX_EPS_CHANNELS - 1];
/** Array of OUT EPs. */
dwc_otg_pcd_ep_t out_ep[ MAX_EPS_CHANNELS - 1];
/** number of valid EPs in the above array. */
// unsigned num_eps : 4;
spinlock_t lock;
/** Timer for SRP. If it expires before SRP is successful
* clear the SRP. */
struct timer_list srp_timer;
/** Tasklet to defer starting of TEST mode transmissions until
* Status Phase has been completed.
*/
struct tasklet_struct test_mode_tasklet;
/** Tasklet to delay starting of xfer in DMA mode */
struct tasklet_struct *start_xfer_tasklet;
/** The test mode to enter when the tasklet is executed. */
unsigned test_mode;
} dwc_otg_pcd_t;
/** DWC_otg request structure.
* This structure is a list of requests.
*/
typedef struct
{
struct usb_request req; /**< USB Request. */
struct list_head queue; /**< queue of these requests. */
} dwc_otg_pcd_request_t;
extern int dwc_otg_pcd_init(struct lm_device *_lmdev);
//extern void dwc_otg_pcd_remove( struct dwc_otg_device *_otg_dev );
extern void dwc_otg_pcd_remove( struct lm_device *_lmdev );
extern int32_t dwc_otg_pcd_handle_intr( dwc_otg_pcd_t *_pcd );
extern void dwc_otg_pcd_start_srp_timer(dwc_otg_pcd_t *_pcd );
extern void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t *_pcd);
extern void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t *_pcd, int set);
#endif
#endif /* DWC_HOST_ONLY */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
/* ==========================================================================
*
* Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
* "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
* otherwise expressly agreed to in writing between Synopsys and you.
*
* The Software IS NOT an item of Licensed Software or Licensed Product under
* any End User Software License Agreement or Agreement for Licensed Product
* with Synopsys or any supplement thereto. You are permitted to use and
* redistribute this Software in source and binary forms, with or without
* modification, provided that redistributions of source code must retain this
* notice. You may not view, use, disclose, copy or distribute this file or
* any information contained herein except pursuant to this license grant from
* Synopsys. If you do not agree with this notice, including the disclaimer
* below, then you are not authorized to use the Software.
*
* THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* ========================================================================== */
#if !defined(__DWC_OTG_PLAT_H__)
#define __DWC_OTG_PLAT_H__
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/arch/regs-usb-otg-hs.h>
#define DWC_HOST_ONLY 1
#define dbg_otg(...)
/**
* Reads the content of a register.
*
* @param _reg address of register to read.
* @return contents of the register.
*
* Usage:<br>
* <code>uint32_t dev_ctl = dwc_read_reg32(&dev_regs->dctl);</code>
*/
static __inline__ uint32_t dwc_read_reg32( volatile uint32_t *_reg)
{
return readl(_reg);
};
/**
* Writes a register with a 32 bit value.
*
* @param _reg address of register to read.
* @param _value to write to _reg.
*
* Usage:<br>
* <code>dwc_write_reg32(&dev_regs->dctl, 0); </code>
*/
static __inline__ void dwc_write_reg32( volatile uint32_t *_reg, const uint32_t _value)
{
writel( _value, _reg );
};
/**
* This function modifies bit values in a register. Using the
* algorithm: (reg_contents & ~clear_mask) | set_mask.
*
* @param _reg address of register to read.
* @param _clear_mask bit mask to be cleared.
* @param _set_mask bit mask to be set.
*
* Usage:<br>
* <code> // Clear the SOF Interrupt Mask bit and <br>
* // set the OTG Interrupt mask bit, leaving all others as they were.
* dwc_modify_reg32(&dev_regs->gintmsk, DWC_SOF_INT, DWC_OTG_INT);</code>
*/
static __inline__
void dwc_modify_reg32( volatile uint32_t *_reg, const uint32_t _clear_mask, const uint32_t _set_mask)
{
writel( (readl(_reg) & ~_clear_mask) | _set_mask, _reg );
};
/*
* Debugging support vanishes in non-debug builds.
*/
/**
* The Debug Level bit-mask variable.
*/
extern uint32_t g_dbg_lvl;
/**
* Set the Debug Level variable.
*/
static inline uint32_t SET_DEBUG_LEVEL( const uint32_t _new )
{
uint32_t old = g_dbg_lvl;
g_dbg_lvl = _new;
return old;
}
/** When debug level has the DBG_CIL bit set, display CIL Debug messages. */
#define DBG_CIL (0x2)
/** When debug level has the DBG_CILV bit set, display CIL Verbose debug
* messages */
#define DBG_CILV (0x20)
/** When debug level has the DBG_PCD bit set, display PCD (Device) debug
* messages */
#define DBG_PCD (0x4)
/** When debug level has the DBG_PCDV set, display PCD (Device) Verbose debug
* messages */
#define DBG_PCDV (0x40)
/** When debug level has the DBG_HCD bit set, display Host debug messages */
#define DBG_HCD (0x8)
/** When debug level has the DBG_HCDV bit set, display Verbose Host debug
* messages */
#define DBG_HCDV (0x80)
/** When debug level has the DBG_HCD_URB bit set, display enqueued URBs in host
* mode. */
#define DBG_HCD_URB (0x800)
/** When debug level has any bit set, display debug messages */
#define DBG_ANY (0xFF)
/** All debug messages off */
#define DBG_OFF 0
/** Prefix string for DWC_DEBUG print macros. */
#define USB_DWC "DWC_otg: "
/**
* Print a debug message when the Global debug level variable contains
* the bit defined in <code>lvl</code>.
*
* @param[in] lvl - Debug level, use one of the DBG_ constants above.
* @param[in] x - like printf
*
* Example:<p>
* <code>
* DWC_DEBUGPL( DBG_ANY, "%s(%p)\n", __func__, _reg_base_addr);
* </code>
* <br>
* results in:<br>
* <code>
* usb-DWC_otg: dwc_otg_cil_init(ca867000)
* </code>
*/
#ifdef DEBUG
# define DWC_DEBUGPL(lvl, x...) do{ if ((lvl)&g_dbg_lvl)printk( KERN_DEBUG USB_DWC x ); }while(0)
# define DWC_DEBUGP(x...) DWC_DEBUGPL(DBG_ANY, x )
# define CHK_DEBUG_LEVEL(level) ((level) & g_dbg_lvl)
#else
# define DWC_DEBUGPL(lvl, x...) do{}while(0)
# define DWC_DEBUGP(x...)
# define CHK_DEBUG_LEVEL(level) (0)
#endif /*DEBUG*/
/**
* Print an Error message.
*/
#define DWC_ERROR(x...) printk( KERN_ERR USB_DWC x )
/**
* Print a Warning message.
*/
#define DWC_WARN(x...) printk( KERN_WARNING USB_DWC x )
/**
* Print a notice (normal but significant message).
*/
#define DWC_NOTICE(x...) printk( KERN_NOTICE USB_DWC x )
/**
* Basic message printing.
*/
#define DWC_PRINT(x...) printk( KERN_INFO USB_DWC x )
#endif

File diff suppressed because it is too large Load Diff