📄 ohci-hcd.c
字号:
* that's not our job. can't recover; must leak ed.
*/
ohci_err (ohci, "ed %p (#%d) state %d%s\n",
ed, epnum, ed->state,
list_empty (&ed->td_list) ? "" : "(has tds)");
td_free (ohci, ed->dummy);
break;
}
dev->ep [epnum] = 0;
done:
spin_unlock_irqrestore (&ohci->lock, flags);
return;
}
static int ohci_get_frame (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
return le16_to_cpu (ohci->hcca->frame_no);
}
/*-------------------------------------------------------------------------*
* HC functions
*-------------------------------------------------------------------------*/
/* reset the HC and BUS */
static int hc_reset (struct ohci_hcd *ohci)
{
u32 temp;
/* SMM owns the HC? not for long!
* On PA-RISC, PDC can leave IR set incorrectly; ignore it there.
*/
#ifndef __hppa__
if (readl (&ohci->regs->control) & OHCI_CTRL_IR) {
ohci_dbg (ohci, "USB HC TakeOver from BIOS/SMM\n");
/* this timeout is arbitrary. we make it long, so systems
* depending on usb keyboards may be usable even if the
* BIOS/SMM code seems pretty broken.
*/
temp = 500; /* arbitrary: five seconds */
writel (OHCI_INTR_OC, &ohci->regs->intrenable);
writel (OHCI_OCR, &ohci->regs->cmdstatus);
while (readl (&ohci->regs->control) & OHCI_CTRL_IR) {
wait_ms (10);
if (--temp == 0) {
ohci_err (ohci, "USB HC TakeOver failed!\n");
return -1;
}
}
}
#endif
/* Disable HC interrupts */
writel (OHCI_INTR_MIE, &ohci->regs->intrdisable);
ohci_dbg (ohci, "USB HC reset_hc %s: ctrl = 0x%x ;\n",
hcd_to_bus (&ohci->hcd)->bus_name,
readl (&ohci->regs->control));
/* Reset USB (needed by some controllers); RemoteWakeupConnected
* saved if boot firmware (BIOS/SMM/...) told us it's connected
*/
ohci->hc_control = readl (&ohci->regs->control);
ohci->hc_control &= OHCI_CTRL_RWC; /* hcfs 0 = RESET */
writel (ohci->hc_control, &ohci->regs->control);
// flush those pci writes
(void) readl (&ohci->regs->control);
wait_ms (50);
/* HC Reset requires max 10 us delay */
writel (OHCI_HCR, &ohci->regs->cmdstatus);
temp = 30; /* ... allow extra time */
while ((readl (&ohci->regs->cmdstatus) & OHCI_HCR) != 0) {
if (--temp == 0) {
ohci_err (ohci, "USB HC reset timed out!\n");
return -1;
}
udelay (1);
}
/* now we're in the SUSPEND state ... must go OPERATIONAL
* within 2msec else HC enters RESUME
*
* ... but some hardware won't init fmInterval "by the book"
* (SiS, OPTi ...), so reset again instead. SiS doesn't need
* this if we write fmInterval after we're OPERATIONAL.
*/
writel (ohci->hc_control, &ohci->regs->control);
// flush those pci writes
(void) readl (&ohci->regs->control);
return 0;
}
/*-------------------------------------------------------------------------*/
#define FI 0x2edf /* 12000 bits per frame (-1) */
#define LSTHRESH 0x628 /* lowspeed bit threshold */
/* Start an OHCI controller, set the BUS operational
* enable interrupts
* connect the virtual root hub
*/
static int hc_start (struct ohci_hcd *ohci)
{
u32 mask, tmp;
struct usb_device *udev;
struct usb_bus *bus;
spin_lock_init (&ohci->lock);
ohci->disabled = 1;
ohci->sleeping = 0;
/* Tell the controller where the control and bulk lists are
* The lists are empty now. */
writel (0, &ohci->regs->ed_controlhead);
writel (0, &ohci->regs->ed_bulkhead);
/* a reset clears this */
writel ((u32) ohci->hcca_dma, &ohci->regs->hcca);
usbprintk("HCCA: %p \n",ohci->regs->hcca);
/* force default fmInterval (we won't adjust it); init thresholds
* for last FS and LS packets, reserve 90% for periodic.
*/
writel ((((6 * (FI - 210)) / 7) << 16) | FI, &ohci->regs->fminterval);
writel (((9 * FI) / 10) & 0x3fff, &ohci->regs->periodicstart);
writel (LSTHRESH, &ohci->regs->lsthresh);
/* some OHCI implementations are finicky about how they init.
* bogus values here mean not even enumeration could work.
*/
if ((readl (&ohci->regs->fminterval) & 0x3fff0000) == 0
|| !readl (&ohci->regs->periodicstart)) {
ohci_err (ohci, "init err\n");
return -EOVERFLOW;
}
/* start controller operations */
ohci->hc_control &= OHCI_CTRL_RWC;
ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
ohci->disabled = 0;
writel (ohci->hc_control, &ohci->regs->control);
/* Choose the interrupts we care about now, others later on demand */
mask = OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_WDH;
writel (mask, &ohci->regs->intrstatus);
writel (mask, &ohci->regs->intrenable);
/* handle root hub init quirks ... */
tmp = roothub_a (ohci);
tmp &= ~(RH_A_PSM | RH_A_OCPM);
if (ohci->flags & OHCI_QUIRK_SUPERIO) {
/* NSC 87560 and maybe others */
tmp |= RH_A_NOCP;
tmp &= ~(RH_A_POTPGT | RH_A_NPS);
} else {
/* hub power always on; required for AMD-756 and some
* Mac platforms, use this mode everywhere by default
*/
tmp |= RH_A_NPS;
}
writel (tmp, &ohci->regs->roothub.a);
writel (RH_HS_LPSC, &ohci->regs->roothub.status);
writel (0, &ohci->regs->roothub.b);
// flush those pci writes
(void) readl (&ohci->regs->control);
// POTPGT delay is bits 24-31, in 2 ms units.
mdelay (((int)(roothub_a (ohci) >> 23) & 0x1fe));
/* connect the virtual root hub */
bus = hcd_to_bus (&ohci->hcd);
bus->root_hub = udev = usb_alloc_dev (NULL, bus);
ohci->hcd.state = USB_STATE_READY;
if (!udev) {
disable (ohci);
ohci->hc_control &= ~OHCI_CTRL_HCFS;
writel (ohci->hc_control, &ohci->regs->control);
ohci_err(ohci,"out of mem");
return -ENOMEM;
}
usb_connect (udev);
udev->speed = USB_SPEED_FULL;
if (hcd_register_root (&ohci->hcd) != 0) {
usb_put_dev (udev);
bus->root_hub = NULL;
disable (ohci);
ohci->hc_control &= ~OHCI_CTRL_HCFS;
writel (ohci->hc_control, &ohci->regs->control);
return -ENODEV;
}
create_debug_files (ohci);
return 0;
}
/*-------------------------------------------------------------------------*/
/* an interrupt happens */
static
int ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
struct ohci_regs *regs = ohci->regs;
int ints;
/* we can eliminate a (slow) readl() if _only_ WDH caused this irq */
if ((ohci->hcca->done_head != 0)
&& ! (le32_to_cpup (&ohci->hcca->done_head) & 0x01)) {
ints = OHCI_INTR_WDH;
/* cardbus/... hardware gone before remove() */
} else if ((ints = readl (®s->intrstatus)) == ~(u32)0) {
disable (ohci);
ohci_dbg (ohci, "device removed!\n");
return 0;
/* interrupt for some other device? */
} else if ((ints &= readl (®s->intrenable)) == 0) {
return 0;
}
if (ints & OHCI_INTR_UE) {
disable (ohci);
ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n");
// e.g. due to PCI Master/Target Abort
ohci_dump (ohci, 1);
hc_reset (ohci);
}
if (ints & OHCI_INTR_WDH) {
writel (OHCI_INTR_WDH, ®s->intrdisable);
dl_done_list (ohci, dl_reverse_done_list (ohci), ptregs);
writel (OHCI_INTR_WDH, ®s->intrenable);
}
/* could track INTR_SO to reduce available PCI/... bandwidth */
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
* when there's still unlinking to be done (next frame).
*/
spin_lock (&ohci->lock);
if (ohci->ed_rm_list)
finish_unlinks (ohci, le16_to_cpu (ohci->hcca->frame_no),
ptregs);
if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list)
writel (OHCI_INTR_SF, ®s->intrdisable);
spin_unlock (&ohci->lock);
writel (ints, ®s->intrstatus);
writel (OHCI_INTR_MIE, ®s->intrenable);
// flush those pci writes
(void) readl (&ohci->regs->control);
return 0;
}
/*-------------------------------------------------------------------------*/
#ifdef DEBUG_MODE
// HCFS itself
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 "?";
}
#endif
static void ohci_stop (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
ohci_dbg (ohci, "stop %s controller%s\n",
hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS),
ohci->disabled ? " (disabled)" : ""
);
//ohci_dump (ohci, 1);
if (!ohci->disabled)
hc_reset (ohci);
//remove_debug_files (ohci);
ohci_mem_cleanup (ohci);
if (ohci->hcca) {
pci_free_consistent (ohci->hcd.pdev, sizeof *ohci->hcca,
ohci->hcca, ohci->hcca_dma);
ohci->hcca = NULL;
ohci->hcca_dma = 0;
}
}
/*-------------------------------------------------------------------------*/
// FIXME: this restart logic should be generic,
// and handle full hcd state cleanup
/* controller died; cleanup debris, then restart */
/* must not be called from interrupt context */
#ifdef CONFIG_PM
static int hc_restart (struct ohci_hcd *ohci)
{
int temp;
int i;
ohci->disabled = 1;
ohci->sleeping = 0;
if (hcd_to_bus (&ohci->hcd)->root_hub)
usb_disconnect (&hcd_to_bus (&ohci->hcd)->root_hub);
/* empty the interrupt branches */
for (i = 0; i < NUM_INTS; i++) ohci->load [i] = 0;
for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0;
/* no EDs to remove */
ohci->ed_rm_list = NULL;
/* empty control and bulk lists */
ohci->ed_controltail = NULL;
ohci->ed_bulktail = NULL;
if ((temp = hc_reset (ohci)) < 0 || (temp = hc_start (ohci)) < 0) {
ohci_err (ohci, "can't restart, %d\n", temp);
return temp;
} else
ohci_dbg (ohci, "restart complete\n");
return 0;
}
#endif
/*-------------------------------------------------------------------------*/
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
MODULE_AUTHOR (DRIVER_AUTHOR);
MODULE_DESCRIPTION (DRIVER_INFO);
MODULE_LICENSE ("GPL");
#ifdef CONFIG_PCI
#include "ohci-pci.c"
#endif
#ifdef CONFIG_SA1111
#include "ohci-sa1111.c"
#endif
#if !(defined(CONFIG_PCI) || defined(CONFIG_SA1111))
#error "missing bus glue for ohci-hcd"
#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -