/*
 * Copyright (c) 2007-2008, Juniper Networks, Inc.
 * Copyright (c) 2008, Excito Elektronik i Skåne AB
 * Copyright (c) 2008, Michael Trimarchi <trimarchimichael@yahoo.it>
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2 of
 * the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */


#include "util.h"		/* for endian conversions */
#include "bas_printf.h"	/* for diagnostics */
#include "wait.h"
#include "cache.h"
#include "usb.h"
#include "ehci.h"

//extern xQueueHandle queue_poll_hub;

#undef DEBUG
#undef SHOW_INFO

static char ehci_inited;
static int rootdev;

static uint16_t portreset;
static uint16_t companion;

struct descriptor {
	struct usb_hub_descriptor hub;
	struct usb_device_descriptor device;
	struct usb_linux_config_descriptor config;
	struct usb_linux_interface_descriptor interface;
	struct usb_endpoint_descriptor endpoint;
} __attribute__ ((packed));

static struct descriptor rom_descriptor = {
	{
		0x8,	/* bDescLength */
		0x29,	/* bDescriptorType: hub descriptor */
		2,		/* bNrPorts -- runtime modified */
		0,		/* wHubCharacteristics */
		0xff,	/* bPwrOn2PwrGood */
		0,		/* bHubCntrCurrent */
		{},		/* Device removable */
		{}		/* at most 7 ports! XXX */
	},
	{
		0x12,	/* bLength */
		1,		/* bDescriptorType: UDESC_DEVICE */
		0x0002,	/* bcdUSB: v2.0 */
		9,		/* bDeviceClass: UDCLASS_HUB */
		0,		/* bDeviceSubClass: UDSUBCLASS_HUB */
		1,		/* bDeviceProtocol: UDPROTO_HSHUBSTT */
		64,		/* bMaxPacketSize: 64 bytes */
		0x0000,	/* idVendor */
		0x0000,	/* idProduct */
		0x0001,	/* bcdDevice */
		1,		/* iManufacturer */
		2,		/* iProduct */
		0,		/* iSerialNumber */
		1		/* bNumConfigurations: 1 */
	},
	{
		0x9,
		2,		/* bDescriptorType: UDESC_CONFIG */
		(0x19 << 8), /* cpu_to_le16(0x19), */
		1,		/* bNumInterface */
		1,		/* bConfigurationValue */
		0,		/* iConfiguration */
		0x40,	/* bmAttributes: UC_SELF_POWER */
		0		/* bMaxPower */
	},
	{
		0x9,	/* bLength */
		4,		/* bDescriptorType: UDESC_INTERFACE */
		0,		/* bInterfaceNumber */
		0,		/* bAlternateSetting */
		1,		/* bNumEndpoints */
		9,		/* bInterfaceClass: UICLASS_HUB */
		0,		/* bInterfaceSubClass: UISUBCLASS_HUB */
		0,		/* bInterfaceProtocol: UIPROTO_HSHUBSTT */
		0		/* iInterface */
	},
	{
		0x7,	/* bLength */
		5,		/* bDescriptorType: UDESC_ENDPOINT */
		0x81,	/* bEndpointAddress: UE_DIR_IN | EHCI_INTR_ENDPT */
		3,		/* bmAttributes: UE_INTERRUPT */
		8, 0,	/* wMaxPacketSize */
		255		/* bInterval */
	},
};

#if defined(CONFIG_EHCI_IS_TDI)
#define ehci_is_TDI()	(1)
#else
#define ehci_is_TDI()	(0)
#endif

struct pci_device_id ehci_usb_pci_table[] = 
{
	{
		PCI_VENDOR_ID_NEC,
		PCI_DEVICE_ID_NEC_USB_2,
		PCI_ANY_ID,
		PCI_ANY_ID,
		PCI_CLASS_SERIAL_USB_EHCI,
		0,
		0
	}, /* NEC PCI OHCI module ids */
	{
		PCI_VENDOR_ID_PHILIPS,
		PCI_DEVICE_ID_PHILIPS_ISP1561_2,
		PCI_ANY_ID,
		PCI_ANY_ID,
		PCI_CLASS_SERIAL_USB_EHCI,
		0,
		0
	}, /* Philips 1561 PCI OHCI module ids */
	/* Please add supported PCI OHCI controller ids here */
	{
		0,
		0,
		0,
		0,
		0,
		0,
		0
	}
};

static struct ehci
{
	/* ------- common part -------- */
	long handle;              /* PCI BIOS */
	const struct pci_device_id *ent;
	int usbnum;
  /* ---- end of common part ---- */
	int big_endian;           /* PCI BIOS */
	struct ehci_hccr *hccr;	/* R/O registers, not need for volatile */
	volatile struct ehci_hcor *hcor;
	struct QH *qh_list_unaligned;
	struct QH *qh_list;
	struct QH *qh_unaligned;
	struct QH *qh;
	struct qTD *td_unaligned[3];
	struct qTD *td[3];
	struct descriptor *descriptor;
	int irq;
	uint32_t dma_offset;
	const char *slot_name;
} gehci;

//#define DEBUG
//#define SHOW_INFO

#ifdef DEBUG
#define debug(format, arg...) xprintf("DEBUG: " format "\r\n", ## arg)
#else
#define debug(format, arg...) do {} while (0)
#endif /* DEBUG */
#define err xprintf
#ifdef SHOW_INFO
#define info(format, arg...) xprintf("INFO: " format "\r\n", ## arg)
#else
#define info(format, arg...) do {} while (0)
#endif


static void cache_qtd(struct qTD *qtd, int flush)
{
	flush_and_invalidate_caches();
}

static inline struct QH *qh_addr(struct QH *qh)
{
	return (struct QH *)((uint32_t)qh & 0xffffffe0);
}

static void cache_qh(struct QH *qh, int flush)
{
	struct qTD *qtd;
	struct qTD *next;
	static struct qTD *first_qtd;

	/* Walk the QH list and flush/invalidate all entries */
	while(1)
	{
		flush_and_invalidate_caches();
		if ((uint32_t)qh & QH_LINK_TYPE_QH)
			break;
		qh = qh_addr(qh);
		qh = (struct QH *)(swpl(qh->qh_link) + gehci.dma_offset);
	}
	qh = qh_addr(qh);
	
	/* Save first qTD pointer, needed for invalidating pass on this QH */
	if (flush)
	{
		qtd = (struct qTD *)(swpl(*(uint32_t *)&qh->qh_overlay) & 0xffffffe0);
		if (qtd != NULL)
			qtd = (struct qTD *)(gehci.dma_offset + (uint32_t)qtd);
		first_qtd = qtd;
	}
	else
		qtd = first_qtd;

	/* Walk the qTD list and flush/invalidate all entries */
	while(1)
	{
		if (qtd == NULL)
			break;
		cache_qtd(qtd, flush);
		next = (struct qTD *)((uint32_t)swpl(qtd->qt_next) & 0xffffffe0);
		if (next != NULL)
			next = (struct qTD *)(gehci.dma_offset + (uint32_t)next);
		if (next == qtd)
			break;
		qtd = next;
	}
}

static inline void ehci_flush_dcache(struct QH *qh)
{
	cache_qh(qh, 1);
}

static inline void ehci_invalidate_dcache(struct QH *qh)
{
	cache_qh(qh, 0);
}

static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec)
{
	uint32_t result;

	do
	{
		result = ehci_readl(ptr);
		if (result == ~(uint32_t)0)
			return -1;
		result &= mask;
		if (result == done)
			return 0;
		wait(1);
		usec--;
	}
	while (usec > 0);
	return -1;
}

static void ehci_free(void *p, size_t sz)
{
}

static int ehci_reset(void)
{
	uint32_t cmd;
	uint32_t tmp;
	uint32_t *reg_ptr;
	int ret = 0;

	if ((gehci.ent->vendor == PCI_VENDOR_ID_NEC) && (gehci.ent->device == PCI_DEVICE_ID_NEC_USB_2))
	{
		debug("ehci_reset set 48MHz clock\r\n");
	 	pci_write_config_longword(gehci.handle, 0xE4, 0x20); // oscillator
	}
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);
	debug("%s cmd: 0x%08x\r\n", __FUNCTION__, cmd);
	cmd |= CMD_RESET;
	ehci_writel(&gehci.hcor->or_usbcmd, cmd);
	ret = handshake((uint32_t *) &gehci.hcor->or_usbcmd, CMD_RESET, 0, 250);

	if (ret < 0)
	{
		err("EHCI fail to reset");
		goto out;
	}
	
	if (ehci_is_TDI())
	{
		reg_ptr = (uint32_t *)((u8 *)gehci.hcor + USBMODE);
		tmp = ehci_readl(reg_ptr);
		tmp |= USBMODE_CM_HC;
		tmp |= USBMODE_BE;
		ehci_writel(reg_ptr, tmp);
	}
out:
	return ret;
}

static void *ehci_alloc(size_t sz, size_t align)
{
	static int ntds;
	void *p;
	switch(sz)
	{
		case sizeof(struct QH):
			p = gehci.qh;
			ntds = 0;
			break;

		case sizeof(struct qTD):
			if (ntds == 3)
			{
				debug("out of TDs\r\n");
				return NULL;
			}
			p = gehci.td[ntds];
			ntds++;
			break;

		default:
			debug("unknown allocation size\r\n");
			return NULL;
	}
	memset(p, sz, 0);
	return p;
}

static int ehci_td_buffer(struct qTD *td, void *buf, size_t sz)
{
	uint32_t addr;
	uint32_t delta;
	uint32_t next;
	int idx;

	addr = (uint32_t)buf;
	idx = 0;

	while (idx < 5)
	{
		td->qt_buffer[idx] = swpl(addr - gehci.dma_offset);
		next = (addr + 4096) & ~4095;
		delta = next - addr;
		if (delta >= sz)
			break;
		sz -= delta;
		addr = next;
		idx++;
	}

	if (idx == 5)
	{
		debug("out of buffer pointers (%u bytes left)\r\n", sz);
		return -1;
	}
	return 0;
}

