/* * 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, };