/*
 * Universal Host Controller Interface driver for USB.
 *
 * (C) Copyright 1999 Linus Torvalds
 *
 * Intel documents this fairly well, and as far as I know there
 * are no royalties or anything like that, but even so there are
 * people who decided that they want to do the same thing in a
 * completely different way.
 *
 * Oh, well. The intel version is the more common by far. As such,
 * that's the one I care about right now.
 *
 * WARNING! The USB documentation is downright evil. Most of it
 * is just crap, written by a committee. You're better off ignoring
 * most of it, the important stuff is:
 *  - the low-level protocol (fairly simple but lots of small details)
 *  - working around the horridness of the rest
 */

#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/malloc.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>

#include "uhci.h"

static struct wait_queue *uhci_configure = NULL;

static void wait_ms(unsigned int ms)
{
	current->state = TASK_UNINTERRUPTIBLE;
	schedule_timeout(1 + ms / 10);
}

static void show_cmd(void *cmd)
{
	unsigned char *c=cmd;
	printk("  cmd: %02x %02x %02x %02x %02x %02x %02x %02x\n",
		c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
}

static void show_td(struct uhci_td * td)
{
	printk("%08x ", td->link);
	printk("%se%d %s%s%s%s%s%s%s%s%s%sLength=%x ",
		((td->status >> 29) & 1) ? "SPD " : "",
		((td->status >> 27) & 3),
		((td->status >> 26) & 1) ? "LS " : "",
		((td->status >> 25) & 1) ? "IOS " : "",
		((td->status >> 24) & 1) ? "IOC " : "",
		((td->status >> 23) & 1) ? "Active " : "",
		((td->status >> 22) & 1) ? "Stalled " : "",
		((td->status >> 21) & 1) ? "DataBufErr " : "",
		((td->status >> 20) & 1) ? "Babble " : "",
		((td->status >> 19) & 1) ? "NAK " : "",
		((td->status >> 18) & 1) ? "CRC/Timeo " : "",
		((td->status >> 17) & 1) ? "BitStuff " : "",
		td->status & 0x7ff);
	printk("MaxLen=%x %sEndPt=%x Dev=%x, PID=%x ",
		td->info >> 21,
		 ((td->info >> 19) & 1) ? "DT " : "",
		 (td->info >> 15) & 15,
		 (td->info >> 8) & 127,
		 td->info & 0xff);
	printk("(buf=%08x)\n", td->buffer);
}

/*
 * Right now this driver only drives a single uhci,
 * so we allocate the structure statically. We should
 * eventually fix this (you can just load multiple
 * copies of the same module for multiple controllers,
 * though).
 *
 * We could allocate all of this dynamically, but
 * it's basically fairly rare to have multiple USB
 * buses.
 */
struct uhci static_uhci = {
	0,			/* IRQ - filled in by PCI probe */
	0,			/* IO Address - filled in by PCI probe */

	/* Device number bitmap */
	{ { 0, } },

	/* Root hub device "usb_device" information */
	{
		-1,			/* devnum */
		1,			/* speed */
		{ 0, },			/* descriptor */
		&static_uhci.devmap,	/* device number allocation map */
		NULL,			/* parent */
		{ NULL, }		/* children */
	}
};

/*
 * Allocate a frame list, and four regular queues.
 *
 * The hardware doesn't really know any difference
 * in the queues, but the order does matter for the
 * protocols higher up. The order is:
 *
 *  - any isochronous events handled before any
 *    of the queues. We don't do that here, because
 *    we'll create the actual TD entries on demand.
 *  - The first queue is the "interrupt queue".
 *  - The second queue is the "control queue".
 *  - The third queue is "bulk data".
 *
 * We could certainly have multiple queues of the same
 * type, and maybe we should. We could have per-device
 * queues, for example. We begin small.
 */
static struct uhci * alloc_uhci(unsigned int io_addr)
{
	struct uhci *uhci = &static_uhci;
	struct uhci_mem *data;

	uhci->irq = -1;
	uhci->io_addr = io_addr;

	/*
	 * We allocate a 8kB area for the USB data. The area
	 * is described by the uhci_mem structure, and basically
	 * contains everything needed for normal operation.
	 *
	 * The first page is used for QH and fixed TD memory,
	 * along with some static per-controller data areas.
	 *
	 * The second page is used for the frame list.
	 */
	data = (struct uhci_mem *) __get_free_pages(GFP_KERNEL, 1);
	uhci->data = data;
	if (!data)
		return NULL;

	memset(data, 0, sizeof(*data));

	/*
	 * Initialize the queues. They all start out empty,
	 * linked to each other in the proper order.
	 */
	data->interrupt_qh.link = 2 | virt_to_bus(&data->control_qh);
	data->interrupt_qh.element = 1;
	data->control_qh.link = 2 | virt_to_bus(&data->bulk_qh);
	data->control_qh.element = 1;
	data->bulk_qh.link = 1;
	data->bulk_qh.element = 1;

	/*
	 * Fill the frame list: make all entries point to
	 * the interrupt queue.
	 */
	{
		int i = 1024;
		unsigned int entry = 2 | virt_to_bus(&data->interrupt_qh);
		unsigned int *frame = data->fl.frame;
		do {
			*frame = entry;
			frame++;
		} while (--i);
	}

	return uhci;
}

/*
 * De-allocate all resources..
 */
static void release_uhci(struct uhci *uhci)
{
	if (uhci->irq >= 0) {
		free_irq(uhci->irq, uhci);
		uhci->irq = -1;
	}

	if (uhci->data) {
		free_pages((unsigned int) uhci->data, 1);
		uhci->data = NULL;
	}
}

static void show_sc(int port, unsigned short status)
{
	printk("  stat%d     =     %04x   %s%s%s%s%s%s%s%s\n",
		port,
		status,
		(status & (1 << 12)) ? " PortSuspend" : "",
		(status & (1 << 9)) ? " PortReset" : "",
		(status & (1 << 8)) ? " LowSpeed" : "",
		(status & 0x40) ? " ResumeDetect" : "",
		(status & 0x08) ? " EnableChange" : "",
		(status & 0x04) ? " PortEnabled" : "",
		(status & 0x02) ? " ConnectChange" : "",
		(status & 0x01) ? " PortConnected" : "");
}

static void show_status(struct uhci *uhci)
{
	unsigned int io_addr = uhci->io_addr;
	unsigned short usbcmd, usbstat, usbint, usbfrnum;
	unsigned int flbaseadd;
	unsigned char sof;
	unsigned short portsc1, portsc2;

	usbcmd    = inw(io_addr + 0);
	usbstat   = inw(io_addr + 2);
	usbint    = inw(io_addr + 4);
	usbfrnum  = inw(io_addr + 6);
	flbaseadd = inl(io_addr + 8);
	sof       = inb(io_addr + 12);
	portsc1   = inw(io_addr + 16);
	portsc2   = inw(io_addr + 18);

	printk("  usbcmd    =     %04x   %s%s%s%s%s%s%s%s\n",
		usbcmd,
		(usbcmd & 0x80) ? " Maxp64" : " Maxp32",
		(usbcmd & 0x40) ? " CF" : "",
		(usbcmd & 0x20) ? " SWDBG" : "",
		(usbcmd & 0x10) ? " FGR" : "",
		(usbcmd & 0x08) ? " EGSM" : "",
		(usbcmd & 0x04) ? " GRESET" : "",
		(usbcmd & 0x02) ? " HCRESET" : "",
		(usbcmd & 0x01) ? " RS" : "");

	printk("  usbstat   =     %04x   %s%s%s%s%s%s\n",
		usbstat,
		(usbstat & 0x20) ? " HCHalted" : "",
		(usbstat & 0x10) ? " HostControllerProcessError" : "",
		(usbstat & 0x08) ? " HostSystemError" : "",
		(usbstat & 0x04) ? " ResumeDetect" : "",
		(usbstat & 0x02) ? " USBError" : "",
		(usbstat & 0x01) ? " USBINT" : "");

	printk("  usbint    =     %04x\n", usbint);
	printk("  usbfrnum  =   (%d)%03x\n", (usbfrnum >> 10) & 1, 0xfff & (4*(unsigned int)usbfrnum));
	printk("  flbaseadd = %08x\n", flbaseadd);
	printk("  sof       =       %02x\n", sof);
	show_sc(1, portsc1);
	show_sc(2, portsc2);
}

/*
 * Wait for a TD, returning the status of
 * it..
 *
 * Do this more sanely some day (we have several sw
 * fields in the TD itself, we should just create a
 * simple list of active entries being waited on in
 * that, and then have the interrupt handler wake
 * us up).
 *
 * This is really cheezy. Don't look.
 */
static int uhci_td_result(struct uhci_td *td)
{
	int count = 40;

	do {
		wait_ms(10);
		/* No longer active? */
		if (!(td->status & (1 << 23)))
			return (td->status >> 16) & 0xff;
	} while (--count);
	return -1;
		
}

/*
 * Send or receive a control message on a pipe.
 *
 * Note that the "pipe" structure is set up to map
 * easily to the uhci destination fields.
 */
static int uhci_control_msg(struct uhci *uhci, unsigned int pipe, void *cmd, void *data, int len)
{
	struct uhci_td *td = uhci->data->control;
	unsigned long destination, status;

	/* The "pipe" thing contains the destination in bits 8--18, 0x2D is SETUP */
	destination = (pipe & 0x0007ff00) | 0x2D;

	/* Status:    slow/fast,       Active,    Short Packet Detect     Three Errors */
	status = (pipe & (1 << 26)) | (1 << 23)   |   (1 << 29)       |    (3 << 27);

	/*
	 * Build the TD for the control request
	 */
	td->link = 4 | virt_to_bus(td+1);		/* Point to next TD for input data */
	td->status = status;				/* Try forever */
	td->info = destination | (7 << 21);		/* 8 bytes of data */
	td->buffer = virt_to_bus(cmd);

	/*
	 * If direction is "send", change the frame from SETUP (0x2D)
	 * to OUT (0xE1). Else change it from SETUP to IN (0x69)
	 */
	destination ^= (0x2D ^ 0x69);			/* SETUP -> IN */
	if (usb_pipeout(pipe))
		destination ^= (0xE1 ^ 0x69);		/* IN -> OUT */

	td++;

	/*
	 * Build the DATA TD's
	 */
	while (len > 0) {
		/* Build the TD for control status */
		int pktsze = len;
		int maxsze = usb_maxpacket(pipe);

		if (pktsze > maxsze)
			pktsze = maxsze;

		/* Alternate Data0/1 (start with Data1) */
		destination ^= 1 << 19;
	
		td->link = 4 | virt_to_bus(td+1);			/* Point to next TD for data */
		td->status = status;					/* Status */
		td->info = destination | ((pktsze-1) << 21);		/* pktsze bytes of data */
		td->buffer = virt_to_bus(data);

		td++;
		data += maxsze;
		len -= maxsze;
	}

	/*
	 * Build the final TD for control status
	 */
	destination ^= (0xE1 ^ 0x69);			/* OUT -> IN */
	destination |= 1 << 19;				/* End in Data1 */

	td->link = 1;					/* Terminate */
	td->status = status | (1 << 24);		/* IOC */
	td->info = destination | (0x7ff << 21);		/* 0 bytes of data */
	td->buffer = 0;

	/* Start it up.. */
	wmb();
	uhci->data->control_qh.element = virt_to_bus(uhci->data->control);

	return uhci_td_result(td);
}

/*
 * This builds up everything needed for a control structure.
 *
 * The control request needs:
 *  - the queue heads		(8 bytes, 16-byte aligned)
 *  - the TD's themselves	(16 bytes, 16-byte aligned + 4 "sw" bytes)
 *  - the actual data area involved.
 *
 * We split up the data areas into 8-byte packets, because
 * (a) that's the smallest packet, so everybody should be able to handle it
 * (b) it allows us to pack all of this in one 32-byte thing with no wasted
 *     space (the QH's have to be 16-byte aligned, so we basically have 8
 *     bytes worth of padding that we might as well use).
 *
 * Allocate this in just one page.
 */
static int uhci_set_address(struct uhci *uhci, struct usb_device *dev)
{
	unsigned char setaddr[8] = { 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
	int result;	

	setaddr[2] = dev->devnum;

	result = uhci_control_msg(uhci, usb_snddefctrl(dev), setaddr, NULL, 0);
	if (result) {
		struct uhci_td *td = uhci->data->control;
		printk("uhci_set_address() failed with status %d\n", result);
		show_status(uhci);
		show_cmd(setaddr);
		show_td(td+0);
		show_td(td+1);
		return -1;
	}
	return 0;
}

static int uhci_get_descriptor(struct uhci *uhci, struct usb_device *dev)
{
	int result;
	unsigned char getdesc[8];
	unsigned char buf[18];

	getdesc[0] = 0x80;		/* Input */
	getdesc[1] = 0x06;		/* GET_DESCRIPTOR */
	getdesc[2] = 0x00;		/* wValue = 0x0100: device descriptor */
	getdesc[3] = 0x01;
	getdesc[4] = 0x00;		/* zero wIndex */
	getdesc[5] = 0x00;
	getdesc[6] =   18;		/* wLength = 18 */
	getdesc[7] = 0x00;

	result = uhci_control_msg(uhci, usb_rcvctrlpipe(dev,0), getdesc, buf, 18);
	if (result) {
		struct uhci_td *td = uhci->data->control;
		printk("uhci_get_descriptor() failed with status %d\n", result);
		show_status(uhci);
		show_cmd(getdesc);
		show_td(td+0);
		show_td(td+1);
		show_td(td+2);
		show_td(td+3);
		show_td(td+4);
		return -1;
	}

	printk("  result: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
		buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9],
		buf[10], buf[11], buf[12], buf[13], buf[14], buf[15], buf[16], buf[17]);
	usb_device_descriptor(dev, buf);
	return 0;
}

/*
 * By the time we get here, the device has gotten a new device ID
 * and is in the default state. We need to identify the thing and
 * get the ball rolling..
 */
static void uhci_new_device(struct uhci *uhci, struct usb_device *dev)
{
	uhci_set_address(uhci, dev);
	uhci_get_descriptor(uhci, dev);
}

/*
 * This gets rid of the USB device tree starting at
 * the specified device (including all devices that
 * we allocated under a hub).
 */
static inline void uhci_disconnect(struct usb_device **pdev)
{
	struct usb_device *dev = *pdev;

	if (dev) {
		*pdev = NULL;
		usb_disconnect(dev);
	}
}

void iormw(unsigned int port, unsigned short set, unsigned short mask)
{
	outw((inw(port) & ~mask) | set, port);
}

/*
 * This is just incredibly fragile.
 */
static void uhci_reset_port(unsigned int port)
{
	/*
	 * Ok, we're on top of it now..
	 *
	 * Enable reset for 10ms, then let it sit
	 * for 100ms. After this the device will
	 * be in the "default" state.
	 */
	outw(USBPORTSC_PR, port);
	wait_ms(50);
	outw(0, port);
	udelay(5);

	/* Enable the port.. */
	outw(USBPORTSC_PE | USBPORTSC_PEC | USBPORTSC_CSC, port);
	wait_ms(10);
}


/*
 * This gets called if the connect status on the root
 * hub (and the root hub only) changes.
 */
static void uhci_connect_change(struct uhci * uhci, unsigned int nr)
{
	struct usb_device * dev;
	unsigned short status;
	unsigned int port;

printk("\n\n-=-=-=-=-=-=- uhci_connect_change(%d) -=-=-=-=-=-=-\n", nr);

	/*
	 * Even if the status says we're connected,
	 * the fact that the status bits changed may
	 * that we got disconnected and then reconnected.
	 *
	 * So start off by getting rid of any old devices..
	 */
	uhci_disconnect(uhci->root_hub.children + nr);

	port = uhci->io_addr + USBPORTSC1 + nr*2;

	status = inw(port);
	show_sc(nr, status);

	/* If we have nothing connected, then clear change status and disable the port */
	status = (status & ~USBPORTSC_PE) | USBPORTSC_PEC;
	if (!(status & USBPORTSC_CCS)) {
		outw(status, port);
		return;
	}

	/*
	 * Ok, we got a new connection. Allocate a USB device to it,
	 * and find out what it wants to do..
	 */
	dev = (struct usb_device *) kmalloc(sizeof (*dev), GFP_KERNEL);
	if (!dev)
		return;

	/* Initialize "dev" */
	usb_connect(dev, &uhci->root_hub, nr);

	uhci_reset_port(port);

	/* Get speed information */
	dev->slow = (inw(port) & USBPORTSC_LSDA) ? 1 : 0;

	/*
	 * Ok, all the stuff specific to the root hub has been done.
	 * The rest is generic for any new USB attach, regardless of
	 * hub type.
	 */
	uhci_new_device(uhci, dev);
}

/*
 * This gets called when the root hub configuration
 * has changed
 */
static void uhci_check_configuration(struct uhci *uhci)
{
	unsigned int io_addr = uhci->io_addr;
	unsigned short status;

	status = inw(io_addr + USBPORTSC1);
	if (status & USBPORTSC_CSC)
		uhci_connect_change(uhci, 0);

	status = inw(io_addr + USBPORTSC2);
	if (status & USBPORTSC_CSC)
		uhci_connect_change(uhci, 1);
}

static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs)
{
	struct uhci *uhci = __uhci;
	unsigned int io_addr = uhci->io_addr;
	unsigned short status;

	/*
	 * Read the interrupt status, and write it back to clear the interrupt cause
	 */
	status = inw(io_addr + USBSTS);
	outw(status, io_addr + USBSTS);
	printk("Interrupt, status = %04x\n", status);

	/*
	 * Check port status - Connect Status Change?
	 * Wake up the configurator.
	 */
	if (waitqueue_active(&uhci_configure)) {
		if ((inw(io_addr + USBPORTSC1) & USBPORTSC_CSC) ||
		    (inw(io_addr + USBPORTSC2) & USBPORTSC_CSC))
			wake_up(&uhci_configure);
	}

	/* We should do the "real data" cases here too */
}