static int ehci_submit_async(struct usb_device *dev, uint32_t pipe, void *buffer, int length, struct devrequest *req)
{
	struct QH *qh;
	struct qTD *td;
	volatile struct qTD *vtd;
	uint32_t ts;
	uint32_t *tdp;
	uint32_t endpt, token, usbsts;
	uint32_t c, toggle;
	uint32_t cmd;
	int ret = 0;

	debug("%s: dev=%p, pipe=%lx, buffer=%p, length=%d, req=%p\r\n", __FUNCTION__, dev, pipe, buffer, length, req);

	if (req != NULL)
		debug("ehci_submit_async req=%u (%#x), type=%u (%#x), value=%u (%#x), index=%u\r\n",
			req->request, req->request,
			req->requesttype, req->requesttype,
			swpw(req->value), swpw(req->value), swpw(req->index));

	qh = ehci_alloc(sizeof(struct QH), 32);
	if (qh == NULL)
	{
		debug("unable to allocate QH\r\n");
		return -1;
	}
	qh->qh_link = swpl(((uint32_t) gehci.qh_list - gehci.dma_offset) | QH_LINK_TYPE_QH);

	c = (usb_pipespeed(pipe) != USB_SPEED_HIGH && usb_pipeendpoint(pipe) == 0) ? 1 : 0;
	endpt = (8 << 28) |
			(c << 27) |
			(usb_maxpacket(dev, pipe) << 16) |
			(0 << 15) |
			(1 << 14) |
			(usb_pipespeed(pipe) << 12) |
			(usb_pipeendpoint(pipe) << 8) |
			(0 << 7) |
			(usb_pipedevice(pipe) << 0);
	qh->qh_endpt1 = swpl(endpt);
	endpt = (1 << 30) |
			(dev->portnr << 23) |
			(dev->parent->devnum << 16) |
			(0 << 8) |
			(0 << 0);
	qh->qh_endpt2 = swpl(endpt);
	qh->qh_overlay.qt_next = swpl(QT_NEXT_TERMINATE);
	qh->qh_overlay.qt_altnext = swpl(QT_NEXT_TERMINATE);
	td = NULL;
	tdp = &qh->qh_overlay.qt_next;
	toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));

	if (req != NULL)
	{
		td = ehci_alloc(sizeof(struct qTD), 32);
		if (td == NULL)
		{
			debug("unable to allocate SETUP td\r\n");
			goto fail;
		}
		td->qt_next = swpl(QT_NEXT_TERMINATE);
		td->qt_altnext = swpl(QT_NEXT_TERMINATE);
		token = (0 << 31) | (sizeof(*req) << 16) | (0 << 15) | (0 << 12) | (3 << 10) | (2 << 8) | (0x80 << 0);
		td->qt_token = swpl(token);
		if (ehci_td_buffer(td, req, sizeof(*req)) != 0)
		{
			debug("unable construct SETUP td\r\n");
			ehci_free(td, sizeof(*td));
			goto fail;
		}
		*tdp = swpl((uint32_t)td - gehci.dma_offset);
		tdp = &td->qt_next;
		toggle = 1;
	}

	if (length > 0 || req == NULL)
	{
		td = ehci_alloc(sizeof(struct qTD), 32);
		if (td == NULL)
		{
			debug("unable to allocate DATA td\r\n");
			goto fail;
		}
		td->qt_next = swpl(QT_NEXT_TERMINATE);
		td->qt_altnext = swpl(QT_NEXT_TERMINATE);
		token = (toggle << 31) | (length << 16) | ((req == NULL ? 1 : 0) << 15) | (0 << 12) | (3 << 10) | ((usb_pipein(pipe) ? 1 : 0) << 8) | (0x80 << 0);
		td->qt_token = swpl(token);
		if (ehci_td_buffer(td, buffer, length) != 0)
		{
			debug("unable construct DATA td\r\n");
			ehci_free(td, sizeof(*td));
			goto fail;
		}
		*tdp = swpl((uint32_t)td - gehci.dma_offset);
		tdp = &td->qt_next;
	}

	if (req != NULL)
	{
		td = ehci_alloc(sizeof(struct qTD), 32);
		if (td == NULL)
		{
			debug("unable to allocate ACK td\r\n");
			goto fail;
		}
		td->qt_next = swpl(QT_NEXT_TERMINATE);
		td->qt_altnext = swpl(QT_NEXT_TERMINATE);
		token = (toggle << 31) | (0 << 16) | (1 << 15) | (0 << 12) | (3 << 10) | ((usb_pipein(pipe) ? 0 : 1) << 8) | (0x80 << 0);
		td->qt_token = swpl(token);
		*tdp = swpl((uint32_t)td - gehci.dma_offset);
		tdp = &td->qt_next;
	}

	gehci.qh_list->qh_link = swpl(((uint32_t)qh - gehci.dma_offset) | QH_LINK_TYPE_QH);
	/* Flush dcache */
	ehci_flush_dcache(gehci.qh_list);
	usbsts = ehci_readl(&gehci.hcor->or_usbsts);
	ehci_writel(&gehci.hcor->or_usbsts, (usbsts & 0x3f));
	/* Enable async. schedule. */
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);
	cmd |= CMD_ASE;
	ehci_writel(&gehci.hcor->or_usbcmd, cmd);

	ret = handshake((uint32_t *)&gehci.hcor->or_usbsts, STD_ASS, STD_ASS, 100 * 1000);
	if (ret < 0)
	{
		err("EHCI fail timeout STD_ASS set (usbsts=%#x)", ehci_readl(&gehci.hcor->or_usbsts));
		goto fail;
	}
	/* Wait for TDs to be processed. */
	ts = 0;
	vtd = td;
	do
	{
		/* Invalidate dcache */
		ehci_invalidate_dcache(gehci.qh_list);
		token = swpl(vtd->qt_token);
		if (!(token & 0x80))
			break;
		wait(1 * 1000);
		ts++;
	}
	while(ts < 1000);
	/* Disable async schedule. */
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);
	cmd &= ~CMD_ASE;
	ehci_writel(&gehci.hcor->or_usbcmd, cmd);
	ret = handshake((uint32_t *)&gehci.hcor->or_usbsts, STD_ASS, 0, 100 * 1000);
	if (ret < 0)
	{
		err("EHCI fail timeout STD_ASS reset (usbsts=%#x)", ehci_readl(&gehci.hcor->or_usbsts));
		goto fail;
	}
	gehci.qh_list->qh_link = swpl(((uint32_t)gehci.qh_list - gehci.dma_offset) | QH_LINK_TYPE_QH);

	token = swpl(qh->qh_overlay.qt_token);
	if (!(token & 0x80))
	{
		debug("TOKEN=%#x\r\n", token);
		switch(token & 0xfc)
		{
			case 0:
				toggle = token >> 31;
				usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), toggle);
				dev->status = 0;
				break;

			case 0x40:
				dev->status = USB_ST_STALLED;
				break;

			case 0xa0:
			case 0x20:
				dev->status = USB_ST_BUF_ERR;
				break;

			case 0x50:
			case 0x10:
				dev->status = USB_ST_BABBLE_DET;
				break;

			default:
				dev->status = USB_ST_CRC_ERR;
				break;
		}
		dev->act_len = length - ((token >> 16) & 0x7fff);
	}
	else
	{
		dev->act_len = 0;
		debug("dev=%u, usbsts=%#x, p[1]=%#x, p[2]=%#x\r\n",
		 	dev->devnum, ehci_readl(&gehci.hcor->or_usbsts),
			ehci_readl(&gehci.hcor->or_portsc[0]), ehci_readl(&gehci.hcor->or_portsc[1]));
	}
	return (dev->status != USB_ST_NOT_PROC) ? 0 : -1;
