📄 usb_ohci.c
字号:
/*
* 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 <gj@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
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
*/
/*
* 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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <absacc.h>
#include "..\hardware\LPC2468.h" /* 24xx Peripheral Registers */
#include "..\rtxOS\rtl.h"
void os_dly_waitms(unsigned int ms);
#include "usb.h"
#include "usbhost.h"
#include "usb_ohci.h"
unsigned int ohci_init( void );
#define OHCI_USE_NPS /* force NoPowerSwitching mode */
/* For initializing controller (mask in an HCFS mode too) */
#define OHCI_CONTROL_INIT \
(OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE
#define readl(a) (*((volatile unsigned long *)(a)))
#define writel(a, b) (*((volatile unsigned long *)(b)) = (unsigned long)a)
#define min_t(x,y) ((x) < (y) ? (x): (y))
#define dbg(format, arg...)
#define err(format, arg...)
#define info(format, arg...)
#define m16_swap(x) swap_16(x)
#define m32_swap(x) swap_32(x)
/* global ohci_t */
static ohci_t gohci;
/* this must be aligned to a 256 byte boundary */
struct ohci_hcca ghcca[32] __at(USB_OHCI_ADDR);
/* a pointer to the aligned storage */
struct ohci_hcca *phcca;
/* this allocates EDs for all possible endpoints */
struct ohci_device ohci_dev;
/* RHSC flag */
int got_rhsc;
/* device which was disconnected */
struct usb_device *devgone;
/*-------------------------------------------------------------------------*/
/* 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 OHCI_QUIRK_AMD756 0xabcd
#define read_roothub(hc, registera, mask) ({ \
unsigned int temp = readl (&hc->regs->roothub.registera); \
if (hc->flags & OHCI_QUIRK_AMD756) \
while (temp & mask) \
temp = readl (&hc->regs->roothub.registera); \
temp; })
static unsigned int roothub_a (struct ohci *hc)
{ unsigned int temp = readl (&hc->regs->roothub.a);
if (hc->flags & OHCI_QUIRK_AMD756)
{ while (temp & 0xfc0fe000)
{ temp = readl (&hc->regs->roothub.a); }
}
return temp;
}
static __inline unsigned int roothub_b (struct ohci *hc)
{ return readl (&hc->regs->roothub.b); }
static __inline unsigned int roothub_status (struct ohci *hc)
{ return readl (&hc->regs->roothub.status); }
static unsigned int roothub_portstatus (struct ohci *hc, int i)
{ unsigned int temp = readl (&hc->regs->roothub.portstatus [i]);
if (hc->flags & OHCI_QUIRK_AMD756)
{ while (temp & 0xffe0fce0)
{ temp = readl (&hc->regs->roothub.portstatus [i]); }
}
return temp;
}
/* forward declaration */
static int hc_interrupt (void);
static void
td_submit_job (struct usb_device * dev, unsigned long pipe, void * buffer,
int transfer_len, struct devrequest * setup, urb_priv_t * urb, int interval);
#define NUM_TD 64
/* +1 so we can align the storage */
td_t gtd[NUM_TD+1];
/* pointers to aligned storage */
td_t *ptd;
/* TDs ... */
static __inline struct td * td_alloc (struct usb_device *usb_dev)
{
int i;
struct td *td;
td = NULL;
for (i = 0; i < NUM_TD; i++)
{
if (ptd[i].usb_dev == NULL)
{
td = &ptd[i];
td->usb_dev = usb_dev;
break;
}
}
return td;
}
__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);
}
/*-------------------------------------------------------------------------*
* Interface functions (URB)
*-------------------------------------------------------------------------*/
/* get a transfer request */
int sohci_submit_job(urb_priv_t *urb, struct devrequest *setup)
{
ohci_t *ohci;
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;
ohci = &gohci;
/* 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 (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 (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(dev, pipe, buffer, transfer_len, setup, purb_priv, interval);
return 0;
}
static __inline int sohci_return_job(struct ohci *hc, urb_priv_t *urb)
{
struct ohci_regs *regs = hc->regs;
switch (usb_pipetype (urb->pipe)) {
case PIPE_INTERRUPT:
/* implicitly requeued */
urb->dev->irq_act_len = urb->actual_length;
if (urb->dev->irq_handle && urb->dev->irq_act_len) {
writel (OHCI_INTR_WDH, ®s->intrenable);
readl (®s->intrenable); /* PCI posting flush */
urb->dev->irq_handle(urb->dev);
writel (OHCI_INTR_WDH, ®s->intrdisable);
readl (®s->intrdisable); /* PCI posting flush */
}
urb->actual_length = 0;
td_submit_job (
urb->dev,
urb->pipe,
urb->transfer_buffer,
urb->transfer_buffer_length,
NULL,
urb,
urb->interval);
break;
case PIPE_CONTROL:
case PIPE_BULK:
break;
default:
return 0;
}
return 1;
}
/*-------------------------------------------------------------------------*
* 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;
unsigned int * ed_p;
ed->state = ED_OPER;
ed->int_interval = 0;
switch (ed->type) {
case PIPE_CONTROL:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -