ref: f9a61a0813fa1eb4ca026373f9b421cc3aeae040
parent: f24038d601ebe23a213ca1eaf2cf2faa47c22873
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Fri Oct 13 11:46:53 EDT 2023
bcm, bcm64: Add bitbang i2cgpio driver Adding a bitbang i2cgpio driver to overcome the limitations and brokenness of the i2cbcm driver and broadcom hardware. Move the i2cbcm driver from bcm64/ to bcm/ and comment it out in all kernel configurations using i2cgpio instead.
--- /dev/null
+++ b/sys/src/9/bcm/i2cbcm.c
@@ -1,0 +1,217 @@
+/*
+ * bcm2835 i2c controller
+ *
+ * Only i2c1 is supported.
+ * i2c2 is reserved for HDMI.
+ * i2c0 SDA0/SCL0 pins are not routed to P1 connector (except for early Rev 0 boards)
+ *
+ * maybe hardware problems lurking, see: https://github.com/raspberrypi/linux/issues/254
+ *
+ * modified by adventuresin9@gmail.com to work with 9Front's port/devi2c
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/i2c.h"
+
+#define I2CREGS (VIRTIO+0x804000)
+
+
+typedef struct Ctlr Ctlr;
+typedef struct Bsc Bsc;
+
+/*
+ * Registers for Broadcom Serial Controller (i2c compatible)
+ */
+struct Bsc {
+ u32int ctrl;
+ u32int stat;
+ u32int dlen;
+ u32int addr;
+ u32int fifo;
+ u32int clkdiv; /* default 1500 => 100 KHz assuming 150Mhz input clock */
+ u32int delay; /* default (48<<16)|48 falling:rising edge */
+ u32int clktimeout; /* default 64 */
+};
+
+/*
+ * Per-controller info
+ */
+struct Ctlr {
+ Bsc *regs;
+ Rendez r;
+ Lock ilock;
+};
+
+static Ctlr ctlr;
+
+enum {
+ /* ctrl */
+ I2cen = 1<<15, /* I2c enable */
+ Intr = 1<<10, /* interrupt on reception */
+ Intt = 1<<9, /* interrupt on transmission */
+ Intd = 1<<8, /* interrupt on done */
+ Start = 1<<7, /* aka ST, start a transfer */
+ Clear = 1<<4, /* clear fifo */
+ Read = 1<<0, /* read transfer */
+ Write = 0<<0, /* write transfer */
+
+ /* stat */
+ Clkt = 1<<9, /* clock stretch timeout */
+ Err = 1<<8, /* NAK */
+ Rxf = 1<<7, /* RX fifo full */
+ Txe = 1<<6, /* TX fifo full */
+ Rxd = 1<<5, /* RX fifo has data */
+ Txd = 1<<4, /* TX fifo has space */
+ Rxr = 1<<3, /* RX fiio needs reading */
+ Txw = 1<<2, /* TX fifo needs writing */
+ Done = 1<<1, /* transfer done */
+ Ta = 1<<0, /* Transfer active */
+
+ /* pin settings */
+ SDA0Pin = 2,
+ SCL0Pin = 3,
+};
+
+static void
+i2cinterrupt(Ureg*, void*)
+{
+ Bsc *r;
+ int st;
+
+ ilock(&ctlr.ilock);
+ r = ctlr.regs;
+ st = 0;
+ if((r->ctrl & Intr) && (r->stat & Rxd))
+ st |= Intr;
+ if((r->ctrl & Intt) && (r->stat & Txd))
+ st |= Intt;
+ if(r->stat & Done)
+ st |= Intd;
+ if(st){
+ r->ctrl &= ~st;
+ wakeup(&ctlr.r);
+ }
+ iunlock(&ctlr.ilock);
+}
+
+static int
+i2cready(void *st)
+{
+ return (ctlr.regs->stat & (uintptr)st);
+}
+
+static int
+i2cinit(I2Cbus*)
+{
+ ctlr.regs = (Bsc*)I2CREGS;
+ ctlr.regs->clkdiv = 2500;
+
+ gpiosel(SDA0Pin, Alt0);
+ gpiosel(SCL0Pin, Alt0);
+ gpiopullup(SDA0Pin);
+ gpiopullup(SCL0Pin);
+
+ intrenable(IRQi2c, i2cinterrupt, nil, BUSUNKNOWN, "i2c");
+
+ return 0;
+}
+
+/*
+ * Basic I²C driver for Raspberry Pi
+ * subaddressing wasn't reliable, so it is just not allowed
+ *
+ * 10 bit addressing is also disabled.
+ */
+static int
+i2cio(I2Cdev *dev, uchar *pkt, int olen, int ilen)
+{
+ Bsc *r;
+ uchar *p;
+ int st;
+ int o;
+ int rw, len;
+ uint addr;
+ o = 0;
+
+ if(dev->subaddr > 0){ /* subaddressing in not implemented */
+ return -1;
+ }
+
+ if((pkt[0] & 0xF8) == 0xF0){ /* b11110xxx reserved for 10bit addressing*/
+ return -1;
+ }
+
+ rw = pkt[0] & 1; /* rw bit is first bit of pkt[0], read == 1 */
+ addr = dev->addr;
+ pkt++; /* move past device addr packet */
+ o++; /* have to at least return processing the dev addr */
+
+ /*
+ * If 9Front is just running a probe
+ * return 1,
+ * else the controller throws an NAK error
+ * when doing a write with just the dev addr
+ */
+
+ if((olen == 1) && (ilen == 0)){
+ return 1;
+ }
+
+ r = ctlr.regs;
+ r->ctrl = I2cen | Clear;
+ r->addr = addr;
+ r->stat = Clkt|Err|Done;
+
+ len = (olen - 1) + ilen;
+ r->dlen = len;
+ r->ctrl = I2cen | Start | Intd | rw;
+
+ p = pkt;
+ st = rw == Read? Rxd : Txd;
+ while(len > 0){
+ while((r->stat & (st|Done)) == 0){
+ r->ctrl |= rw == Read? Intr : Intt;
+ sleep(&ctlr.r, i2cready, (void*)(st|Done));
+ }
+ if(r->stat & (Err|Clkt)){
+ r->ctrl = 0;
+ return -1;
+ }
+ if(rw == Read){
+ do{
+ *p++ = r->fifo;
+ len--;
+ o++;
+ }while ((r->stat & Rxd) && len > 0);
+ }else{
+ do{
+ r->fifo = *p++;
+ len--;
+ o++;
+ }while((r->stat & Txd) && len > 0);
+ }
+ }
+
+ while((r->stat & Done) == 0)
+ sleep(&ctlr.r, i2cready, (void*)Done);
+ if(r->stat & (Err|Clkt)){
+ r->ctrl = 0;
+ return -1;
+ }
+ r->ctrl = 0;
+ return o;
+}
+
+
+void
+i2cbcmlink(void)
+{
+ static I2Cbus i2c = {"i2c1", 400000, &ctlr, i2cinit, i2cio};
+ addi2cbus(&i2c);
+}
--- /dev/null
+++ b/sys/src/9/bcm/i2cgpio.c
@@ -1,0 +1,216 @@
+/*
+ * I²C bitbang driver using GPIO pins.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/i2c.h"
+
+typedef struct Ctlr Ctlr;
+struct Ctlr
+{
+ uint sda, scl;
+ int delay;
+};
+
+static void
+Setpin(uint pin, int val)
+{
+ gpiosel(pin, val?Input:Output);
+ gpioout(pin, val);
+}
+
+static int
+Getpin(uint pin)
+{
+ return gpioin(pin);
+}
+
+static void
+Delay(Ctlr *ctlr)
+{
+ microdelay(ctlr->delay);
+}
+
+static int
+Stretch(Ctlr *ctlr)
+{
+ ulong to;
+
+ to = µs();
+ do {
+ if(Getpin(ctlr->scl))
+ return 0;
+ } while(µs() - to < 25*1000);
+ return -1;
+}
+
+static int
+Writebit(Ctlr *ctlr, int bit)
+{
+ Setpin(ctlr->sda, bit);
+ Delay(ctlr);
+ Setpin(ctlr->scl, 1);
+ Delay(ctlr);
+ if(Stretch(ctlr) < 0)
+ return -1;
+ if(bit && !Getpin(ctlr->sda))
+ return -2;
+ Setpin(ctlr->scl, 0);
+ return 0;
+}
+
+static int
+Readbit(Ctlr *ctlr)
+{
+ int bit;
+
+ Setpin(ctlr->sda, 1);
+ Delay(ctlr);
+ Setpin(ctlr->scl, 1);
+ if(Stretch(ctlr) < 0)
+ return -1;
+ Delay(ctlr);
+ bit = Getpin(ctlr->sda);
+ Setpin(ctlr->scl, 0);
+ return bit;
+}
+
+static int
+Readbyte(Ctlr *ctlr, int nack)
+{
+ int byte, i, e;
+
+ byte = 0;
+ for(i=0; i<8; i++){
+ if((e = Readbit(ctlr)) < 0)
+ return e;
+ byte <<= 1;
+ byte |= e;
+ }
+ if((e = Writebit(ctlr, nack)) < 0)
+ return e;
+ return byte;
+}
+
+static int
+Writebyte(Ctlr *ctlr, int byte)
+{
+ int i, e;
+
+ for(i=0; i<8; i++){
+ if((e = Writebit(ctlr, (byte>>7)&1)) < 0)
+ return e;
+ byte <<= 1;
+ }
+ return Readbit(ctlr);
+}
+
+static int
+Start(Ctlr *ctlr)
+{
+ if(!Getpin(ctlr->sda) || !Getpin(ctlr->scl))
+ return -2;
+ Setpin(ctlr->sda, 0);
+ Delay(ctlr);
+ Setpin(ctlr->scl, 0);
+ return 0;
+}
+
+static int
+Restart(Ctlr *ctlr)
+{
+ Setpin(ctlr->sda, 1);
+ Delay(ctlr);
+ Setpin(ctlr->scl, 1);
+ if(Stretch(ctlr) < 0)
+ return -1;
+ Delay(ctlr);
+ return Start(ctlr);
+}
+
+static int
+Stop(Ctlr *ctlr)
+{
+ Setpin(ctlr->sda, 0);
+ Delay(ctlr);
+ Setpin(ctlr->scl, 1);
+ if(Stretch(ctlr) < 0){
+ Setpin(ctlr->sda, 1);
+ return -1;
+ }
+ Delay(ctlr);
+ Setpin(ctlr->sda, 1);
+ Delay(ctlr);
+ return 0;
+}
+
+static int
+init(I2Cbus *bus)
+{
+ Ctlr *ctlr = bus->ctlr;
+
+ ctlr->delay = 1 + (1000000 / (2 * bus->speed));
+ Setpin(ctlr->sda, 1);
+ Setpin(ctlr->scl, 1);
+ gpiopullup(ctlr->sda);
+ gpiopullup(ctlr->scl);
+ return 0;
+}
+
+static int
+io(I2Cdev *dev, uchar *pkt, int olen, int ilen)
+{
+ I2Cbus *bus = dev->bus;
+ Ctlr *ctlr = bus->ctlr;
+ int i, o, v, alen;
+
+ alen = olen > 0;
+ if(olen > alen && (pkt[0] & 0xF8) == 0xF0)
+ alen++;
+
+ if((v = Start(ctlr)) < 0)
+ return -1;
+ if(olen > alen)
+ pkt[0] &= ~1;
+ for(o=0; o<olen; o++){
+ if((v = Writebyte(ctlr, pkt[o])) != 0)
+ goto Stop;
+ }
+ if(ilen <= 0 || olen <= 0)
+ goto Stop;
+ if((pkt[0]&1) == 0){
+ if((v = Restart(ctlr)) < 0)
+ goto Stop;
+ pkt[0] |= 1;
+ for(i=0; i<alen; i++){
+ if((v = Writebyte(ctlr, pkt[i])) != 0)
+ goto Stop;
+ }
+ }
+ for(i=1; i<=ilen; i++){
+ if((v = Readbyte(ctlr, i==ilen)) < 0)
+ goto Stop;
+ pkt[o++] = v;
+ }
+Stop:
+ if(v == -2) /* arbitration lost */
+ return -1;
+ Stop(ctlr);
+ return o;
+}
+
+void
+i2cgpiolink(void)
+{
+ static Ctlr ctlr1 = {
+ .sda = 2,
+ .scl = 3,
+ };
+ static I2Cbus i2c1 = {"i2c1", 100*1000, &ctlr1, init, io};
+ addi2cbus(&i2c1);
+}
--- a/sys/src/9/bcm/mkfile
+++ b/sys/src/9/bcm/mkfile
@@ -112,6 +112,7 @@
main.$O: errstr.h rebootcode.i
devmouse.$O mouse.$O screen.$O: screen.h
usbdwc.$O: dwcotg.h ../port/usb.h
+i2cbcm.$O i2cgpio.$O: ../port/i2c.h
arch.$O archbcm.$O archbcm2.$O clock.$O coproc.$O fpiarn.$O mmu.$O trap.$O vfp3.$O rebootcode.$O: arm.h mem.h
rebootcode.$O: arm.s cache.v7.s
--- a/sys/src/9/bcm/pi
+++ b/sys/src/9/bcm/pi
@@ -19,6 +19,7 @@
mouse mouse
uart gpio
gpio gpio
+ i2c
sd
usb
@@ -29,6 +30,8 @@
loopbackmedium
netdevmedium
emmc
+# i2cbcm devi2c
+ i2cgpio devi2c gpio
ip
tcp
--- a/sys/src/9/bcm/pi2
+++ b/sys/src/9/bcm/pi2
@@ -19,6 +19,7 @@
mouse mouse
uart gpio
gpio gpio
+ i2c
sd
usb
@@ -29,6 +30,8 @@
loopbackmedium
netdevmedium
emmc
+# i2cbcm devi2c
+ i2cgpio devi2c gpio
ip
tcp
--- a/sys/src/9/bcm64/i2cbcm.c
+++ /dev/null
@@ -1,217 +1,0 @@
-/*
- * bcm2835 i2c controller
- *
- * Only i2c1 is supported.
- * i2c2 is reserved for HDMI.
- * i2c0 SDA0/SCL0 pins are not routed to P1 connector (except for early Rev 0 boards)
- *
- * maybe hardware problems lurking, see: https://github.com/raspberrypi/linux/issues/254
- *
- * modified by adventuresin9@gmail.com to work with 9Front's port/devi2c
- */
-
-#include "u.h"
-#include "../port/lib.h"
-#include "../port/error.h"
-#include "mem.h"
-#include "dat.h"
-#include "fns.h"
-#include "io.h"
-#include "../port/i2c.h"
-
-#define I2CREGS (VIRTIO+0x804000)
-
-
-typedef struct Ctlr Ctlr;
-typedef struct Bsc Bsc;
-
-/*
- * Registers for Broadcom Serial Controller (i2c compatible)
- */
-struct Bsc {
- u32int ctrl;
- u32int stat;
- u32int dlen;
- u32int addr;
- u32int fifo;
- u32int clkdiv; /* default 1500 => 100 KHz assuming 150Mhz input clock */
- u32int delay; /* default (48<<16)|48 falling:rising edge */
- u32int clktimeout; /* default 64 */
-};
-
-/*
- * Per-controller info
- */
-struct Ctlr {
- Bsc *regs;
- Rendez r;
- Lock ilock;
-};
-
-static Ctlr ctlr;
-
-enum {
- /* ctrl */
- I2cen = 1<<15, /* I2c enable */
- Intr = 1<<10, /* interrupt on reception */
- Intt = 1<<9, /* interrupt on transmission */
- Intd = 1<<8, /* interrupt on done */
- Start = 1<<7, /* aka ST, start a transfer */
- Clear = 1<<4, /* clear fifo */
- Read = 1<<0, /* read transfer */
- Write = 0<<0, /* write transfer */
-
- /* stat */
- Clkt = 1<<9, /* clock stretch timeout */
- Err = 1<<8, /* NAK */
- Rxf = 1<<7, /* RX fifo full */
- Txe = 1<<6, /* TX fifo full */
- Rxd = 1<<5, /* RX fifo has data */
- Txd = 1<<4, /* TX fifo has space */
- Rxr = 1<<3, /* RX fiio needs reading */
- Txw = 1<<2, /* TX fifo needs writing */
- Done = 1<<1, /* transfer done */
- Ta = 1<<0, /* Transfer active */
-
- /* pin settings */
- SDA0Pin = 2,
- SCL0Pin = 3,
-};
-
-static void
-i2cinterrupt(Ureg*, void*)
-{
- Bsc *r;
- int st;
-
- ilock(&ctlr.ilock);
- r = ctlr.regs;
- st = 0;
- if((r->ctrl & Intr) && (r->stat & Rxd))
- st |= Intr;
- if((r->ctrl & Intt) && (r->stat & Txd))
- st |= Intt;
- if(r->stat & Done)
- st |= Intd;
- if(st){
- r->ctrl &= ~st;
- wakeup(&ctlr.r);
- }
- iunlock(&ctlr.ilock);
-}
-
-static int
-i2cready(void *st)
-{
- return (ctlr.regs->stat & (uintptr)st);
-}
-
-static int
-i2cinit(I2Cbus*)
-{
- ctlr.regs = (Bsc*)I2CREGS;
- ctlr.regs->clkdiv = 2500;
-
- gpiosel(SDA0Pin, Alt0);
- gpiosel(SCL0Pin, Alt0);
- gpiopullup(SDA0Pin);
- gpiopullup(SCL0Pin);
-
- intrenable(IRQi2c, i2cinterrupt, nil, BUSUNKNOWN, "i2c");
-
- return 0;
-}
-
-/*
- * Basic I²C driver for Raspberry Pi
- * subaddressing wasn't reliable, so it is just not allowed
- *
- * 10 bit addressing is also disabled.
- */
-static int
-i2cio(I2Cdev *dev, uchar *pkt, int olen, int ilen)
-{
- Bsc *r;
- uchar *p;
- int st;
- int o;
- int rw, len;
- uint addr;
- o = 0;
-
- if(dev->subaddr > 0){ /* subaddressing in not implemented */
- return -1;
- }
-
- if((pkt[0] & 0xF8) == 0xF0){ /* b11110xxx reserved for 10bit addressing*/
- return -1;
- }
-
- rw = pkt[0] & 1; /* rw bit is first bit of pkt[0], read == 1 */
- addr = dev->addr;
- pkt++; /* move past device addr packet */
- o++; /* have to at least return processing the dev addr */
-
- /*
- * If 9Front is just running a probe
- * return 1,
- * else the controller throws an NAK error
- * when doing a write with just the dev addr
- */
-
- if((olen == 1) && (ilen == 0)){
- return 1;
- }
-
- r = ctlr.regs;
- r->ctrl = I2cen | Clear;
- r->addr = addr;
- r->stat = Clkt|Err|Done;
-
- len = (olen - 1) + ilen;
- r->dlen = len;
- r->ctrl = I2cen | Start | Intd | rw;
-
- p = pkt;
- st = rw == Read? Rxd : Txd;
- while(len > 0){
- while((r->stat & (st|Done)) == 0){
- r->ctrl |= rw == Read? Intr : Intt;
- sleep(&ctlr.r, i2cready, (void*)(st|Done));
- }
- if(r->stat & (Err|Clkt)){
- r->ctrl = 0;
- return -1;
- }
- if(rw == Read){
- do{
- *p++ = r->fifo;
- len--;
- o++;
- }while ((r->stat & Rxd) && len > 0);
- }else{
- do{
- r->fifo = *p++;
- len--;
- o++;
- }while((r->stat & Txd) && len > 0);
- }
- }
-
- while((r->stat & Done) == 0)
- sleep(&ctlr.r, i2cready, (void*)Done);
- if(r->stat & (Err|Clkt)){
- r->ctrl = 0;
- return -1;
- }
- r->ctrl = 0;
- return o;
-}
-
-
-void
-i2cbcmlink(void)
-{
- static I2Cbus i2c = {"i2c1", 400000, &ctlr, i2cinit, i2cio};
- addi2cbus(&i2c);
-}
--- a/sys/src/9/bcm64/mkfile
+++ b/sys/src/9/bcm64/mkfile
@@ -107,6 +107,7 @@
devmouse.$O mouse.$O screen.$O: screen.h
usbdwc.$O: dwcotg.h ../port/usb.h
+i2cbcm.$O i2cgpio.$O: ../port/i2c.h
io.h: ../bcm/io.h
touch $target
--- a/sys/src/9/bcm64/pi3
+++ b/sys/src/9/bcm64/pi3
@@ -31,7 +31,8 @@
loopbackmedium
netdevmedium
emmc
- i2cbcm devi2c
+# i2cbcm devi2c
+ i2cgpio devi2c gpio
ip
tcp
--- a/sys/src/9/bcm64/pi4
+++ b/sys/src/9/bcm64/pi4
@@ -20,6 +20,7 @@
mouse mouse
uart gpio
gpio gpio
+ i2c
pci pci
sd
usb
@@ -35,6 +36,8 @@
loopbackmedium
netdevmedium
sdhc
+# i2cbcm devi2c
+ i2cgpio devi2c gpio
ip
tcp