fail:
	td = (void *) swpl(qh->qh_overlay.qt_next);
	if (td != (void *)QT_NEXT_TERMINATE)
		td = (struct qTD *)(gehci.dma_offset + (uint32_t)td);
	while(td != (void *)QT_NEXT_TERMINATE)
	{
		qh->qh_overlay.qt_next = td->qt_next;
		ehci_free(td, sizeof(*td));
		td = (void *)swpl(qh->qh_overlay.qt_next);
		if (td != (void *)QT_NEXT_TERMINATE)
			td = (struct qTD *)(gehci.dma_offset + (uint32_t)td);
	}
	ehci_free(qh, sizeof(*qh));
	if (ehci_readl(&gehci.hcor->or_usbsts) & STS_HSE) /* Host System Error */
	{
		unsigned short status = pci_read_config_word(gehci.handle, PCISR);
		err("EHCI Host System Error, controller usb-%s disabled\r\n(SR:0x%04X%s%s%s%s%s%s)",
			gehci.slot_name, status & 0xFFFF, status & 0x8000 ? ", Parity error" : "",
			status & 0x4000 ? ", Signaled system error" : "",
			status & 0x2000 ? ", Received master abort" : "",
			status & 0x1000 ? ", Received target abort" : "",
			status & 0x800 ? ", Signaled target abort" : "",
			status & 0x100 ? ", Data parity error" : "");
	}
	return -1;
}

static inline int min3(int a, int b, int c)
{
	if (b < a)
		a = b;
	if (c < a)
		a = c;
	return a;
}

