Subversion Repositories shark

Rev

Rev 846 | 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);