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

static PCMslot	slot[2];
int nslot = 2;

struct {
	Ref;
} pcmcia;

enum
{
	Qdir,
	Qmem,
	Qattr,
	Qctl,

	Nents = 3,
};

enum
{
	/*
	 *  configuration registers - they start at an offset in attribute
	 *  memory found in the CIS.
	 */
	Rconfig=	0,
	 Creset=	 (1<<7),	/*  reset device */
	 Clevel=	 (1<<6),	/*  level sensitive interrupt line */
};

#define SLOTNO(c)	((c->qid.path>>8)&0xff)
#define TYPE(c)		(c->qid.path&0xff)
#define QID(s,t)	(((s)<<8)|(t))

static void increfp(PCMslot*);
static void decrefp(PCMslot*);
static void slotmap(int, ulong, ulong, ulong);
static void slottiming(int, int, int, int, int);
static void slotinfo(Ureg*, void*);

static int
pcmgen(Chan *c, Dirtab *, int , int i, Dir *dp)
{
	int slotno;
	Qid qid;
	long len;
	PCMslot *sp;
	char name[NAMELEN];

	if(i == DEVDOTDOT){
		devdir(c, (Qid){CHDIR, 0}, "#y", 0, eve, 0555, dp);
		return 1;
	}

	if(i >= Nents*nslot)
		return -1;

	slotno = i/Nents;
	sp = slot + slotno;
	len = 0;
	switch(i%Nents){
	case 0:
		qid.path = QID(slotno, Qmem);
		sprint(name, "pcm%dmem", slotno);
		len = sp->memlen;
		break;
	case 1:
		qid.path = QID(slotno, Qattr);
		sprint(name, "pcm%dattr", slotno);
		len = sp->memlen;
		break;
	case 2:
		qid.path = QID(slotno, Qctl);
		sprint(name, "pcm%dctl", slotno);
		break;
	}
	qid.vers = 0;
	devdir(c, qid, name, len, eve, 0660, dp);
	return 1;
}

static int
bitno(ulong x)
{
	int i;

	for(i = 0; i < 8*sizeof(x); i++)
		if((1<<i) & x)
			break;
	return i;
}

/*
 *  set up the cards, default timing is 300 ns
 */
static void
pcmciareset(void)
{
	/* staticly map the whole area */
	slotmap(0, PHYSPCM0REGS, PYHSPCM0ATTR, PYHSPCM0MEM);
	slotmap(1, PHYSPCM1REGS, PYHSPCM1ATTR, PYHSPCM1MEM);

	/* set timing to the default, 300 */
	slottiming(0, 300, 300, 300, 0);
	slottiming(1, 300, 300, 300, 0);

	/* if there's no pcmcia sleave, no interrupts */
	if(gpioregs->level & GPIO_OPT_IND_i)
		return;

	/* sleave there, interrupt on card removal */
	intrenable(GPIOrising, bitno(GPIO_CARD_IND1_i), slotinfo, nil, "pcmcia slot1 status");
	intrenable(GPIOrising, bitno(GPIO_CARD_IND0_i), slotinfo, nil, "pcmcia slot0 status");
}

static Chan*
pcmciaattach(char *spec)
{
	return devattach('y', spec);
}

static int
pcmciawalk(Chan *c, char *name)
{
	return devwalk(c, name, 0, 0, pcmgen);
}

static void
pcmciastat(Chan *c, char *db)
{
	devstat(c, db, 0, 0, pcmgen);
}

