/*
 * Kirkwood-specific code for
 * USB Enhanced Host Controller Interface (EHCI) driver
 * High speed USB 2.0.
 */

#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"
#include	"../port/error.h"
#include	"../port/usb.h"
#include	"usbehci.h"

#define WINTARG(ctl)	(((ctl) >> 4) & 017)
#define WINATTR(ctl)	(((ctl) >> 8) & 0377)
#define WIN64KSIZE(ctl)	(((ctl) >> 16) + 1)

#define SIZETO64KSIZE(size) ((size) / (64*1024) - 1)

enum {
	Debug = 0,
};

typedef struct Kwusb Kwusb;
typedef struct Kwusbtt Kwusbtt;
typedef struct Usbwin Usbwin;

/* kirkwood usb transaction translator registers? (undocumented) */
struct Kwusbtt {		/* at soc.ehci */
	ulong	id;
	ulong	hwgeneral;
	ulong	hwhost;
	ulong	hwdevice;
	ulong	hwtxbuf;
	ulong	hwrxbuf;
	ulong	hwtttxbuf;
	ulong	hwttrxbuf;
};

/* kirkwood usb bridge & phy registers */
struct Kwusb {			/* at offset 0x300 from soc.ehci */
	ulong	bcs;		/* bridge ctl & sts */
	uchar	_pad0[0x310-0x304];

	ulong	bic;		/* bridge intr. cause */
	ulong	bim;		/* bridge intr. mask */
	ulong	_pad1;
	ulong	bea;		/* bridge error addr. */
	struct Usbwin {
		ulong	ctl;	/* see Winenable in io.h */
		ulong	base;
		ulong	_pad2[2];
	} win[4];
	ulong	phycfg;		/* phy config. */
	uchar	_pad3[0x400-0x364];

	ulong	pwrctl;		/* power control */
	uchar	_pad4[0x410-0x404];
	ulong	phypll;		/* phy pll control */
	uchar	_pad5[0x420-0x414];
	ulong	phytxctl;	/* phy transmit control */
	uchar	_pad6[0x430-0x424];
	ulong	phyrxctl;	/* phy receive control */
	uchar	_pad7[0x440-0x434];
	ulong	phyivref;	/* phy ivref control */
};

static Ctlr* ctlrs[Nhcis];

static void
addrmapdump(void)
{
	int i;
	ulong ctl, targ, attr, size64k;
	Kwusb *map;
	Usbwin *win;

	if (!Debug)
		return;
	map = (Kwusb *)(soc.ehci + 0x300);
	for (i = 0; i < nelem(map->win); i++) {
		win = &map->win[i];
		ctl = win->ctl;
		if (ctl & Winenable) {
			targ = WINTARG(ctl);
			attr = WINATTR(ctl);
			size64k = WIN64KSIZE(ctl);
			print("usbehci: address map window %d: "
				"targ %ld attr %#lux size %,ld addr %#lux\n",
				i, targ, attr, size64k * 64*1024, win->base);
		}
	}
}

/* assumes ctlr is ilocked */
static void
ctlrreset(Ctlr *ctlr)
{
	int i;
	Eopio *opio;

	opio = ctlr->opio;
	opio->cmd |= Chcreset;
	coherence();
	/* wait for it to come out of reset */
	for(i = 0; i < 100 && opio->cmd & Chcreset; i++)
		delay(1);
	if(i >= 100)
		print("ehci %#p controller reset timed out\n", ctlr->capio);
	/*
	 * Marvell errata FE-USB-340 workaround: 1 << 4 magic:
	 * disable streaming.  Magic 3 (usb host mode) from the linux driver
	 * makes it work.  Ick.
	 */
	opio->usbmode |= 1 << 4 | 3;
	coherence();
}

/*
 * configure window `win' as 256MB dram with attribute `attr' and
 * base address
 */
static void
setaddrwin(Kwusb *kw, int win, int attr, ulong base)
{
	kw->win[win].ctl = Winenable | Targdram << 4 | attr << 8 |
		SIZETO64KSIZE(256*MB) << 16;
	kw->win[win].base = base;
}

