Rev 846 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* 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.
*/
#ifdef CONFIG_PMAC_PBOOK
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/pci-bridge.h>
#include <asm/prom.h>
#ifndef CONFIG_PM
# define CONFIG_PM
#endif
#endif
#ifndef CONFIG_PCI
#error "This file is PCI bus glue. CONFIG_PCI must be defined."
#endif
/*-------------------------------------------------------------------------*/
static int
ohci_pci_reset
(struct usb_hcd
*hcd
)
{
struct ohci_hcd
*ohci
= hcd_to_ohci
(hcd
);
ohci
->regs
= hcd
->regs
;
return hc_reset
(ohci
);
}
static int __devinit
ohci_pci_start
(struct usb_hcd
*hcd
)
{
struct ohci_hcd
*ohci
= hcd_to_ohci
(hcd
);
int ret
;
if (hcd
->pdev
) {
ohci
->hcca
= pci_alloc_consistent_usb
(hcd
->pdev
,
sizeof *ohci
->hcca
, &ohci
->hcca_dma
);
if (!ohci
->hcca
)
return -ENOMEM
;
/* AMD 756, for most chips (early revs), corrupts register
* values on read ... so enable the vendor workaround.
*/
if (hcd
->pdev
->vendor
== PCI_VENDOR_ID_AMD
&& hcd
->pdev
->device
== 0x740c) {
ohci
->flags
= OHCI_QUIRK_AMD756
;
ohci_info
(ohci
, "AMD756 erratum 4 workaround\n");
}
/* FIXME for some of the early AMD 760 southbridges, OHCI
* won't work at all. blacklist them.
*/
/* 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 ...)
*/
else if (hcd
->pdev
->vendor
== PCI_VENDOR_ID_OPTI
&& hcd
->pdev
->device
== 0xc861) {
ohci_info
(ohci
,
"WARNING: OPTi workarounds unavailable\n");
}
/* 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.
*/
else if (hcd
->pdev
->vendor
== PCI_VENDOR_ID_NS
) {
struct pci_dev
*b
, *hc
;
hc
= hcd
->pdev
;
b
= pci_find_slot
(hc
->bus
->number
,
PCI_DEVFN
(PCI_SLOT
(hc
->devfn
), 1));
if (b
&& b
->device
== PCI_DEVICE_ID_NS_87560_LIO
&& b
->vendor
== PCI_VENDOR_ID_NS
) {
ohci
->flags
|= OHCI_QUIRK_SUPERIO
;
ohci_info
(ohci
, "Using NSC SuperIO setup\n");
}
}
}
memset (ohci
->hcca
, 0, sizeof (struct ohci_hcca
));
if ((ret
= ohci_mem_init
(ohci
)) < 0) {
ohci_stop
(hcd
);
return ret
;
}
if (hc_start
(ohci
) < 0) {
ohci_err
(ohci
, "can't start\n");
ohci_stop
(hcd
);
return -EBUSY
;
}
create_debug_files
(ohci
);
#ifdef DEBUG
ohci_dump
(ohci
, 1);
#endif
return 0;
}
#ifdef CONFIG_PM
static int ohci_pci_suspend
(struct usb_hcd
*hcd
, u32 state
)
{
struct ohci_hcd
*ohci
= hcd_to_ohci
(hcd
);
u16 cmd
;
u32 tmp
;
if ((ohci
->hc_control
& OHCI_CTRL_HCFS
) != OHCI_USB_OPER
) {
ohci_dbg
(ohci
, "can't suspend (state is %s)\n",
hcfs2string
(ohci
->hc_control
& OHCI_CTRL_HCFS
));
return -EIO
;
}
/* act as if usb suspend can always be used */
ohci_dbg
(ohci
, "suspend to %d\n", state
);
/* First stop processing */
spin_lock_irq
(&ohci
->lock
);
ohci
->hc_control
&=
~
(OHCI_CTRL_PLE
|OHCI_CTRL_CLE
|OHCI_CTRL_BLE
|OHCI_CTRL_IE
);
writel
(ohci
->hc_control
, &ohci
->regs
->control
);
writel
(OHCI_INTR_SF
, &ohci
->regs
->intrstatus
);
(void) readl
(&ohci
->regs
->intrstatus
);
spin_unlock_irq
(&ohci
->lock
);
/* Wait a frame or two */
mdelay
(1);
if (!readl
(&ohci
->regs
->intrstatus
) & OHCI_INTR_SF
)
mdelay
(1);
#ifdef CONFIG_PMAC_PBOOK
if (_machine
== _MACH_Pmac
)
disable_irq
(hcd
->pdev
->irq
);
/* else, 2.4 assumes shared irqs -- don't disable */
#endif
/* Enable remote wakeup */
writel
(readl
(&ohci
->regs
->intrenable
) | OHCI_INTR_RD
,
&ohci
->regs
->intrenable
);
/* Suspend chip and let things settle down a bit */
spin_lock_irq
(&ohci
->lock
);
ohci
->hc_control
= OHCI_USB_SUSPEND
;
writel
(ohci
->hc_control
, &ohci
->regs
->control
);
(void) readl
(&ohci
->regs
->control
);
spin_unlock_irq
(&ohci
->lock
);
set_current_state
(TASK_UNINTERRUPTIBLE
);
schedule_timeout
(HZ
/2);
tmp
= readl
(&ohci
->regs
->control
) | OHCI_CTRL_HCFS
;
switch (tmp
) {
case OHCI_USB_RESET
:
case OHCI_USB_RESUME
:
case OHCI_USB_OPER
:
ohci_err
(ohci
, "can't suspend; hcfs %d\n", tmp
);
break;
case OHCI_USB_SUSPEND
:
ohci_dbg
(ohci
, "suspended\n");
break;
}
/* In some rare situations, Apple's OHCI have happily trashed
* memory during sleep. We disable its bus master bit during
* suspend
*/
pci_read_config_word
(hcd
->pdev
, PCI_COMMAND
, &cmd
);
cmd
&= ~PCI_COMMAND_MASTER
;
pci_write_config_word
(hcd
->pdev
, PCI_COMMAND
, cmd
);
#ifdef CONFIG_PMAC_PBOOK
{
struct device_node
*of_node
;
/* Disable USB PAD & cell clock */
of_node
= pci_device_to_OF_node
(hcd
->pdev
);
if (of_node
)
pmac_call_feature
(PMAC_FTR_USB_ENABLE
, of_node
, 0, 0);
}
#endif
return 0;
}
static int ohci_pci_resume
(struct usb_hcd
*hcd
)
{
struct ohci_hcd
*ohci
= hcd_to_ohci
(hcd
);
int temp
;
int retval
= 0;
#ifdef CONFIG_PMAC_PBOOK
{
struct device_node
*of_node
;
/* Re-enable USB PAD & cell clock */
of_node
= pci_device_to_OF_node
(hcd
->pdev
);
if (of_node
)
pmac_call_feature
(PMAC_FTR_USB_ENABLE
, of_node
, 0, 1);
}
#endif
/* did we suspend, or were we powered off? */
ohci
->hc_control
= readl
(&ohci
->regs
->control
);
temp
= ohci
->hc_control
& OHCI_CTRL_HCFS
;
#ifdef DEBUG
/* the registers may look crazy here */
ohci_dump_status
(ohci
, 0, 0);
#endif
/* Re-enable bus mastering */
pci_set_master
(ohci
->hcd.
pdev);
switch (temp
) {
case OHCI_USB_RESET
: // lost power
restart
:
ohci_info
(ohci
, "USB restart\n");
retval
= hc_restart
(ohci
);
break;
case OHCI_USB_SUSPEND
: // host wakeup
case OHCI_USB_RESUME
: // remote wakeup
ohci_info
(ohci
, "USB continue from %s wakeup\n",
(temp
== OHCI_USB_SUSPEND
)
? "host" : "remote");
/* we "should" only need RESUME if we're SUSPENDed ... */
ohci
->hc_control
= OHCI_USB_RESUME
;
writel
(ohci
->hc_control
, &ohci
->regs
->control
);
(void) readl
(&ohci
->regs
->control
);
/* Some controllers (lucent) need extra-long delays */
mdelay
(35); /* no schedule here ! */
temp
= readl
(&ohci
->regs
->control
);
temp
= ohci
->hc_control
& OHCI_CTRL_HCFS
;
if (temp
!= OHCI_USB_RESUME
) {
ohci_err
(ohci
, "controller won't resume\n");
/* maybe we can reset */
goto restart
;
}
/* Then re-enable operations */
writel
(OHCI_USB_OPER
, &ohci
->regs
->control
);
(void) readl
(&ohci
->regs
->control
);
mdelay
(3);
spin_lock_irq
(&ohci
->lock
);
ohci
->hc_control
= OHCI_CONTROL_INIT
| OHCI_USB_OPER
;
if (!ohci
->ed_rm_list
) {
if (ohci
->ed_controltail
)
ohci
->hc_control
|= OHCI_CTRL_CLE
;
if (ohci
->ed_bulktail
)
ohci
->hc_control
|= OHCI_CTRL_BLE
;
}
hcd
->state
= USB_STATE_RUNNING
;
writel
(ohci
->hc_control
, &ohci
->regs
->control
);
/* trigger a start-frame interrupt (why?) */
writel
(OHCI_INTR_SF
, &ohci
->regs
->intrstatus
);
writel
(OHCI_INTR_SF
, &ohci
->regs
->intrenable
);
writel
(OHCI_INTR_WDH
, &ohci
->regs
->intrdisable
);
(void) readl
(&ohci
->regs
->intrdisable
);
spin_unlock_irq
(&ohci
->lock
);
#ifdef CONFIG_PMAC_PBOOK
if (_machine
== _MACH_Pmac
)
enable_irq
(hcd
->pdev
->irq
);
#endif
/* Check for a pending done list */
if (ohci
->hcca
->done_head
)
dl_done_list
(ohci
, dl_reverse_done_list
(ohci
), NULL
);
writel
(OHCI_INTR_WDH
, &ohci
->regs
->intrenable
);
/* assume there are TDs on the bulk and control lists */
writel
(OHCI_BLF
| OHCI_CLF
, &ohci
->regs
->cmdstatus
);
break;
default:
ohci_warn
(ohci
, "odd PCI resume\n");
}
return retval
;
}
#endif /* CONFIG_PM */
/*-------------------------------------------------------------------------*/
static const struct hc_driver ohci_pci_hc_driver
= {
.
description = hcd_name
,
/*
* generic hardware linkage
*/
.
irq = ohci_irq
,
.
flags = HCD_MEMORY
| HCD_USB11
,
/*
* basic lifecycle operations
*/
.
reset = ohci_pci_reset
,
.
start = ohci_pci_start
,
#ifdef CONFIG_PM
.
suspend = ohci_pci_suspend
,
.
resume = ohci_pci_resume
,
#endif
.
stop = ohci_stop
,
/*
* memory lifecycle (except per-request)
*/
.
hcd_alloc = ohci_hcd_alloc
,
.
hcd_free = ohci_hcd_free
,
/*
* 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
,
};
/*-------------------------------------------------------------------------*/
static const struct pci_device_id pci_ids
[] = { {
/* handle any USB OHCI controller */
PCI_DEVICE_CLASS
((PCI_CLASS_SERIAL_USB
<< 8) | 0x10, ~
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
};
/*static*/ int __init ohci_hcd_pci_init
(void)
{
printk
(KERN_DEBUG
"%s: " DRIVER_INFO
" (PCI)\n", hcd_name
);
if (usb_disabled
())
return -ENODEV
;
printk
(KERN_DEBUG
"%s: block sizes: ed %Zd td %Zd\n", hcd_name
,
sizeof (struct ed
), sizeof (struct td
));
//*** printk (KERN_DEBUG "File: %s @Line:%d\n", __FILE__, __LINE__);
return pci_module_init
(&ohci_pci_driver
);
}
module_init
(ohci_hcd_pci_init
);
/*-------------------------------------------------------------------------*/
/*static*/ void /*__exit*/ ohci_hcd_pci_cleanup
(void)
{
pci_unregister_driver
(&ohci_pci_driver
);
}
module_exit
(ohci_hcd_pci_cleanup
);