
#define UHCI_USBLEGSUP          0x00c0  /* legacy support */
#define UHCI_USBCMD                  0  /* command register */
#define UHCI_USBINTR                 4  /* interrupt register */
#define UHCI_USBLEGSUP_RWC      0x8f00  /* the R/WC bits */
#define UHCI_USBLEGSUP_RO       0x5040  /* R/O and reserved bits */
#define UHCI_USBCMD_RUN         0x0001  /* RUN/STOP bit */
#define UHCI_USBCMD_HCRESET     0x0002  /* Host Controller reset */
#define UHCI_USBCMD_EGSM        0x0008  /* Global Suspend Mode */
#define UHCI_USBCMD_CONFIGURE   0x0040  /* Config Flag */
#define UHCI_USBINTR_RESUME     0x0002  /* Resume interrupt enable */


#define USBCMD                       0
#define  USBCMD_RS              0x0001  /* Run/Stop */
#define  USBCMD_HCRESET         0x0002  /* Host reset */
#define  USBCMD_GRESET          0x0004  /* Global reset */
#define  USBCMD_EGSM            0x0008  /* Global Suspend Mode */
#define  USBCMD_FGR             0x0010  /* Force Global Resume */
#define  USBCMD_SWDBG           0x0020  /* SW Debug mode */
#define  USBCMD_CF              0x0040  /* Config Flag (sw only) */
#define  USBCMD_MAXP            0x0080  /* Max Packet (0 = 32, 1 = 64) */

#define  USBSTS                      2
#define   USBSTS_USBINT         0x0001  /* Interrupt due to IOC */
#define   USBSTS_ERROR          0x0002  /* Interrupt due to error */
#define   USBSTS_RD             0x0004  /* Resume Detect */
#define   USBSTS_HSE            0x0008  /* Host System Error: PCI problems */
#define   USBSTS_HCPE           0x0010  /* Host Controller Process Error:
                                         * the schedule is buggy */
#define   USBSTS_HCH            0x0020  /* HC Halted */


#define  USBFRNUM                    6
#define  USBFLBASEADD                8
#define  USBSOF                     12
#define  USBSOF_DEFAULT             64  /* Frame length is exactly 1 ms */

#define  USBPORTSC1                 16
#define  USBPORTSC2                 18

#define  UHCI_RH_MAXCHILD            7


/*
 * Make sure the controller is completely inactive, unable to
 * generate interrupts or do DMA.
 */
void uhci_reset_hc(hc_t *hc)
{
	/* Turn off PIRQ enable and SMI enable.  (This also turns off the
	 * BIOS's USB Legacy Support.)  Turn off all the R/WC bits too.
	 */
    pciWriteWord(hc->PciTag, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);

	/* Reset the HC - this will force us to get a
	 * new notification of any already connected
	 * ports due to the virtual disconnect that it
	 * implies.
	 */
    out16(hc->iobase + UHCI_USBCMD, UHCI_USBCMD_HCRESET);
    __asm__ __volatile__ ("":::"memory");

    delay(20/10);

    if (in16(hc->iobase + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)
        dbgprintf("HCRESET not completed yet!\n");

	/* Just to be safe, disable interrupt requests and
	 * make sure the controller is stopped.
	 */
    out16(hc->iobase + UHCI_USBINTR, 0);
    out16(hc->iobase + UHCI_USBCMD, 0);
};

int uhci_check_and_reset_hc(hc_t *hc)
{
    u16_t legsup;
	unsigned int cmd, intr;

	/*
	 * When restarting a suspended controller, we expect all the
	 * settings to be the same as we left them:
	 *
	 *	PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
	 *	Controller is stopped and configured with EGSM set;
	 *	No interrupts enabled except possibly Resume Detect.
	 *
	 * If any of these conditions are violated we do a complete reset.
	 */
    legsup = pciReadWord(hc->PciTag, UHCI_USBLEGSUP);
	if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) {
        dbgprintf("%s: legsup = 0x%04x\n",__FUNCTION__, legsup);
		goto reset_needed;
	}

    cmd = in16(hc->iobase + UHCI_USBCMD);
    if ( (cmd & UHCI_USBCMD_RUN) ||
        !(cmd & UHCI_USBCMD_CONFIGURE) ||
        !(cmd & UHCI_USBCMD_EGSM))
    {
        dbgprintf("%s: cmd = 0x%04x\n", __FUNCTION__, cmd);
		goto reset_needed;
	}

    intr = in16(hc->iobase + UHCI_USBINTR);
    if (intr & (~UHCI_USBINTR_RESUME))
    {
        dbgprintf("%s: intr = 0x%04x\n", __FUNCTION__, intr);
		goto reset_needed;
	}
	return 0;

reset_needed:
    dbgprintf("Performing full reset\n");
    uhci_reset_hc(hc);
	return 1;
}