/*
 * We init one packet, and mark it just IOC and _not_
 * active. Which will result in no actual USB traffic,
 * but _will_ result in an interrupt every second.
 *
 * Which is exactly what we want.
 */
static void uhci_init_ticktd(struct uhci_mem *data)
{
	struct uhci_td *td = &data->ticktd;

	td->link = 2 | virt_to_bus(&data->interrupt_qh);
	td->status = (1 << 24);					/* interrupt on completion */
	td->info = (15 << 21) | 0x7f69;				/* (ignored) input packet, 16 bytes, device 127 */
	td->buffer = 0;
	data->fl.frame[0] = virt_to_bus(td);
}

static void reset_hc(struct uhci *uhci)
{
	unsigned int io_addr = uhci->io_addr;

	/* Global reset for 100ms */
	outw(USBCMD_GRESET, io_addr+USBCMD);
	current->state = TASK_UNINTERRUPTIBLE;
	wait_ms(50);
	outw(0, io_addr+USBCMD);
	wait_ms(10);
}

static void start_hc(struct uhci *uhci)
{
	unsigned int io_addr = uhci->io_addr;
	int timeout = 1000;

	uhci_init_ticktd(uhci->data);
	show_td(&uhci->data->ticktd);

	/*
	 * 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.
	 */
	outw(USBCMD_HCRESET, io_addr + USBCMD);
	while (inw(io_addr + USBCMD) & USBCMD_HCRESET) {
		if (!--timeout) {
			printk("USBCMD_HCRESET timed out!\n");
			break;
		}
	}

	outw(0x0f, io_addr + 4);
	outw(0, io_addr+6);
	outl(virt_to_bus(&uhci->data->fl), io_addr+8);

	/* Run and mark it configured with a 64-byte max packet */
	outw(USBCMD_RS | USBCMD_CF, io_addr + USBCMD);
}