static int ehci_submit_root(struct usb_device *dev, uint32_t pipe, void *buffer, int length, struct devrequest *req)
{
	uint8_t tmpbuf[4];
	u16 typeReq;
	void *srcptr = NULL;
	int len, srclen;
	uint32_t reg;
	uint32_t *status_reg;
	if (swpw(req->index) > CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS)
	{
		err("the requested port(%d) is not configured\r\n", swpw(req->index) - 1);
		return -1;
	}
	status_reg = (uint32_t *)&gehci.hcor->or_portsc[swpw(req->index) - 1];
	srclen = 0;
	debug("ehci_submit_root req=%u (%#x), type=%u (%#x), value=%u, index=%u\r\n",
	 req->request, req->request, req->requesttype, req->requesttype, swpw(req->value), swpw(req->index));
	typeReq = req->request | req->requesttype << 8;
	switch(typeReq)
	{
		case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
			switch(swpw(req->value) >> 8)
			{
				case USB_DT_DEVICE:
					debug("USB_DT_DEVICE request\r\n");
					srcptr = &gehci.descriptor->device;
					srclen = 0x12;
					break;

				case USB_DT_CONFIG:
					debug("USB_DT_CONFIG config\r\n");
					srcptr = &gehci.descriptor->config;
					srclen = 0x19;
					break;

				case USB_DT_STRING:
					debug("USB_DT_STRING config\r\n");
					switch(swpw(req->value) & 0xff)
					{
						case 0:	/* Language */
							srcptr = "\4\3\1\0";
							srclen = 4;
							break;

						case 1:	/* Vendor */
							srcptr = "\2\3";
							srclen = 2;
							break;

						case 2:	/* Product */
							srcptr = "\34\3E\0H\0C\0I\0 \0R\0o\0o\0t\0 \0H\0u\0b\0";
							srclen = 28;
							break;

						default:
							debug("unknown value DT_STRING %x\r\n",
							swpw(req->value));
							goto unknown;
					}
					break;
				default:
					debug("unknown value %x\r\n", swpw(req->value));
					goto unknown;
			}
			break;

		case USB_REQ_GET_DESCRIPTOR | ((USB_DIR_IN | USB_RT_HUB) << 8):
			switch(swpw(req->value) >> 8)
			{
				case USB_DT_HUB:
					debug("USB_DT_HUB config\r\n");
					srcptr = &gehci.descriptor->hub;
					srclen = 0x8;
					break;

				default:
					debug("unknown value %x\r\n", swpw(req->value));
					goto unknown;
			}
			break;

		case USB_REQ_SET_ADDRESS | (USB_RECIP_DEVICE << 8):
			debug("USB_REQ_SET_ADDRESS\r\n");
			rootdev = swpw(req->value);
			break;

		case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
			debug("USB_REQ_SET_CONFIGURATION\r\n");
			/* Nothing to do */
			break;

		case USB_REQ_GET_STATUS | ((USB_DIR_IN | USB_RT_HUB) << 8):
			tmpbuf[0] = 1;	/* USB_STATUS_SELFPOWERED */
			tmpbuf[1] = 0;
			srcptr = tmpbuf;
			srclen = 2;
			break;

		case USB_REQ_GET_STATUS | ((USB_RT_PORT | USB_DIR_IN) << 8):
			memset(tmpbuf, 0, 4);
			reg = ehci_readl(status_reg);	

			if ((reg & EHCI_PS_PR) && (portreset & (1 << swpw(req->index))))
			{
				int ret;
				/* force reset to complete */
				reg = reg & ~(EHCI_PS_PR | EHCI_PS_CLEAR);
				ehci_writel(status_reg, reg);
				ret = handshake(status_reg, EHCI_PS_PR, 0, 2 * 1000);
				if (!ret)
				{
					tmpbuf[0] |= USB_PORT_STAT_RESET;
          			reg = ehci_readl(status_reg);
				}
				else
					err("port(%d) reset error", swpw(req->index) - 1);
			}

			if (reg & EHCI_PS_CS)
				tmpbuf[0] |= USB_PORT_STAT_CONNECTION;
			if (reg & EHCI_PS_PE)
				tmpbuf[0] |= USB_PORT_STAT_ENABLE;
			if (reg & EHCI_PS_SUSP)
				tmpbuf[0] |= USB_PORT_STAT_SUSPEND;
			if (reg & EHCI_PS_OCA)
				tmpbuf[0] |= USB_PORT_STAT_OVERCURRENT;
			if (reg & EHCI_PS_PP)
				tmpbuf[1] |= USB_PORT_STAT_POWER >> 8;
			if (ehci_is_TDI())
			{
				switch((reg >> 26) & 3)
				{
					case 0:
						break;

					case 1:
						tmpbuf[1] |= USB_PORT_STAT_LOW_SPEED >> 8;
						break;

					case 2:
					default:
						tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8;
						break;
				}
			}
			else
				tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8;    	

			if (reg & EHCI_PS_CSC)
				tmpbuf[2] |= USB_PORT_STAT_C_CONNECTION;
			if (reg & EHCI_PS_PEC)
				tmpbuf[2] |= USB_PORT_STAT_C_ENABLE;
			if (reg & EHCI_PS_OCC)
				tmpbuf[2] |= USB_PORT_STAT_C_OVERCURRENT;
			if (portreset & (1 << swpw(req->index)))
				tmpbuf[2] |= USB_PORT_STAT_C_RESET;

			srcptr = tmpbuf;
			srclen = 4;
			break;

		case USB_REQ_SET_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8):
			reg = ehci_readl(status_reg);
			reg &= ~EHCI_PS_CLEAR;
			switch(swpw(req->value))
			{
				case USB_PORT_FEAT_ENABLE:
					reg |= EHCI_PS_PE;
					ehci_writel(status_reg, reg);
					break;

				case USB_PORT_FEAT_POWER:
					if (HCS_PPC(ehci_readl(&gehci.hccr->cr_hcsparams)))
					{
						reg |= EHCI_PS_PP;
						ehci_writel(status_reg, reg);
					}
					break;

				case USB_PORT_FEAT_RESET:
					if ((reg & (EHCI_PS_PE | EHCI_PS_CS)) == EHCI_PS_CS && !ehci_is_TDI() && EHCI_PS_IS_LOWSPEED(reg))
					{
						/* Low speed device, give up ownership. */
						debug("port %d low speed --> companion\r\n", swpw(req->index));
						reg |= EHCI_PS_PO;
						ehci_writel(status_reg, reg);
						companion |= (1 << swpw(req->index));
						break;
					}
					else
					{
						reg |= EHCI_PS_PR;
						reg &= ~EHCI_PS_PE;
						ehci_writel(status_reg, reg);
						/*
						 * caller must wait, then call GetPortStatus
						 * usb 2.0 specification say 50 ms resets on root
						 */
						wait(50 * 1000);
						portreset |= (1 << swpw(req->index));
					}
					break;

				default:
					debug("unknown feature %x\r\n", swpw(req->value));
					goto unknown;
			}
			/* unblock posted writes */
			(void) ehci_readl(&gehci.hcor->or_usbcmd);
			break;

		case USB_REQ_CLEAR_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8):
			reg = ehci_readl(status_reg);
			switch(swpw(req->value))
			{
				case USB_PORT_FEAT_ENABLE:
					reg &= ~EHCI_PS_PE;
					break;

				case USB_PORT_FEAT_C_ENABLE:
					reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_PE;
					break;

				case USB_PORT_FEAT_POWER:
					if (HCS_PPC(ehci_readl(&gehci.hccr->cr_hcsparams)))
						reg = reg & ~(EHCI_PS_CLEAR | EHCI_PS_PP);
				case USB_PORT_FEAT_C_CONNECTION:
					reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_CSC;
					break;

				case USB_PORT_FEAT_OVER_CURRENT:
					reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_OCC;
					break;

				case USB_PORT_FEAT_C_RESET:
					portreset &= ~(1 << swpw(req->index));
					break;

				default:
					debug("unknown feature %x\r\n", swpw(req->value));
					goto unknown;
			}
			ehci_writel(status_reg, reg);
			/* unblock posted write */
			(void) ehci_readl(&gehci.hcor->or_usbcmd);
			break;

		default:
			debug("Unknown request\r\n");
			goto unknown;
	}
	wait(1 * 1000);
	len = min3(srclen, swpw(req->length), length);
	if (srcptr != NULL && len > 0)
		memcpy(buffer, srcptr, len);
	else
		debug("Len is 0\r\n");
	dev->act_len = len;
	dev->status = 0;
	return 0;
