/*
 * minimal spi interface for testing
 */

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

#define SPIREGS	(VIRTIO+0x204000)

extern int qstate(Queue*);

enum {
	QMAX		= 64*1024,
	Nspislave	= 2,
};

typedef struct Spi Spi;

struct Spi {
	int	csel;
	int	opens;
	QLock;
	Queue	*iq;
	Queue	*oq;
};

Spi spidev[Nspislave];

enum{
	Qdir = 0,
	Qctl,
	Qspi,
};

Dirtab spidir[]={
	".",	{Qdir, 0, QTDIR},	0,	0555,
	"spictl",		{Qctl, 0},	0,	0664,
	"spi0",		{Qspi+0, 0},	0,	0664,
	"spi1",		{Qspi+1, 0}, 0, 0664,
};

#define DEVID(path)	((ulong)path - Qspi)

enum {
	CMclock,
	CMmode,
	CMlossi,
};

Cmdtab spitab[] = {
	{CMclock, "clock", 2},
	{CMmode, "mode", 2},
	{CMlossi, "lossi", 1},
};

static void
spikick(void *a)
{
	Block *b;
	Spi *spi;

	spi = a;
	b = qget(spi->oq);
	if(b == nil)
		return;
	if(waserror()){
		freeb(b);
		nexterror();
	}
	spirw(spi->csel, b->rp, BLEN(b));
	qpass(spi->iq, b);
	poperror();
}

static void
spiinit(void)
{
}

static long
spiread(Chan *c, void *a, long n, vlong off)
{
	Spi *spi;
	u32int *sp;
	char *p, *e;
	char buf[256];

	if(c->qid.type & QTDIR)
		return devdirread(c, a, n, spidir, nelem(spidir), devgen);

	if(c->qid.path == Qctl) {
		sp = (u32int *)SPIREGS;
		p = buf;
		e = p + sizeof(buf);
		p = seprint(p, e, "CS: %08x\n", sp[0]);
		p = seprint(p, e, "CLK: %08x\n", sp[2]);
		p = seprint(p, e, "DLEN: %08x\n", sp[3]);
		p = seprint(p, e, "LTOH: %08x\n", sp[4]);
		seprint(p, e, "DC: %08x\n", sp[5]);
		return readstr(off, a, n, buf);
	}

	spi = &spidev[DEVID(c->qid.path)];
	n = qread(spi->iq, a, n);

	return n;
}

static long
spiwrite(Chan*c, void *a, long n, vlong)
{
	Spi *spi;
	Cmdbuf *cb;
	Cmdtab *ct;

	if(c->qid.type & QTDIR)
		error(Eperm);

	if(c->qid.path == Qctl) {
		cb = parsecmd(a, n);
		if(waserror()) {
			free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, spitab, nelem(spitab));
		switch(ct->index) {
		case CMclock:
			spiclock(atoi(cb->f[1]));
			break;
		case CMmode:
			spimode(atoi(cb->f[1]));
			break;
		case CMlossi:
			break;
		}
		poperror();
		return n;
	}

	spi = &spidev[DEVID(c->qid.path)];
	n = qwrite(spi->oq, a, n);

	return n;
}

static Chan*
spiattach(char* spec)
{
	return devattach(L'π', spec);
}

static Walkqid*	 
spiwalk(Chan* c, Chan *nc, char** name, int nname)
{
	return devwalk(c, nc, name, nname, spidir, nelem(spidir), devgen);
}

static int	 
spistat(Chan* c, uchar* dp, int n)
{
	return devstat(c, dp, n, spidir, nelem(spidir), devgen);
}

static Chan*
spiopen(Chan* c, int omode)
{
	Spi *spi;

	c = devopen(c, omode, spidir, nelem(spidir), devgen);
	if(c->qid.path < Qspi)
		return c;

	spi = &spidev[DEVID(c->qid.path)];
	qlock(spi);
	if(spi->opens++ == 0){
		spi->csel = DEVID(c->qid.path);
		if(spi->iq == nil)
			spi->iq = qopen(QMAX, 0, nil, nil);
		else
			qreopen(spi->iq);
		if(spi->oq == nil)
			spi->oq = qopen(QMAX, Qkick, spikick, spi);
		else
			qreopen(spi->oq);
	}
	qunlock(spi);
	c->iounit = qiomaxatomic;
	return c;
}

static void	 
spiclose(Chan *c)
{
	Spi *spi;

	if(c->qid.path < Qspi)
		return;
	if((c->flag & COPEN) == 0)
		return;
	spi = &spidev[DEVID(c->qid.path)];
	qlock(spi);
	if(--spi->opens == 0){
		qclose(spi->iq);
		qhangup(spi->oq, nil);
		qclose(spi->oq);
	}
	qunlock(spi);
}

Dev spidevtab = {
	L'π',
	"spi",

	devreset,
	spiinit,
	devshutdown,
	spiattach,
	spiwalk,
	spistat,
	spiopen,
	devcreate,
	spiclose,
	spiread,
	devbread,
	spiwrite,
	devbwrite,
	devremove,
	devwstat,
};