static void
ehcireset(Ctlr *ctlr)
{
	int i, amp, txvdd;
	ulong v;
	Eopio *opio;
	Kwusb *kw;

	ilock(ctlr);
	dprint("ehci %#p reset\n", ctlr->capio);
	opio = ctlr->opio;

	kw = (Kwusb *)(soc.ehci + 0x300);
	kw->bic = 0;
	kw->bim = (1<<4) - 1;		/* enable all defined intrs */
	ctlrreset(ctlr);

	/*
	 * clear high 32 bits of address signals if it's 64 bits capable.
	 * This is probably not needed but it does not hurt and others do it.
	 */
	if((ctlr->capio->capparms & C64) != 0){
		dprint("ehci: 64 bits\n");
		opio->seg = 0;
	}

	/* requesting more interrupts per µframe may miss interrupts */
	opio->cmd |= Citc8;		/* 1 intr. per ms */
	switch(opio->cmd & Cflsmask){
	case Cfls1024:
		ctlr->nframes = 1024;
		break;
	case Cfls512:
		ctlr->nframes = 512;
		break;
	case Cfls256:
		ctlr->nframes = 256;
		break;
	default:
		panic("ehci: unknown fls %ld", opio->cmd & Cflsmask);
	}
	dprint("ehci: %d frames\n", ctlr->nframes);

	/*
	 * set up the USB address map (bridge address decoding)
	 */
	for (i = 0; i < nelem(kw->win); i++)
		kw->win[i].ctl = kw->win[i].base = 0;
	coherence();

	setaddrwin(kw, 0, Attrcs0, 0);
	setaddrwin(kw, 1, Attrcs1, 256*MB);
	coherence();

	if (Debug)
		if (kw->bcs & (1 << 4))
			print("usbehci: not swapping bytes\n");
		else
			print("usbehci: swapping bytes\n");
	addrmapdump();				/* verify sanity */

	kw->pwrctl |= 1 << 0 | 1 << 1;		/* Pu | PuPll */
	coherence();

	/*
	 * Marvell guideline GL-USB-160.
	 */
	kw->phypll |= 1 << 21;		/* VCOCAL_START: PLL calibration */
	coherence();
	microdelay(100);
	kw->phypll &= ~(1 << 21);

	v = kw->phytxctl & ~(017 << 27 | 7);	/* REG_EXT_FS_RCALL & AMP_2_0 */
	switch (m->socrev) {
	default:
		print("usbehci: bad 6281 soc rev %d\n", m->socrev);
		/* fall through */
	case Socreva0:
		amp = 4;
		txvdd = 1;
		break;
	case Socreva1:
		amp = 3;
		txvdd = 3;
		break;
	}
	/* REG_EXT_FS_RCALL_EN | REG_RCAL_START | AMP_2_0 */
	kw->phytxctl = v | 1 << 26 | 1 << 12 | amp;
	coherence();
	microdelay(100);
	kw->phytxctl &= ~(1 << 12);

	v = kw->phyrxctl & ~(3 << 2 | 017 << 4); /* LPF_COEF_1_0 & SQ_THRESH_3_0 */
	kw->phyrxctl = v | 1 << 2 | 8 << 4;

	v = kw->phyivref & ~(3 << 8);		/* TXVDD12 */
	kw->phyivref = v | txvdd << 8;
	coherence();

	ehcirun(ctlr, 0);
	ctlrreset(ctlr);

	iunlock(ctlr);
}

static void
setdebug(Hci*, int d)
{
	ehcidebug = d;
}

static void
shutdown(Hci *hp)
{
	Ctlr *ctlr;
	Eopio *opio;

	ctlr = hp->aux;
	ilock(ctlr);
	ctlrreset(ctlr);

	delay(100);
	ehcirun(ctlr, 0);

	opio = ctlr->opio;
	opio->frbase = 0;
	coherence();
	iunlock(ctlr);
}

static void
findehcis(void)		/* actually just use fixed addresses on sheeva */
{
	int i;
	Ctlr *ctlr;
	static int already = 0;

	if(already)
		return;
	already = 1;

	ctlr = smalloc(sizeof(Ctlr));
	/* the sheeva's usb 2.0 otg uses a superset of the ehci registers */
	ctlr->capio = (Ecapio *)(soc.ehci + 0x100);
	ctlr->opio  = (Eopio *) (soc.ehci + 0x140);
	dprint("usbehci: port %#p\n", ctlr->capio);

	for(i = 0; i < Nhcis; i++)
		if(ctlrs[i] == nil){
			ctlrs[i] = ctlr;
			break;
		}
	if(i == Nhcis)
		print("ehci: bug: more than %d controllers\n", Nhcis);
}

static int
reset(Hci *hp)
{
	static Lock resetlck;
	int i;
	Ctlr *ctlr;
	Ecapio *capio;

	ilock(&resetlck);
	findehcis();

	/*
	 * Any adapter matches if no hp->port is supplied,
	 * otherwise the ports must match.
	 */
	ctlr = nil;
	for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){
		ctlr = ctlrs[i];
		if(ctlr->active == 0)
		if(hp->port == 0 || hp->port == (uintptr)ctlr->capio){
			ctlr->active = 1;
			break;
		}
	}
	iunlock(&resetlck);
	if(ctlrs[i] == nil || i == Nhcis)
		return -1;

	hp->aux = ctlr;
	hp->port = (uintptr)ctlr->capio;
	hp->irq = IRQ0usb0;
	hp->tbdf = 0;

	capio = ctlr->capio;
	hp->nports = capio->parms & Cnports;

	ddprint("echi: %s, ncc %lud npcc %lud\n",
		capio->parms & 0x10000 ? "leds" : "no leds",
		(capio->parms >> 12) & 0xf, (capio->parms >> 8) & 0xf);
	ddprint("ehci: routing %s, %sport power ctl, %d ports\n",
		capio->parms & 0x40 ? "explicit" : "automatic",
		capio->parms & 0x10 ? "" : "no ", hp->nports);

	ctlr->tdalloc = ucallocalign;
	ctlr->dmaalloc = ucalloc;
	ctlr->dmafree = ucfree;

	ehcireset(ctlr);
	ehcimeminit(ctlr);

	/*
	 * Linkage to the generic HCI driver.
	 */
	ehcilinkage(hp);
	hp->shutdown = shutdown;
	hp->debug = setdebug;

	intrenable(Irqlo, hp->irq, hp->interrupt, hp, hp->type);

	return 0;
}

void
usbehcilink(void)
{
	addhcitype("ehci", reset);
}