unknown:
	debug("requesttype=%x, request=%x, value=%x, index=%x, length=%x\r\n",
			req->requesttype, req->request, swpw(req->value), swpw(req->index), swpw(req->length));
	dev->act_len = 0;
	dev->status = USB_ST_STALLED;
	return -1;
}

/* an interrupt happens */
static int hc_interrupt(struct ehci *ehci)
{
	uint32_t status = ehci_readl(&ehci->hcor->or_usbsts);
	if (status & STS_PCD) /* port change detect */
	{
		uint32_t reg = ehci_readl(&ehci->hccr->cr_hcsparams);
		uint32_t i = HCS_N_PORTS(reg);
		while(i)
		{
			uint32_t pstatus = ehci_readl(&ehci->hcor->or_portsc[i-1]);
			if (pstatus & EHCI_PS_PO)
			{
				i--;
				continue;
			}

			if (companion & (1 << i))
			{
				/* Low speed device, give up ownership. */
				pstatus |= EHCI_PS_PO;
				ehci_writel(&ehci->hcor->or_portsc[i-1], pstatus);
			}
			i--;
		}
	} 
	ehci_writel(&ehci->hcor->or_usbsts, status);

	return(1); /* interrupt was from this card */
}

void ehci_usb_enable_interrupt(int enable)
{
	if (enable);
}

static int handle_usb_interrupt(struct ehci *ehci)
{
	return(hc_interrupt(ehci));
}