Bool init_hc(hc_t *hc)
{
	int port;
    u32_t ifl;
    u16_t dev_status;
    int i;

    dbgprintf("\n\ninit uhci %x\n\n", hc->pciId);

    for(i=0;i<6;i++)
    {
        if(hc->ioBase[i]){
           hc->iobase = hc->ioBase[i];
       //    dbgprintf("Io base_%d 0x%x\n", i,hc->ioBase[i]);
           break;
        };
    };

	/* The UHCI spec says devices must have 2 ports, and goes on to say
	 * they may have more but gives no way to determine how many there
	 * are.  However according to the UHCI spec, Bit 7 of the port
	 * status and control register is always set to 1.  So we try to
	 * use this to our advantage.  Another common failure mode when
	 * a nonexistent register is addressed is to return all ones, so
	 * we test for that also.
	 */
    for (port = 0; port < 2; port++)
    {
        u32_t status;

        status = in16(hc->iobase + USBPORTSC1 + (port * 2));
        dbgprintf("port%d status %x\n", port, status);
        if (!(status & 0x0080) || status == 0xffff)
			break;
	}
    dbgprintf("detected %d ports\n\n", port);

    hc->numports = port;

	/* Kick BIOS off this hardware and reset if the controller
	 * isn't already safely quiescent.
	 */
    uhci_check_and_reset_hc(hc);

    hc->frame_base   = (u32_t*)KernelAlloc(4096);
    hc->frame_dma    = GetPgAddr(hc->frame_base);
    hc->frame_number = 0;

    qh_t *qh = alloc_qh();

    qh->qlink = 1;
    qh->qelem = 1;

    hc->qh1 = qh;

 //   dbgprintf("alloc qh %x dma %x\n", qh, qh->dma);

    for(i=0; i<1024; i++)
        hc->frame_base[i] = qh->dma | 2;


    /* Set the frame length to the default: 1 ms exactly */
    out8(hc->iobase + USBSOF, USBSOF_DEFAULT);

	/* Store the frame list base address */
    out32(hc->iobase + USBFLBASEADD, hc->frame_dma);

	/* Set the current frame number */
    out16(hc->iobase + USBFRNUM, 0);

    out16(hc->iobase + USBSTS, 0x3F);
    out16(hc->iobase + USBCMD, USBCMD_RS | USBCMD_CF |
                               USBCMD_MAXP);

    for (port = 0; port < hc->numports; ++port)
        out16(hc->iobase + USBPORTSC1 + (port * 2), 0x200);
    delay(100/10);

    for (port = 0; port < 2; ++port)
    {
        time_t timeout;

        u32_t status = in16(hc->iobase + USBPORTSC1 + (port * 2));
        dbgprintf("port%d status %x\n", port, status);

        out16(hc->iobase + USBPORTSC1 + (port * 2), 0);

        timeout = 100/10;
        while(timeout--)
        {
            delay(10/10);
            status = in16(hc->iobase + USBPORTSC1 + (port * 2));
            if(status & 1)
            {
                udev_t *dev = malloc(sizeof(udev_t));

                out16(hc->iobase + USBPORTSC1 + (port * 2), 0x0E);

                delay(20/10);

                dbgprintf("enable port\n");
                status = in16(hc->iobase + USBPORTSC1 + (port * 2));
                dbgprintf("port%d status %x\n", port, status);

                link_initialize(&dev->link);
                dev->id       = 0;
                dev->host     = hc;
                dev->addr     = 0;
                dev->port     = port;
                dev->ep0_size = 8;
                dev->status   = status;

                dbgprintf("port%d connected", port);
                if(status & 4)
                    dbgprintf(" enabled");
                else
                    dbgprintf(" disabled");
                if(status & 0x100){
                    dev->speed = 0x4000000;
                    dbgprintf(" low speed\n");
                } else {
                    dev->speed = 0;
                    dbgprintf(" full speed\n");
                };

                if(set_address(dev)) {
                    list_prepend(&dev->link, &newdev_list);
                    hc->port_map |= 1<<port;
                }
                else {
                    free(dev);
                    out16(hc->iobase + USBPORTSC1 + (port * 2), 0);
                }
                break;
            };
        };
    };
    return TRUE;
};

u16_t __attribute__((aligned(16)))
      req_descr[4]  = {0x0680,0x0100,0x0000,8};

/*
IN(69) OUT(E1) SETUP(2D)
SETUP(0) IN(1)
SETUP(0) OUT(1) OUT(0) OUT(1)...IN(1)
SETUP(0) IN(1) IN(0) IN(1)...OUT(0)
*/