static Chan*
pcmciaopen(Chan *c, int omode)
{
	if(c->qid.path == CHDIR){
		if(omode != OREAD)
			error(Eperm);
	} else
		increfp(slot + SLOTNO(c));
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

static void
pcmciaclose(Chan *c)
{
	if(c->flag & COPEN)
		if(c->qid.path != CHDIR)
			decrefp(slot+SLOTNO(c));
}

/* a memmove using only bytes */
static void
memmoveb(uchar *to, uchar *from, int n)
{
	while(n-- > 0)
		*to++ = *from++;
}

/* a memmove using only shorts & bytes */
static void
memmoves(uchar *to, uchar *from, int n)
{
	ushort *t, *f;

	if((((ulong)to) & 1) || (((ulong)from) & 1) || (n & 1)){
		while(n-- > 0)
			*to++ = *from++;
	} else {
		n = n/2;
		t = (ushort*)to;
		f = (ushort*)from;
		while(n-- > 0)
			*t++ = *f++;
	}
}

static long
pcmread(void *a, long n, ulong off, PCMslot *sp, uchar *start, ulong len)
{
	rlock(sp);
	if(waserror()){
		runlock(sp);
		nexterror();
	}
	if(off > len)
		return 0;
	if(off + n > len)
		n = len - off;
	memmoveb(a, start+off, n);
	runlock(sp);
	poperror();
	return n;
}

static long
pcmctlread(void *a, long n, ulong off, PCMslot *sp)
{
	char *p, *buf, *e;

	buf = p = malloc(READSTR);
	if(waserror()){
		free(buf);
		nexterror();
	}
	e = p + READSTR;

	buf[0] = 0;
	if(sp->occupied){
		p = seprint(p, e, "occupied\n");
		if(sp->verstr[0])
			p = seprint(p, e, "version %s\n", sp->verstr);
	}
	USED(p);

	n = readstr(off, a, n, buf);
	free(buf);
	poperror();
	return n;
}

static long
pcmciaread(Chan *c, void *a, long n, vlong off)
{
	PCMslot *sp;
	ulong offset = off;

	sp = slot + SLOTNO(c);

	switch(TYPE(c)){
	case Qdir:
		return devdirread(c, a, n, 0, 0, pcmgen);
	case Qmem:
		if(!sp->occupied)
			error(Eio);
		return pcmread(a, n, offset, sp, sp->mem, 64*OneMeg);
	case Qattr:
		if(!sp->occupied)
			error(Eio);
		return pcmread(a, n, offset, sp, sp->attr, OneMeg);
	case Qctl:
		return pcmctlread(a, n, offset, sp);
	}
	error(Ebadarg);
	return -1;	/* not reached */
}

static long
pcmwrite(void *a, long n, ulong off, PCMslot *sp, uchar *start, ulong len)
{
	rlock(sp);
	if(waserror()){
		runlock(sp);
		nexterror();
	}
	if(off > len)
		error(Eio);
	if(off + n > len)
		error(Eio);
	memmoveb(start+off, a, n);
	poperror();
	runlock(sp);
	return n;
}

static long
pcmctlwrite(char *p, long n, ulong, PCMslot *sp)
{
	Cmdbuf *cmd;
	uchar *cp;
	int index, i, dtx;
	Rune r;
	DevConf cf;

	cmd = parsecmd(p, n);
	if(strcmp(cmd->f[0], "configure") == 0){
		wlock(sp);
		if(waserror()){
			wunlock(sp);
			nexterror();
		}

		/* see if driver exists and is configurable */
		if(cmd->nf < 3)
			error(Ebadarg);
		p = cmd->f[1];
		if(*p++ != '#')
			error(Ebadarg);
		p += chartorune(&r, p);
		dtx = devno(r, 1);
		if(dtx < 0)
			error("no such device type");
		if(devtab[dtx]->config == nil)
			error("not a dynamicly configurable device");

		/* set pcmcia card configuration */
		index = 0;
		if(sp->def != nil)
			index = sp->def->index;
		if(cmd->nf > 3){
			i = atoi(cmd->f[3]);
			if(i < 0 || i >= sp->nctab)
				error("bad configuration index");
			index = i;
		}
		if(sp->cpresent & (1<<Rconfig)){
			cp = sp->attr;
			cp += sp->caddr + Rconfig;
			*cp = index;
		}

		/* configure device */
		strncpy(cf.type, cmd->f[2], sizeof(cf.type)-1);
		cf.type[sizeof(cf.type)-1] = 0;
		cf.mem = (ulong)sp->mem;
		cf.port = (ulong)sp->regs;
		cf.itype = GPIOfalling;
		cf.interrupt = bitno(sp == slot ? GPIO_CARD_IRQ0_i : GPIO_CARD_IRQ1_i);
		cf.size = 0;
		if(devtab[dtx]->config(1, p, &cf) < 0)
			error("couldn't configure device");

		wunlock(sp);
		poperror();

		/* don't let the power turn off */
		increfp(sp);
	}
	free(cmd);

	return 0;
}

static long
pcmciawrite(Chan *c, void *a, long n, vlong off)
{
	PCMslot *sp;
	ulong offset = off;

	sp = slot + SLOTNO(c);

	switch(TYPE(c)){
	case Qmem:
		if(!sp->occupied)
			error(Eio);
		return pcmwrite(a, n, offset, sp, sp->mem, 64*OneMeg);
	case Qattr:
		if(!sp->occupied)
			error(Eio);
		return pcmwrite(a, n, offset, sp, sp->attr, OneMeg);
	case Qctl:
		if(!sp->occupied)
			error(Eio);
		return pcmctlwrite(a, n, offset, sp);
	}
	error(Ebadarg);
	return -1;	/* not reached */
}

Dev pcmciadevtab = {
	'y',
	"pcmcia",

	pcmciareset,
	devinit,
	pcmciaattach,
	devclone,
	pcmciawalk,
	pcmciastat,
	pcmciaopen,
	devcreate,
	pcmciaclose,
	pcmciaread,
	devbread,
	pcmciawrite,
	devbwrite,
	devremove,
	devwstat,
};

/* see what's there */
static void
slotinfo(Ureg*, void*)
{
	ulong x = gpioregs->level;

	if(x & GPIO_OPT_IND_i){
		/* no expansion pack */
		slot[0].occupied = 0;
		slot[1].occupied = 0;
	} else {
		if(x & GPIO_CARD_IND0_i){
			slot[0].occupied = 0;
			slot[0].cisread = 0;
		} else {
			if(slot[0].occupied == 0)
				slot[0].cisread = 0;
			slot[0].occupied = 1;
		}
		if(x & GPIO_CARD_IND1_i){
			slot[1].occupied = 0;
			slot[1].cisread = 0;
		} else {
			if(slot[1].occupied == 0)
				slot[1].cisread = 0;
			slot[1].occupied = 1;
		}
	}
}

/* use reference card to turn cards on and off */
static void
increfp(PCMslot *sp)
{
	wlock(sp);
	if(waserror()){
		wunlock(sp);
		nexterror();
	}

	if(incref(&pcmcia) == 1){
		egpiobits(EGPIO_exp_nvram_power|EGPIO_exp_full_power, 1);
		delay(200);
		egpiobits(EGPIO_pcmcia_reset, 1);
		delay(100);
		egpiobits(EGPIO_pcmcia_reset, 0);
		delay(500);
	}
	incref(&sp->ref);
	slotinfo(nil, nil);
	if(sp->occupied && sp->cisread == 0)
		pcmcisread(sp);

	wunlock(sp);
	poperror();
}

static void
decrefp(PCMslot *sp)
{
	decref(&sp->ref);
	if(decref(&pcmcia) == 0)
		egpiobits(EGPIO_exp_nvram_power|EGPIO_exp_full_power, 0);
}

/*
 *  the regions are staticly masped
 */
static void
slotmap(int slotno, ulong regs, ulong attr, ulong mem)
{
	PCMslot *sp;

	sp = &slot[slotno];
	sp->slotno = slotno;
	sp->memlen = 64*OneMeg;
	sp->verstr[0] = 0;

	sp->mem = mapmem(mem, 64*OneMeg, 0);
	sp->memmap.ca = 0;
	sp->memmap.cea = 64*MB;
	sp->memmap.isa = (ulong)mem;
	sp->memmap.len = 64*OneMeg;
	sp->memmap.attr = 0;

	sp->attr = mapmem(attr, OneMeg, 0);
	sp->attrmap.ca = 0;
	sp->attrmap.cea = MB;
	sp->attrmap.isa = (ulong)attr;
	sp->attrmap.len = OneMeg;
	sp->attrmap.attr = 1;

	sp->regs = mapspecial(regs, 32*1024);
}
PCMmap*
pcmmap(int slotno, ulong, int, int attr)
{
	if(slotno > nslot)
		panic("pcmmap");
	if(attr)
		return &slot[slotno].attrmap;
	else
		return &slot[slotno].memmap;
}
void
pcmunmap(int, PCMmap*)
{
}

/*
 *  setup card timings
 *    times are in ns
 *    count = ceiling[access-time/(2*3*T)] - 1, where T is a processor cycle
 *
 */
static int
ns2count(int ns)
{
	ulong y;

	/* get 100 times cycle time */
	y = 100000000/(conf.hz/1000);

	/* get 10 times ns/(cycle*6) */
	y = (1000*ns)/(6*y);

	/* round up */
	y += 9;
	y /= 10;

	/* subtract 1 */
	return y-1;
}
static void
slottiming(int slotno, int tio, int tattr, int tmem, int fast)
{
	ulong x;

	x = 0;
	if(fast)
		x |= 1<<MECR_fast0;
	x |= ns2count(tio) << MECR_io0;
	x |= ns2count(tattr) << MECR_attr0;
	x |= ns2count(tmem) << MECR_mem0;
	if(slotno == 0){
		x |= memconfregs->mecr & 0xffff0000;
	} else {
		x <<= 16;
		x |= memconfregs->mecr & 0xffff;
	}
	memconfregs->mecr = x;
}