static void hc_free_buffers(struct ehci *ehci)
{
	int i;

	if (ehci->descriptor != NULL)
	{
		driver_mem_free(ehci->descriptor);
		ehci->descriptor = NULL;
	}

	for (i = 0; i < 3; i++)
	{
		if (ehci->td_unaligned[i] != NULL)
		{
			driver_mem_free(ehci->td_unaligned[i]);
			ehci->td_unaligned[i] = NULL;
		}
	}

	if (ehci->qh_unaligned != NULL)
	{
		driver_mem_free(ehci->qh_unaligned);
		ehci->qh_unaligned = NULL;
	}

	if (ehci->qh_list_unaligned != NULL)
	{
		driver_mem_free(ehci->qh_list_unaligned);
		ehci->qh_list_unaligned = NULL;
	}
}

int ehci_usb_lowlevel_init(long handle, const struct pci_device_id *ent, void **priv)
{
	int i;
	uint32_t reg;
	uint32_t cmd;
	uint32_t usb_base_addr = 0xFFFFFFFF;
	struct pci_rd *pci_rsc_desc;

	pci_rsc_desc = pci_get_resource(handle); /* USB EHCI */
	if (handle && (ent != NULL))
	{
		memset(&gehci, 0, sizeof(struct ehci));
		gehci.handle = handle;
		gehci.ent = ent;
	}
	else if (!gehci.handle) /* for restart USB cmd */
		return(-1);	

	gehci.qh_list_unaligned = (struct QH *)driver_mem_alloc(sizeof(struct QH) + 32);
	if (gehci.qh_list_unaligned == NULL)
	{
		debug("QHs malloc failed");
		hc_free_buffers(&gehci);
		return(-1);
	}

	gehci.qh_list = (struct QH *)(((uint32_t)gehci.qh_list_unaligned + 31) & ~31);
	memset(gehci.qh_list, 0, sizeof(struct QH));
	gehci.qh_unaligned = (struct QH *)driver_mem_alloc(sizeof(struct QH) + 32);

	if (gehci.qh_unaligned == NULL)
	{
		debug("QHs malloc failed");
		hc_free_buffers(&gehci);
		return(-1);
	}
	gehci.qh = (struct QH *)(((uint32_t)gehci.qh_unaligned + 31) & ~31);
	memset(gehci.qh, 0, sizeof(struct QH));

	for (i = 0; i < 3; i++)
	{
		gehci.td_unaligned[i] = (struct qTD *)driver_mem_alloc(sizeof(struct qTD) + 32);
		if (gehci.td_unaligned[i] == NULL)
		{
			debug("TDs malloc failed");
			hc_free_buffers(&gehci);
			return(-1);
		}
		gehci.td[i] = (struct qTD *)(((uint32_t)gehci.td_unaligned[i] + 31) & ~31);
		memset(gehci.td[i], 0, sizeof(struct qTD));	
	}

	gehci.descriptor = (struct descriptor *)driver_mem_alloc(sizeof(struct descriptor));
	if (gehci.descriptor == NULL)
	{
		debug("decriptor malloc failed");
		hc_free_buffers(&gehci);
		return(-1);
	}
	memcpy(gehci.descriptor, &rom_descriptor, sizeof(struct descriptor));

	if ((long) pci_rsc_desc >= 0)
	{
		unsigned short flags;
		do
		{
			debug("PCI USB descriptors (at %p): flags 0x%04x start 0x%08lx \r\n offset 0x%08lx dmaoffset 0x%08lx length 0x%08lx\r\n", pci_rsc_desc,
			 pci_rsc_desc->flags, pci_rsc_desc->start, pci_rsc_desc->offset, pci_rsc_desc->dmaoffset, pci_rsc_desc->length);
			if (!(pci_rsc_desc->flags & FLG_IO))
			{
				if (usb_base_addr == 0xFFFFFFFF)
				{
					usb_base_addr = pci_rsc_desc->start;
					gehci.hccr = (struct ehci_hccr *)(pci_rsc_desc->offset + pci_rsc_desc->start);
					gehci.dma_offset = pci_rsc_desc->dmaoffset;
					if ((pci_rsc_desc->flags & FLG_ENDMASK) == ORD_MOTOROLA)
						gehci.big_endian = 0; /* host bridge make swapping intel -> motorola */
					else
						gehci.big_endian = 1; /* driver must swapping intel -> motorola */
				}
			}
			flags = pci_rsc_desc->flags;
			pci_rsc_desc = (struct pci_rd *)((uint32_t)pci_rsc_desc->next + (uint32_t)pci_rsc_desc);
		}
		while (!(flags & FLG_LAST));
	}
	else
	{
		hc_free_buffers(&gehci);
		return(-1); /* get_resource error */
	}

	if (usb_base_addr == 0xFFFFFFFF)
	{
		hc_free_buffers(&gehci);
		return(-1);
	}

	if (handle && (ent != NULL))
	{
		switch(ent->vendor)
		{
			case PCI_VENDOR_ID_NEC: gehci.slot_name = "uPD720101"; break;
			case PCI_VENDOR_ID_PHILIPS: gehci.slot_name = "isp1561"; break;
			default: gehci.slot_name = "generic"; break;
		}
	}
	gehci.hcor = (struct ehci_hcor *)((uint32_t)gehci.hccr + HC_LENGTH(ehci_readl(&gehci.hccr->cr_capbase)));
	xprintf("EHCI usb-%s, regs address 0x%08X, PCI handle 0x%X\r\n", gehci.slot_name, gehci.hccr, handle);

	/* EHCI spec section 4.1 */
	if (ehci_reset() != 0)
	{
		hc_free_buffers(&gehci);
		return(-1);
	}

	/* Set head of reclaim list */
	gehci.qh_list->qh_link = swpl(((uint32_t)gehci.qh_list - gehci.dma_offset) | QH_LINK_TYPE_QH);
	gehci.qh_list->qh_endpt1 = swpl((1 << 15) | (USB_SPEED_HIGH << 12));
	gehci.qh_list->qh_curtd = swpl(QT_NEXT_TERMINATE);
	gehci.qh_list->qh_overlay.qt_next = swpl(QT_NEXT_TERMINATE);
	gehci.qh_list->qh_overlay.qt_altnext = swpl(QT_NEXT_TERMINATE);
	gehci.qh_list->qh_overlay.qt_token = swpl(0x40);
	/* Set async. queue head pointer. */
	ehci_writel(&gehci.hcor->or_asynclistaddr, (uint32_t)gehci.qh_list - gehci.dma_offset);
	reg = ehci_readl(&gehci.hccr->cr_hcsparams);
	gehci.descriptor->hub.bNbrPorts = HCS_N_PORTS(reg);
	info("Register %x NbrPorts %d", reg, gehci.descriptor->hub.bNbrPorts);

	/* Port Indicators */
	if (HCS_INDICATOR(reg))
		gehci.descriptor->hub.wHubCharacteristics |= 0x80;
	/* Port Power Control */
	if (HCS_PPC(reg))
		gehci.descriptor->hub.wHubCharacteristics |= 0x01;
	/* Start the host controller. */
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);
	/*
	 * Philips, Intel, and maybe others need CMD_RUN before the
	 * root hub will detect new devices (why?); NEC doesn't
	 */
	cmd &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET);
	cmd |= CMD_RUN;
	ehci_writel(&gehci.hcor->or_usbcmd, cmd);
	/* take control over the ports */
	ehci_writel(&gehci.hcor->or_configflag, FLAG_CF);
	/* unblock posted write */
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);
	wait(5 * 1000);
	reg = HC_VERSION(ehci_readl(&gehci.hccr->cr_capbase));
	info("USB EHCI %x.%02x", reg >> 8, reg & 0xff);
  /* turn on interrupts */
	pci_hook_interrupt(handle, handle_usb_interrupt, &gehci);
	ehci_writel(&gehci.hcor->or_usbintr, INTR_PCDE);
	rootdev = 0;
	if (priv != NULL)
		*priv = (void *)&gehci;
	ehci_inited = 1;
	return(0);
}

