305 lines
4.7 KiB
C
305 lines
4.7 KiB
C
/*
|
|
* 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 void
|
|
twsifinish(void)
|
|
{
|
|
Kwtwsi *krp = (Kwtwsi *)soc.twsi;
|
|
|
|
twsi.done = 1;
|
|
krp->ctl |= Twsistop;
|
|
coherence();
|
|
}
|
|
|
|
static void
|
|
twsidoread(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 void
|
|
twsidowrite(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 int
|
|
twsigotintr(void *)
|
|
{
|
|
return twsi.intr;
|
|
}
|
|
|
|
static long
|
|
twsixfer(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 void
|
|
interrupt(Ureg *, void *)
|
|
{
|
|
Kwtwsi *krp = (Kwtwsi *)soc.twsi;
|
|
|
|
twsi.intr = 1;
|
|
wakeup(&twsi.nextbyte);
|
|
|
|
krp->ctl &= ~Twsiinten; /* stop further interrupts */
|
|
coherence();
|
|
intrclear(Irqlo, IRQ0twsi);
|
|
}
|
|
|
|
static void
|
|
twsiinit(void)
|
|
{
|
|
Kwtwsi *krp = (Kwtwsi *)soc.twsi;
|
|
|
|
intrenable(Irqlo, IRQ0twsi, interrupt, nil, "twsi");
|
|
krp->ctl &= ~Twsiint;
|
|
krp->ctl |= Twsiinten;
|
|
coherence();
|
|
}
|
|
|
|
static void
|
|
twsishutdown(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 int
|
|
twsistat(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 void
|
|
twsiclose(Chan *)
|
|
{
|
|
}
|
|
|
|
static long
|
|
twsiread(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 long
|
|
twsiwrite(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,
|
|
};
|