linux/drivers/usb/host/ohci-au1xxx.c
<<
>>
Prefs
   1/*
   2 * OHCI HCD (Host Controller Driver) for USB.
   3 *
   4 * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
   5 * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
   6 * (C) Copyright 2002 Hewlett-Packard Company
   7 *
   8 * Bus Glue for AMD Alchemy Au1xxx
   9 *
  10 * Written by Christopher Hoover <ch@hpl.hp.com>
  11 * Based on fragments of previous driver by Russell King et al.
  12 *
  13 * Modified for LH7A404 from ohci-sa1111.c
  14 *  by Durgesh Pattamatta <pattamattad@sharpsec.com>
  15 * Modified for AMD Alchemy Au1xxx
  16 *  by Matt Porter <mporter@kernel.crashing.org>
  17 *
  18 * This file is licenced under the GPL.
  19 */
  20
  21#include <linux/platform_device.h>
  22#include <linux/signal.h>
  23
  24#include <asm/mach-au1x00/au1000.h>
  25
  26#ifndef CONFIG_SOC_AU1200
  27
  28#define USBH_ENABLE_BE (1<<0)
  29#define USBH_ENABLE_C  (1<<1)
  30#define USBH_ENABLE_E  (1<<2)
  31#define USBH_ENABLE_CE (1<<3)
  32#define USBH_ENABLE_RD (1<<4)
  33
  34#ifdef __LITTLE_ENDIAN
  35#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C)
  36#elif __BIG_ENDIAN
  37#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C | \
  38                          USBH_ENABLE_BE)
  39#else
  40#error not byte order defined
  41#endif
  42
  43#else   /* Au1200 */
  44
  45#define USB_HOST_CONFIG    (USB_MSR_BASE + USB_MSR_MCFG)
  46#define USB_MCFG_PFEN     (1<<31)
  47#define USB_MCFG_RDCOMB   (1<<30)
  48#define USB_MCFG_SSDEN    (1<<23)
  49#define USB_MCFG_OHCCLKEN (1<<16)
  50#ifdef CONFIG_DMA_COHERENT
  51#define USB_MCFG_UCAM     (1<<7)
  52#else
  53#define USB_MCFG_UCAM     (0)
  54#endif
  55#define USB_MCFG_OBMEN    (1<<1)
  56#define USB_MCFG_OMEMEN   (1<<0)
  57
  58#define USBH_ENABLE_CE    USB_MCFG_OHCCLKEN
  59
  60#define USBH_ENABLE_INIT  (USB_MCFG_PFEN  | USB_MCFG_RDCOMB     |       \
  61                           USBH_ENABLE_CE | USB_MCFG_SSDEN      |       \
  62                           USB_MCFG_UCAM  |                             \
  63                           USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
  64
  65#define USBH_DISABLE      (USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
  66
  67#endif  /* Au1200 */
  68
  69extern int usb_disabled(void);
  70
  71static void au1xxx_start_ohc(void)
  72{
  73        /* enable host controller */
  74#ifndef CONFIG_SOC_AU1200
  75        au_writel(USBH_ENABLE_CE, USB_HOST_CONFIG);
  76        au_sync();
  77        udelay(1000);
  78
  79        au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG);
  80        au_sync();
  81        udelay(1000);
  82
  83        /* wait for reset complete (read register twice; see au1500 errata) */
  84        while (au_readl(USB_HOST_CONFIG),
  85                !(au_readl(USB_HOST_CONFIG) & USBH_ENABLE_RD))
  86                udelay(1000);
  87
  88#else   /* Au1200 */
  89        au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_CE, USB_HOST_CONFIG);
  90        au_sync();
  91        udelay(1000);
  92
  93        au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG);
  94        au_sync();
  95        udelay(2000);
  96#endif  /* Au1200 */
  97}
  98
  99static void au1xxx_stop_ohc(void)
 100{
 101#ifdef CONFIG_SOC_AU1200
 102        /* Disable mem */
 103        au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_DISABLE, USB_HOST_CONFIG);
 104        au_sync();
 105        udelay(1000);
 106#endif
 107        /* Disable clock */
 108        au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_ENABLE_CE, USB_HOST_CONFIG);
 109        au_sync();
 110}
 111
 112static int __devinit ohci_au1xxx_start(struct usb_hcd *hcd)
 113{
 114        struct ohci_hcd *ohci = hcd_to_ohci(hcd);
 115        int ret;
 116
 117        ohci_dbg(ohci, "ohci_au1xxx_start, ohci:%p", ohci);
 118
 119        if ((ret = ohci_init(ohci)) < 0)
 120                return ret;
 121
 122        if ((ret = ohci_run(ohci)) < 0) {
 123                err ("can't start %s", hcd->self.bus_name);
 124                ohci_stop(hcd);
 125                return ret;
 126        }
 127
 128        return 0;
 129}
 130
 131static const struct hc_driver ohci_au1xxx_hc_driver = {
 132        .description =          hcd_name,
 133        .product_desc =         "Au1xxx OHCI",
 134        .hcd_priv_size =        sizeof(struct ohci_hcd),
 135
 136        /*
 137         * generic hardware linkage
 138         */
 139        .irq =                  ohci_irq,
 140        .flags =                HCD_USB11 | HCD_MEMORY,
 141
 142        /*
 143         * basic lifecycle operations
 144         */
 145        .start =                ohci_au1xxx_start,
 146        .stop =                 ohci_stop,
 147        .shutdown =             ohci_shutdown,
 148
 149        /*
 150         * managing i/o requests and associated device resources
 151         */
 152        .urb_enqueue =          ohci_urb_enqueue,
 153        .urb_dequeue =          ohci_urb_dequeue,
 154        .endpoint_disable =     ohci_endpoint_disable,
 155
 156        /*
 157         * scheduling support
 158         */
 159        .get_frame_number =     ohci_get_frame,
 160
 161        /*
 162         * root hub support
 163         */
 164        .hub_status_data =      ohci_hub_status_data,
 165        .hub_control =          ohci_hub_control,
 166#ifdef  CONFIG_PM
 167        .bus_suspend =          ohci_bus_suspend,
 168        .bus_resume =           ohci_bus_resume,
 169#endif
 170        .start_port_reset =     ohci_start_port_reset,
 171};
 172
 173static int ohci_hcd_au1xxx_drv_probe(struct platform_device *pdev)
 174{
 175        int ret;
 176        struct usb_hcd *hcd;
 177
 178        if (usb_disabled())
 179                return -ENODEV;
 180
 181#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
 182        /* Au1200 AB USB does not support coherent memory */
 183        if (!(read_c0_prid() & 0xff)) {
 184                printk(KERN_INFO "%s: this is chip revision AB !!\n",
 185                        pdev->name);
 186                printk(KERN_INFO "%s: update your board or re-configure "
 187                                 "the kernel\n", pdev->name);
 188                return -ENODEV;
 189        }
 190#endif
 191
 192        if (pdev->resource[1].flags != IORESOURCE_IRQ) {
 193                pr_debug("resource[1] is not IORESOURCE_IRQ\n");
 194                return -ENOMEM;
 195        }
 196
 197        hcd = usb_create_hcd(&ohci_au1xxx_hc_driver, &pdev->dev, "au1xxx");
 198        if (!hcd)
 199                return -ENOMEM;
 200
 201        hcd->rsrc_start = pdev->resource[0].start;
 202        hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1;
 203
 204        if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
 205                pr_debug("request_mem_region failed\n");
 206                ret = -EBUSY;
 207                goto err1;
 208        }
 209
 210        hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
 211        if (!hcd->regs) {
 212                pr_debug("ioremap failed\n");
 213                ret = -ENOMEM;
 214                goto err2;
 215        }
 216
 217        au1xxx_start_ohc();
 218        ohci_hcd_init(hcd_to_ohci(hcd));
 219
 220        ret = usb_add_hcd(hcd, pdev->resource[1].start,
 221                          IRQF_DISABLED | IRQF_SHARED);
 222        if (ret == 0) {
 223                platform_set_drvdata(pdev, hcd);
 224                return ret;
 225        }
 226
 227        au1xxx_stop_ohc();
 228        iounmap(hcd->regs);
 229err2:
 230        release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
 231err1:
 232        usb_put_hcd(hcd);
 233        return ret;
 234}
 235
 236static int ohci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
 237{
 238        struct usb_hcd *hcd = platform_get_drvdata(pdev);
 239
 240        usb_remove_hcd(hcd);
 241        au1xxx_stop_ohc();
 242        iounmap(hcd->regs);
 243        release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
 244        usb_put_hcd(hcd);
 245        platform_set_drvdata(pdev, NULL);
 246
 247        return 0;
 248}
 249
 250#ifdef CONFIG_PM
 251static int ohci_hcd_au1xxx_drv_suspend(struct device *dev)
 252{
 253        struct usb_hcd *hcd = dev_get_drvdata(dev);
 254        struct ohci_hcd *ohci = hcd_to_ohci(hcd);
 255        unsigned long flags;
 256        int rc;
 257
 258        rc = 0;
 259
 260        /* Root hub was already suspended. Disable irq emission and
 261         * mark HW unaccessible, bail out if RH has been resumed. Use
 262         * the spinlock to properly synchronize with possible pending
 263         * RH suspend or resume activity.
 264         *
 265         * This is still racy as hcd->state is manipulated outside of
 266         * any locks =P But that will be a different fix.
 267         */
 268        spin_lock_irqsave(&ohci->lock, flags);
 269        if (hcd->state != HC_STATE_SUSPENDED) {
 270                rc = -EINVAL;
 271                goto bail;
 272        }
 273        ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
 274        (void)ohci_readl(ohci, &ohci->regs->intrdisable);
 275
 276        clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 277
 278        au1xxx_stop_ohc();
 279bail:
 280        spin_unlock_irqrestore(&ohci->lock, flags);
 281
 282        return rc;
 283}
 284
 285static int ohci_hcd_au1xxx_drv_resume(struct device *dev)
 286{
 287        struct usb_hcd *hcd = dev_get_drvdata(dev);
 288
 289        au1xxx_start_ohc();
 290
 291        set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 292        ohci_finish_controller_resume(hcd);
 293
 294        return 0;
 295}
 296
 297static struct dev_pm_ops au1xxx_ohci_pmops = {
 298        .suspend        = ohci_hcd_au1xxx_drv_suspend,
 299        .resume         = ohci_hcd_au1xxx_drv_resume,
 300};
 301
 302#define AU1XXX_OHCI_PMOPS &au1xxx_ohci_pmops
 303
 304#else
 305#define AU1XXX_OHCI_PMOPS NULL
 306#endif
 307
 308static struct platform_driver ohci_hcd_au1xxx_driver = {
 309        .probe          = ohci_hcd_au1xxx_drv_probe,
 310        .remove         = ohci_hcd_au1xxx_drv_remove,
 311        .shutdown       = usb_hcd_platform_shutdown,
 312        .driver         = {
 313                .name   = "au1xxx-ohci",
 314                .owner  = THIS_MODULE,
 315                .pm     = AU1XXX_OHCI_PMOPS,
 316        },
 317};
 318
 319MODULE_ALIAS("platform:au1xxx-ohci");
 320