Bool set_address(udev_t *dev)
{
    static  udev_id   = 0;
    static  udev_addr = 0;
    static  u16_t __attribute__((aligned(16)))
            req_addr[4] = {0x0500,0x0001,0x0000,0x0000};

    static  u16_t __attribute__((aligned(16)))
            req_descr[4]  = {0x0680,0x0100,0x0000,8};

    static  u32_t   data[2] __attribute__((aligned(16)));

    qh_t  *qh;
    td_t  *td0, *td1, *td2;
    u32_t   dev_status;
    count_t timeout;
    int address;

    address   = ++udev_addr;

    req_addr[1] = address;

    if( !ctrl_request(dev, &req_addr, DOUT, NULL, 0))
        return FALSE;

    dev->addr = address;
    dev->id   = (++udev_id << 8) | address;

    dbgprintf("set address %d\n", address);

    data[0] = 0;
    data[1] = 0;

    if( !ctrl_request(dev, &req_descr, DIN, data, 8))
        return FALSE;

    dev_descr_t *descr = (dev_descr_t*)&data;
    dev->ep0_size = descr->bMaxPacketSize0;

    return TRUE;
}

request_t *create_request(udev_t *dev, endp_t *enp, u32_t dir,
                          void *data, size_t req_size)
{
    td_t  *td, *td_prev;
    addr_t data_dma;

    request_t *rq = (request_t*)malloc(sizeof(request_t));

    link_initialize(&rq->link);

    rq->td_head = 0;
    rq->td_tail = 0;

    rq->data = (addr_t)data;
    rq->size = req_size;
    rq->dev = dev;

    if(data)
        data_dma = DMA(data);

    td_prev = NULL;

    while(req_size >= enp->size)
    {
        td = alloc_td();
        td->link   = 1;

        if(rq->td_head == NULL)
            rq->td_head = td;

        if( td_prev )
            td_prev->link   = td->dma | 4;
        td->status = 0x00800000 | dev->speed;
        td->token  = TOKEN(enp->size,enp->toggle,enp->address,
                           dev->addr,dir);
        td->buffer = data_dma;
        td->bk     = td_prev;

        td_prev = td;

        data_dma+= enp->size;
        req_size-= enp->size;
        enp->toggle ^= DATA1;
    }
    if(req_size)
    {
        td = alloc_td();
        td->link   = 1;

        if(rq->td_head == NULL)
            rq->td_head = td;

        if( td_prev )
            td_prev->link   = td->dma | 4;

        td->status = 0x00800000 | dev->speed;
        td->token  = TOKEN( req_size, enp->toggle, enp->address,
                            dev->addr, dir);
        td->buffer = data_dma;
        td->bk     = td_prev;

        enp->toggle ^= DATA1;
    }
    rq->td_tail = td;
/*
    dbgprintf("create request %x\n"
              "head           %x\n"
              "tail           %x\n"
              "data           %x\n"
              "size           %x\n",
              rq, rq->td_head, rq->td_tail,
              rq->data, rq->size);
*/
    return rq;
}

Bool ctrl_request(udev_t *dev, void *req, u32_t pid,
                  void *data, size_t req_size)
{
    size_t  packet_size = dev->ep0_size;
    size_t  size = req_size;
    u32_t   toggle = DATA1;

    td_t   *td0, *td, *td_prev;
    qh_t   *qh;
    addr_t  data_dma = 0;
    Bool    retval;

    td0 = alloc_td();

    td0->status = 0x00800000 | dev->speed;
    td0->token  = TOKEN( 8, DATA0, 0, dev->addr, 0x2D);
    td0->buffer = DMA(req);
    td0->bk     = NULL;

    if(data)
        data_dma = DMA(data);

    td_prev = td0;

    while(size >= packet_size)
    {
        td = alloc_td();
        td_prev->link   = td->dma | 4;
        td->status = 0x00800000 | dev->speed;
        td->token  = TOKEN(packet_size, toggle, 0,dev->addr, pid);
        td->buffer = data_dma;
        td->bk     = td_prev;

        td_prev = td;

        data_dma+= packet_size;
        size-= packet_size;
        toggle ^= DATA1;
    }
    if(size)
    {
        td = alloc_td();
        td_prev->link   = td->dma | 4;
        td->status = 0x00800000 | dev->speed;
        td->token  = ((size-1)<<21)|toggle|(dev->addr<<8)|pid;
        td->buffer = data_dma;
        td->bk     = td_prev;

        td_prev = td;

        data_dma+= packet_size;
        size-= packet_size;
        toggle ^= DATA1;
    }

    td = alloc_td();
    td_prev->link   = td->dma | 4;

    pid = (pid == DIN) ? DOUT : DIN;

    td->link = 1;
    td->status = 0x00800000 | dev->speed;
    td->token  = (0x7FF<<21)|DATA1|(dev->addr<<8)|pid;
    td->buffer = 0;
    td->bk     = td_prev;

    qh = dev->host->qh1;

    qh->qelem = td0->dma;
    __asm__ __volatile__ ("":::"memory");

    count_t timeout  = 25;
    while(timeout--){
        delay(10/10);
        if( !(td->status & TD_CTRL_ACTIVE))
            break;
    }

    if( (td0->status & TD_ANY_ERROR) ||
        (td_prev->status & TD_ANY_ERROR) ||
        (td->status & TD_ANY_ERROR))
    {
        u32_t dev_status = in16(dev->host->iobase + USBSTS);

        dbgprintf("\nframe %x, cmd %x status %x\n",
                   in16(dev->host->iobase + USBFRNUM),
                   in16(dev->host->iobase + USBCMD),
                    dev_status);
        dbgprintf("td0 status %x\n",td0->status);
        dbgprintf("td_prev status %x\n",td_prev->status);
        dbgprintf("td status %x\n",td->status);
        dbgprintf("qh %x \n", qh->qelem);

        retval = FALSE;
    } else retval = TRUE;

    do
    {
        td_prev = td->bk;
        free_td(td);
        td = td_prev;
    }while( td != NULL);

    return retval;
};