int ehci_usb_lowlevel_stop(void *priv)
{
	uint32_t cmd;

	if (priv);
	if (!ehci_inited)
		return(0);

	/* turn off interrupts */
	ehci_writel(&gehci.hcor->or_usbintr, 0);
	pci_unhook_interrupt(gehci.handle);

	/* stop the controller */
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);
	cmd &= ~CMD_RUN;
	ehci_writel(&gehci.hcor->or_usbcmd, cmd);

	/* turn off all ports => todo */
	/* use the companions */
	ehci_writel(&gehci.hcor->or_configflag, 0);

	/* unblock posted write */
	cmd = ehci_readl(&gehci.hcor->or_usbcmd);		
	ehci_reset();
	hc_free_buffers(&gehci);
	ehci_inited = 0;
	return(0);
}

int ehci_submit_bulk_msg(struct usb_device *dev, uint32_t pipe, void *buffer, int length)
{
	if (usb_pipetype(pipe) != PIPE_BULK)
	{
		debug("non-bulk pipe (type=%lu)", usb_pipetype(pipe));
		return -1;
	}
	return ehci_submit_async(dev, pipe, buffer, length, NULL);
}

int ehci_submit_control_msg(struct usb_device *dev, uint32_t pipe, void *buffer, int length, struct devrequest *setup)
{
	if (usb_pipetype(pipe) != PIPE_CONTROL)
	{
		debug("non-control pipe (type=%lu)", usb_pipetype(pipe));
		return -1;
	}

	if (usb_pipedevice(pipe) == rootdev)
	{
		if (rootdev == 0)
			dev->speed = USB_SPEED_HIGH;
		return ehci_submit_root(dev, pipe, buffer, length, setup);
	}
	return ehci_submit_async(dev, pipe, buffer, length, setup);
}

int ehci_submit_int_msg(struct usb_device *dev, uint32_t pipe, void *buffer, int length, int interval)
{
	debug("submit_int_msg dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", dev, pipe, buffer, length, interval);
	return -1;
}

