Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
232
drivers/usb/host/Kconfig
Normal file
232
drivers/usb/host/Kconfig
Normal 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
19
drivers/usb/host/Makefile
Normal 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/
|
||||
279
drivers/usb/host/ehci-au1xxx.c
Normal file
279
drivers/usb/host/ehci-au1xxx.c
Normal 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
805
drivers/usb/host/ehci-dbg.c
Normal 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
337
drivers/usb/host/ehci-fsl.c
Normal 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",
|
||||
},
|
||||
};
|
||||
37
drivers/usb/host/ehci-fsl.h
Normal file
37
drivers/usb/host/ehci-fsl.h
Normal 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
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
752
drivers/usb/host/ehci-hub.c
Normal 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
231
drivers/usb/host/ehci-mem.c
Normal 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
397
drivers/usb/host/ehci-pci.c
Normal 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
193
drivers/usb/host/ehci-ps3.c
Normal 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
1116
drivers/usb/host/ehci-q.c
Normal file
File diff suppressed because it is too large
Load Diff
2227
drivers/usb/host/ehci-sched.c
Normal file
2227
drivers/usb/host/ehci-sched.c
Normal file
File diff suppressed because it is too large
Load Diff
707
drivers/usb/host/ehci.h
Normal file
707
drivers/usb/host/ehci.h
Normal 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 */
|
||||
4550
drivers/usb/host/hc_crisv10.c
Normal file
4550
drivers/usb/host/hc_crisv10.c
Normal file
File diff suppressed because it is too large
Load Diff
289
drivers/usb/host/hc_crisv10.h
Normal file
289
drivers/usb/host/hc_crisv10.h
Normal file
@@ -0,0 +1,289 @@
|
||||
#ifndef __LINUX_ETRAX_USB_H
|
||||
#define __LINUX_ETRAX_USB_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
typedef struct USB_IN_Desc {
|
||||
volatile __u16 sw_len;
|
||||
volatile __u16 command;
|
||||
volatile unsigned long next;
|
||||
volatile unsigned long buf;
|
||||
volatile __u16 hw_len;
|
||||
volatile __u16 status;
|
||||
} USB_IN_Desc_t;
|
||||
|
||||
typedef struct USB_SB_Desc {
|
||||
volatile __u16 sw_len;
|
||||
volatile __u16 command;
|
||||
volatile unsigned long next;
|
||||
volatile unsigned long buf;
|
||||
__u32 dummy;
|
||||
} USB_SB_Desc_t;
|
||||
|
||||
typedef struct USB_EP_Desc {
|
||||
volatile __u16 hw_len;
|
||||
volatile __u16 command;
|
||||
volatile unsigned long sub;
|
||||
volatile unsigned long next;
|
||||
__u32 dummy;
|
||||
} USB_EP_Desc_t;
|
||||
|
||||
struct virt_root_hub {
|
||||
int devnum;
|
||||
void *urb;
|
||||
void *int_addr;
|
||||
int send;
|
||||
int interval;
|
||||
int numports;
|
||||
struct timer_list rh_int_timer;
|
||||
volatile __u16 wPortChange_1;
|
||||
volatile __u16 wPortChange_2;
|
||||
volatile __u16 prev_wPortStatus_1;
|
||||
volatile __u16 prev_wPortStatus_2;
|
||||
};
|
||||
|
||||
struct etrax_usb_intr_traffic {
|
||||
int sleeping;
|
||||
int error;
|
||||
struct wait_queue *wq;
|
||||
};
|
||||
|
||||
typedef struct etrax_usb_hc {
|
||||
struct usb_bus *bus;
|
||||
struct virt_root_hub rh;
|
||||
struct etrax_usb_intr_traffic intr;
|
||||
} etrax_hc_t;
|
||||
|
||||
typedef enum {
|
||||
STARTED,
|
||||
NOT_STARTED,
|
||||
UNLINK,
|
||||
TRANSFER_DONE,
|
||||
WAITING_FOR_DESCR_INTR
|
||||
} etrax_usb_urb_state_t;
|
||||
|
||||
|
||||
|
||||
typedef struct etrax_usb_urb_priv {
|
||||
/* The first_sb field is used for freeing all SB descriptors belonging
|
||||
to an urb. The corresponding ep descriptor's sub pointer cannot be
|
||||
used for this since the DMA advances the sub pointer as it processes
|
||||
the sb list. */
|
||||
USB_SB_Desc_t *first_sb;
|
||||
/* The last_sb field referes to the last SB descriptor that belongs to
|
||||
this urb. This is important to know so we can free the SB descriptors
|
||||
that ranges between first_sb and last_sb. */
|
||||
USB_SB_Desc_t *last_sb;
|
||||
|
||||
/* The rx_offset field is used in ctrl and bulk traffic to keep track
|
||||
of the offset in the urb's transfer_buffer where incoming data should be
|
||||
copied to. */
|
||||
__u32 rx_offset;
|
||||
|
||||
/* Counter used in isochronous transfers to keep track of the
|
||||
number of packets received/transmitted. */
|
||||
__u32 isoc_packet_counter;
|
||||
|
||||
/* This field is used to pass information about the urb's current state between
|
||||
the various interrupt handlers (thus marked volatile). */
|
||||
volatile etrax_usb_urb_state_t urb_state;
|
||||
|
||||
/* Connection between the submitted urb and ETRAX epid number */
|
||||
__u8 epid;
|
||||
|
||||
/* The rx_data_list field is used for periodic traffic, to hold
|
||||
received data for later processing in the the complete_urb functions,
|
||||
where the data us copied to the urb's transfer_buffer. Basically, we
|
||||
use this intermediate storage because we don't know when it's safe to
|
||||
reuse the transfer_buffer (FIXME?). */
|
||||
struct list_head rx_data_list;
|
||||
} etrax_urb_priv_t;
|
||||
|
||||
/* This struct is for passing data from the top half to the bottom half. */
|
||||
typedef struct usb_interrupt_registers
|
||||
{
|
||||
etrax_hc_t *hc;
|
||||
__u32 r_usb_epid_attn;
|
||||
__u8 r_usb_status;
|
||||
__u16 r_usb_rh_port_status_1;
|
||||
__u16 r_usb_rh_port_status_2;
|
||||
__u32 r_usb_irq_mask_read;
|
||||
__u32 r_usb_fm_number;
|
||||
struct work_struct usb_bh;
|
||||
} usb_interrupt_registers_t;
|
||||
|
||||
/* This struct is for passing data from the isoc top half to the isoc bottom half. */
|
||||
typedef struct usb_isoc_complete_data
|
||||
{
|
||||
struct urb *urb;
|
||||
struct work_struct usb_bh;
|
||||
} usb_isoc_complete_data_t;
|
||||
|
||||
/* This struct holds data we get from the rx descriptors for DMA channel 9
|
||||
for periodic traffic (intr and isoc). */
|
||||
typedef struct rx_data
|
||||
{
|
||||
void *data;
|
||||
int length;
|
||||
struct list_head list;
|
||||
} rx_data_t;
|
||||
|
||||
typedef struct urb_entry
|
||||
{
|
||||
struct urb *urb;
|
||||
struct list_head list;
|
||||
} urb_entry_t;
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Virtual Root HUB
|
||||
------------------------------------------------------------------------- */
|
||||
/* destination of request */
|
||||
#define RH_INTERFACE 0x01
|
||||
#define RH_ENDPOINT 0x02
|
||||
#define RH_OTHER 0x03
|
||||
|
||||
#define RH_CLASS 0x20
|
||||
#define RH_VENDOR 0x40
|
||||
|
||||
/* Requests: bRequest << 8 | bmRequestType */
|
||||
#define RH_GET_STATUS 0x0080
|
||||
#define RH_CLEAR_FEATURE 0x0100
|
||||
#define RH_SET_FEATURE 0x0300
|
||||
#define RH_SET_ADDRESS 0x0500
|
||||
#define RH_GET_DESCRIPTOR 0x0680
|
||||
#define RH_SET_DESCRIPTOR 0x0700
|
||||
#define RH_GET_CONFIGURATION 0x0880
|
||||
#define RH_SET_CONFIGURATION 0x0900
|
||||
#define RH_GET_STATE 0x0280
|
||||
#define RH_GET_INTERFACE 0x0A80
|
||||
#define RH_SET_INTERFACE 0x0B00
|
||||
#define RH_SYNC_FRAME 0x0C80
|
||||
/* Our Vendor Specific Request */
|
||||
#define RH_SET_EP 0x2000
|
||||
|
||||
|
||||
/* Hub port features */
|
||||
#define RH_PORT_CONNECTION 0x00
|
||||
#define RH_PORT_ENABLE 0x01
|
||||
#define RH_PORT_SUSPEND 0x02
|
||||
#define RH_PORT_OVER_CURRENT 0x03
|
||||
#define RH_PORT_RESET 0x04
|
||||
#define RH_PORT_POWER 0x08
|
||||
#define RH_PORT_LOW_SPEED 0x09
|
||||
#define RH_C_PORT_CONNECTION 0x10
|
||||
#define RH_C_PORT_ENABLE 0x11
|
||||
#define RH_C_PORT_SUSPEND 0x12
|
||||
#define RH_C_PORT_OVER_CURRENT 0x13
|
||||
#define RH_C_PORT_RESET 0x14
|
||||
|
||||
/* Hub features */
|
||||
#define RH_C_HUB_LOCAL_POWER 0x00
|
||||
#define RH_C_HUB_OVER_CURRENT 0x01
|
||||
|
||||
#define RH_DEVICE_REMOTE_WAKEUP 0x00
|
||||
#define RH_ENDPOINT_STALL 0x01
|
||||
|
||||
/* Our Vendor Specific feature */
|
||||
#define RH_REMOVE_EP 0x00
|
||||
|
||||
|
||||
#define RH_ACK 0x01
|
||||
#define RH_REQ_ERR -1
|
||||
#define RH_NACK 0x00
|
||||
|
||||
/* Field definitions for */
|
||||
|
||||
#define USB_IN_command__eol__BITNR 0 /* command macros */
|
||||
#define USB_IN_command__eol__WIDTH 1
|
||||
#define USB_IN_command__eol__no 0
|
||||
#define USB_IN_command__eol__yes 1
|
||||
|
||||
#define USB_IN_command__intr__BITNR 3
|
||||
#define USB_IN_command__intr__WIDTH 1
|
||||
#define USB_IN_command__intr__no 0
|
||||
#define USB_IN_command__intr__yes 1
|
||||
|
||||
#define USB_IN_status__eop__BITNR 1 /* status macros. */
|
||||
#define USB_IN_status__eop__WIDTH 1
|
||||
#define USB_IN_status__eop__no 0
|
||||
#define USB_IN_status__eop__yes 1
|
||||
|
||||
#define USB_IN_status__eot__BITNR 5
|
||||
#define USB_IN_status__eot__WIDTH 1
|
||||
#define USB_IN_status__eot__no 0
|
||||
#define USB_IN_status__eot__yes 1
|
||||
|
||||
#define USB_IN_status__error__BITNR 6
|
||||
#define USB_IN_status__error__WIDTH 1
|
||||
#define USB_IN_status__error__no 0
|
||||
#define USB_IN_status__error__yes 1
|
||||
|
||||
#define USB_IN_status__nodata__BITNR 7
|
||||
#define USB_IN_status__nodata__WIDTH 1
|
||||
#define USB_IN_status__nodata__no 0
|
||||
#define USB_IN_status__nodata__yes 1
|
||||
|
||||
#define USB_IN_status__epid__BITNR 8
|
||||
#define USB_IN_status__epid__WIDTH 5
|
||||
|
||||
#define USB_EP_command__eol__BITNR 0
|
||||
#define USB_EP_command__eol__WIDTH 1
|
||||
#define USB_EP_command__eol__no 0
|
||||
#define USB_EP_command__eol__yes 1
|
||||
|
||||
#define USB_EP_command__eof__BITNR 1
|
||||
#define USB_EP_command__eof__WIDTH 1
|
||||
#define USB_EP_command__eof__no 0
|
||||
#define USB_EP_command__eof__yes 1
|
||||
|
||||
#define USB_EP_command__intr__BITNR 3
|
||||
#define USB_EP_command__intr__WIDTH 1
|
||||
#define USB_EP_command__intr__no 0
|
||||
#define USB_EP_command__intr__yes 1
|
||||
|
||||
#define USB_EP_command__enable__BITNR 4
|
||||
#define USB_EP_command__enable__WIDTH 1
|
||||
#define USB_EP_command__enable__no 0
|
||||
#define USB_EP_command__enable__yes 1
|
||||
|
||||
#define USB_EP_command__hw_valid__BITNR 5
|
||||
#define USB_EP_command__hw_valid__WIDTH 1
|
||||
#define USB_EP_command__hw_valid__no 0
|
||||
#define USB_EP_command__hw_valid__yes 1
|
||||
|
||||
#define USB_EP_command__epid__BITNR 8
|
||||
#define USB_EP_command__epid__WIDTH 5
|
||||
|
||||
#define USB_SB_command__eol__BITNR 0 /* command macros. */
|
||||
#define USB_SB_command__eol__WIDTH 1
|
||||
#define USB_SB_command__eol__no 0
|
||||
#define USB_SB_command__eol__yes 1
|
||||
|
||||
#define USB_SB_command__eot__BITNR 1
|
||||
#define USB_SB_command__eot__WIDTH 1
|
||||
#define USB_SB_command__eot__no 0
|
||||
#define USB_SB_command__eot__yes 1
|
||||
|
||||
#define USB_SB_command__intr__BITNR 3
|
||||
#define USB_SB_command__intr__WIDTH 1
|
||||
#define USB_SB_command__intr__no 0
|
||||
#define USB_SB_command__intr__yes 1
|
||||
|
||||
#define USB_SB_command__tt__BITNR 4
|
||||
#define USB_SB_command__tt__WIDTH 2
|
||||
#define USB_SB_command__tt__zout 0
|
||||
#define USB_SB_command__tt__in 1
|
||||
#define USB_SB_command__tt__out 2
|
||||
#define USB_SB_command__tt__setup 3
|
||||
|
||||
|
||||
#define USB_SB_command__rem__BITNR 8
|
||||
#define USB_SB_command__rem__WIDTH 6
|
||||
|
||||
#define USB_SB_command__full__BITNR 6
|
||||
#define USB_SB_command__full__WIDTH 1
|
||||
#define USB_SB_command__full__no 0
|
||||
#define USB_SB_command__full__yes 1
|
||||
|
||||
#endif
|
||||
1741
drivers/usb/host/isp116x-hcd.c
Normal file
1741
drivers/usb/host/isp116x-hcd.c
Normal file
File diff suppressed because it is too large
Load Diff
607
drivers/usb/host/isp116x.h
Normal file
607
drivers/usb/host/isp116x.h
Normal 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) */
|
||||
340
drivers/usb/host/ohci-at91.c
Normal file
340
drivers/usb/host/ohci-at91.c
Normal 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, ®s->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, ®s->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,
|
||||
},
|
||||
};
|
||||
|
||||
347
drivers/usb/host/ohci-au1xxx.c
Normal file
347
drivers/usb/host/ohci-au1xxx.c
Normal 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
708
drivers/usb/host/ohci-dbg.c
Normal 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, ®s->revision) & 0xff;
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"OHCI %d.%d, %s legacy support registers\n",
|
||||
0x03 & (temp >> 4), (temp & 0x0f),
|
||||
(temp & 0x0100) ? "with" : "NO");
|
||||
|
||||
temp = ohci_readl (controller, ®s->control);
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"control 0x%03x%s%s%s HCFS=%s%s%s%s%s CBSR=%d\n",
|
||||
temp,
|
||||
(temp & OHCI_CTRL_RWE) ? " RWE" : "",
|
||||
(temp & OHCI_CTRL_RWC) ? " RWC" : "",
|
||||
(temp & OHCI_CTRL_IR) ? " IR" : "",
|
||||
hcfs2string (temp & OHCI_CTRL_HCFS),
|
||||
(temp & OHCI_CTRL_BLE) ? " BLE" : "",
|
||||
(temp & OHCI_CTRL_CLE) ? " CLE" : "",
|
||||
(temp & OHCI_CTRL_IE) ? " IE" : "",
|
||||
(temp & OHCI_CTRL_PLE) ? " PLE" : "",
|
||||
temp & OHCI_CTRL_CBSR
|
||||
);
|
||||
|
||||
temp = ohci_readl (controller, ®s->cmdstatus);
|
||||
ohci_dbg_sw (controller, next, size,
|
||||
"cmdstatus 0x%05x SOC=%d%s%s%s%s\n", temp,
|
||||
(temp & OHCI_SOC) >> 16,
|
||||
(temp & OHCI_OCR) ? " OCR" : "",
|
||||
(temp & OHCI_BLF) ? " BLF" : "",
|
||||
(temp & OHCI_CLF) ? " CLF" : "",
|
||||
(temp & OHCI_HCR) ? " HCR" : ""
|
||||
);
|
||||
|
||||
ohci_dump_intr_mask (controller, "intrstatus",
|
||||
ohci_readl (controller, ®s->intrstatus),
|
||||
next, size);
|
||||
ohci_dump_intr_mask (controller, "intrenable",
|
||||
ohci_readl (controller, ®s->intrenable),
|
||||
next, size);
|
||||
// intrdisable always same as intrenable
|
||||
|
||||
maybe_print_eds (controller, "ed_periodcurrent",
|
||||
ohci_readl (controller, ®s->ed_periodcurrent),
|
||||
next, size);
|
||||
|
||||
maybe_print_eds (controller, "ed_controlhead",
|
||||
ohci_readl (controller, ®s->ed_controlhead),
|
||||
next, size);
|
||||
maybe_print_eds (controller, "ed_controlcurrent",
|
||||
ohci_readl (controller, ®s->ed_controlcurrent),
|
||||
next, size);
|
||||
|
||||
maybe_print_eds (controller, "ed_bulkhead",
|
||||
ohci_readl (controller, ®s->ed_bulkhead),
|
||||
next, size);
|
||||
maybe_print_eds (controller, "ed_bulkcurrent",
|
||||
ohci_readl (controller, ®s->ed_bulkcurrent),
|
||||
next, size);
|
||||
|
||||
maybe_print_eds (controller, "donehead",
|
||||
ohci_readl (controller, ®s->donehead), next, size);
|
||||
}
|
||||
|
||||
#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, ®s->fminterval);
|
||||
temp = scnprintf (next, size,
|
||||
"fmintvl 0x%08x %sFSMPS=0x%04x FI=0x%04x\n",
|
||||
rdata, (rdata >> 31) ? "FIT " : "",
|
||||
(rdata >> 16) & 0xefff, rdata & 0xffff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
rdata = ohci_readl (ohci, ®s->fmremaining);
|
||||
temp = scnprintf (next, size, "fmremaining 0x%08x %sFR=0x%04x\n",
|
||||
rdata, (rdata >> 31) ? "FRT " : "",
|
||||
rdata & 0x3fff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
rdata = ohci_readl (ohci, ®s->periodicstart);
|
||||
temp = scnprintf (next, size, "periodicstart 0x%04x\n",
|
||||
rdata & 0x3fff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
rdata = ohci_readl (ohci, ®s->lsthresh);
|
||||
temp = scnprintf (next, size, "lsthresh 0x%04x\n",
|
||||
rdata & 0x3fff);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
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
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
216
drivers/usb/host/ohci-ep93xx.c
Normal file
216
drivers/usb/host/ohci-ep93xx.c
Normal 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
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
724
drivers/usb/host/ohci-hub.c
Normal 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;
|
||||
}
|
||||
|
||||
253
drivers/usb/host/ohci-lh7a404.c
Normal file
253
drivers/usb/host/ohci-lh7a404.c
Normal 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
138
drivers/usb/host/ohci-mem.c
Normal 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);
|
||||
}
|
||||
|
||||
546
drivers/usb/host/ohci-omap.c
Normal file
546
drivers/usb/host/ohci-omap.c
Normal 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
313
drivers/usb/host/ohci-pci.c
Normal 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,
|
||||
};
|
||||
|
||||
467
drivers/usb/host/ohci-pnx4008.c
Normal file
467
drivers/usb/host/ohci-pnx4008.c
Normal 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,
|
||||
};
|
||||
|
||||
242
drivers/usb/host/ohci-pnx8550.c
Normal file
242
drivers/usb/host/ohci-pnx8550.c
Normal 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,
|
||||
};
|
||||
|
||||
232
drivers/usb/host/ohci-ppc-of.c
Normal file
232
drivers/usb/host/ohci-ppc-of.c
Normal 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,
|
||||
},
|
||||
};
|
||||
|
||||
210
drivers/usb/host/ohci-ppc-soc.c
Normal file
210
drivers/usb/host/ohci-ppc-soc.c
Normal 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
196
drivers/usb/host/ohci-ps3.c
Normal 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,
|
||||
};
|
||||
371
drivers/usb/host/ohci-pxa27x.c
Normal file
371
drivers/usb/host/ohci-pxa27x.c
Normal 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
1117
drivers/usb/host/ohci-q.c
Normal file
File diff suppressed because it is too large
Load Diff
935
drivers/usb/host/ohci-s3c2410.c
Normal file
935
drivers/usb/host/ohci-s3c2410.c
Normal 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",
|
||||
},
|
||||
};
|
||||
|
||||
271
drivers/usb/host/ohci-sa1111.c
Normal file
271
drivers/usb/host/ohci-sa1111.c
Normal 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
687
drivers/usb/host/ohci.h
Normal 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); }
|
||||
352
drivers/usb/host/pci-quirks.c
Normal file
352
drivers/usb/host/pci-quirks.c
Normal 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);
|
||||
7
drivers/usb/host/pci-quirks.h
Normal file
7
drivers/usb/host/pci-quirks.h
Normal 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
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
266
drivers/usb/host/sl811.h
Normal file
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* SL811HS register declarations and HCD data structures
|
||||
*
|
||||
* Copyright (C) 2004 Psion Teklogix
|
||||
* Copyright (C) 2004 David Brownell
|
||||
* Copyright (C) 2001 Cypress Semiconductor Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
* SL811HS has transfer registers, and control registers. In host/master
|
||||
* mode one set of registers is used; in peripheral/slave mode, another.
|
||||
* - SL11H only has some "A" transfer registers from 0x00-0x04
|
||||
* - SL811HS also has "B" registers from 0x08-0x0c
|
||||
* - SL811S (or HS in slave mode) has four A+B sets, at 00, 10, 20, 30
|
||||
*/
|
||||
|
||||
#define SL811_EP_A(base) ((base) + 0)
|
||||
#define SL811_EP_B(base) ((base) + 8)
|
||||
|
||||
#define SL811_HOST_BUF 0x00
|
||||
#define SL811_PERIPH_EP0 0x00
|
||||
#define SL811_PERIPH_EP1 0x10
|
||||
#define SL811_PERIPH_EP2 0x20
|
||||
#define SL811_PERIPH_EP3 0x30
|
||||
|
||||
|
||||
/* TRANSFER REGISTERS: host and peripheral sides are similar
|
||||
* except for the control models (master vs slave).
|
||||
*/
|
||||
#define SL11H_HOSTCTLREG 0
|
||||
# define SL11H_HCTLMASK_ARM 0x01
|
||||
# define SL11H_HCTLMASK_ENABLE 0x02
|
||||
# define SL11H_HCTLMASK_IN 0x00
|
||||
# define SL11H_HCTLMASK_OUT 0x04
|
||||
# define SL11H_HCTLMASK_ISOCH 0x10
|
||||
# define SL11H_HCTLMASK_AFTERSOF 0x20
|
||||
# define SL11H_HCTLMASK_TOGGLE 0x40
|
||||
# define SL11H_HCTLMASK_PREAMBLE 0x80
|
||||
#define SL11H_BUFADDRREG 1
|
||||
#define SL11H_BUFLNTHREG 2
|
||||
#define SL11H_PKTSTATREG 3 /* read */
|
||||
# define SL11H_STATMASK_ACK 0x01
|
||||
# define SL11H_STATMASK_ERROR 0x02
|
||||
# define SL11H_STATMASK_TMOUT 0x04
|
||||
# define SL11H_STATMASK_SEQ 0x08
|
||||
# define SL11H_STATMASK_SETUP 0x10
|
||||
# define SL11H_STATMASK_OVF 0x20
|
||||
# define SL11H_STATMASK_NAK 0x40
|
||||
# define SL11H_STATMASK_STALL 0x80
|
||||
#define SL11H_PIDEPREG 3 /* write */
|
||||
# define SL_SETUP 0xd0
|
||||
# define SL_IN 0x90
|
||||
# define SL_OUT 0x10
|
||||
# define SL_SOF 0x50
|
||||
# define SL_PREAMBLE 0xc0
|
||||
# define SL_NAK 0xa0
|
||||
# define SL_STALL 0xe0
|
||||
# define SL_DATA0 0x30
|
||||
# define SL_DATA1 0xb0
|
||||
#define SL11H_XFERCNTREG 4 /* read */
|
||||
#define SL11H_DEVADDRREG 4 /* write */
|
||||
|
||||
|
||||
/* CONTROL REGISTERS: host and peripheral are very different.
|
||||
*/
|
||||
#define SL11H_CTLREG1 5
|
||||
# define SL11H_CTL1MASK_SOF_ENA 0x01
|
||||
# define SL11H_CTL1MASK_FORCE 0x18
|
||||
# define SL11H_CTL1MASK_NORMAL 0x00
|
||||
# define SL11H_CTL1MASK_SE0 0x08 /* reset */
|
||||
# define SL11H_CTL1MASK_J 0x10
|
||||
# define SL11H_CTL1MASK_K 0x18 /* resume */
|
||||
# define SL11H_CTL1MASK_LSPD 0x20
|
||||
# define SL11H_CTL1MASK_SUSPEND 0x40
|
||||
#define SL11H_IRQ_ENABLE 6
|
||||
# define SL11H_INTMASK_DONE_A 0x01
|
||||
# define SL11H_INTMASK_DONE_B 0x02
|
||||
# define SL11H_INTMASK_SOFINTR 0x10
|
||||
# define SL11H_INTMASK_INSRMV 0x20 /* to/from SE0 */
|
||||
# define SL11H_INTMASK_RD 0x40
|
||||
# define SL11H_INTMASK_DP 0x80 /* only in INTSTATREG */
|
||||
#define SL11S_ADDRESS 7
|
||||
|
||||
/* 0x08-0x0c are for the B buffer (not in SL11) */
|
||||
|
||||
#define SL11H_IRQ_STATUS 0x0D /* write to ack */
|
||||
#define SL11H_HWREVREG 0x0E /* read */
|
||||
# define SL11H_HWRMASK_HWREV 0xF0
|
||||
#define SL11H_SOFLOWREG 0x0E /* write */
|
||||
#define SL11H_SOFTMRREG 0x0F /* read */
|
||||
|
||||
/* a write to this register enables SL811HS features.
|
||||
* HOST flag presumably overrides the chip input signal?
|
||||
*/
|
||||
#define SL811HS_CTLREG2 0x0F
|
||||
# define SL811HS_CTL2MASK_SOF_MASK 0x3F
|
||||
# define SL811HS_CTL2MASK_DSWAP 0x40
|
||||
# define SL811HS_CTL2MASK_HOST 0x80
|
||||
|
||||
#define SL811HS_CTL2_INIT (SL811HS_CTL2MASK_HOST | 0x2e)
|
||||
|
||||
|
||||
/* DATA BUFFERS: registers from 0x10..0xff are for data buffers;
|
||||
* that's 240 bytes, which we'll split evenly between A and B sides.
|
||||
* Only ISO can use more than 64 bytes per packet.
|
||||
* (The SL11S has 0x40..0xff for buffers.)
|
||||
*/
|
||||
#define H_MAXPACKET 120 /* bytes in A or B fifos */
|
||||
|
||||
#define SL11H_DATA_START 0x10
|
||||
#define SL811HS_PACKET_BUF(is_a) ((is_a) \
|
||||
? SL11H_DATA_START \
|
||||
: (SL11H_DATA_START + H_MAXPACKET))
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */
|
||||
#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE)
|
||||
|
||||
struct sl811 {
|
||||
spinlock_t lock;
|
||||
void __iomem *addr_reg;
|
||||
void __iomem *data_reg;
|
||||
struct sl811_platform_data *board;
|
||||
struct proc_dir_entry *pde;
|
||||
|
||||
unsigned long stat_insrmv;
|
||||
unsigned long stat_wake;
|
||||
unsigned long stat_sof;
|
||||
unsigned long stat_a;
|
||||
unsigned long stat_b;
|
||||
unsigned long stat_lost;
|
||||
unsigned long stat_overrun;
|
||||
|
||||
/* sw model */
|
||||
struct timer_list timer;
|
||||
struct sl811h_ep *next_periodic;
|
||||
struct sl811h_ep *next_async;
|
||||
|
||||
struct sl811h_ep *active_a;
|
||||
unsigned long jiffies_a;
|
||||
struct sl811h_ep *active_b;
|
||||
unsigned long jiffies_b;
|
||||
|
||||
u32 port1;
|
||||
u8 ctrl1, ctrl2, irq_enable;
|
||||
u16 frame;
|
||||
|
||||
/* async schedule: control, bulk */
|
||||
struct list_head async;
|
||||
|
||||
/* periodic schedule: interrupt, iso */
|
||||
u16 load[PERIODIC_SIZE];
|
||||
struct sl811h_ep *periodic[PERIODIC_SIZE];
|
||||
unsigned periodic_count;
|
||||
};
|
||||
|
||||
static inline struct sl811 *hcd_to_sl811(struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct sl811 *) (hcd->hcd_priv);
|
||||
}
|
||||
|
||||
static inline struct usb_hcd *sl811_to_hcd(struct sl811 *sl811)
|
||||
{
|
||||
return container_of((void *) sl811, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
struct sl811h_ep {
|
||||
struct usb_host_endpoint *hep;
|
||||
struct usb_device *udev;
|
||||
|
||||
u8 defctrl;
|
||||
u8 maxpacket;
|
||||
u8 epnum;
|
||||
u8 nextpid;
|
||||
|
||||
u16 error_count;
|
||||
u16 nak_count;
|
||||
u16 length; /* of current packet */
|
||||
|
||||
/* periodic schedule */
|
||||
u16 period;
|
||||
u16 branch;
|
||||
u16 load;
|
||||
struct sl811h_ep *next;
|
||||
|
||||
/* async schedule */
|
||||
struct list_head schedule;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* These register utilities should work for the SL811S register API too
|
||||
* NOTE: caller must hold sl811->lock.
|
||||
*/
|
||||
|
||||
static inline u8 sl811_read(struct sl811 *sl811, int reg)
|
||||
{
|
||||
writeb(reg, sl811->addr_reg);
|
||||
return readb(sl811->data_reg);
|
||||
}
|
||||
|
||||
static inline void sl811_write(struct sl811 *sl811, int reg, u8 val)
|
||||
{
|
||||
writeb(reg, sl811->addr_reg);
|
||||
writeb(val, sl811->data_reg);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sl811_write_buf(struct sl811 *sl811, int addr, const void *buf, size_t count)
|
||||
{
|
||||
const u8 *data;
|
||||
void __iomem *data_reg;
|
||||
|
||||
if (!count)
|
||||
return;
|
||||
writeb(addr, sl811->addr_reg);
|
||||
|
||||
data = buf;
|
||||
data_reg = sl811->data_reg;
|
||||
do {
|
||||
writeb(*data++, data_reg);
|
||||
} while (--count);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sl811_read_buf(struct sl811 *sl811, int addr, void *buf, size_t count)
|
||||
{
|
||||
u8 *data;
|
||||
void __iomem *data_reg;
|
||||
|
||||
if (!count)
|
||||
return;
|
||||
writeb(addr, sl811->addr_reg);
|
||||
|
||||
data = buf;
|
||||
data_reg = sl811->data_reg;
|
||||
do {
|
||||
*data++ = readb(data_reg);
|
||||
} while (--count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(stuff...) printk(KERN_DEBUG "sl811: " stuff)
|
||||
#else
|
||||
#define DBG(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#ifdef VERBOSE
|
||||
# define VDBG DBG
|
||||
#else
|
||||
# define VDBG(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#ifdef PACKET_TRACE
|
||||
# define PACKET VDBG
|
||||
#else
|
||||
# define PACKET(stuff...) do{}while(0)
|
||||
#endif
|
||||
|
||||
#define ERR(stuff...) printk(KERN_ERR "sl811: " stuff)
|
||||
#define WARN(stuff...) printk(KERN_WARNING "sl811: " stuff)
|
||||
#define INFO(stuff...) printk(KERN_INFO "sl811: " stuff)
|
||||
|
||||
327
drivers/usb/host/sl811_cs.c
Normal file
327
drivers/usb/host/sl811_cs.c
Normal 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
3248
drivers/usb/host/u132-hcd.c
Normal file
File diff suppressed because it is too large
Load Diff
592
drivers/usb/host/uhci-debug.c
Normal file
592
drivers/usb/host/uhci-debug.c
Normal 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
974
drivers/usb/host/uhci-hcd.c
Normal 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
480
drivers/usb/host/uhci-hcd.h
Normal 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
414
drivers/usb/host/uhci-hub.c
Normal 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
1752
drivers/usb/host/uhci-q.c
Normal file
File diff suppressed because it is too large
Load Diff
15
drivers/usb/host/usb_otg/Makefile
Normal file
15
drivers/usb/host/usb_otg/Makefile
Normal 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
|
||||
1575
drivers/usb/host/usb_otg/dummy_audio.c
Normal file
1575
drivers/usb/host/usb_otg/dummy_audio.c
Normal file
File diff suppressed because it is too large
Load Diff
1077
drivers/usb/host/usb_otg/dwc_otg_attr.c
Normal file
1077
drivers/usb/host/usb_otg/dwc_otg_attr.c
Normal file
File diff suppressed because it is too large
Load Diff
67
drivers/usb/host/usb_otg/dwc_otg_attr.h
Normal file
67
drivers/usb/host/usb_otg/dwc_otg_attr.h
Normal 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
|
||||
3228
drivers/usb/host/usb_otg/dwc_otg_cil.c
Normal file
3228
drivers/usb/host/usb_otg/dwc_otg_cil.c
Normal file
File diff suppressed because it is too large
Load Diff
960
drivers/usb/host/usb_otg/dwc_otg_cil.h
Normal file
960
drivers/usb/host/usb_otg/dwc_otg_cil.h
Normal 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
|
||||
721
drivers/usb/host/usb_otg/dwc_otg_cil_intr.c
Normal file
721
drivers/usb/host/usb_otg/dwc_otg_cil_intr.c
Normal 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;
|
||||
}
|
||||
|
||||
1395
drivers/usb/host/usb_otg/dwc_otg_driver.c
Normal file
1395
drivers/usb/host/usb_otg/dwc_otg_driver.c
Normal file
File diff suppressed because it is too large
Load Diff
72
drivers/usb/host/usb_otg/dwc_otg_driver.h
Normal file
72
drivers/usb/host/usb_otg/dwc_otg_driver.h
Normal 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
|
||||
2810
drivers/usb/host/usb_otg/dwc_otg_hcd.c
Normal file
2810
drivers/usb/host/usb_otg/dwc_otg_hcd.c
Normal file
File diff suppressed because it is too large
Load Diff
645
drivers/usb/host/usb_otg/dwc_otg_hcd.h
Normal file
645
drivers/usb/host/usb_otg/dwc_otg_hcd.h
Normal 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 */
|
||||
1770
drivers/usb/host/usb_otg/dwc_otg_hcd_intr.c
Normal file
1770
drivers/usb/host/usb_otg/dwc_otg_hcd_intr.c
Normal file
File diff suppressed because it is too large
Load Diff
660
drivers/usb/host/usb_otg/dwc_otg_hcd_queue.c
Normal file
660
drivers/usb/host/usb_otg/dwc_otg_hcd_queue.c
Normal 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 */
|
||||
2154
drivers/usb/host/usb_otg/dwc_otg_pcd.c
Normal file
2154
drivers/usb/host/usb_otg/dwc_otg_pcd.c
Normal file
File diff suppressed because it is too large
Load Diff
230
drivers/usb/host/usb_otg/dwc_otg_pcd.h
Normal file
230
drivers/usb/host/usb_otg/dwc_otg_pcd.h
Normal 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 */
|
||||
3716
drivers/usb/host/usb_otg/dwc_otg_pcd_intr.c
Normal file
3716
drivers/usb/host/usb_otg/dwc_otg_pcd_intr.c
Normal file
File diff suppressed because it is too large
Load Diff
192
drivers/usb/host/usb_otg/dwc_otg_plat.h
Normal file
192
drivers/usb/host/usb_otg/dwc_otg_plat.h
Normal 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
|
||||
|
||||
1977
drivers/usb/host/usb_otg/dwc_otg_regs.h
Normal file
1977
drivers/usb/host/usb_otg/dwc_otg_regs.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user