/*
* kirkwood TWSI 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 = 0,
Qtwsi,
Isleep = 1000000,
};
struct kw_twsi
{
ulong slave_addr;
ulong data;
ulong control;
union {
ulong status; /* read only */
ulong rate; /* write only */
};
ulong slave_addr_ext;
ulong soft_reset;
ulong last_data;
};
enum
{
TWSI_do_write,
TWSI_do_read,
TWSI_ack = 1<<2,
TWSI_int = 1<<3,
TWSI_stop = 1<<4,
TWSI_start = 1<<5,
TWSI_slave_en = 1<<6,
TWSI_int_en = 1<<7,
TWSI_bus_error = 0x0,
TWSI_started = 0x08,
TWSI_write_ack = 0x18,
TWSI_addr2_write_ack = 0xd0,
TWSI_write_data_ack = 0x28,
TWSI_read_ack = 0x40,
TWSI_addr2_read_ack = 0xe0,
TWSI_read_data_ack = 0x50,
TWSI_read_data_noack = 0x58,
};
struct
{
QLock;
Rendez vous;
struct kw_twsi *reg;
int done;
void (*handle)(void);
ulong addr, len, off;
uchar *buf;
} twsi;
Dirtab
twsidir[] =
{
".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
"twsi", {Qtwsi}, 0, 0660,
};
static int
twsidone(void *){
return twsi.done;
}
uchar readbuf[256];
static void
twsi_handle_read(void){
switch(twsi.reg->status){
case TWSI_started:
twsi.reg->data = twsi.addr << 1 | TWSI_do_read;
break;
case TWSI_read_ack:
twsi.reg->control |= TWSI_ack;
break;
case TWSI_read_data_ack:
/* if this goes straight into the first buffer, it faults */
readbuf[twsi.off] = (uchar)twsi.reg->data;
twsi.off++;
if(twsi.off != twsi.len)
twsi.reg->control |= TWSI_ack;
else
twsi.reg->control &= ~(TWSI_ack);
break;
default:
error("abnormal status\n");
case TWSI_read_data_noack:
twsi.handle = nil;
twsi.done = 1;
wakeup(&twsi.vous);
twsi.reg->control |= TWSI_stop;
break;
}
}
static long
twsi_run(uchar *buf, ulong len, ulong addr){
qlock(&twsi);
twsi.addr = addr;
twsi.len = len;
twsi.done = 0;
twsi.off = 0;
twsi.reg->control = ~TWSI_int & twsi.reg->control | TWSI_start;
tsleep(&twsi.vous, twsidone, 0, Isleep);
if(twsi.done == 0) {
twsi.handle=nil;
error("timeout");
}
for(len=0; len < twsi.off; len++)
buf[len]=readbuf[len];
qunlock(&twsi);
return len;
}
long
twsi_read(uchar *buf, ulong len, ulong addr){
twsi.handle = twsi_handle_read;
return twsi_run(buf, len, addr);
}
static void
interrupt(Ureg *, void */*arg*/)
{
/* iprint("µs %lud data %lux control %lux status %lux off %lux\n",
µs(), twsi.reg->data, twsi.reg->control, twsi.reg->status, twsi.off);*/
if(twsi.handle)
twsi.handle();
else
twsi.reg->control |= TWSI_stop;
twsi.reg->control &= ~TWSI_int;
intrclear(Irqlo, IRQ0twsi);
}
static void
twsiinit(void)
{
twsi.reg = (void *)AddrTwsi;
twsi.reg->control &= ~(TWSI_int);
twsi.reg->control |= TWSI_int_en;
twsi.reg->rate = 4 << 3 | 4; /* Tclk / (10 * 5 * 2⁵) = 1/8 Mhz */
twsi.handle = nil;
intrenable(Irqlo, IRQ0twsi, interrupt, nil, "TWSI");
}
static void
twsideinit(void) {
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);
break;
case Qdir:
break;
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 *c)
{
switch((ulong)c->qid.path) {
default:
error(Eperm);
break;
case Qtwsi:
break;
}
}
static long
twsiread(Chan *c, void *v, long n, vlong off)
{
switch((ulong)c->qid.path) {
default:
error(Eperm);
break;
case Qdir:
return devdirread(c, v, n, twsidir, nelem(twsidir), devgen);
case Qtwsi:
return twsi_read(v, n, off);
break;
}
return 0;
}
static long
twsiwrite(Chan *c, void *v, long n, vlong off)
{
switch((ulong)c->qid.path) {
default:
error(Eperm);
break;
case Qtwsi:
// return twsi_write(v, n, off);
break;
}
return 0;
}
Dev twsidevtab = {
L'',
"twsi",
devreset,
twsiinit,
devshutdown,
twsiattach,
twsiwalk,
twsistat,
twsiopen,
devcreate,
twsiclose,
twsiread,
devbread,
twsiwrite,
devbwrite,
devremove,
devwstat,
};
|