ref: 276f2039a9bceb4bc23b0fa1ce3169057aac405e
parent: 3e176bd975492427b232308e37ff51e7389d08e7
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Jun 11 17:06:39 EDT 2022
devi2c: add generic i2c bus driver
--- /dev/null
+++ b/sys/src/9/port/devi2c.c
@@ -1,0 +1,597 @@
+/* I²C bus driver */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "../port/i2c.h"
+
+enum {
+ Qdir = 0, /* #J */
+ Qbus, /* #J/bus */
+ Qctl, /* #J/bus/i2c.n.ctl */
+ Qdata, /* #J/bus/i2c.n.data */
+};
+
+#define TYPE(q) ((ulong)(q).path & 0x0F)
+#define BUS(q) (((ulong)(q).path>>4) & 0xFF)
+#define DEV(q) (((ulong)(q).path>>12) & 0xFFF)
+#define QID(d, b, t) (((d)<<12)|((b)<<4)|(t))
+
+static I2Cbus *buses[16];
+
+static I2Cdev *devs[1024];
+static Lock devslock;
+
+static void probebus(I2Cbus *bus);
+
+void
+addi2cbus(I2Cbus *bus)
+{
+ int i;
+
+ if(bus == nil)
+ return;
+
+ for(i = 0; i < nelem(buses); i++){
+ if(buses[i] == nil
+ || buses[i] == bus
+ || strcmp(bus->name, buses[i]->name) == 0){
+ buses[i] = bus;
+ break;
+ }
+ }
+}
+
+I2Cbus*
+i2cbus(char *name)
+{
+ I2Cbus *bus;
+ int i;
+
+ for(i = 0; i < nelem(buses); i++){
+ bus = buses[i];
+ if(bus == nil)
+ break;
+ if(strcmp(bus->name, name) == 0){
+ probebus(bus);
+ return bus;
+ }
+ }
+ return nil;
+}
+
+void
+addi2cdev(I2Cdev *dev)
+{
+ int i;
+
+ if(dev == nil || dev->bus == nil)
+ return;
+
+ lock(&devslock);
+ for(i = 0; i < nelem(devs); i++){
+ if(devs[i] == nil
+ || devs[i] == dev
+ || devs[i]->addr == dev->addr && devs[i]->bus == dev->bus){
+ devs[i] = dev;
+ unlock(&devslock);
+ return;
+ }
+ }
+ unlock(&devslock);
+}
+
+I2Cdev*
+i2cdev(I2Cbus *bus, int addr)
+{
+ I2Cdev *dev;
+ int i;
+
+ if(bus == nil || addr < 0 || addr >= 1<<10)
+ return nil;
+
+ lock(&devslock);
+ for(i = 0; i < nelem(devs) && (dev = devs[i]) != nil; i++){
+ if(dev->addr == addr && dev->bus == bus){
+ unlock(&devslock);
+ return dev;
+ }
+ }
+ unlock(&devslock);
+
+ return nil;
+}
+
+
+static int
+enterbus(I2Cbus *bus)
+{
+ if(up != nil && islo()){
+ eqlock(bus);
+ return 1;
+ } else {
+ while(!canqlock(bus))
+ ;
+ return 0;
+ }
+}
+
+static void
+leavebus(I2Cbus *bus)
+{
+ qunlock(bus);
+}
+
+int
+i2cbusio(I2Cbus *bus, uchar *pkt, int olen, int ilen)
+{
+ int user, n;
+
+ user = enterbus(bus);
+ if(!bus->probed){
+ leavebus(bus);
+ return -1;
+ }
+ if(user && waserror()){
+ (*bus->io)(bus, nil, 0, 0);
+ leavebus(bus);
+ nexterror();
+ }
+// iprint("%s: <- %.*H\n", bus->name, olen, pkt);
+ n = (*bus->io)(bus, pkt, olen, ilen);
+// if(n > olen) iprint("%s: -> %.*H\n", bus->name, n - olen, pkt+olen);
+
+ leavebus(bus);
+ if(user) poperror();
+
+ return n;
+}
+
+static int
+putaddr(I2Cdev *dev, int isread, uchar *pkt, vlong addr)
+{
+ int n, o = 0;
+
+ if(dev->a10){
+ pkt[o++] = 0xF0 | (dev->addr>>(8-1))&6 | (isread != 0);
+ pkt[o++] = dev->addr;
+ } else
+ pkt[o++] = dev->addr<<1 | (isread != 0);
+
+ if(addr >= 0){
+ for(n=0; n<dev->subaddr; n++)
+ pkt[o++] = addr >> (n*8);
+ }
+
+ return o;
+}
+
+int
+i2csend(I2Cdev *dev, void *data, int len, vlong addr)
+{
+ uchar pkt[138];
+ int o;
+
+ o = putaddr(dev, 0, pkt, addr);
+ if(o+len > sizeof(pkt))
+ len = sizeof(pkt)-o;
+
+ if(len > 0)
+ memmove(pkt+o, data, len);
+
+ return i2cbusio(dev->bus, pkt, o + len, 0) - o;
+}
+
+int
+i2crecv(I2Cdev *dev, void *data, int len, vlong addr)
+{
+ uchar pkt[138];
+ int o;
+
+ o = putaddr(dev, 1, pkt, addr);
+ if(o+len > sizeof(pkt))
+ len = sizeof(pkt)-o;
+
+ len = i2cbusio(dev->bus, pkt, o, len) - o;
+ if(len > 0)
+ memmove(data, pkt+o, len);
+
+ return len;
+}
+
+int
+i2cquick(I2Cdev *dev, int rw)
+{
+ uchar pkt[2];
+ int o = putaddr(dev, rw, pkt, -1);
+ if(i2cbusio(dev->bus, pkt, o, 0) != o)
+ return -1;
+ return rw != 0;
+}
+int
+i2crecvbyte(I2Cdev *dev)
+{
+ uchar pkt[2+1];
+ int o = putaddr(dev, 1, pkt, -1);
+ if(i2cbusio(dev->bus, pkt, o, 1) - o != 1)
+ return -1;
+ return pkt[o];
+}
+int
+i2csendbyte(I2Cdev *dev, uchar b)
+{
+ uchar pkt[2+1];
+ int o = putaddr(dev, 0, pkt, -1);
+ pkt[o] = b;
+ if(i2cbusio(dev->bus, pkt, o+1, 0) - o != 1)
+ return -1;
+ return b;
+}
+int
+i2creadbyte(I2Cdev *dev, ulong addr)
+{
+ uchar pkt[2+4+1];
+ int o = putaddr(dev, 1, pkt, addr);
+ if(i2cbusio(dev->bus, pkt, o, 1) - o != 1)
+ return -1;
+ return pkt[o];
+}
+int
+i2cwritebyte(I2Cdev *dev, ulong addr, uchar b)
+{
+ uchar pkt[2+4+1];
+ int o = putaddr(dev, 0, pkt, addr);
+ pkt[o] = b;
+ if(i2cbusio(dev->bus, pkt, o+1, 0) - o != 1)
+ return -1;
+ return b;
+}
+int
+i2creadword(I2Cdev *dev, ulong addr)
+{
+ uchar pkt[2+4+2];
+ int o = putaddr(dev, 1, pkt, addr);
+ if(i2cbusio(dev->bus, pkt, o, 2) - o != 2)
+ return -1;
+ return pkt[o] | (ushort)pkt[o+1]<<8;
+}
+int
+i2cwriteword(I2Cdev *dev, ulong addr, ushort w)
+{
+ uchar pkt[2+4+2];
+ int o = putaddr(dev, 0, pkt, addr);
+ pkt[o+0] = w;
+ pkt[o+1] = w>>8;
+ if(i2cbusio(dev->bus, pkt, o+2, 0) - o != 2)
+ return -1;
+ return w;
+}
+vlong
+i2cread32(I2Cdev *dev, ulong addr)
+{
+ uchar pkt[2+4+4];
+ int o = putaddr(dev, 1, pkt, addr);
+ if(i2cbusio(dev->bus, pkt, o, 4) - o != 4)
+ return -1;
+ return pkt[o] | (ulong)pkt[o+1]<<8 | (ulong)pkt[o+2]<<16 | (ulong)pkt[o+3]<<24;
+}
+vlong
+i2cwrite32(I2Cdev *dev, ulong addr, ulong u)
+{
+ uchar pkt[2+4+4];
+ int o = putaddr(dev, 0, pkt, addr);
+ pkt[o+0] = u;
+ pkt[o+1] = u>>8;
+ pkt[o+2] = u>>16;
+ pkt[o+3] = u>>24;
+ if(i2cbusio(dev->bus, pkt, o+4, 0) - o != 4)
+ return -1;
+ return u;
+}
+
+static void
+probeddev(I2Cdev *dummy)
+{
+ I2Cdev *dev = smalloc(sizeof(I2Cdev));
+ memmove(dev, dummy, sizeof(I2Cdev));
+ addi2cdev(dev);
+}
+
+static void
+probebus(I2Cbus *bus)
+{
+ I2Cdev dummy;
+ uchar pkt[2];
+ int user, n;
+
+ if(bus->probed)
+ return;
+
+ user = enterbus(bus);
+ if(bus->probed){
+ leavebus(bus);
+ return;
+ }
+ if(user && waserror()
+ || (*bus->init)(bus)){
+ leavebus(bus);
+ if(user) nexterror();
+ return;
+ }
+
+ memset(&dummy, 0, sizeof(dummy));
+ dummy.bus = bus;
+
+ dummy.a10 = 0;
+ for(dummy.addr = 8; dummy.addr < 0x78; dummy.addr++) {
+ if(i2cdev(bus, dummy.addr) != nil)
+ continue;
+ if(user && waserror()){
+ (*bus->io)(bus, nil, 0, 0);
+ continue;
+ }
+ n = putaddr(&dummy, 0, pkt, -1);
+ if((*bus->io)(bus, pkt, n, 0) == n)
+ probeddev(&dummy);
+ if(user) poperror();
+ }
+
+ dummy.a10 = 1;
+ for(dummy.addr = 0; dummy.addr < (1<<10); dummy.addr++) {
+ if(i2cdev(bus, dummy.addr) != nil)
+ continue;
+ if(user && waserror()){
+ (*bus->io)(bus, nil, 0, 0);
+ continue;
+ }
+ n = putaddr(&dummy, 0, pkt, -1);
+ if((*bus->io)(bus, pkt, n, 0) == n)
+ probeddev(&dummy);
+ if(user) poperror();
+ }
+
+ bus->probed = 1;
+ leavebus(bus);
+ if(user) poperror();
+}
+
+static int
+i2cgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
+{
+ I2Cbus *bus;
+ I2Cdev *dev;
+
+ Qid q;
+
+ switch(TYPE(c->qid)){
+ case Qdir:
+ if(s == DEVDOTDOT){
+ Gendir:
+ mkqid(&q, QID(0, 0, Qdir), 0, QTDIR);
+ snprint(up->genbuf, sizeof up->genbuf, "#J");
+ devdir(c, q, up->genbuf, 0, eve, 0500, dp);
+ return 1;
+ }
+ if(s >= nelem(buses))
+ return -1;
+ bus = buses[s];
+ if(bus == nil)
+ return -1;
+ mkqid(&q, QID(0, s, Qbus), 0, QTDIR);
+ devdir(c, q, bus->name, 0, eve, 0500, dp);
+ return 1;
+ case Qbus:
+ if(s == DEVDOTDOT)
+ goto Gendir;
+ if((s/2) >= nelem(devs))
+ return -1;
+
+ bus = buses[BUS(c->qid)];
+ probebus(bus);
+
+ lock(&devslock);
+ dev = devs[s/2];
+ unlock(&devslock);
+
+ if(dev == nil)
+ return -1;
+ if(dev->bus != bus)
+ return 0;
+ if(s & 1){
+ mkqid(&q, QID(dev->addr, BUS(c->qid), Qdata), 0, 0);
+ goto Gendata;
+ }
+ mkqid(&q, QID(dev->addr, BUS(c->qid), Qctl), 0, 0);
+ goto Genctl;
+ case Qctl:
+ q = c->qid;
+ Genctl:
+ snprint(up->genbuf, sizeof up->genbuf, "i2c.%lux.ctl", DEV(q));
+ devdir(c, q, up->genbuf, 0, eve, 0600, dp);
+ return 1;
+ case Qdata:
+ q = c->qid;
+ bus = buses[BUS(q)];
+ dev = i2cdev(bus, DEV(q));
+ if(dev == nil)
+ return -1;
+ Gendata:
+ snprint(up->genbuf, sizeof up->genbuf, "i2c.%lux.data", DEV(q));
+ devdir(c, q, up->genbuf, dev->size, eve, 0600, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static Chan*
+i2cattach(char *spec)
+{
+ return devattach('J', spec);
+}
+
+static Chan*
+i2copen(Chan *c, int mode)
+{
+ c = devopen(c, mode, nil, 0, i2cgen);
+ switch(TYPE(c->qid)){
+ case Qctl:
+ case Qdata:
+ c->aux = i2cdev(buses[BUS(c->qid)], DEV(c->qid));
+ if(c->aux == nil)
+ error(Enonexist);
+ break;
+ }
+ return c;
+}
+
+enum {
+ CMsize,
+ CMsubaddress,
+};
+
+static Cmdtab i2cctlmsg[] =
+{
+ CMsize, "size", 2,
+ CMsubaddress, "subaddress", 2,
+};
+
+static long
+i2cwrctl(I2Cdev *dev, void *data, long len)
+{
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ ulong u;
+
+ cb = parsecmd(data, len);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, i2cctlmsg, nelem(i2cctlmsg));
+ switch(ct->index){
+ case CMsize:
+ dev->size = strtoul(cb->f[1], nil, 0);
+ break;
+ case CMsubaddress:
+ u = strtoul(cb->f[1], nil, 0);
+ if(u > 4)
+ cmderror(cb, Ebadarg);
+ dev->subaddr = u;
+ break;
+ default:
+ cmderror(cb, Ebadarg);
+ }
+ free(cb);
+ poperror();
+ return len;
+}
+
+static long
+i2crdctl(I2Cdev *dev, void *data, long len, vlong offset)
+{
+ char cfg[64];
+
+ snprint(cfg, sizeof(cfg), "size %lud\nsubaddress %d\n", dev->size, dev->subaddr);
+ return readstr((ulong)offset, data, len, cfg);
+}
+
+static long
+i2cwrite(Chan *c, void *data, long len, vlong offset)
+{
+ I2Cdev *dev;
+
+ switch(TYPE(c->qid)){
+ default:
+ error(Egreg);
+ return -1;
+ case Qctl:
+ dev = c->aux;
+ return i2cwrctl(dev, data, len);
+ case Qdata:
+ break;
+ }
+ dev = c->aux;
+ if(dev->size){
+ if(offset+len > dev->size){
+ if(offset >= dev->size)
+ return 0;
+ len = dev->size - offset;
+ }
+ }
+ len = i2csend(dev, data, len, offset);
+ if(len < 0)
+ error(Eio);
+ return len;
+}
+
+static long
+i2cread(Chan *c, void *data, long len, vlong offset)
+{
+ I2Cdev *dev;
+
+ if(c->qid.type == QTDIR)
+ return devdirread(c, data, len, nil, 0, i2cgen);
+
+ switch(TYPE(c->qid)){
+ default:
+ error(Egreg);
+ case Qctl:
+ dev = c->aux;
+ return i2crdctl(dev, data, len, offset);
+ case Qdata:
+ break;
+ }
+ dev = c->aux;
+ if(dev->size){
+ if(offset+len > dev->size){
+ if(offset >= dev->size)
+ return 0;
+ len = dev->size - offset;
+ }
+ }
+ len = i2crecv(dev, data, len, offset);
+ if(len < 0)
+ error(Eio);
+ return len;
+}
+
+void
+i2cclose(Chan*)
+{
+}
+
+static Walkqid*
+i2cwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, i2cgen);
+}
+
+static int
+i2cstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, nil, 0, i2cgen);
+}
+
+Dev i2cdevtab = {
+ 'J',
+ "i2c",
+
+ devreset,
+ devinit,
+ devshutdown,
+ i2cattach,
+ i2cwalk,
+ i2cstat,
+ i2copen,
+ devcreate,
+ i2cclose,
+ i2cread,
+ devbread,
+ i2cwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+ devpower,
+};
--- /dev/null
+++ b/sys/src/9/port/i2c.h
@@ -1,0 +1,56 @@
+typedef struct I2Cbus I2Cbus;
+struct I2Cbus
+{
+ char *name;
+ int speed;
+
+ void *ctlr;
+ int (*init)(I2Cbus *bus);
+ int (*io)(I2Cbus *bus, uchar *pkt, int olen, int ilen);
+
+ int probed;
+ QLock;
+};
+
+typedef struct I2Cdev I2Cdev;
+struct I2Cdev
+{
+ I2Cbus *bus;
+
+ int a10;
+ int addr;
+ int subaddr;
+ ulong size;
+};
+
+/*
+ * Register busses (controllers) and devices (addresses)
+ */
+extern void addi2cbus(I2Cbus *bus);
+extern void addi2cdev(I2Cdev *dev);
+
+/*
+ * Look-up busses and devices by name and address
+ */
+extern I2Cbus* i2cbus(char *name);
+extern I2Cdev* i2cdev(I2Cbus *bus, int addr);
+
+/*
+ * generic I/O
+ */
+extern int i2cbusio(I2Cbus *bus, uchar *pkt, int olen, int ilen);
+extern int i2crecv(I2Cdev *dev, void *data, int len, vlong addr);
+extern int i2csend(I2Cdev *dev, void *data, int len, vlong addr);
+
+/*
+ * common I/O for SMbus
+ */
+extern int i2cquick(I2Cdev *dev, int rw);
+extern int i2crecvbyte(I2Cdev *dev);
+extern int i2csendbyte(I2Cdev *dev, uchar b);
+extern int i2creadbyte(I2Cdev *dev, ulong addr);
+extern int i2cwritebyte(I2Cdev *dev, ulong addr, uchar b);
+extern int i2creadword(I2Cdev *dev, ulong addr);
+extern int i2cwriteword(I2Cdev *dev, ulong addr, ushort w);
+extern vlong i2cread32(I2Cdev *dev, ulong addr);
+extern vlong i2cwrite32(I2Cdev *dev, ulong addr, ulong u);