mirror of
https://github.com/u-boot/u-boot.git
synced 2024-11-26 13:44:29 +08:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
2237 lines
58 KiB
C
2237 lines
58 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* URB OHCI HCD (Host Controller Driver) for USB on the AT91RM9200 and PCI bus.
|
|
*
|
|
* Interrupt support is added. Now, it has been tested
|
|
* on ULI1575 chip and works well with USB keyboard.
|
|
*
|
|
* (C) Copyright 2007
|
|
* Zhang Wei, Freescale Semiconductor, Inc. <wei.zhang@freescale.com>
|
|
*
|
|
* (C) Copyright 2003
|
|
* Gary Jennejohn, DENX Software Engineering <garyj@denx.de>
|
|
*
|
|
* Note: Much of this code has been derived from Linux 2.4
|
|
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
|
* (C) Copyright 2000-2002 David Brownell
|
|
*
|
|
* Modified for the MP2USB by (C) Copyright 2005 Eric Benard
|
|
* ebenard@eukrea.com - based on s3c24x0's driver
|
|
*/
|
|
/*
|
|
* IMPORTANT NOTES
|
|
* 1 - Read doc/README.generic_usb_ohci
|
|
* 2 - this driver is intended for use with USB Mass Storage Devices
|
|
* (BBB) and USB keyboard. There is NO support for Isochronous pipes!
|
|
* 2 - when running on a PQFP208 AT91RM9200, define CONFIG_AT91C_PQFP_UHPBUG
|
|
* to activate workaround for bug #41 or this driver will NOT work!
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <asm/byteorder.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
|
|
#if defined(CONFIG_PCI_OHCI)
|
|
# include <pci.h>
|
|
#if !defined(CONFIG_PCI_OHCI_DEVNO)
|
|
#define CONFIG_PCI_OHCI_DEVNO 0
|
|
#endif
|
|
#endif
|
|
|
|
#include <malloc.h>
|
|
#include <memalign.h>
|
|
#include <usb.h>
|
|
|
|
#include "ohci.h"
|
|
|
|
#ifdef CONFIG_AT91RM9200
|
|
#include <asm/arch/hardware.h> /* needed for AT91_USB_HOST_BASE */
|
|
#endif
|
|
|
|
#if defined(CONFIG_CPU_ARM920T) || \
|
|
defined(CONFIG_PCI_OHCI) || \
|
|
defined(CONFIG_SYS_OHCI_USE_NPS)
|
|
# define OHCI_USE_NPS /* force NoPowerSwitching mode */
|
|
#endif
|
|
|
|
#undef OHCI_VERBOSE_DEBUG /* not always helpful */
|
|
#undef DEBUG
|
|
#undef SHOW_INFO
|
|
#undef OHCI_FILL_TRACE
|
|
|
|
/* For initializing controller (mask in an HCFS mode too) */
|
|
#define OHCI_CONTROL_INIT \
|
|
(OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE
|
|
|
|
#ifdef CONFIG_PCI_OHCI
|
|
static struct pci_device_id ohci_pci_ids[] = {
|
|
{0x10b9, 0x5237}, /* ULI1575 PCI OHCI module ids */
|
|
{0x1033, 0x0035}, /* NEC PCI OHCI module ids */
|
|
{0x1131, 0x1561}, /* Philips 1561 PCI OHCI module ids */
|
|
/* Please add supported PCI OHCI controller ids here */
|
|
{0, 0}
|
|
};
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCI_EHCI_DEVNO
|
|
static struct pci_device_id ehci_pci_ids[] = {
|
|
{0x1131, 0x1562}, /* Philips 1562 PCI EHCI module ids */
|
|
/* Please add supported PCI EHCI controller ids here */
|
|
{0, 0}
|
|
};
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
#define dbg(format, arg...) printf("DEBUG: " format "\n", ## arg)
|
|
#else
|
|
#define dbg(format, arg...) do {} while (0)
|
|
#endif /* DEBUG */
|
|
#define err(format, arg...) printf("ERROR: " format "\n", ## arg)
|
|
#ifdef SHOW_INFO
|
|
#define info(format, arg...) printf("INFO: " format "\n", ## arg)
|
|
#else
|
|
#define info(format, arg...) do {} while (0)
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYS_OHCI_BE_CONTROLLER
|
|
# define m16_swap(x) cpu_to_be16(x)
|
|
# define m32_swap(x) cpu_to_be32(x)
|
|
#else
|
|
# define m16_swap(x) cpu_to_le16(x)
|
|
# define m32_swap(x) cpu_to_le32(x)
|
|
#endif /* CONFIG_SYS_OHCI_BE_CONTROLLER */
|
|
|
|
/* We really should do proper cache flushing everywhere */
|
|
#define flush_dcache_buffer(addr, size) \
|
|
flush_dcache_range((unsigned long)(addr), \
|
|
ALIGN((unsigned long)(addr) + size, ARCH_DMA_MINALIGN))
|
|
#define invalidate_dcache_buffer(addr, size) \
|
|
invalidate_dcache_range((unsigned long)(addr), \
|
|
ALIGN((unsigned long)(addr) + size, ARCH_DMA_MINALIGN))
|
|
|
|
/* Do not use sizeof(ed / td) as our ed / td structs contain extra members */
|
|
#define flush_dcache_ed(addr) flush_dcache_buffer(addr, 16)
|
|
#define flush_dcache_td(addr) flush_dcache_buffer(addr, 16)
|
|
#define flush_dcache_iso_td(addr) flush_dcache_buffer(addr, 32)
|
|
#define flush_dcache_hcca(addr) flush_dcache_buffer(addr, 256)
|
|
#define invalidate_dcache_ed(addr) invalidate_dcache_buffer(addr, 16)
|
|
#define invalidate_dcache_td(addr) invalidate_dcache_buffer(addr, 16)
|
|
#define invalidate_dcache_iso_td(addr) invalidate_dcache_buffer(addr, 32)
|
|
#define invalidate_dcache_hcca(addr) invalidate_dcache_buffer(addr, 256)
|
|
|
|
#ifdef CONFIG_DM_USB
|
|
/*
|
|
* The various ohci_mdelay(1) calls in the code seem unnecessary. We keep
|
|
* them around when building for older boards not yet converted to the dm
|
|
* just in case (to avoid regressions), for dm this turns them into nops.
|
|
*/
|
|
#define ohci_mdelay(x)
|
|
#else
|
|
#define ohci_mdelay(x) mdelay(x)
|
|
#endif
|
|
|
|
#ifndef CONFIG_DM_USB
|
|
/* global ohci_t */
|
|
static ohci_t gohci;
|
|
/* this must be aligned to a 256 byte boundary */
|
|
struct ohci_hcca ghcca[1];
|
|
#endif
|
|
|
|
/* mapping of the OHCI CC status to error codes */
|
|
static int cc_to_error[16] = {
|
|
/* No Error */ 0,
|
|
/* CRC Error */ USB_ST_CRC_ERR,
|
|
/* Bit Stuff */ USB_ST_BIT_ERR,
|
|
/* Data Togg */ USB_ST_CRC_ERR,
|
|
/* Stall */ USB_ST_STALLED,
|
|
/* DevNotResp */ -1,
|
|
/* PIDCheck */ USB_ST_BIT_ERR,
|
|
/* UnExpPID */ USB_ST_BIT_ERR,
|
|
/* DataOver */ USB_ST_BUF_ERR,
|
|
/* DataUnder */ USB_ST_BUF_ERR,
|
|
/* reservd */ -1,
|
|
/* reservd */ -1,
|
|
/* BufferOver */ USB_ST_BUF_ERR,
|
|
/* BuffUnder */ USB_ST_BUF_ERR,
|
|
/* Not Access */ -1,
|
|
/* Not Access */ -1
|
|
};
|
|
|
|
static const char *cc_to_string[16] = {
|
|
"No Error",
|
|
"CRC: Last data packet from endpoint contained a CRC error.",
|
|
"BITSTUFFING: Last data packet from endpoint contained a bit " \
|
|
"stuffing violation",
|
|
"DATATOGGLEMISMATCH: Last packet from endpoint had data toggle PID\n" \
|
|
"that did not match the expected value.",
|
|
"STALL: TD was moved to the Done Queue because the endpoint returned" \
|
|
" a STALL PID",
|
|
"DEVICENOTRESPONDING: Device did not respond to token (IN) or did\n" \
|
|
"not provide a handshake (OUT)",
|
|
"PIDCHECKFAILURE: Check bits on PID from endpoint failed on data PID\n"\
|
|
"(IN) or handshake (OUT)",
|
|
"UNEXPECTEDPID: Receive PID was not valid when encountered or PID\n" \
|
|
"value is not defined.",
|
|
"DATAOVERRUN: The amount of data returned by the endpoint exceeded\n" \
|
|
"either the size of the maximum data packet allowed\n" \
|
|
"from the endpoint (found in MaximumPacketSize field\n" \
|
|
"of ED) or the remaining buffer size.",
|
|
"DATAUNDERRUN: The endpoint returned less than MaximumPacketSize\n" \
|
|
"and that amount was not sufficient to fill the\n" \
|
|
"specified buffer",
|
|
"reserved1",
|
|
"reserved2",
|
|
"BUFFEROVERRUN: During an IN, HC received data from endpoint faster\n" \
|
|
"than it could be written to system memory",
|
|
"BUFFERUNDERRUN: During an OUT, HC could not retrieve data from\n" \
|
|
"system memory fast enough to keep up with data USB " \
|
|
"data rate.",
|
|
"NOT ACCESSED: This code is set by software before the TD is placed" \
|
|
"on a list to be processed by the HC.(1)",
|
|
"NOT ACCESSED: This code is set by software before the TD is placed" \
|
|
"on a list to be processed by the HC.(2)",
|
|
};
|
|
|
|
static inline u32 roothub_a(struct ohci *hc)
|
|
{ return ohci_readl(&hc->regs->roothub.a); }
|
|
static inline u32 roothub_b(struct ohci *hc)
|
|
{ return ohci_readl(&hc->regs->roothub.b); }
|
|
static inline u32 roothub_status(struct ohci *hc)
|
|
{ return ohci_readl(&hc->regs->roothub.status); }
|
|
static inline u32 roothub_portstatus(struct ohci *hc, int i)
|
|
{ return ohci_readl(&hc->regs->roothub.portstatus[i]); }
|
|
|
|
/* forward declaration */
|
|
static int hc_interrupt(ohci_t *ohci);
|
|
static void td_submit_job(ohci_t *ohci, struct usb_device *dev,
|
|
unsigned long pipe, void *buffer, int transfer_len,
|
|
struct devrequest *setup, urb_priv_t *urb,
|
|
int interval);
|
|
static int ep_link(ohci_t * ohci, ed_t * ed);
|
|
static int ep_unlink(ohci_t * ohci, ed_t * ed);
|
|
static ed_t *ep_add_ed(ohci_dev_t *ohci_dev, struct usb_device *usb_dev,
|
|
unsigned long pipe, int interval, int load);
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* TDs ... */
|
|
static struct td *td_alloc(ohci_dev_t *ohci_dev, struct usb_device *usb_dev)
|
|
{
|
|
int i;
|
|
struct td *td;
|
|
|
|
td = NULL;
|
|
for (i = 0; i < NUM_TD; i++)
|
|
{
|
|
if (ohci_dev->tds[i].usb_dev == NULL)
|
|
{
|
|
td = &ohci_dev->tds[i];
|
|
td->usb_dev = usb_dev;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return td;
|
|
}
|
|
|
|
static inline void ed_free(struct ed *ed)
|
|
{
|
|
ed->usb_dev = NULL;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* URB support functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* free HCD-private data associated with this URB */
|
|
|
|
static void urb_free_priv(urb_priv_t *urb)
|
|
{
|
|
int i;
|
|
int last;
|
|
struct td *td;
|
|
|
|
last = urb->length - 1;
|
|
if (last >= 0) {
|
|
for (i = 0; i <= last; i++) {
|
|
td = urb->td[i];
|
|
if (td) {
|
|
td->usb_dev = NULL;
|
|
urb->td[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
free(urb);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
#ifdef DEBUG
|
|
static int sohci_get_current_frame_number(ohci_t *ohci);
|
|
|
|
/* debug| print the main components of an URB
|
|
* small: 0) header + data packets 1) just header */
|
|
|
|
static void pkt_print(ohci_t *ohci, urb_priv_t *purb, struct usb_device *dev,
|
|
unsigned long pipe, void *buffer, int transfer_len,
|
|
struct devrequest *setup, char *str, int small)
|
|
{
|
|
dbg("%s URB:[%4x] dev:%2lu,ep:%2lu-%c,type:%s,len:%d/%d stat:%#lx",
|
|
str,
|
|
sohci_get_current_frame_number(ohci),
|
|
usb_pipedevice(pipe),
|
|
usb_pipeendpoint(pipe),
|
|
usb_pipeout(pipe)? 'O': 'I',
|
|
usb_pipetype(pipe) < 2 ? \
|
|
(usb_pipeint(pipe)? "INTR": "ISOC"): \
|
|
(usb_pipecontrol(pipe)? "CTRL": "BULK"),
|
|
(purb ? purb->actual_length : 0),
|
|
transfer_len, dev->status);
|
|
#ifdef OHCI_VERBOSE_DEBUG
|
|
if (!small) {
|
|
int i, len;
|
|
|
|
if (usb_pipecontrol(pipe)) {
|
|
printf(__FILE__ ": cmd(8):");
|
|
for (i = 0; i < 8 ; i++)
|
|
printf(" %02x", ((__u8 *) setup) [i]);
|
|
printf("\n");
|
|
}
|
|
if (transfer_len > 0 && buffer) {
|
|
printf(__FILE__ ": data(%d/%d):",
|
|
(purb ? purb->actual_length : 0),
|
|
transfer_len);
|
|
len = usb_pipeout(pipe)? transfer_len:
|
|
(purb ? purb->actual_length : 0);
|
|
for (i = 0; i < 16 && i < len; i++)
|
|
printf(" %02x", ((__u8 *) buffer) [i]);
|
|
printf("%s\n", i < len? "...": "");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* just for debugging; prints non-empty branches of the int ed tree
|
|
* inclusive iso eds */
|
|
void ep_print_int_eds(ohci_t *ohci, char *str)
|
|
{
|
|
int i, j;
|
|
__u32 *ed_p;
|
|
for (i = 0; i < 32; i++) {
|
|
j = 5;
|
|
ed_p = &(ohci->hcca->int_table [i]);
|
|
if (*ed_p == 0)
|
|
continue;
|
|
invalidate_dcache_ed(ed_p);
|
|
printf(__FILE__ ": %s branch int %2d(%2x):", str, i, i);
|
|
while (*ed_p != 0 && j--) {
|
|
ed_t *ed = (ed_t *)m32_swap(ed_p);
|
|
invalidate_dcache_ed(ed);
|
|
printf(" ed: %4x;", ed->hwINFO);
|
|
ed_p = &ed->hwNextED;
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
static void ohci_dump_intr_mask(char *label, __u32 mask)
|
|
{
|
|
dbg("%s: 0x%08x%s%s%s%s%s%s%s%s%s",
|
|
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(char *label, __u32 value)
|
|
{
|
|
ed_t *edp = (ed_t *)value;
|
|
|
|
if (value) {
|
|
dbg("%s %08x", label, value);
|
|
invalidate_dcache_ed(edp);
|
|
dbg("%08x", edp->hwINFO);
|
|
dbg("%08x", edp->hwTailP);
|
|
dbg("%08x", edp->hwHeadP);
|
|
dbg("%08x", edp->hwNextED);
|
|
}
|
|
}
|
|
|
|
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(ohci_t *controller)
|
|
{
|
|
struct ohci_regs *regs = controller->regs;
|
|
__u32 temp;
|
|
|
|
temp = ohci_readl(®s->revision) & 0xff;
|
|
if (temp != 0x10)
|
|
dbg("spec %d.%d", (temp >> 4), (temp & 0x0f));
|
|
|
|
temp = ohci_readl(®s->control);
|
|
dbg("control: 0x%08x%s%s%s HCFS=%s%s%s%s%s CBSR=%d", 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(®s->cmdstatus);
|
|
dbg("cmdstatus: 0x%08x SOC=%d%s%s%s%s", 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("intrstatus", ohci_readl(®s->intrstatus));
|
|
ohci_dump_intr_mask("intrenable", ohci_readl(®s->intrenable));
|
|
|
|
maybe_print_eds("ed_periodcurrent",
|
|
ohci_readl(®s->ed_periodcurrent));
|
|
|
|
maybe_print_eds("ed_controlhead", ohci_readl(®s->ed_controlhead));
|
|
maybe_print_eds("ed_controlcurrent",
|
|
ohci_readl(®s->ed_controlcurrent));
|
|
|
|
maybe_print_eds("ed_bulkhead", ohci_readl(®s->ed_bulkhead));
|
|
maybe_print_eds("ed_bulkcurrent", ohci_readl(®s->ed_bulkcurrent));
|
|
|
|
maybe_print_eds("donehead", ohci_readl(®s->donehead));
|
|
}
|
|
|
|
static void ohci_dump_roothub(ohci_t *controller, int verbose)
|
|
{
|
|
__u32 temp, ndp, i;
|
|
|
|
temp = roothub_a(controller);
|
|
ndp = (temp & RH_A_NDP);
|
|
#ifdef CONFIG_AT91C_PQFP_UHPBUG
|
|
ndp = (ndp == 2) ? 1:0;
|
|
#endif
|
|
if (verbose) {
|
|
dbg("roothub.a: %08x POTPGT=%d%s%s%s%s%s NDP=%d", temp,
|
|
((temp & RH_A_POTPGT) >> 24) & 0xff,
|
|
(temp & RH_A_NOCP) ? " NOCP" : "",
|
|
(temp & RH_A_OCPM) ? " OCPM" : "",
|
|
(temp & RH_A_DT) ? " DT" : "",
|
|
(temp & RH_A_NPS) ? " NPS" : "",
|
|
(temp & RH_A_PSM) ? " PSM" : "",
|
|
ndp
|
|
);
|
|
temp = roothub_b(controller);
|
|
dbg("roothub.b: %08x PPCM=%04x DR=%04x",
|
|
temp,
|
|
(temp & RH_B_PPCM) >> 16,
|
|
(temp & RH_B_DR)
|
|
);
|
|
temp = roothub_status(controller);
|
|
dbg("roothub.status: %08x%s%s%s%s%s%s",
|
|
temp,
|
|
(temp & RH_HS_CRWE) ? " CRWE" : "",
|
|
(temp & RH_HS_OCIC) ? " OCIC" : "",
|
|
(temp & RH_HS_LPSC) ? " LPSC" : "",
|
|
(temp & RH_HS_DRWE) ? " DRWE" : "",
|
|
(temp & RH_HS_OCI) ? " OCI" : "",
|
|
(temp & RH_HS_LPS) ? " LPS" : ""
|
|
);
|
|
}
|
|
|
|
for (i = 0; i < ndp; i++) {
|
|
temp = roothub_portstatus(controller, i);
|
|
dbg("roothub.portstatus [%d] = 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s",
|
|
i,
|
|
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(ohci_t *controller, int verbose)
|
|
{
|
|
dbg("OHCI controller usb-%s state", controller->slot_name);
|
|
|
|
/* dumps some of the state we know about */
|
|
ohci_dump_status(controller);
|
|
if (verbose)
|
|
ep_print_int_eds(controller, "hcca");
|
|
invalidate_dcache_hcca(controller->hcca);
|
|
dbg("hcca frame #%04x", controller->hcca->frame_no);
|
|
ohci_dump_roothub(controller, 1);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Interface functions (URB)
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* get a transfer request */
|
|
|
|
int sohci_submit_job(ohci_t *ohci, ohci_dev_t *ohci_dev, urb_priv_t *urb,
|
|
struct devrequest *setup)
|
|
{
|
|
ed_t *ed;
|
|
urb_priv_t *purb_priv = urb;
|
|
int i, size = 0;
|
|
struct usb_device *dev = urb->dev;
|
|
unsigned long pipe = urb->pipe;
|
|
void *buffer = urb->transfer_buffer;
|
|
int transfer_len = urb->transfer_buffer_length;
|
|
int interval = urb->interval;
|
|
|
|
/* when controller's hung, permit only roothub cleanup attempts
|
|
* such as powering down ports */
|
|
if (ohci->disabled) {
|
|
err("sohci_submit_job: EPIPE");
|
|
return -1;
|
|
}
|
|
|
|
/* we're about to begin a new transaction here so mark the
|
|
* URB unfinished */
|
|
urb->finished = 0;
|
|
|
|
/* every endpoint has a ed, locate and fill it */
|
|
ed = ep_add_ed(ohci_dev, dev, pipe, interval, 1);
|
|
if (!ed) {
|
|
err("sohci_submit_job: ENOMEM");
|
|
return -1;
|
|
}
|
|
|
|
/* for the private part of the URB we need the number of TDs (size) */
|
|
switch (usb_pipetype(pipe)) {
|
|
case PIPE_BULK: /* one TD for every 4096 Byte */
|
|
size = (transfer_len - 1) / 4096 + 1;
|
|
break;
|
|
case PIPE_CONTROL:/* 1 TD for setup, 1 for ACK and 1 for every 4096 B */
|
|
size = (transfer_len == 0)? 2:
|
|
(transfer_len - 1) / 4096 + 3;
|
|
break;
|
|
case PIPE_INTERRUPT: /* 1 TD */
|
|
size = 1;
|
|
break;
|
|
}
|
|
|
|
ed->purb = urb;
|
|
|
|
if (size >= (N_URB_TD - 1)) {
|
|
err("need %d TDs, only have %d", size, N_URB_TD);
|
|
return -1;
|
|
}
|
|
purb_priv->pipe = pipe;
|
|
|
|
/* fill the private part of the URB */
|
|
purb_priv->length = size;
|
|
purb_priv->ed = ed;
|
|
purb_priv->actual_length = 0;
|
|
|
|
/* allocate the TDs */
|
|
/* note that td[0] was allocated in ep_add_ed */
|
|
for (i = 0; i < size; i++) {
|
|
purb_priv->td[i] = td_alloc(ohci_dev, dev);
|
|
if (!purb_priv->td[i]) {
|
|
purb_priv->length = i;
|
|
urb_free_priv(purb_priv);
|
|
err("sohci_submit_job: ENOMEM");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (ed->state == ED_NEW || (ed->state & ED_DEL)) {
|
|
urb_free_priv(purb_priv);
|
|
err("sohci_submit_job: EINVAL");
|
|
return -1;
|
|
}
|
|
|
|
/* link the ed into a chain if is not already */
|
|
if (ed->state != ED_OPER)
|
|
ep_link(ohci, ed);
|
|
|
|
/* fill the TDs and link it to the ed */
|
|
td_submit_job(ohci, dev, pipe, buffer, transfer_len,
|
|
setup, purb_priv, interval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
#ifdef DEBUG
|
|
/* tell us the current USB frame number */
|
|
static int sohci_get_current_frame_number(ohci_t *ohci)
|
|
{
|
|
invalidate_dcache_hcca(ohci->hcca);
|
|
return m16_swap(ohci->hcca->frame_no);
|
|
}
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* ED handling functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* search for the right branch to insert an interrupt ed into the int tree
|
|
* do some load ballancing;
|
|
* returns the branch and
|
|
* sets the interval to interval = 2^integer (ld (interval)) */
|
|
|
|
static int ep_int_ballance(ohci_t *ohci, int interval, int load)
|
|
{
|
|
int i, branch = 0;
|
|
|
|
/* search for the least loaded interrupt endpoint
|
|
* branch of all 32 branches
|
|
*/
|
|
for (i = 0; i < 32; i++)
|
|
if (ohci->ohci_int_load [branch] > ohci->ohci_int_load [i])
|
|
branch = i;
|
|
|
|
branch = branch % interval;
|
|
for (i = branch; i < 32; i += interval)
|
|
ohci->ohci_int_load [i] += load;
|
|
|
|
return branch;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* 2^int( ld (inter)) */
|
|
|
|
static int ep_2_n_interval(int inter)
|
|
{
|
|
int i;
|
|
for (i = 0; ((inter >> i) > 1) && (i < 5); i++);
|
|
return 1 << i;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* the int tree is a binary tree
|
|
* in order to process it sequentially the indexes of the branches have to
|
|
* be mapped the mapping reverses the bits of a word of num_bits length */
|
|
static int ep_rev(int num_bits, int word)
|
|
{
|
|
int i, wout = 0;
|
|
|
|
for (i = 0; i < num_bits; i++)
|
|
wout |= (((word >> i) & 1) << (num_bits - i - 1));
|
|
return wout;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* ED handling functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* link an ed into one of the HC chains */
|
|
|
|
static int ep_link(ohci_t *ohci, ed_t *edi)
|
|
{
|
|
volatile ed_t *ed = edi;
|
|
int int_branch;
|
|
int i;
|
|
int inter;
|
|
int interval;
|
|
int load;
|
|
__u32 *ed_p;
|
|
|
|
ed->state = ED_OPER;
|
|
ed->int_interval = 0;
|
|
|
|
switch (ed->type) {
|
|
case PIPE_CONTROL:
|
|
ed->hwNextED = 0;
|
|
flush_dcache_ed(ed);
|
|
if (ohci->ed_controltail == NULL)
|
|
ohci_writel((uintptr_t)ed, &ohci->regs->ed_controlhead);
|
|
else
|
|
ohci->ed_controltail->hwNextED =
|
|
m32_swap((unsigned long)ed);
|
|
|
|
ed->ed_prev = ohci->ed_controltail;
|
|
if (!ohci->ed_controltail && !ohci->ed_rm_list[0] &&
|
|
!ohci->ed_rm_list[1] && !ohci->sleeping) {
|
|
ohci->hc_control |= OHCI_CTRL_CLE;
|
|
ohci_writel(ohci->hc_control, &ohci->regs->control);
|
|
}
|
|
ohci->ed_controltail = edi;
|
|
break;
|
|
|
|
case PIPE_BULK:
|
|
ed->hwNextED = 0;
|
|
flush_dcache_ed(ed);
|
|
if (ohci->ed_bulktail == NULL)
|
|
ohci_writel((uintptr_t)ed, &ohci->regs->ed_bulkhead);
|
|
else
|
|
ohci->ed_bulktail->hwNextED =
|
|
m32_swap((unsigned long)ed);
|
|
|
|
ed->ed_prev = ohci->ed_bulktail;
|
|
if (!ohci->ed_bulktail && !ohci->ed_rm_list[0] &&
|
|
!ohci->ed_rm_list[1] && !ohci->sleeping) {
|
|
ohci->hc_control |= OHCI_CTRL_BLE;
|
|
ohci_writel(ohci->hc_control, &ohci->regs->control);
|
|
}
|
|
ohci->ed_bulktail = edi;
|
|
break;
|
|
|
|
case PIPE_INTERRUPT:
|
|
load = ed->int_load;
|
|
interval = ep_2_n_interval(ed->int_period);
|
|
ed->int_interval = interval;
|
|
int_branch = ep_int_ballance(ohci, interval, load);
|
|
ed->int_branch = int_branch;
|
|
|
|
for (i = 0; i < ep_rev(6, interval); i += inter) {
|
|
inter = 1;
|
|
for (ed_p = &(ohci->hcca->int_table[\
|
|
ep_rev(5, i) + int_branch]);
|
|
(*ed_p != 0) &&
|
|
(((ed_t *)ed_p)->int_interval >= interval);
|
|
ed_p = &(((ed_t *)ed_p)->hwNextED))
|
|
inter = ep_rev(6,
|
|
((ed_t *)ed_p)->int_interval);
|
|
ed->hwNextED = *ed_p;
|
|
flush_dcache_ed(ed);
|
|
*ed_p = m32_swap((unsigned long)ed);
|
|
flush_dcache_hcca(ohci->hcca);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* scan the periodic table to find and unlink this ED */
|
|
static void periodic_unlink(struct ohci *ohci, volatile struct ed *ed,
|
|
unsigned index, unsigned period)
|
|
{
|
|
__maybe_unused unsigned long aligned_ed_p;
|
|
|
|
for (; index < NUM_INTS; index += period) {
|
|
__u32 *ed_p = &ohci->hcca->int_table [index];
|
|
|
|
/* ED might have been unlinked through another path */
|
|
while (*ed_p != 0) {
|
|
if (((struct ed *)(uintptr_t)
|
|
m32_swap((unsigned long)ed_p)) == ed) {
|
|
*ed_p = ed->hwNextED;
|
|
aligned_ed_p = (unsigned long)ed_p;
|
|
aligned_ed_p &= ~(ARCH_DMA_MINALIGN - 1);
|
|
flush_dcache_range(aligned_ed_p,
|
|
aligned_ed_p + ARCH_DMA_MINALIGN);
|
|
break;
|
|
}
|
|
ed_p = &(((struct ed *)(uintptr_t)
|
|
m32_swap((unsigned long)ed_p))->hwNextED);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* unlink an ed from one of the HC chains.
|
|
* just the link to the ed is unlinked.
|
|
* the link from the ed still points to another operational ed or 0
|
|
* so the HC can eventually finish the processing of the unlinked ed */
|
|
|
|
static int ep_unlink(ohci_t *ohci, ed_t *edi)
|
|
{
|
|
volatile ed_t *ed = edi;
|
|
int i;
|
|
|
|
ed->hwINFO |= m32_swap(OHCI_ED_SKIP);
|
|
flush_dcache_ed(ed);
|
|
|
|
switch (ed->type) {
|
|
case PIPE_CONTROL:
|
|
if (ed->ed_prev == NULL) {
|
|
if (!ed->hwNextED) {
|
|
ohci->hc_control &= ~OHCI_CTRL_CLE;
|
|
ohci_writel(ohci->hc_control,
|
|
&ohci->regs->control);
|
|
}
|
|
ohci_writel(m32_swap(*((__u32 *)&ed->hwNextED)),
|
|
&ohci->regs->ed_controlhead);
|
|
} else {
|
|
ed->ed_prev->hwNextED = ed->hwNextED;
|
|
flush_dcache_ed(ed->ed_prev);
|
|
}
|
|
if (ohci->ed_controltail == ed) {
|
|
ohci->ed_controltail = ed->ed_prev;
|
|
} else {
|
|
((ed_t *)(uintptr_t)m32_swap(
|
|
*((__u32 *)&ed->hwNextED)))->ed_prev = ed->ed_prev;
|
|
}
|
|
break;
|
|
|
|
case PIPE_BULK:
|
|
if (ed->ed_prev == NULL) {
|
|
if (!ed->hwNextED) {
|
|
ohci->hc_control &= ~OHCI_CTRL_BLE;
|
|
ohci_writel(ohci->hc_control,
|
|
&ohci->regs->control);
|
|
}
|
|
ohci_writel(m32_swap(*((__u32 *)&ed->hwNextED)),
|
|
&ohci->regs->ed_bulkhead);
|
|
} else {
|
|
ed->ed_prev->hwNextED = ed->hwNextED;
|
|
flush_dcache_ed(ed->ed_prev);
|
|
}
|
|
if (ohci->ed_bulktail == ed) {
|
|
ohci->ed_bulktail = ed->ed_prev;
|
|
} else {
|
|
((ed_t *)(uintptr_t)m32_swap(
|
|
*((__u32 *)&ed->hwNextED)))->ed_prev = ed->ed_prev;
|
|
}
|
|
break;
|
|
|
|
case PIPE_INTERRUPT:
|
|
periodic_unlink(ohci, ed, 0, 1);
|
|
for (i = ed->int_branch; i < 32; i += ed->int_interval)
|
|
ohci->ohci_int_load[i] -= ed->int_load;
|
|
break;
|
|
}
|
|
ed->state = ED_UNLINK;
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* add/reinit an endpoint; this should be done once at the
|
|
* usb_set_configuration command, but the USB stack is a little bit
|
|
* stateless so we do it at every transaction if the state of the ed
|
|
* is ED_NEW then a dummy td is added and the state is changed to
|
|
* ED_UNLINK in all other cases the state is left unchanged the ed
|
|
* info fields are setted anyway even though most of them should not
|
|
* change
|
|
*/
|
|
static ed_t *ep_add_ed(ohci_dev_t *ohci_dev, struct usb_device *usb_dev,
|
|
unsigned long pipe, int interval, int load)
|
|
{
|
|
td_t *td;
|
|
ed_t *ed_ret;
|
|
volatile ed_t *ed;
|
|
|
|
ed = ed_ret = &ohci_dev->ed[(usb_pipeendpoint(pipe) << 1) |
|
|
(usb_pipecontrol(pipe)? 0: usb_pipeout(pipe))];
|
|
|
|
if ((ed->state & ED_DEL) || (ed->state & ED_URB_DEL)) {
|
|
err("ep_add_ed: pending delete");
|
|
/* pending delete request */
|
|
return NULL;
|
|
}
|
|
|
|
if (ed->state == ED_NEW) {
|
|
/* dummy td; end of td list for ed */
|
|
td = td_alloc(ohci_dev, usb_dev);
|
|
ed->hwTailP = m32_swap((unsigned long)td);
|
|
ed->hwHeadP = ed->hwTailP;
|
|
ed->state = ED_UNLINK;
|
|
ed->type = usb_pipetype(pipe);
|
|
ohci_dev->ed_cnt++;
|
|
}
|
|
|
|
ed->hwINFO = m32_swap(usb_pipedevice(pipe)
|
|
| usb_pipeendpoint(pipe) << 7
|
|
| (usb_pipeisoc(pipe)? 0x8000: 0)
|
|
| (usb_pipecontrol(pipe)? 0: \
|
|
(usb_pipeout(pipe)? 0x800: 0x1000))
|
|
| (usb_dev->speed == USB_SPEED_LOW) << 13
|
|
| usb_maxpacket(usb_dev, pipe) << 16);
|
|
|
|
if (ed->type == PIPE_INTERRUPT && ed->state == ED_UNLINK) {
|
|
ed->int_period = interval;
|
|
ed->int_load = load;
|
|
}
|
|
|
|
flush_dcache_ed(ed);
|
|
|
|
return ed_ret;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* TD handling functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */
|
|
|
|
static void td_fill(ohci_t *ohci, unsigned int info,
|
|
void *data, int len,
|
|
struct usb_device *dev, int index, urb_priv_t *urb_priv)
|
|
{
|
|
volatile td_t *td, *td_pt;
|
|
#ifdef OHCI_FILL_TRACE
|
|
int i;
|
|
#endif
|
|
|
|
if (index > urb_priv->length) {
|
|
err("index > length");
|
|
return;
|
|
}
|
|
/* use this td as the next dummy */
|
|
td_pt = urb_priv->td [index];
|
|
td_pt->hwNextTD = 0;
|
|
flush_dcache_td(td_pt);
|
|
|
|
/* fill the old dummy TD */
|
|
td = urb_priv->td [index] =
|
|
(td_t *)(uintptr_t)
|
|
(m32_swap(urb_priv->ed->hwTailP) & ~0xf);
|
|
|
|
td->ed = urb_priv->ed;
|
|
td->next_dl_td = NULL;
|
|
td->index = index;
|
|
td->data = (uintptr_t)data;
|
|
#ifdef OHCI_FILL_TRACE
|
|
if (usb_pipebulk(urb_priv->pipe) && usb_pipeout(urb_priv->pipe)) {
|
|
for (i = 0; i < len; i++)
|
|
printf("td->data[%d] %#2x ", i, ((unsigned char *)td->data)[i]);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
if (!len)
|
|
data = 0;
|
|
|
|
td->hwINFO = m32_swap(info);
|
|
td->hwCBP = m32_swap((unsigned long)data);
|
|
if (data)
|
|
td->hwBE = m32_swap((unsigned long)(data + len - 1));
|
|
else
|
|
td->hwBE = 0;
|
|
|
|
td->hwNextTD = m32_swap((unsigned long)td_pt);
|
|
flush_dcache_td(td);
|
|
|
|
/* append to queue */
|
|
td->ed->hwTailP = td->hwNextTD;
|
|
flush_dcache_ed(td->ed);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* prepare all TDs of a transfer */
|
|
|
|
static void td_submit_job(ohci_t *ohci, struct usb_device *dev,
|
|
unsigned long pipe, void *buffer, int transfer_len,
|
|
struct devrequest *setup, urb_priv_t *urb,
|
|
int interval)
|
|
{
|
|
int data_len = transfer_len;
|
|
void *data;
|
|
int cnt = 0;
|
|
__u32 info = 0;
|
|
unsigned int toggle = 0;
|
|
|
|
flush_dcache_buffer(buffer, data_len);
|
|
|
|
/* OHCI handles the DATA-toggles itself, we just use the USB-toggle
|
|
* bits for resetting */
|
|
if (usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe))) {
|
|
toggle = TD_T_TOGGLE;
|
|
} else {
|
|
toggle = TD_T_DATA0;
|
|
usb_settoggle(dev, usb_pipeendpoint(pipe),
|
|
usb_pipeout(pipe), 1);
|
|
}
|
|
urb->td_cnt = 0;
|
|
if (data_len)
|
|
data = buffer;
|
|
else
|
|
data = 0;
|
|
|
|
switch (usb_pipetype(pipe)) {
|
|
case PIPE_BULK:
|
|
info = usb_pipeout(pipe)?
|
|
TD_CC | TD_DP_OUT : TD_CC | TD_DP_IN ;
|
|
while (data_len > 4096) {
|
|
td_fill(ohci, info | (cnt? TD_T_TOGGLE:toggle),
|
|
data, 4096, dev, cnt, urb);
|
|
data += 4096; data_len -= 4096; cnt++;
|
|
}
|
|
info = usb_pipeout(pipe)?
|
|
TD_CC | TD_DP_OUT : TD_CC | TD_R | TD_DP_IN ;
|
|
td_fill(ohci, info | (cnt? TD_T_TOGGLE:toggle), data,
|
|
data_len, dev, cnt, urb);
|
|
cnt++;
|
|
|
|
if (!ohci->sleeping) {
|
|
/* start bulk list */
|
|
ohci_writel(OHCI_BLF, &ohci->regs->cmdstatus);
|
|
}
|
|
break;
|
|
|
|
case PIPE_CONTROL:
|
|
/* Setup phase */
|
|
info = TD_CC | TD_DP_SETUP | TD_T_DATA0;
|
|
flush_dcache_buffer(setup, 8);
|
|
td_fill(ohci, info, setup, 8, dev, cnt++, urb);
|
|
|
|
/* Optional Data phase */
|
|
if (data_len > 0) {
|
|
info = usb_pipeout(pipe)?
|
|
TD_CC | TD_R | TD_DP_OUT | TD_T_DATA1 :
|
|
TD_CC | TD_R | TD_DP_IN | TD_T_DATA1;
|
|
/* NOTE: mishandles transfers >8K, some >4K */
|
|
td_fill(ohci, info, data, data_len, dev, cnt++, urb);
|
|
}
|
|
|
|
/* Status phase */
|
|
info = (usb_pipeout(pipe) || data_len == 0) ?
|
|
TD_CC | TD_DP_IN | TD_T_DATA1:
|
|
TD_CC | TD_DP_OUT | TD_T_DATA1;
|
|
td_fill(ohci, info, data, 0, dev, cnt++, urb);
|
|
|
|
if (!ohci->sleeping) {
|
|
/* start Control list */
|
|
ohci_writel(OHCI_CLF, &ohci->regs->cmdstatus);
|
|
}
|
|
break;
|
|
|
|
case PIPE_INTERRUPT:
|
|
info = usb_pipeout(urb->pipe)?
|
|
TD_CC | TD_DP_OUT | toggle:
|
|
TD_CC | TD_R | TD_DP_IN | toggle;
|
|
td_fill(ohci, info, data, data_len, dev, cnt++, urb);
|
|
break;
|
|
}
|
|
if (urb->length != cnt)
|
|
dbg("TD LENGTH %d != CNT %d", urb->length, cnt);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Done List handling functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* calculate the transfer length and update the urb */
|
|
|
|
static void dl_transfer_length(td_t *td)
|
|
{
|
|
__u32 tdBE, tdCBP;
|
|
urb_priv_t *lurb_priv = td->ed->purb;
|
|
|
|
tdBE = m32_swap(td->hwBE);
|
|
tdCBP = m32_swap(td->hwCBP);
|
|
|
|
if (!(usb_pipecontrol(lurb_priv->pipe) &&
|
|
((td->index == 0) || (td->index == lurb_priv->length - 1)))) {
|
|
if (tdBE != 0) {
|
|
if (td->hwCBP == 0)
|
|
lurb_priv->actual_length += tdBE - td->data + 1;
|
|
else
|
|
lurb_priv->actual_length += tdCBP - td->data;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void check_status(td_t *td_list)
|
|
{
|
|
urb_priv_t *lurb_priv = td_list->ed->purb;
|
|
int urb_len = lurb_priv->length;
|
|
__u32 *phwHeadP = &td_list->ed->hwHeadP;
|
|
int cc;
|
|
|
|
cc = TD_CC_GET(m32_swap(td_list->hwINFO));
|
|
if (cc) {
|
|
err(" USB-error: %s (%x)", cc_to_string[cc], cc);
|
|
|
|
invalidate_dcache_ed(td_list->ed);
|
|
if (*phwHeadP & m32_swap(0x1)) {
|
|
if (lurb_priv &&
|
|
((td_list->index + 1) < urb_len)) {
|
|
*phwHeadP =
|
|
(lurb_priv->td[urb_len - 1]->hwNextTD &\
|
|
m32_swap(0xfffffff0)) |
|
|
(*phwHeadP & m32_swap(0x2));
|
|
|
|
lurb_priv->td_cnt += urb_len -
|
|
td_list->index - 1;
|
|
} else
|
|
*phwHeadP &= m32_swap(0xfffffff2);
|
|
flush_dcache_ed(td_list->ed);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* replies to the request have to be on a FIFO basis so
|
|
* we reverse the reversed done-list */
|
|
static td_t *dl_reverse_done_list(ohci_t *ohci)
|
|
{
|
|
uintptr_t td_list_hc;
|
|
td_t *td_rev = NULL;
|
|
td_t *td_list = NULL;
|
|
|
|
invalidate_dcache_hcca(ohci->hcca);
|
|
td_list_hc = m32_swap(ohci->hcca->done_head) & 0xfffffff0;
|
|
ohci->hcca->done_head = 0;
|
|
flush_dcache_hcca(ohci->hcca);
|
|
|
|
while (td_list_hc) {
|
|
td_list = (td_t *)td_list_hc;
|
|
invalidate_dcache_td(td_list);
|
|
check_status(td_list);
|
|
td_list->next_dl_td = td_rev;
|
|
td_rev = td_list;
|
|
td_list_hc = m32_swap(td_list->hwNextTD) & 0xfffffff0;
|
|
}
|
|
return td_list;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void finish_urb(ohci_t *ohci, urb_priv_t *urb, int status)
|
|
{
|
|
if ((status & (ED_OPER | ED_UNLINK)) && (urb->state != URB_DEL))
|
|
urb->finished = 1;
|
|
else
|
|
dbg("finish_urb: strange.., ED state %x, \n", status);
|
|
}
|
|
|
|
/*
|
|
* Used to take back a TD from the host controller. This would normally be
|
|
* called from within dl_done_list, however it may be called directly if the
|
|
* HC no longer sees the TD and it has not appeared on the donelist (after
|
|
* two frames). This bug has been observed on ZF Micro systems.
|
|
*/
|
|
static int takeback_td(ohci_t *ohci, td_t *td_list)
|
|
{
|
|
ed_t *ed;
|
|
int cc;
|
|
int stat = 0;
|
|
/* urb_t *urb; */
|
|
urb_priv_t *lurb_priv;
|
|
__u32 tdINFO, edHeadP, edTailP;
|
|
|
|
invalidate_dcache_td(td_list);
|
|
tdINFO = m32_swap(td_list->hwINFO);
|
|
|
|
ed = td_list->ed;
|
|
lurb_priv = ed->purb;
|
|
|
|
dl_transfer_length(td_list);
|
|
|
|
lurb_priv->td_cnt++;
|
|
|
|
/* error code of transfer */
|
|
cc = TD_CC_GET(tdINFO);
|
|
if (cc) {
|
|
err("USB-error: %s (%x)", cc_to_string[cc], cc);
|
|
stat = cc_to_error[cc];
|
|
}
|
|
|
|
/* see if this done list makes for all TD's of current URB,
|
|
* and mark the URB finished if so */
|
|
if (lurb_priv->td_cnt == lurb_priv->length)
|
|
finish_urb(ohci, lurb_priv, ed->state);
|
|
|
|
dbg("dl_done_list: processing TD %x, len %x\n",
|
|
lurb_priv->td_cnt, lurb_priv->length);
|
|
|
|
if (ed->state != ED_NEW && (!usb_pipeint(lurb_priv->pipe))) {
|
|
invalidate_dcache_ed(ed);
|
|
edHeadP = m32_swap(ed->hwHeadP) & 0xfffffff0;
|
|
edTailP = m32_swap(ed->hwTailP);
|
|
|
|
/* unlink eds if they are not busy */
|
|
if ((edHeadP == edTailP) && (ed->state == ED_OPER))
|
|
ep_unlink(ohci, ed);
|
|
}
|
|
return stat;
|
|
}
|
|
|
|
static int dl_done_list(ohci_t *ohci)
|
|
{
|
|
int stat = 0;
|
|
td_t *td_list = dl_reverse_done_list(ohci);
|
|
|
|
while (td_list) {
|
|
td_t *td_next = td_list->next_dl_td;
|
|
stat = takeback_td(ohci, td_list);
|
|
td_list = td_next;
|
|
}
|
|
return stat;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Virtual Root Hub
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
#include <usbroothubdes.h>
|
|
|
|
/* Hub class-specific descriptor is constructed dynamically */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
#define OK(x) len = (x); break
|
|
#ifdef DEBUG
|
|
#define WR_RH_STAT(x) {info("WR:status %#8x", (x)); ohci_writel((x), \
|
|
&ohci->regs->roothub.status); }
|
|
#define WR_RH_PORTSTAT(x) {info("WR:portstatus[%d] %#8x", wIndex-1, \
|
|
(x)); ohci_writel((x), &ohci->regs->roothub.portstatus[wIndex-1]); }
|
|
#else
|
|
#define WR_RH_STAT(x) ohci_writel((x), &ohci->regs->roothub.status)
|
|
#define WR_RH_PORTSTAT(x) ohci_writel((x), \
|
|
&ohci->regs->roothub.portstatus[wIndex-1])
|
|
#endif
|
|
#define RD_RH_STAT roothub_status(ohci)
|
|
#define RD_RH_PORTSTAT roothub_portstatus(ohci, wIndex-1)
|
|
|
|
/* request to virtual root hub */
|
|
|
|
int rh_check_port_status(ohci_t *controller)
|
|
{
|
|
__u32 temp, ndp, i;
|
|
int res;
|
|
|
|
res = -1;
|
|
temp = roothub_a(controller);
|
|
ndp = (temp & RH_A_NDP);
|
|
#ifdef CONFIG_AT91C_PQFP_UHPBUG
|
|
ndp = (ndp == 2) ? 1:0;
|
|
#endif
|
|
for (i = 0; i < ndp; i++) {
|
|
temp = roothub_portstatus(controller, i);
|
|
/* check for a device disconnect */
|
|
if (((temp & (RH_PS_PESC | RH_PS_CSC)) ==
|
|
(RH_PS_PESC | RH_PS_CSC)) &&
|
|
((temp & RH_PS_CCS) == 0)) {
|
|
res = i;
|
|
break;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int ohci_submit_rh_msg(ohci_t *ohci, struct usb_device *dev,
|
|
unsigned long pipe, void *buffer, int transfer_len,
|
|
struct devrequest *cmd)
|
|
{
|
|
void *data = buffer;
|
|
int leni = transfer_len;
|
|
int len = 0;
|
|
int stat = 0;
|
|
__u16 bmRType_bReq;
|
|
__u16 wValue;
|
|
__u16 wIndex;
|
|
__u16 wLength;
|
|
ALLOC_ALIGN_BUFFER(__u8, databuf, 16, sizeof(u32));
|
|
|
|
#ifdef DEBUG
|
|
pkt_print(ohci, NULL, dev, pipe, buffer, transfer_len,
|
|
cmd, "SUB(rh)", usb_pipein(pipe));
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
if (usb_pipeint(pipe)) {
|
|
info("Root-Hub submit IRQ: NOT implemented");
|
|
return 0;
|
|
}
|
|
|
|
bmRType_bReq = cmd->requesttype | (cmd->request << 8);
|
|
wValue = le16_to_cpu(cmd->value);
|
|
wIndex = le16_to_cpu(cmd->index);
|
|
wLength = le16_to_cpu(cmd->length);
|
|
|
|
info("Root-Hub: adr: %2x cmd(%1x): %08x %04x %04x %04x",
|
|
dev->devnum, 8, bmRType_bReq, wValue, wIndex, wLength);
|
|
|
|
switch (bmRType_bReq) {
|
|
/* Request Destination:
|
|
without flags: Device,
|
|
RH_INTERFACE: interface,
|
|
RH_ENDPOINT: endpoint,
|
|
RH_CLASS means HUB here,
|
|
RH_OTHER | RH_CLASS almost ever means HUB_PORT here
|
|
*/
|
|
|
|
case RH_GET_STATUS:
|
|
*(u16 *)databuf = cpu_to_le16(1);
|
|
OK(2);
|
|
case RH_GET_STATUS | RH_INTERFACE:
|
|
*(u16 *)databuf = cpu_to_le16(0);
|
|
OK(2);
|
|
case RH_GET_STATUS | RH_ENDPOINT:
|
|
*(u16 *)databuf = cpu_to_le16(0);
|
|
OK(2);
|
|
case RH_GET_STATUS | RH_CLASS:
|
|
*(u32 *)databuf = cpu_to_le32(
|
|
RD_RH_STAT & ~(RH_HS_CRWE | RH_HS_DRWE));
|
|
OK(4);
|
|
case RH_GET_STATUS | RH_OTHER | RH_CLASS:
|
|
*(u32 *)databuf = cpu_to_le32(RD_RH_PORTSTAT);
|
|
OK(4);
|
|
|
|
case RH_CLEAR_FEATURE | RH_ENDPOINT:
|
|
switch (wValue) {
|
|
case (RH_ENDPOINT_STALL):
|
|
OK(0);
|
|
}
|
|
break;
|
|
|
|
case RH_CLEAR_FEATURE | RH_CLASS:
|
|
switch (wValue) {
|
|
case RH_C_HUB_LOCAL_POWER:
|
|
OK(0);
|
|
case (RH_C_HUB_OVER_CURRENT):
|
|
WR_RH_STAT(RH_HS_OCIC);
|
|
OK(0);
|
|
}
|
|
break;
|
|
|
|
case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
|
|
switch (wValue) {
|
|
case (RH_PORT_ENABLE): WR_RH_PORTSTAT(RH_PS_CCS); OK(0);
|
|
case (RH_PORT_SUSPEND): WR_RH_PORTSTAT(RH_PS_POCI); OK(0);
|
|
case (RH_PORT_POWER): WR_RH_PORTSTAT(RH_PS_LSDA); OK(0);
|
|
case (RH_C_PORT_CONNECTION): WR_RH_PORTSTAT(RH_PS_CSC); OK(0);
|
|
case (RH_C_PORT_ENABLE): WR_RH_PORTSTAT(RH_PS_PESC); OK(0);
|
|
case (RH_C_PORT_SUSPEND): WR_RH_PORTSTAT(RH_PS_PSSC); OK(0);
|
|
case (RH_C_PORT_OVER_CURRENT):WR_RH_PORTSTAT(RH_PS_OCIC); OK(0);
|
|
case (RH_C_PORT_RESET): WR_RH_PORTSTAT(RH_PS_PRSC); OK(0);
|
|
}
|
|
break;
|
|
|
|
case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
|
|
switch (wValue) {
|
|
case (RH_PORT_SUSPEND):
|
|
WR_RH_PORTSTAT(RH_PS_PSS); OK(0);
|
|
case (RH_PORT_RESET): /* BUG IN HUP CODE *********/
|
|
if (RD_RH_PORTSTAT & RH_PS_CCS)
|
|
WR_RH_PORTSTAT(RH_PS_PRS);
|
|
OK(0);
|
|
case (RH_PORT_POWER):
|
|
WR_RH_PORTSTAT(RH_PS_PPS);
|
|
OK(0);
|
|
case (RH_PORT_ENABLE): /* BUG IN HUP CODE *********/
|
|
if (RD_RH_PORTSTAT & RH_PS_CCS)
|
|
WR_RH_PORTSTAT(RH_PS_PES);
|
|
OK(0);
|
|
}
|
|
break;
|
|
|
|
case RH_SET_ADDRESS:
|
|
ohci->rh.devnum = wValue;
|
|
OK(0);
|
|
|
|
case RH_GET_DESCRIPTOR:
|
|
switch ((wValue & 0xff00) >> 8) {
|
|
case (0x01): /* device descriptor */
|
|
len = min_t(unsigned int,
|
|
leni,
|
|
min_t(unsigned int,
|
|
sizeof(root_hub_dev_des),
|
|
wLength));
|
|
databuf = root_hub_dev_des; OK(len);
|
|
case (0x02): /* configuration descriptor */
|
|
len = min_t(unsigned int,
|
|
leni,
|
|
min_t(unsigned int,
|
|
sizeof(root_hub_config_des),
|
|
wLength));
|
|
databuf = root_hub_config_des; OK(len);
|
|
case (0x03): /* string descriptors */
|
|
if (wValue == 0x0300) {
|
|
len = min_t(unsigned int,
|
|
leni,
|
|
min_t(unsigned int,
|
|
sizeof(root_hub_str_index0),
|
|
wLength));
|
|
databuf = root_hub_str_index0;
|
|
OK(len);
|
|
}
|
|
if (wValue == 0x0301) {
|
|
len = min_t(unsigned int,
|
|
leni,
|
|
min_t(unsigned int,
|
|
sizeof(root_hub_str_index1),
|
|
wLength));
|
|
databuf = root_hub_str_index1;
|
|
OK(len);
|
|
}
|
|
default:
|
|
stat = USB_ST_STALLED;
|
|
}
|
|
break;
|
|
|
|
case RH_GET_DESCRIPTOR | RH_CLASS:
|
|
{
|
|
__u32 temp = roothub_a(ohci);
|
|
|
|
databuf[0] = 9; /* min length; */
|
|
databuf[1] = 0x29;
|
|
databuf[2] = temp & RH_A_NDP;
|
|
#ifdef CONFIG_AT91C_PQFP_UHPBUG
|
|
databuf[2] = (databuf[2] == 2) ? 1 : 0;
|
|
#endif
|
|
databuf[3] = 0;
|
|
if (temp & RH_A_PSM) /* per-port power switching? */
|
|
databuf[3] |= 0x1;
|
|
if (temp & RH_A_NOCP) /* no overcurrent reporting? */
|
|
databuf[3] |= 0x10;
|
|
else if (temp & RH_A_OCPM)/* per-port overcurrent reporting? */
|
|
databuf[3] |= 0x8;
|
|
|
|
databuf[4] = 0;
|
|
databuf[5] = (temp & RH_A_POTPGT) >> 24;
|
|
databuf[6] = 0;
|
|
temp = roothub_b(ohci);
|
|
databuf[7] = temp & RH_B_DR;
|
|
if (databuf[2] < 7) {
|
|
databuf[8] = 0xff;
|
|
} else {
|
|
databuf[0] += 2;
|
|
databuf[8] = (temp & RH_B_DR) >> 8;
|
|
databuf[10] = databuf[9] = 0xff;
|
|
}
|
|
|
|
len = min_t(unsigned int, leni,
|
|
min_t(unsigned int, databuf[0], wLength));
|
|
OK(len);
|
|
}
|
|
|
|
case RH_GET_CONFIGURATION:
|
|
databuf[0] = 0x01;
|
|
OK(1);
|
|
|
|
case RH_SET_CONFIGURATION:
|
|
WR_RH_STAT(0x10000);
|
|
OK(0);
|
|
|
|
default:
|
|
dbg("unsupported root hub command");
|
|
stat = USB_ST_STALLED;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
ohci_dump_roothub(ohci, 1);
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
|
|
len = min_t(int, len, leni);
|
|
if (data != databuf)
|
|
memcpy(data, databuf, len);
|
|
dev->act_len = len;
|
|
dev->status = stat;
|
|
|
|
#ifdef DEBUG
|
|
pkt_print(ohci, NULL, dev, pipe, buffer,
|
|
transfer_len, cmd, "RET(rh)", 0/*usb_pipein(pipe)*/);
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
|
|
return stat;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static ohci_dev_t *ohci_get_ohci_dev(ohci_t *ohci, int devnum, int intr)
|
|
{
|
|
int i;
|
|
|
|
if (!intr)
|
|
return &ohci->ohci_dev;
|
|
|
|
/* First see if we already have an ohci_dev for this dev. */
|
|
for (i = 0; i < NUM_INT_DEVS; i++) {
|
|
if (ohci->int_dev[i].devnum == devnum)
|
|
return &ohci->int_dev[i];
|
|
}
|
|
|
|
/* If not then find a free one. */
|
|
for (i = 0; i < NUM_INT_DEVS; i++) {
|
|
if (ohci->int_dev[i].devnum == -1) {
|
|
ohci->int_dev[i].devnum = devnum;
|
|
return &ohci->int_dev[i];
|
|
}
|
|
}
|
|
|
|
printf("ohci: Error out of ohci_devs for interrupt endpoints\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* common code for handling submit messages - used for all but root hub */
|
|
/* accesses. */
|
|
static urb_priv_t *ohci_alloc_urb(struct usb_device *dev, unsigned long pipe,
|
|
void *buffer, int transfer_len, int interval)
|
|
{
|
|
urb_priv_t *urb;
|
|
|
|
urb = calloc(1, sizeof(urb_priv_t));
|
|
if (!urb) {
|
|
printf("ohci: Error out of memory allocating urb\n");
|
|
return NULL;
|
|
}
|
|
|
|
urb->dev = dev;
|
|
urb->pipe = pipe;
|
|
urb->transfer_buffer = buffer;
|
|
urb->transfer_buffer_length = transfer_len;
|
|
urb->interval = interval;
|
|
|
|
return urb;
|
|
}
|
|
|
|
static int submit_common_msg(ohci_t *ohci, struct usb_device *dev,
|
|
unsigned long pipe, void *buffer, int transfer_len,
|
|
struct devrequest *setup, int interval)
|
|
{
|
|
int stat = 0;
|
|
int maxsize = usb_maxpacket(dev, pipe);
|
|
int timeout;
|
|
urb_priv_t *urb;
|
|
ohci_dev_t *ohci_dev;
|
|
|
|
urb = ohci_alloc_urb(dev, pipe, buffer, transfer_len, interval);
|
|
if (!urb)
|
|
return -ENOMEM;
|
|
|
|
#ifdef DEBUG
|
|
urb->actual_length = 0;
|
|
pkt_print(ohci, urb, dev, pipe, buffer, transfer_len,
|
|
setup, "SUB", usb_pipein(pipe));
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
if (!maxsize) {
|
|
err("submit_common_message: pipesize for pipe %lx is zero",
|
|
pipe);
|
|
return -1;
|
|
}
|
|
|
|
ohci_dev = ohci_get_ohci_dev(ohci, dev->devnum, usb_pipeint(pipe));
|
|
if (!ohci_dev)
|
|
return -ENOMEM;
|
|
|
|
if (sohci_submit_job(ohci, ohci_dev, urb, setup) < 0) {
|
|
err("sohci_submit_job failed");
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
mdelay(10);
|
|
/* ohci_dump_status(ohci); */
|
|
#endif
|
|
|
|
timeout = USB_TIMEOUT_MS(pipe);
|
|
|
|
/* wait for it to complete */
|
|
for (;;) {
|
|
/* check whether the controller is done */
|
|
stat = hc_interrupt(ohci);
|
|
if (stat < 0) {
|
|
stat = USB_ST_CRC_ERR;
|
|
break;
|
|
}
|
|
|
|
/* NOTE: since we are not interrupt driven in U-Boot and always
|
|
* handle only one URB at a time, we cannot assume the
|
|
* transaction finished on the first successful return from
|
|
* hc_interrupt().. unless the flag for current URB is set,
|
|
* meaning that all TD's to/from device got actually
|
|
* transferred and processed. If the current URB is not
|
|
* finished we need to re-iterate this loop so as
|
|
* hc_interrupt() gets called again as there needs to be some
|
|
* more TD's to process still */
|
|
if ((stat >= 0) && (stat != 0xff) && (urb->finished)) {
|
|
/* 0xff is returned for an SF-interrupt */
|
|
break;
|
|
}
|
|
|
|
if (--timeout) {
|
|
mdelay(1);
|
|
if (!urb->finished)
|
|
dbg("*");
|
|
|
|
} else {
|
|
if (!usb_pipeint(pipe))
|
|
err("CTL:TIMEOUT ");
|
|
dbg("submit_common_msg: TO status %x\n", stat);
|
|
urb->finished = 1;
|
|
stat = USB_ST_CRC_ERR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
dev->status = stat;
|
|
dev->act_len = urb->actual_length;
|
|
|
|
if (usb_pipein(pipe) && dev->status == 0 && dev->act_len)
|
|
invalidate_dcache_buffer(buffer, dev->act_len);
|
|
|
|
#ifdef DEBUG
|
|
pkt_print(ohci, urb, dev, pipe, buffer, transfer_len,
|
|
setup, "RET(ctlr)", usb_pipein(pipe));
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
urb_free_priv(urb);
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_INT_QUEUESIZE 8
|
|
|
|
struct int_queue {
|
|
int queuesize;
|
|
int curr_urb;
|
|
urb_priv_t *urb[MAX_INT_QUEUESIZE];
|
|
};
|
|
|
|
static struct int_queue *_ohci_create_int_queue(ohci_t *ohci,
|
|
struct usb_device *udev, unsigned long pipe, int queuesize,
|
|
int elementsize, void *buffer, int interval)
|
|
{
|
|
struct int_queue *queue;
|
|
ohci_dev_t *ohci_dev;
|
|
int i;
|
|
|
|
if (queuesize > MAX_INT_QUEUESIZE)
|
|
return NULL;
|
|
|
|
ohci_dev = ohci_get_ohci_dev(ohci, udev->devnum, 1);
|
|
if (!ohci_dev)
|
|
return NULL;
|
|
|
|
queue = malloc(sizeof(*queue));
|
|
if (!queue) {
|
|
printf("ohci: Error out of memory allocating int queue\n");
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < queuesize; i++) {
|
|
queue->urb[i] = ohci_alloc_urb(udev, pipe,
|
|
buffer + i * elementsize,
|
|
elementsize, interval);
|
|
if (!queue->urb[i])
|
|
break;
|
|
|
|
if (sohci_submit_job(ohci, ohci_dev, queue->urb[i], NULL)) {
|
|
printf("ohci: Error submitting int queue job\n");
|
|
urb_free_priv(queue->urb[i]);
|
|
break;
|
|
}
|
|
}
|
|
if (i == 0) {
|
|
/* We did not succeed in submitting even 1 urb */
|
|
free(queue);
|
|
return NULL;
|
|
}
|
|
|
|
queue->queuesize = i;
|
|
queue->curr_urb = 0;
|
|
|
|
return queue;
|
|
}
|
|
|
|
static void *_ohci_poll_int_queue(ohci_t *ohci, struct usb_device *udev,
|
|
struct int_queue *queue)
|
|
{
|
|
if (queue->curr_urb == queue->queuesize)
|
|
return NULL; /* Queue depleted */
|
|
|
|
if (hc_interrupt(ohci) < 0)
|
|
return NULL;
|
|
|
|
if (queue->urb[queue->curr_urb]->finished) {
|
|
void *ret = queue->urb[queue->curr_urb]->transfer_buffer;
|
|
queue->curr_urb++;
|
|
return ret;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int _ohci_destroy_int_queue(ohci_t *ohci, struct usb_device *dev,
|
|
struct int_queue *queue)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < queue->queuesize; i++)
|
|
urb_free_priv(queue->urb[i]);
|
|
|
|
free(queue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef CONFIG_DM_USB
|
|
/* submit routines called from usb.c */
|
|
int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
int transfer_len)
|
|
{
|
|
info("submit_bulk_msg");
|
|
return submit_common_msg(&gohci, dev, pipe, buffer, transfer_len,
|
|
NULL, 0);
|
|
}
|
|
|
|
int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
int transfer_len, int interval)
|
|
{
|
|
info("submit_int_msg");
|
|
return submit_common_msg(&gohci, dev, pipe, buffer, transfer_len, NULL,
|
|
interval);
|
|
}
|
|
|
|
struct int_queue *create_int_queue(struct usb_device *dev,
|
|
unsigned long pipe, int queuesize, int elementsize,
|
|
void *buffer, int interval)
|
|
{
|
|
return _ohci_create_int_queue(&gohci, dev, pipe, queuesize,
|
|
elementsize, buffer, interval);
|
|
}
|
|
|
|
void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
|
|
{
|
|
return _ohci_poll_int_queue(&gohci, dev, queue);
|
|
}
|
|
|
|
int destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
|
|
{
|
|
return _ohci_destroy_int_queue(&gohci, dev, queue);
|
|
}
|
|
#endif
|
|
|
|
static int _ohci_submit_control_msg(ohci_t *ohci, struct usb_device *dev,
|
|
unsigned long pipe, void *buffer, int transfer_len,
|
|
struct devrequest *setup)
|
|
{
|
|
int maxsize = usb_maxpacket(dev, pipe);
|
|
|
|
info("submit_control_msg");
|
|
#ifdef DEBUG
|
|
pkt_print(ohci, NULL, dev, pipe, buffer, transfer_len,
|
|
setup, "SUB", usb_pipein(pipe));
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
if (!maxsize) {
|
|
err("submit_control_message: pipesize for pipe %lx is zero",
|
|
pipe);
|
|
return -1;
|
|
}
|
|
if (((pipe >> 8) & 0x7f) == ohci->rh.devnum) {
|
|
ohci->rh.dev = dev;
|
|
/* root hub - redirect */
|
|
return ohci_submit_rh_msg(ohci, dev, pipe, buffer,
|
|
transfer_len, setup);
|
|
}
|
|
|
|
return submit_common_msg(ohci, dev, pipe, buffer, transfer_len,
|
|
setup, 0);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* HC functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* reset the HC and BUS */
|
|
|
|
static int hc_reset(ohci_t *ohci)
|
|
{
|
|
#ifdef CONFIG_PCI_EHCI_DEVNO
|
|
pci_dev_t pdev;
|
|
#endif
|
|
int timeout = 30;
|
|
int smm_timeout = 50; /* 0,5 sec */
|
|
|
|
dbg("%s\n", __FUNCTION__);
|
|
|
|
#ifdef CONFIG_PCI_EHCI_DEVNO
|
|
/*
|
|
* Some multi-function controllers (e.g. ISP1562) allow root hub
|
|
* resetting via EHCI registers only.
|
|
*/
|
|
pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVNO);
|
|
if (pdev != -1) {
|
|
u32 base;
|
|
int timeout = 1000;
|
|
|
|
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &base);
|
|
base += EHCI_USBCMD_OFF;
|
|
ohci_writel(ohci_readl(base) | EHCI_USBCMD_HCRESET, base);
|
|
|
|
while (ohci_readl(base) & EHCI_USBCMD_HCRESET) {
|
|
if (timeout-- <= 0) {
|
|
printf("USB RootHub reset timed out!");
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
} else
|
|
printf("No EHCI func at %d index!\n", CONFIG_PCI_EHCI_DEVNO);
|
|
#endif
|
|
if (ohci_readl(&ohci->regs->control) & OHCI_CTRL_IR) {
|
|
/* SMM owns the HC, request ownership */
|
|
ohci_writel(OHCI_OCR, &ohci->regs->cmdstatus);
|
|
info("USB HC TakeOver from SMM");
|
|
while (ohci_readl(&ohci->regs->control) & OHCI_CTRL_IR) {
|
|
mdelay(10);
|
|
if (--smm_timeout == 0) {
|
|
err("USB HC TakeOver failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Disable HC interrupts */
|
|
ohci_writel(OHCI_INTR_MIE, &ohci->regs->intrdisable);
|
|
|
|
dbg("USB HC reset_hc usb-%s: ctrl = 0x%X ;\n",
|
|
ohci->slot_name,
|
|
ohci_readl(&ohci->regs->control));
|
|
|
|
/* Reset USB (needed by some controllers) */
|
|
ohci->hc_control = 0;
|
|
ohci_writel(ohci->hc_control, &ohci->regs->control);
|
|
|
|
/* HC Reset requires max 10 us delay */
|
|
ohci_writel(OHCI_HCR, &ohci->regs->cmdstatus);
|
|
while ((ohci_readl(&ohci->regs->cmdstatus) & OHCI_HCR) != 0) {
|
|
if (--timeout == 0) {
|
|
err("USB HC reset timed out!");
|
|
return -1;
|
|
}
|
|
udelay(1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* Start an OHCI controller, set the BUS operational
|
|
* enable interrupts
|
|
* connect the virtual root hub */
|
|
|
|
static int hc_start(ohci_t *ohci)
|
|
{
|
|
__u32 mask;
|
|
unsigned int fminterval;
|
|
int i;
|
|
|
|
ohci->disabled = 1;
|
|
for (i = 0; i < NUM_INT_DEVS; i++)
|
|
ohci->int_dev[i].devnum = -1;
|
|
|
|
/* Tell the controller where the control and bulk lists are
|
|
* The lists are empty now. */
|
|
|
|
ohci_writel(0, &ohci->regs->ed_controlhead);
|
|
ohci_writel(0, &ohci->regs->ed_bulkhead);
|
|
|
|
ohci_writel((uintptr_t)ohci->hcca,
|
|
&ohci->regs->hcca); /* reset clears this */
|
|
|
|
fminterval = 0x2edf;
|
|
ohci_writel((fminterval * 9) / 10, &ohci->regs->periodicstart);
|
|
fminterval |= ((((fminterval - 210) * 6) / 7) << 16);
|
|
ohci_writel(fminterval, &ohci->regs->fminterval);
|
|
ohci_writel(0x628, &ohci->regs->lsthresh);
|
|
|
|
/* start controller operations */
|
|
ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
|
|
ohci->disabled = 0;
|
|
ohci_writel(ohci->hc_control, &ohci->regs->control);
|
|
|
|
/* disable all interrupts */
|
|
mask = (OHCI_INTR_SO | OHCI_INTR_WDH | OHCI_INTR_SF | OHCI_INTR_RD |
|
|
OHCI_INTR_UE | OHCI_INTR_FNO | OHCI_INTR_RHSC |
|
|
OHCI_INTR_OC | OHCI_INTR_MIE);
|
|
ohci_writel(mask, &ohci->regs->intrdisable);
|
|
/* clear all interrupts */
|
|
mask &= ~OHCI_INTR_MIE;
|
|
ohci_writel(mask, &ohci->regs->intrstatus);
|
|
/* Choose the interrupts we care about now - but w/o MIE */
|
|
mask = OHCI_INTR_RHSC | OHCI_INTR_UE | OHCI_INTR_WDH | OHCI_INTR_SO;
|
|
ohci_writel(mask, &ohci->regs->intrenable);
|
|
|
|
#ifdef OHCI_USE_NPS
|
|
/* required for AMD-756 and some Mac platforms */
|
|
ohci_writel((roothub_a(ohci) | RH_A_NPS) & ~RH_A_PSM,
|
|
&ohci->regs->roothub.a);
|
|
ohci_writel(RH_HS_LPSC, &ohci->regs->roothub.status);
|
|
#endif /* OHCI_USE_NPS */
|
|
|
|
/* connect the virtual root hub */
|
|
ohci->rh.devnum = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* an interrupt happens */
|
|
|
|
static int hc_interrupt(ohci_t *ohci)
|
|
{
|
|
struct ohci_regs *regs = ohci->regs;
|
|
int ints;
|
|
int stat = -1;
|
|
|
|
invalidate_dcache_hcca(ohci->hcca);
|
|
|
|
if ((ohci->hcca->done_head != 0) &&
|
|
!(m32_swap(ohci->hcca->done_head) & 0x01)) {
|
|
ints = OHCI_INTR_WDH;
|
|
} else {
|
|
ints = ohci_readl(®s->intrstatus);
|
|
if (ints == ~(u32)0) {
|
|
ohci->disabled++;
|
|
err("%s device removed!", ohci->slot_name);
|
|
return -1;
|
|
} else {
|
|
ints &= ohci_readl(®s->intrenable);
|
|
if (ints == 0) {
|
|
dbg("hc_interrupt: returning..\n");
|
|
return 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* dbg("Interrupt: %x frame: %x", ints,
|
|
le16_to_cpu(ohci->hcca->frame_no)); */
|
|
|
|
if (ints & OHCI_INTR_RHSC)
|
|
stat = 0xff;
|
|
|
|
if (ints & OHCI_INTR_UE) {
|
|
ohci->disabled++;
|
|
err("OHCI Unrecoverable Error, controller usb-%s disabled",
|
|
ohci->slot_name);
|
|
/* e.g. due to PCI Master/Target Abort */
|
|
|
|
#ifdef DEBUG
|
|
ohci_dump(ohci, 1);
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
/* FIXME: be optimistic, hope that bug won't repeat often. */
|
|
/* Make some non-interrupt context restart the controller. */
|
|
/* Count and limit the retries though; either hardware or */
|
|
/* software errors can go forever... */
|
|
hc_reset(ohci);
|
|
return -1;
|
|
}
|
|
|
|
if (ints & OHCI_INTR_WDH) {
|
|
ohci_mdelay(1);
|
|
ohci_writel(OHCI_INTR_WDH, ®s->intrdisable);
|
|
(void)ohci_readl(®s->intrdisable); /* flush */
|
|
stat = dl_done_list(ohci);
|
|
ohci_writel(OHCI_INTR_WDH, ®s->intrenable);
|
|
(void)ohci_readl(®s->intrdisable); /* flush */
|
|
}
|
|
|
|
if (ints & OHCI_INTR_SO) {
|
|
dbg("USB Schedule overrun\n");
|
|
ohci_writel(OHCI_INTR_SO, ®s->intrenable);
|
|
stat = -1;
|
|
}
|
|
|
|
/* FIXME: this assumes SOF (1/ms) interrupts don't get lost... */
|
|
if (ints & OHCI_INTR_SF) {
|
|
unsigned int frame = m16_swap(ohci->hcca->frame_no) & 1;
|
|
mdelay(1);
|
|
ohci_writel(OHCI_INTR_SF, ®s->intrdisable);
|
|
if (ohci->ed_rm_list[frame] != NULL)
|
|
ohci_writel(OHCI_INTR_SF, ®s->intrenable);
|
|
stat = 0xff;
|
|
}
|
|
|
|
ohci_writel(ints, ®s->intrstatus);
|
|
return stat;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
#ifndef CONFIG_DM_USB
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* De-allocate all resources.. */
|
|
|
|
static void hc_release_ohci(ohci_t *ohci)
|
|
{
|
|
dbg("USB HC release ohci usb-%s", ohci->slot_name);
|
|
|
|
if (!ohci->disabled)
|
|
hc_reset(ohci);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* low level initalisation routine, called from usb.c
|
|
*/
|
|
static char ohci_inited = 0;
|
|
|
|
int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
|
|
{
|
|
#ifdef CONFIG_PCI_OHCI
|
|
pci_dev_t pdev;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT
|
|
/* cpu dependant init */
|
|
if (usb_cpu_init())
|
|
return -1;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT
|
|
/* board dependant init */
|
|
if (board_usb_init(index, USB_INIT_HOST))
|
|
return -1;
|
|
#endif
|
|
memset(&gohci, 0, sizeof(ohci_t));
|
|
|
|
/* align the storage */
|
|
if ((__u32)&ghcca[0] & 0xff) {
|
|
err("HCCA not aligned!!");
|
|
return -1;
|
|
}
|
|
gohci.hcca = &ghcca[0];
|
|
info("aligned ghcca %p", gohci.hcca);
|
|
memset(gohci.hcca, 0, sizeof(struct ohci_hcca));
|
|
|
|
gohci.disabled = 1;
|
|
gohci.sleeping = 0;
|
|
gohci.irq = -1;
|
|
#ifdef CONFIG_PCI_OHCI
|
|
pdev = pci_find_devices(ohci_pci_ids, CONFIG_PCI_OHCI_DEVNO);
|
|
|
|
if (pdev != -1) {
|
|
u16 vid, did;
|
|
u32 base;
|
|
pci_read_config_word(pdev, PCI_VENDOR_ID, &vid);
|
|
pci_read_config_word(pdev, PCI_DEVICE_ID, &did);
|
|
printf("OHCI pci controller (%04x, %04x) found @(%d:%d:%d)\n",
|
|
vid, did, (pdev >> 16) & 0xff,
|
|
(pdev >> 11) & 0x1f, (pdev >> 8) & 0x7);
|
|
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &base);
|
|
printf("OHCI regs address 0x%08x\n", base);
|
|
gohci.regs = (struct ohci_regs *)base;
|
|
} else
|
|
return -1;
|
|
#else
|
|
gohci.regs = (struct ohci_regs *)CONFIG_SYS_USB_OHCI_REGS_BASE;
|
|
#endif
|
|
|
|
gohci.flags = 0;
|
|
gohci.slot_name = CONFIG_SYS_USB_OHCI_SLOT_NAME;
|
|
|
|
if (hc_reset (&gohci) < 0) {
|
|
hc_release_ohci (&gohci);
|
|
err ("can't reset usb-%s", gohci.slot_name);
|
|
#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT
|
|
/* board dependant cleanup */
|
|
board_usb_cleanup(index, USB_INIT_HOST);
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT
|
|
/* cpu dependant cleanup */
|
|
usb_cpu_init_fail();
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
if (hc_start(&gohci) < 0) {
|
|
err("can't start usb-%s", gohci.slot_name);
|
|
hc_release_ohci(&gohci);
|
|
/* Initialization failed */
|
|
#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT
|
|
/* board dependant cleanup */
|
|
usb_board_stop();
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT
|
|
/* cpu dependant cleanup */
|
|
usb_cpu_stop();
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
ohci_dump(&gohci, 1);
|
|
#else
|
|
ohci_mdelay(1);
|
|
#endif
|
|
ohci_inited = 1;
|
|
return 0;
|
|
}
|
|
|
|
int usb_lowlevel_stop(int index)
|
|
{
|
|
/* this gets called really early - before the controller has */
|
|
/* even been initialized! */
|
|
if (!ohci_inited)
|
|
return 0;
|
|
/* TODO release any interrupts, etc. */
|
|
/* call hc_release_ohci() here ? */
|
|
hc_reset(&gohci);
|
|
|
|
#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT
|
|
/* board dependant cleanup */
|
|
if (usb_board_stop())
|
|
return -1;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT
|
|
/* cpu dependant cleanup */
|
|
if (usb_cpu_stop())
|
|
return -1;
|
|
#endif
|
|
/* This driver is no longer initialised. It needs a new low-level
|
|
* init (board/cpu) before it can be used again. */
|
|
ohci_inited = 0;
|
|
return 0;
|
|
}
|
|
|
|
int submit_control_msg(struct usb_device *dev, unsigned long pipe,
|
|
void *buffer, int transfer_len, struct devrequest *setup)
|
|
{
|
|
return _ohci_submit_control_msg(&gohci, dev, pipe, buffer,
|
|
transfer_len, setup);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DM_USB
|
|
static int ohci_submit_control_msg(struct udevice *dev, struct usb_device *udev,
|
|
unsigned long pipe, void *buffer, int length,
|
|
struct devrequest *setup)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(usb_get_bus(dev));
|
|
|
|
return _ohci_submit_control_msg(ohci, udev, pipe, buffer,
|
|
length, setup);
|
|
}
|
|
|
|
static int ohci_submit_bulk_msg(struct udevice *dev, struct usb_device *udev,
|
|
unsigned long pipe, void *buffer, int length)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(usb_get_bus(dev));
|
|
|
|
return submit_common_msg(ohci, udev, pipe, buffer, length, NULL, 0);
|
|
}
|
|
|
|
static int ohci_submit_int_msg(struct udevice *dev, struct usb_device *udev,
|
|
unsigned long pipe, void *buffer, int length,
|
|
int interval)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(usb_get_bus(dev));
|
|
|
|
return submit_common_msg(ohci, udev, pipe, buffer, length,
|
|
NULL, interval);
|
|
}
|
|
|
|
static struct int_queue *ohci_create_int_queue(struct udevice *dev,
|
|
struct usb_device *udev, unsigned long pipe, int queuesize,
|
|
int elementsize, void *buffer, int interval)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(usb_get_bus(dev));
|
|
|
|
return _ohci_create_int_queue(ohci, udev, pipe, queuesize, elementsize,
|
|
buffer, interval);
|
|
}
|
|
|
|
static void *ohci_poll_int_queue(struct udevice *dev, struct usb_device *udev,
|
|
struct int_queue *queue)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(usb_get_bus(dev));
|
|
|
|
return _ohci_poll_int_queue(ohci, udev, queue);
|
|
}
|
|
|
|
static int ohci_destroy_int_queue(struct udevice *dev, struct usb_device *udev,
|
|
struct int_queue *queue)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(usb_get_bus(dev));
|
|
|
|
return _ohci_destroy_int_queue(ohci, udev, queue);
|
|
}
|
|
|
|
int ohci_register(struct udevice *dev, struct ohci_regs *regs)
|
|
{
|
|
struct usb_bus_priv *priv = dev_get_uclass_priv(dev);
|
|
ohci_t *ohci = dev_get_priv(dev);
|
|
u32 reg;
|
|
|
|
priv->desc_before_addr = true;
|
|
|
|
ohci->regs = regs;
|
|
ohci->hcca = memalign(256, sizeof(struct ohci_hcca));
|
|
if (!ohci->hcca)
|
|
return -ENOMEM;
|
|
memset(ohci->hcca, 0, sizeof(struct ohci_hcca));
|
|
flush_dcache_hcca(ohci->hcca);
|
|
|
|
if (hc_reset(ohci) < 0)
|
|
return -EIO;
|
|
|
|
if (hc_start(ohci) < 0)
|
|
return -EIO;
|
|
|
|
reg = ohci_readl(®s->revision);
|
|
printf("USB OHCI %x.%x\n", (reg >> 4) & 0xf, reg & 0xf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ohci_deregister(struct udevice *dev)
|
|
{
|
|
ohci_t *ohci = dev_get_priv(dev);
|
|
|
|
if (hc_reset(ohci) < 0)
|
|
return -EIO;
|
|
|
|
free(ohci->hcca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct dm_usb_ops ohci_usb_ops = {
|
|
.control = ohci_submit_control_msg,
|
|
.bulk = ohci_submit_bulk_msg,
|
|
.interrupt = ohci_submit_int_msg,
|
|
.create_int_queue = ohci_create_int_queue,
|
|
.poll_int_queue = ohci_poll_int_queue,
|
|
.destroy_int_queue = ohci_destroy_int_queue,
|
|
};
|
|
|
|
#endif
|