static int found_uhci(int irq, unsigned int io_addr)
{
	struct uhci *uhci;

	uhci = alloc_uhci(io_addr);
	if (!uhci)
		return -ENOMEM;

	reset_hc(uhci);
	printk("\n\nReset USB controller at %x (irq %d)\n", io_addr, irq);

	if (request_irq(irq, uhci_interrupt, SA_SHIRQ, "usb", uhci) == 0) {
		printk("Allocated interrupt %d for usb\n", irq);
		uhci->irq = irq;

		start_hc(uhci);
		printk("Starting USB controller\n");

		/*
		 * Loop waiting for new events...
		 */
		do {
			interruptible_sleep_on(&uhci_configure);
			uhci_check_configuration(uhci);
			break;
		} while (!signal_pending(current));

		printk("END\n");
	}

	reset_hc(uhci);
	release_uhci(uhci);
	return -EBUSY;
}

static int init_uhci(struct pci_dev *dev)
{
	int i;

	/* Search for the IO base address.. */
	for (i = 0; i < 6; i++) {
		unsigned int io_addr = dev->base_address[i];
		int retval;

		/* IO address? */
		if (!(io_addr & 1))
			continue;

		io_addr &= PCI_BASE_ADDRESS_IO_MASK;

		/* Is it already in use? */
		if (check_region(io_addr, 32))
			break;

{
   unsigned short word;
  int retval;
  retval = pci_read_config_word(dev, 0xc0, &word);
  printk("Config register %04x (%d)\n", word, retval);
}

		request_region(io_addr, 32, "usb");
		retval = found_uhci(dev->irq, io_addr);
		release_region(io_addr, 32);
		return retval;
	}
	return -1;
}

int init_module(void)
{
	struct pci_dev *dev = NULL;
	u8 type;

	printk("Probing for USB devices\n");

	for (;;) {
		dev = pci_find_class(PCI_CLASS_SERIAL_USB<<8, dev);
		if (!dev)
			break;
		/* Is it UHCI */
		pci_read_config_byte(dev, PCI_CLASS_PROG, &type);
		if(type!=0)
			continue;
		/* Ok set it up */
		if (init_uhci(dev) < 0)
			continue;
		return -2;
	}
	return -1;
}