Bool init_device(udev_t *dev)
{
    static  u16_t __attribute__((aligned(16)))
            req_descr[4]  = {0x0680,0x0100,0x0000,18};

    static  u16_t __attribute__((aligned(16)))
            req_conf[4]  = {0x0680,0x0200,0x0000,9};

    static dev_descr_t __attribute__((aligned(16))) descr;

    interface_descr_t *interface;

    u32_t  data[8];

    u8_t *dptr;
    conf_descr_t      *conf;

    dbgprintf("\ninit device %x, host %x, port %d\n\n",
               dev->id, dev->host->pciId, dev->port);

    if( !ctrl_request(dev, req_descr, DIN, &descr, 18))
        return;

    dev->dev_descr = descr;

    dbgprintf("device descriptor:\n\n"
              "bLength             %d\n"
              "bDescriptorType     %d\n"
              "bcdUSB              %x\n"
              "bDeviceClass        %x\n"
              "bDeviceSubClass     %x\n"
              "bDeviceProtocol     %x\n"
              "bMaxPacketSize0     %d\n"
              "idVendor            %x\n"
              "idProduct           %x\n"
              "bcdDevice           %x\n"
              "iManufacturer       %x\n"
              "iProduct            %x\n"
              "iSerialNumber       %x\n"
              "bNumConfigurations  %d\n\n",
              descr.bLength, descr.bDescriptorType,
              descr.bcdUSB,  descr.bDeviceClass,
              descr.bDeviceSubClass, descr.bDeviceProtocol,
              descr.bMaxPacketSize0, descr.idVendor,
              descr.idProduct, descr.bcdDevice,
              descr.iManufacturer, descr.iProduct,
              descr.iSerialNumber, descr.bNumConfigurations);

    req_conf[3] = 8;
    if( !ctrl_request(dev, req_conf, DIN, &data, 8))
        return;

    conf = (conf_descr_t*)&data;

    size_t conf_size = conf->wTotalLength;

    req_conf[3] = conf_size;
    conf = malloc(conf_size);

    if( !ctrl_request(dev, req_conf, DIN, conf, conf_size))
        return;

    dptr = (u8_t*)conf;
    dptr+= conf->bLength;

    dbgprintf("configuration descriptor\n\n"
              "bLength             %d\n"
              "bDescriptorType     %d\n"
              "wTotalLength        %d\n"
              "bNumInterfaces      %d\n"
              "bConfigurationValue %x\n"
              "iConfiguration      %d\n"
              "bmAttributes        %x\n"
              "bMaxPower           %dmA\n\n",
              conf->bLength,
              conf->bDescriptorType,
              conf->wTotalLength,
              conf->bNumInterfaces,
              conf->bConfigurationValue,
              conf->iConfiguration,
              conf->bmAttributes,
              conf->bMaxPower*2);

        interface = (interface_descr_t*)dptr;

        switch(interface->bInterfaceClass)
        {
            case USB_CLASS_AUDIO:
                dbgprintf( "audio device\n");
                break;
            case USB_CLASS_HID:
                dev->conf = conf;
                list_remove(&dev->link);
                return init_hid(dev);

            case USB_CLASS_PRINTER:
                dbgprintf("printer\n");
                break;
            case USB_CLASS_MASS_STORAGE:
                dbgprintf("mass storage device\n");
                break;
            case USB_CLASS_HUB:
                dbgprintf("hub device\n");
                break;
            default:
                dbgprintf("unknown device\n");
        };
};
