Add preliminary RouterBoard RB1xx support
[openwrt.git] / target / linux / rb1xx-2.6 / files / drivers / usb / host / adm5120-hcd.c
diff --git a/target/linux/rb1xx-2.6/files/drivers/usb/host/adm5120-hcd.c b/target/linux/rb1xx-2.6/files/drivers/usb/host/adm5120-hcd.c
new file mode 100644 (file)
index 0000000..b4a1899
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+ *     HCD driver for ADM5120 SoC
+ *
+ *     Copyright (C) 2005 Jeroen Vreeken (pe1rxq@amsat.org)
+ *
+ *     Based on the ADMtek 2.4 driver 
+ *     (C) Copyright 2003 Junius Chen <juniusc@admtek.com.tw>
+ *     Which again was based on the ohci and uhci drivers.
+ */
+
+#include <linux/autoconf.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/platform_device.h>
+
+#include "../core/hcd.h"
+
+MODULE_DESCRIPTION("ADM5120 USB Host Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jeroen Vreeken (pe1rxq@amsat.org)");
+
+#define ADMHCD_REG_CONTROL             0x00
+#define ADMHCD_REG_INTSTATUS           0x04
+#define ADMHCD_REG_INTENABLE           0x08
+#define ADMHCD_REG_HOSTCONTROL         0x10
+#define ADMHCD_REG_FMINTERVAL          0x18
+#define ADMHCD_REG_FMNUMBER            0x1c
+#define ADMHCD_REG_LSTHRESH            0x70
+#define ADMHCD_REG_RHDESCR             0x74
+#define ADMHCD_REG_PORTSTATUS0         0x78
+#define ADMHCD_REG_PORTSTATUS1         0x7c
+#define ADMHCD_REG_HOSTHEAD            0x80
+
+
+#define ADMHCD_NUMPORTS                2
+
+#define ADMHCD_HOST_EN         0x00000001      /* Host enable */
+#define ADMHCD_SW_INTREQ       0x00000002      /* request software int */
+#define ADMHCD_SW_RESET                0x00000008      /* Reset */
+
+#define ADMHCD_INT_TD          0x00100000      /* TD completed */
+#define ADMHCD_INT_SW          0x20000000      /* software interrupt */
+#define ADMHCD_INT_FATAL       0x40000000      /* Fatal interrupt */
+#define ADMHCD_INT_ACT         0x80000000      /* Interrupt active */
+
+#define ADMHCD_STATE_RST       0x00000000      /* bus state reset */
+#define ADMHCD_STATE_RES       0x00000001      /* bus state resume */
+#define ADMHCD_STATE_OP                0x00000002      /* bus state operational */
+#define ADMHCD_STATE_SUS       0x00000003      /* bus state suspended */
+#define ADMHCD_DMA_EN          0x00000004      /* enable dma engine */
+
+#define ADMHCD_NPS             0x00000020      /* No Power Switch */
+#define ADMHCD_LPSC            0x04000000      /* Local power switch change */
+
+#define ADMHCD_CCS             0x00000001      /* current connect status */
+#define ADMHCD_PES             0x00000002      /* port enable status */
+#define ADMHCD_PSS             0x00000004      /* port suspend status */
+#define ADMHCD_POCI            0x00000008      /* port overcurrent indicator */
+#define ADMHCD_PRS             0x00000010      /* port reset status */
+#define ADMHCD_PPS             0x00000100      /* port power status */
+#define ADMHCD_LSDA            0x00000200      /* low speed device attached */
+#define ADMHCD_CSC             0x00010000      /* connect status change */
+#define ADMHCD_PESC            0x00020000      /* enable status change */
+#define ADMHCD_PSSC            0x00040000      /* suspend status change */
+#define ADMHCD_OCIC            0x00080000      /* overcurrent change*/
+#define ADMHCD_PRSC            0x00100000      /* reset status change */
+
+
+struct admhcd_ed {
+       /* Don't change first four, they used for DMA */
+       u32                             control;
+       struct admhcd_td                *tail;
+       struct admhcd_td                *head;
+       struct admhcd_ed                *next;
+       /* the rest is for the driver only: */
+       struct admhcd_td                *cur;
+       struct usb_host_endpoint        *ep;
+       struct urb                      *urb;
+       struct admhcd_ed                *real;
+} __attribute__ ((packed));
+
+#define ADMHCD_ED_EPSHIFT      7               /* Shift for endpoint number */
+#define ADMHCD_ED_INT          0x00000800      /* Is this an int endpoint */
+#define ADMHCD_ED_SPEED                0x00002000      /* Is it a high speed dev? */
+#define ADMHCD_ED_SKIP         0x00004000      /* Skip this ED */
+#define ADMHCD_ED_FORMAT       0x00008000      /* Is this an isoc endpoint */
+#define ADMHCD_ED_MAXSHIFT     16              /* Shift for max packet size */
+
+struct admhcd_td {
+       /* Don't change first four, they are used for DMA */
+       u32                     control;
+       u32                     buffer;
+       u32                     buflen;
+       struct admhcd_td        *next;
+       /* the rest is for the driver only: */
+       struct urb              *urb;
+       struct admhcd_td        *real;
+} __attribute__ ((packed));
+
+#define ADMHCD_TD_OWN          0x80000000
+#define ADMHCD_TD_TOGGLE       0x00000000
+#define ADMHCD_TD_DATA0                0x01000000
+#define ADMHCD_TD_DATA1                0x01800000
+#define ADMHCD_TD_OUT          0x00200000
+#define ADMHCD_TD_IN           0x00400000
+#define ADMHCD_TD_SETUP                0x00000000
+#define ADMHCD_TD_ISO          0x00010000
+#define ADMHCD_TD_R            0x00040000
+#define ADMHCD_TD_INTEN                0x00010000
+
+static int admhcd_td_err[16] = {
+       0,              /* No */
+       -EREMOTEIO,             /* CRC */
+       -EREMOTEIO,     /* bit stuff */
+       -EREMOTEIO,             /* data toggle */
+       -EPIPE,         /* stall */
+       -ETIMEDOUT,     /* timeout */
+       -EPROTO,        /* pid err */
+       -EPROTO,        /* unexpected pid */
+       -EREMOTEIO,     /* data overrun */
+       -EREMOTEIO,     /* data underrun */
+       -ETIMEDOUT,     /* 1010 */
+       -ETIMEDOUT,     /* 1011 */
+       -EREMOTEIO,     /* buffer overrun */
+       -EREMOTEIO,     /* buffer underrun */
+       -ETIMEDOUT,     /* 1110 */
+       -ETIMEDOUT,     /* 1111 */
+};
+
+#define ADMHCD_TD_ERRMASK      0x38000000
+#define ADMHCD_TD_ERRSHIFT     27
+
+#define TD(td) ((struct admhcd_td *)(((u32)(td)) & ~0xf))
+#define ED(ed) ((struct admhcd_ed *)(((u32)(ed)) & ~0xf))
+
+struct admhcd {
+       u32             base;
+       u32             dma_en;
+       spinlock_t      lock;
+       unsigned long   flags;
+};
+
+#define hcd_to_admhcd(hcd) ((struct admhcd *)(hcd)->hcd_priv)
+
+static char hcd_name[] = "adm5120-hcd";
+
+static u32 admhcd_reg_get(struct admhcd *ahcd, int reg)
+{
+       return *(volatile u32 *)KSEG1ADDR(ahcd->base+reg);
+}
+
+static void admhcd_reg_set(struct admhcd *ahcd, int reg, u32 val)
+{
+       *(volatile u32 *)KSEG1ADDR(ahcd->base+reg) = val;
+}
+
+static void admhcd_lock(struct admhcd *ahcd)
+{
+       spin_lock_irqsave(&ahcd->lock, ahcd->flags);
+       ahcd->dma_en = admhcd_reg_get(ahcd, ADMHCD_REG_HOSTCONTROL) &
+           ADMHCD_DMA_EN;
+       admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP);
+}
+
+static void admhcd_unlock(struct admhcd *ahcd)
+{
+       admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL,
+           ADMHCD_STATE_OP | ahcd->dma_en);
+       spin_unlock_irqrestore(&ahcd->lock, ahcd->flags);
+}
+
+static struct admhcd_td *admhcd_td_alloc(struct admhcd_ed *ed, struct urb *urb)
+{
+       struct admhcd_td *tdn, *td;
+
+       tdn = kmalloc(sizeof(struct admhcd_td), GFP_ATOMIC);
+       if (!tdn)
+               return NULL;
+       tdn->real = tdn;
+       tdn = (struct admhcd_td *)KSEG1ADDR(tdn);
+       memset(tdn, 0, sizeof(struct admhcd_td));
+       if (ed->cur == NULL) {
+               ed->cur = tdn;
+               ed->head = tdn;
+               ed->tail = tdn; 
+               td = tdn;
+       } else {
+               /* Supply back the old tail and link in new td as tail */
+               td = TD(ed->tail);
+               TD(ed->tail)->next = tdn;
+               ed->tail = tdn;
+       }
+       td->urb = urb;
+
+       return td;
+}
+
+static void admhcd_td_free(struct admhcd_ed *ed, struct urb *urb)
+{
+       struct admhcd_td *td, **tdp;
+
+       if (urb == NULL)
+               ed->control |= ADMHCD_ED_SKIP;
+       tdp = &ed->cur;
+       td = ed->cur;
+       do {
+               if (td->urb == urb)
+                       break;
+               tdp = &td->next;
+               td = TD(td->next);
+       } while (td);
+       while (td && td->urb == urb) {
+               *tdp = TD(td->next);
+               kfree(td->real);
+               td = *tdp;
+       }
+}
+
+/* Find an endpoint's descriptor, if needed allocate a new one and link it
+   in the DMA chain
+ */
+static struct admhcd_ed *admhcd_get_ed(struct admhcd *ahcd,
+    struct usb_host_endpoint *ep, struct urb *urb)
+{
+       struct admhcd_ed *hosthead;
+       struct admhcd_ed *found = NULL, *ed = NULL;
+       unsigned int pipe = urb->pipe;
+
+       admhcd_lock(ahcd);
+       hosthead = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD);
+       if (hosthead) {
+               for (ed = hosthead;; ed = ED(ed->next)) {
+                       if (ed->ep == ep) {
+                               found = ed;
+                               break;
+                       }
+                       if (ED(ed->next) == hosthead)
+                               break;
+               }
+       }
+       if (!found) {
+               found = kmalloc(sizeof(struct admhcd_ed), GFP_ATOMIC);
+               if (!found)
+                       goto out;
+               memset(found, 0, sizeof(struct admhcd_ed));
+               found->real = found;
+               found->ep = ep;
+               found = (struct admhcd_ed *)KSEG1ADDR(found);
+               found->control = usb_pipedevice(pipe) |
+                   (usb_pipeendpoint(pipe) << ADMHCD_ED_EPSHIFT) |
+                   (usb_pipeint(pipe) ? ADMHCD_ED_INT : 0) |
+                   (urb->dev->speed == USB_SPEED_FULL ? ADMHCD_ED_SPEED : 0) |
+                   (usb_pipeisoc(pipe) ? ADMHCD_ED_FORMAT : 0) |
+                   (usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)) << ADMHCD_ED_MAXSHIFT);
+               /* Alloc first dummy td */
+               admhcd_td_alloc(found, NULL);
+               if (hosthead) {
+                       found->next = hosthead;
+                       ed->next = found;
+               } else {
+                       found->next = found;
+                       admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, (u32)found);
+               }
+       }
+out:
+       admhcd_unlock(ahcd);
+       return found;
+}
+
+static struct admhcd_td *admhcd_td_fill(u32 control, struct admhcd_td *td,
+    dma_addr_t data, int len)
+{
+       td->buffer = data;
+       td->buflen = len;
+       td->control = control;
+       return TD(td->next);
+}
+
+static void admhcd_ed_start(struct admhcd *ahcd, struct admhcd_ed *ed)
+{
+       struct admhcd_td *td = ed->cur;
+
+       if (ed->urb)
+               return;
+       if (td->urb) {
+               ed->urb = td->urb;
+               while (1) {
+                       td->control |= ADMHCD_TD_OWN;
+                       if (TD(td->next)->urb != td->urb) {
+                               td->buflen |= ADMHCD_TD_INTEN;
+                               break;
+                       }
+                       td = TD(td->next);
+               }
+       }
+       ed->head = TD(ed->head);
+       ahcd->dma_en |= ADMHCD_DMA_EN;
+}
+
+static irqreturn_t adm5120hcd_irq(int irq, void *ptr, struct pt_regs *regs)
+{
+       struct usb_hcd *hcd = (struct usb_hcd *)ptr;
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+       u32 intstatus;
+
+       intstatus = admhcd_reg_get(ahcd, ADMHCD_REG_INTSTATUS);
+       if (intstatus & ADMHCD_INT_FATAL) {
+               admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_FATAL);
+               //
+       }
+       if (intstatus & ADMHCD_INT_SW) {
+               admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_SW);
+               //
+       }
+       if (intstatus & ADMHCD_INT_TD) {
+               struct admhcd_ed *ed, *head;
+               
+               admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_TD);
+
+               head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD);
+               ed = head;
+               if (ed) do {
+                       /* Is it a finished TD? */
+                       if (ed->urb && !(ed->cur->control & ADMHCD_TD_OWN)) {
+                               struct admhcd_td *td;
+                               int error;
+                               
+                               td = ed->cur;
+                               error = (td->control & ADMHCD_TD_ERRMASK) >>
+                                   ADMHCD_TD_ERRSHIFT;
+                               ed->urb->status = admhcd_td_err[error];
+                               admhcd_td_free(ed, ed->urb);
+                               // Calculate real length!!!
+                               ed->urb->actual_length = ed->urb->transfer_buffer_length;
+                               ed->urb->hcpriv = NULL;
+                               usb_hcd_giveback_urb(hcd, ed->urb);
+                               ed->urb = NULL;
+                       }
+                       admhcd_ed_start(ahcd, ed);
+                       ed = ED(ed->next);
+               } while (ed != head);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep,
+    struct urb *urb, int mem_flags)
+{
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+       struct admhcd_ed *ed;
+       struct admhcd_td *td;
+       int size = 0, i, zero = 0, ret = 0;
+       unsigned int pipe = urb->pipe, toggle = 0;
+       dma_addr_t data = (dma_addr_t)urb->transfer_buffer;
+       int data_len = urb->transfer_buffer_length;
+
+       ed = admhcd_get_ed(ahcd, ep, urb);
+       if (!ed)
+               return -ENOMEM;
+
+       switch(usb_pipetype(pipe)) {
+               case PIPE_CONTROL:
+                       size = 2;
+               case PIPE_INTERRUPT:
+               case PIPE_BULK:
+               default:
+                       size += urb->transfer_buffer_length / 4096;
+                       if (urb->transfer_buffer_length % 4096)
+                               size++;
+                       if (size == 0)
+                               size++;
+                       else if (urb->transfer_flags & URB_ZERO_PACKET &&
+                           !(urb->transfer_buffer_length %
+                             usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)))) {
+                               size++;
+                               zero = 1;
+                       }
+                       break;
+               case PIPE_ISOCHRONOUS:
+                       size = urb->number_of_packets;
+                       break;
+       }
+
+       admhcd_lock(ahcd);
+       /* Remember the first td */
+       td = admhcd_td_alloc(ed, urb);
+       if (!td) {
+               ret = -ENOMEM;
+               goto out;
+       }
+       /* Allocate additionall tds first */
+       for (i = 1; i < size; i++) {
+               if (admhcd_td_alloc(ed, urb) == NULL) {
+                       admhcd_td_free(ed, urb);
+                       ret = -ENOMEM;
+                       goto out;
+               }
+       }
+
+       if (usb_gettoggle(urb->dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)))
+               toggle = ADMHCD_TD_TOGGLE;
+       else {
+               toggle = ADMHCD_TD_DATA0;
+               usb_settoggle(urb->dev, usb_pipeendpoint(pipe),
+                   usb_pipeout(pipe), 1);
+       }
+
+       switch(usb_pipetype(pipe)) {
+               case PIPE_CONTROL:
+                       td = admhcd_td_fill(ADMHCD_TD_SETUP | ADMHCD_TD_DATA0,
+                           td, (dma_addr_t)urb->setup_packet, 8);
+                       while (data_len > 0) {
+                               td = admhcd_td_fill(ADMHCD_TD_DATA1 
+                                   | ADMHCD_TD_R |
+                                   (usb_pipeout(pipe) ?
+                                   ADMHCD_TD_OUT : ADMHCD_TD_IN), td,
+                                   data, data_len % 4097);
+                               data_len -= 4096;
+                       }
+                       admhcd_td_fill(ADMHCD_TD_DATA1 | (usb_pipeout(pipe) ?
+                           ADMHCD_TD_IN : ADMHCD_TD_OUT), td,
+                           data, 0);
+                       break;
+               case PIPE_INTERRUPT:
+               case PIPE_BULK:
+                       //info ok for interrupt?
+                       i = 0;
+                       while(data_len > 4096) {
+                               td = admhcd_td_fill((usb_pipeout(pipe) ?
+                                   ADMHCD_TD_OUT : 
+                                   ADMHCD_TD_IN | ADMHCD_TD_R) |
+                                   (i ? ADMHCD_TD_TOGGLE : toggle), td,
+                                   data, 4096);
+                               data += 4096;
+                               data_len -= 4096;
+                               i++;
+                       }
+                       td = admhcd_td_fill((usb_pipeout(pipe) ? 
+                           ADMHCD_TD_OUT : ADMHCD_TD_IN) |
+                           (i ? ADMHCD_TD_TOGGLE : toggle), td, data, data_len);
+                       i++;
+                       if (zero)
+                               admhcd_td_fill((usb_pipeout(pipe) ?
+                                   ADMHCD_TD_OUT : ADMHCD_TD_IN) |
+                                   (i ? ADMHCD_TD_TOGGLE : toggle), td, 0, 0);
+                       break;
+               case PIPE_ISOCHRONOUS:
+                       for (i = 0; i < urb->number_of_packets; i++) {
+                               td = admhcd_td_fill(ADMHCD_TD_ISO |
+                                   ((urb->start_frame + i) & 0xffff), td,
+                                   data + urb->iso_frame_desc[i].offset,
+                                   urb->iso_frame_desc[i].length);
+                       }
+                       break;
+       }
+       urb->hcpriv = ed;
+       admhcd_ed_start(ahcd, ed);
+out:
+       admhcd_unlock(ahcd);
+       return ret;
+}
+
+static int admhcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
+{
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+       struct admhcd_ed *ed;
+
+       admhcd_lock(ahcd);
+
+       ed = urb->hcpriv;
+       if (ed && ed->urb != urb)
+               admhcd_td_free(ed, urb);
+
+       admhcd_unlock(ahcd);
+       return 0;
+}
+
+static void admhcd_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+       struct admhcd_ed *ed, *edt, *head;
+
+       admhcd_lock(ahcd);
+
+       head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD);
+       if (!head)
+               goto out;
+       for (ed = head; ED(ed->next) != head; ed = ED(ed->next))
+               if (ed->ep == ep)
+                       break;
+       if (ed->ep != ep)
+               goto out;
+       while (ed->cur)
+               admhcd_td_free(ed, ed->cur->urb);
+       if (head == ed) {
+               if (ED(ed->next) == ed) {
+                       admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0);
+                       ahcd->dma_en = 0;
+                       goto out_free;
+               }
+               head = ED(ed->next);
+               for (edt = head; ED(edt->next) != head; edt = ED(edt->next));
+               edt->next = ED(ed->next);
+               admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, (u32)ed->next);
+               goto out_free;
+       }
+       for (edt = head; edt->next != ed; edt = edt->next);
+       edt->next = ed->next;
+out_free:
+       kfree(ed->real);
+out:
+       admhcd_unlock(ahcd);
+}
+
+static int admhcd_get_frame_number(struct usb_hcd *hcd)
+{
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+
+       return admhcd_reg_get(ahcd, ADMHCD_REG_FMNUMBER) & 0x0000ffff;
+}
+
+static int admhcd_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+       int port;
+
+       *buf = 0;
+       for (port = 0; port < ADMHCD_NUMPORTS; port++) {
+               if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4) &
+                   (ADMHCD_CSC | ADMHCD_PESC | ADMHCD_PSSC | ADMHCD_OCIC |
+                    ADMHCD_PRSC))
+                       *buf |= (1 << (port + 1));
+       }
+       return !!*buf;
+}
+
+static __u8 root_hub_hub_des[] = {
+       0x09,           /* __u8  bLength; */
+       0x29,           /* __u8  bDescriptorType; Hub-descriptor */
+       0x02,           /* __u8  bNbrPorts; */
+       0x0a, 0x00,     /* __u16 wHubCharacteristics; */
+       0x01,           /* __u8  bPwrOn2pwrGood; 2ms */
+       0x00,           /* __u8  bHubContrCurrent; 0mA */
+       0x00,           /* __u8  DeviceRemovable; */
+       0xff,           /* __u8  PortPwrCtrlMask; */
+};
+
+static int admhcd_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+    u16 wIndex, char *buf, u16 wLength)
+{
+       struct admhcd *ahcd = hcd_to_admhcd(hcd);
+       int retval = 0, len;
+       unsigned int port = wIndex -1;
+
+       switch (typeReq) {
+
+       case GetHubStatus:
+               *(__le32 *)buf = cpu_to_le32(0);
+               break;
+       case GetPortStatus:
+               if (port >= ADMHCD_NUMPORTS)
+                       goto err;
+               *(__le32 *)buf = cpu_to_le32(
+                   admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4));
+               break;
+       case SetHubFeature:             /* We don't implement these */
+       case ClearHubFeature:
+               switch (wValue) {
+               case C_HUB_OVER_CURRENT:
+               case C_HUB_LOCAL_POWER:
+                       break;
+               default:
+                       goto err;
+               }
+       case SetPortFeature:
+               if (port >= ADMHCD_NUMPORTS)
+                       goto err;
+
+               switch (wValue) {
+               case USB_PORT_FEAT_SUSPEND:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_PSS);
+                       break;
+               case USB_PORT_FEAT_RESET:
+                       if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4)
+                           & ADMHCD_CCS) {
+                               admhcd_reg_set(ahcd, 
+                                   ADMHCD_REG_PORTSTATUS0 + port*4,
+                                   ADMHCD_PRS | ADMHCD_CSC);
+                               mdelay(50);
+                               admhcd_reg_set(ahcd,
+                                   ADMHCD_REG_PORTSTATUS0 + port*4,
+                                   ADMHCD_PES | ADMHCD_CSC);
+                       }
+                       break;
+               case USB_PORT_FEAT_POWER:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_PPS);
+                       break;
+               default:
+                       goto err;
+               }
+               break;
+       case ClearPortFeature:
+               if (port >= ADMHCD_NUMPORTS)
+                       goto err;
+
+               switch (wValue) {
+               case USB_PORT_FEAT_ENABLE:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_CCS);
+                       break;
+               case USB_PORT_FEAT_C_ENABLE:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_PESC);
+                       break;
+               case USB_PORT_FEAT_SUSPEND:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_POCI);
+                       break;
+               case USB_PORT_FEAT_C_SUSPEND:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_PSSC);
+               case USB_PORT_FEAT_POWER:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_LSDA);
+                       break;
+               case USB_PORT_FEAT_C_CONNECTION:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_CSC);
+                       break;
+               case USB_PORT_FEAT_C_OVER_CURRENT:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_OCIC);
+                       break;
+               case USB_PORT_FEAT_C_RESET:
+                       admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4,
+                           ADMHCD_PRSC);
+                       break;
+               default:
+                       goto err;
+               }
+               break;
+       case GetHubDescriptor:
+               len = min_t(unsigned int, sizeof(root_hub_hub_des), wLength);
+               memcpy(buf, root_hub_hub_des, len);
+               break;
+       default:
+err:
+               retval = -EPIPE;
+       }
+
+       return retval;
+}
+
+static struct hc_driver adm5120_hc_driver = {
+       .description =          hcd_name,
+       .product_desc =         "ADM5120 HCD",
+       .hcd_priv_size =        sizeof(struct admhcd),
+       .flags =                HCD_USB11,
+       .urb_enqueue =          admhcd_urb_enqueue,
+       .urb_dequeue =          admhcd_urb_dequeue,
+       .endpoint_disable =     admhcd_endpoint_disable,
+       .get_frame_number =     admhcd_get_frame_number,
+       .hub_status_data =      admhcd_hub_status_data,
+       .hub_control =          admhcd_hub_control,
+};
+
+static int __init adm5120hcd_probe(struct platform_device *pdev)
+{
+       struct usb_hcd *hcd;
+       struct admhcd *ahcd;
+       struct usb_device *udev;
+       int err = 0;
+
+       if (!request_mem_region(pdev->resource[0].start,
+           pdev->resource[0].end - pdev->resource[0].start, hcd_name)) {
+               pr_debug("couldn't request mem\n");
+               err = -EBUSY;
+               goto out;
+       }
+
+       //hcd = usb_create_hcd(&adm5120_hc_driver, pdev, pdev->bus_id);
+       if (!hcd)
+               goto out_mem;
+
+       ahcd = hcd_to_admhcd(hcd);
+       dev_set_drvdata(pdev, ahcd);
+       hcd->self.controller = pdev;
+       //hcd->self.bus_name = pdev->bus_id;
+       hcd->irq = pdev->resource[1].start;
+       hcd->regs = (void *)pdev->resource[0].start;
+       hcd->product_desc = hcd_name;
+       ahcd->base = pdev->resource[0].start;
+
+       if (request_irq(pdev->resource[1].start, adm5120hcd_irq, 0, hcd_name,
+           hcd)) {
+               pr_debug("couldn't request irq\n");
+               err = -EBUSY;
+               goto out_hcd;
+       }
+
+       //err = usb_register_bus(&hcd->self);
+       //if (err < 0)
+       //      goto out_irq;
+
+       spin_lock_init(&ahcd->lock);
+
+       admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0);
+       mdelay(10);
+       admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET);
+       while (admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET)
+               mdelay(1);
+
+       admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_HOST_EN);
+       admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0x00000000);
+       admhcd_reg_set(ahcd, ADMHCD_REG_FMINTERVAL, 0x20002edf);
+       admhcd_reg_set(ahcd, ADMHCD_REG_LSTHRESH, 0x628);
+       admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE,
+           ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD);
+       admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS,
+           ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD);
+       admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, ADMHCD_NPS | ADMHCD_LPSC);
+       admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP);
+
+       udev = usb_alloc_dev(NULL, &hcd->self, 0);
+       if (!udev) {
+               err = -ENOMEM;
+               goto out_bus;
+       }
+
+       udev->speed = USB_SPEED_FULL;
+       hcd->state = HC_STATE_RUNNING;
+
+       //err = hcd_register_root(udev, hcd);
+       //if (err != 0) {
+       //      usb_put_dev(udev);
+       //      goto out_dev;
+       //}
+
+       return 0;
+
+out_dev:
+       usb_put_dev(udev);
+out_bus:
+       //usb_deregister_bus(&hcd->self);
+out_irq:
+       free_irq(pdev->resource[1].start, hcd);
+out_hcd:
+       usb_put_hcd(hcd);
+out_mem:
+       release_mem_region(pdev->resource[0].start,
+           pdev->resource[0].end - pdev->resource[0].start);
+out:
+       return err;
+}
+
+static int __init_or_module adm5120hcd_remove(struct platform_device *pdev)
+{
+       device_init_wakeup(&pdev->dev, 0);
+}
+
+static struct platform_driver adm5120hcd_driver = {
+       .probe =        adm5120hcd_probe,
+       .remove =       adm5120hcd_remove,
+       .driver =       {
+               .name   = "adm5120-hcd",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init adm5120hcd_init(void)
+{
+       if (usb_disabled())
+               return -ENODEV;
+
+       return platform_driver_register(&adm5120hcd_driver);
+}
+
+static void __exit adm5120hcd_exit(void)
+{
+       platform_driver_unregister(&adm5120hcd_driver);
+}
+
+module_init(adm5120hcd_init);
+module_exit(adm5120hcd_exit);