305 lines · 4.7 KB · C
RawDownload
/* * kirkwood two-wire serial interface (TWSI) and * inter-integrated circuit (I²C) driver */#include "u.h"#include "../port/lib.h"#include "mem.h"#include "dat.h"#include "fns.h"#include "../port/error.h"#include "io.h" enum {	Qdir,	Qtwsi,}; typedef struct Kwtwsi Kwtwsi;typedef struct Twsi Twsi; struct Kwtwsi {				/* device registers */	ulong	saddr;	ulong	data;	ulong	ctl;	union {		ulong	status;		/* ro */		ulong	rate;		/* wo: baud rate */	}; 	ulong	saddrext;	uchar	_pad0[0x1c-0x14];	ulong	reset;	uchar	_pad1[0x98-0x20];	ulong	initlastdata;}; enum {	Twsidowrite,	Twsidoread, 	/* ctl bits */	Twsiack		= 1<<2,		/* recv'd data; clear to ack */	Twsiint		= 1<<3,		/* interrupt conditions true */	Twsistop	= 1<<4,			Twsistart	= 1<<5,	Twsislaveen	= 1<<6,	Twsiinten	= 1<<7,		/* interrupts enabled */ 	/* status codes */	SStart	= 0x08,	SWa	= 0x18,	SWda	= 0x28,	SRa	= 0x40,	SRda	= 0x50,	SRna	= 0x58,}; struct Twsi {	QLock;	Rendez	nextbyte; 	/* remainder is state needed to track the operation in progress */	int	intr;	int	done; 	uchar	*bp;			/* current ptr into buf */	uchar	*end; 	ulong	addr;			/* device address */	char	*error;}; static Twsi twsi; static Dirtab twsidir[] = {	".",	{Qdir, 0, QTDIR},	0,	DMDIR|0555,	"twsi",	{Qtwsi},		0,	0660,}; static char Eabsts[] = "abnormal status"; static voidtwsifinish(void){	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	twsi.done = 1;	krp->ctl |= Twsistop;	coherence();} static voidtwsidoread(void){	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	switch(krp->status){	case SStart:		krp->data = twsi.addr << 1 | Twsidoread;		break;	case SRa:		krp->ctl |= Twsiack;		break;	case SRda:		if(twsi.bp < twsi.end) {			*twsi.bp++ = krp->data;			krp->ctl |= Twsiack;		} else			krp->ctl &= ~Twsiack;		break;	case SRna:		twsifinish();		break;	default:		twsifinish();		twsi.error = Eabsts;		break;	}} static voidtwsidowrite(void){	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	switch(krp->status){	case SStart:		krp->data = twsi.addr << 1 | Twsidowrite;		break;	case SWa:	case SWda:		if(twsi.bp < twsi.end)			krp->data = *twsi.bp++;		else			twsifinish();		break;	default:		twsifinish();		twsi.error = Eabsts;		break;	}} static inttwsigotintr(void *){	return twsi.intr;} static longtwsixfer(uchar *buf, ulong len, ulong offset, void (*op)(void)){	ulong off;	char *err;	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	qlock(&twsi);	twsi.bp = buf;	twsi.end = buf + len; 	twsi.addr = offset;	twsi.done = twsi.intr = 0;	twsi.error = nil; 	krp->ctl = (krp->ctl & ~Twsiint) | Twsistart;	coherence();	while (!twsi.done) {		sleep(&twsi.nextbyte, twsigotintr, 0);		twsi.intr = 0;		(*op)();		/* signal to start new op & extinguish intr source */		krp->ctl &= ~Twsiint;		coherence();		krp->ctl |= Twsiinten;		coherence();	}	twsifinish();	err = twsi.error;	off = twsi.bp - buf;	twsi.bp = nil;				/* prevent accidents */	qunlock(&twsi); 	if(err)		error(err);	return off;} static voidinterrupt(Ureg *, void *){	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	twsi.intr = 1;	wakeup(&twsi.nextbyte); 	krp->ctl &= ~Twsiinten;			/* stop further interrupts */	coherence();	intrclear(Irqlo, IRQ0twsi);} static voidtwsiinit(void){	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	intrenable(Irqlo, IRQ0twsi, interrupt, nil, "twsi");	krp->ctl &= ~Twsiint;	krp->ctl |= Twsiinten;	coherence();} static voidtwsishutdown(void){	Kwtwsi *krp = (Kwtwsi *)soc.twsi; 	krp->ctl &= ~Twsiinten;	coherence();	intrdisable(Irqlo, IRQ0twsi, interrupt, nil, "twsi");} static Chan*twsiattach(char *param){	return devattach(L'²', param);} static Walkqid*twsiwalk(Chan *c, Chan *nc, char **name, int nname){	return devwalk(c, nc, name, nname, twsidir, nelem(twsidir), devgen);} static inttwsistat(Chan *c, uchar *db, int n){	return devstat(c, db, n, twsidir, nelem(twsidir), devgen);} static Chan*twsiopen(Chan *c, int omode){	switch((ulong)c->qid.path){	default:		error(Eperm);	case Qdir:	case Qtwsi:		break;	}	c = devopen(c, omode, twsidir, nelem(twsidir), devgen);	c->mode = openmode(omode);	c->flag |= COPEN;	c->offset = 0;	return c;} static voidtwsiclose(Chan *){} static longtwsiread(Chan *c, void *v, long n, vlong off){	switch((ulong)c->qid.path){	default:		error(Eperm);	case Qdir:		return devdirread(c, v, n, twsidir, nelem(twsidir), devgen);	case Qtwsi:		return twsixfer(v, n, off, twsidoread);	}} static longtwsiwrite(Chan *c, void *v, long n, vlong off){	switch((ulong)c->qid.path){	default:		error(Eperm);	case Qtwsi:		return twsixfer(v, n, off, twsidowrite);	}} Dev twsidevtab = {	L'²',	"twsi", 	devreset,	twsiinit,	twsishutdown,	twsiattach,	twsiwalk,	twsistat,	twsiopen,	devcreate,	twsiclose,	twsiread,	devbread,	twsiwrite,	devbwrite,	devremove,	devwstat,};