shithub: plan9front

Download patch

ref: a23e9ac65d85d31ebc6155050070546fa3a26df6
parent: 476e7341d52f8d893d942d14343033edc0781e12
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Jun 18 08:48:26 EDT 2022

imx8: add i2c bus driver

--- /dev/null	Thu Apr  7 21:41:36 2022
+++ b/sys/src/9/imx8/i2cimx.c	Sat Jun 18 08:48:26 2022
@@ -0,0 +1,270 @@
+#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"
+
+enum {
+	Moduleclk = 25*Mhz,
+
+	I2C_IADR	= 0x00,
+	I2C_IFDR	= 0x04,
+	I2C_I2CR	= 0x08,
+		I2CR_IEN	= 1<<7,
+		I2CR_IIEN	= 1<<6,
+		I2CR_MSTA	= 1<<5,
+		I2CR_MTX	= 1<<4,
+		I2CR_TXAK	= 1<<3,
+		I2CR_RSTA	= 1<<2,
+	I2C_I2SR	= 0x0C,
+		I2SR_ICF	= 1<<7,
+		I2SR_IAAS	= 1<<6,
+		I2SR_IBB	= 1<<5,
+		I2SR_IAL	= 1<<4,
+		I2SR_SRW	= 1<<2,
+		I2SR_IIF	= 1<<1,
+		I2SR_RXAK	= 1<<0,
+	I2C_I2DR	= 0x10,
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr
+{
+	void	*regs;
+	int	irq;
+
+	Rendez;
+};
+
+static void
+interrupt(Ureg*, void *arg)
+{
+	I2Cbus *bus = arg;
+	Ctlr *ctlr = bus->ctlr;
+	wakeup(ctlr);
+}
+
+static int
+haveirq(void *arg)
+{
+	uchar *regs = arg;
+	return regs[I2C_I2SR] & (I2SR_IAL|I2SR_IIF);
+}
+
+static int
+waitsr(Ctlr *ctlr, int inv, int mask)
+{
+	uchar *regs = ctlr->regs;
+	int sr;
+
+	for(;;){
+		sr = regs[I2C_I2SR];
+		if(sr & I2SR_IAL){
+			regs[I2C_I2SR] = sr & ~(I2SR_IAL|I2SR_IIF);
+			break;
+		}
+		if(sr & I2SR_IIF)
+			regs[I2C_I2SR] = sr & ~I2SR_IIF;
+		if((sr ^ inv) & mask)
+			break;
+
+		/* polling mode */
+		if(up == nil || !islo())
+			continue;
+
+		tsleep(ctlr, haveirq, regs, 1);
+	}
+	return sr ^ inv;
+}
+
+static uchar dummy;
+
+static int
+io(I2Cbus *bus, uchar *pkt, int olen, int ilen)
+{
+	Ctlr *ctlr = bus->ctlr;
+	uchar *regs = ctlr->regs;
+	int cr, sr, alen, o, i;
+
+	cr = regs[I2C_I2CR];
+	if((cr & I2CR_IEN) == 0)
+		return -1;
+
+	o = 0;
+	if(olen <= 0)
+		goto Stop;
+
+	alen = 1;
+	if((pkt[0] & 0xF8) == 0xF0 && olen > alen)
+		alen++;
+
+	regs[I2C_IADR] = (pkt[0]&0xFE)^0xFE;	/* make sure doesnt match */
+
+	/* wait for bus idle */
+	waitsr(ctlr, I2SR_IBB, I2SR_IBB);
+
+	/* start */
+	cr |= I2CR_MSTA | I2CR_MTX | I2CR_TXAK | I2CR_IIEN;
+	regs[I2C_I2CR] = cr;
+
+	/* wait for bus busy */
+	if(waitsr(ctlr, 0, I2SR_IBB) & I2SR_IAL)
+		goto Err;
+
+	if(olen > alen)
+		pkt[0] &= ~1;
+
+	for(o=0; o<olen; o++){
+		regs[I2C_I2DR] = pkt[o];
+		sr = waitsr(ctlr, 0, I2SR_IIF);
+		if(sr & I2SR_IAL)
+			goto Err;
+		if(sr & I2SR_RXAK)
+			goto Stop;
+	}
+
+	if(ilen <= 0)
+		goto Stop;
+
+	if((pkt[0]&1) == 0){
+		regs[I2C_I2CR] = cr | I2CR_RSTA;
+
+		pkt[0] |= 1;
+		for(i=0; i<alen; i++){
+			regs[I2C_I2DR] = pkt[i];
+			sr = waitsr(ctlr, 0, I2SR_IIF);
+			if(sr & I2SR_IAL)
+				goto Err;
+			if(sr & I2SR_RXAK)
+				goto Stop;
+		}
+	}
+
+	cr &= ~(I2CR_MTX | I2CR_TXAK);
+	if(ilen == 1) cr |= I2CR_TXAK;
+	regs[I2C_I2CR] = cr;
+	dummy = regs[I2C_I2DR];	/* start the next read */
+
+	for(i=1; i<=ilen; i++){
+		sr = waitsr(ctlr, I2SR_ICF, I2SR_IIF);
+		if(sr & I2SR_IAL)
+			goto Err;
+		if(sr & I2SR_ICF)
+			goto Stop;
+		if(i == ilen){
+			cr &= ~(I2CR_MSTA|I2CR_IIEN);
+			regs[I2C_I2CR] = cr;
+		}
+		else if(i == ilen-1){
+			cr |= I2CR_TXAK;
+			regs[I2C_I2CR] = cr;
+		}
+		pkt[o++] = regs[I2C_I2DR];
+	}
+
+	return o;
+Err:
+	o = -1;
+Stop:
+	cr &= ~(I2CR_MTX|I2CR_MSTA|I2CR_RSTA|I2CR_IIEN);
+	regs[I2C_I2CR] = cr;
+	return o;
+}
+
+static int
+divindex(int v)
+{
+	static int tab[] = {
+	/* 0x00 */  30,  32,  36,  42,  48,  52,  60,  72,
+	/* 0x08 */  80,  88, 104, 128, 144, 160, 192, 240,
+	/* 0x10 */ 288, 320, 384, 480, 576, 640, 768, 960,
+	/* 0x18 */1152,1280,1536,1920,2304,2560,3072,3840,
+	/* 0x20 */  22,  24,  26,  28,  32,  36,  40,  44,
+	/* 0x28 */  48,  56,  64,  72,  80,  96, 112, 128,
+	/* 0x30 */ 160, 192, 224, 256, 320, 384, 448, 512,
+	/* 0x38 */ 640, 768, 896,1024,1280,1536,1792,2048,
+	};
+	int i, x = -1;
+	for(i = 0; i < nelem(tab); i++){
+		if(tab[i] < v)
+			continue;
+		if(x < 0 || tab[i] < tab[x]){
+			x = i;
+			if(tab[i] == v)
+				break;
+		}
+	}
+	return x;
+}
+
+static void
+clkenable(char *name, int on)
+{
+	char clk[32];
+
+	snprint(clk, sizeof(clk), "%s.ipg_clk_patref", name);
+	setclkgate(clk, 0);
+	if(on) {
+		setclkrate(clk, "osc_25m_ref_clk", Moduleclk);
+		setclkgate(clk, 1);
+	}
+}
+
+static int
+init(I2Cbus *bus)
+{
+	Ctlr *ctlr = bus->ctlr;
+	uchar *regs = ctlr->regs;
+
+	clkenable(bus->name, 1);
+
+	regs[I2C_IFDR] = divindex(Moduleclk / bus->speed);
+	regs[I2C_IADR] = 0;
+	
+	regs[I2C_I2CR] = I2CR_IEN;
+	delay(1);
+
+	intrenable(ctlr->irq, interrupt, bus, BUSUNKNOWN, bus->name);
+
+	return 0;
+}
+
+static Ctlr ctlr1 = {
+	.regs = (void*)(VIRTIO + 0xA20000),
+	.irq = IRQi2c1,
+};
+static Ctlr ctlr2 = {
+	.regs = (void*)(VIRTIO + 0xA30000),
+	.irq = IRQi2c2,
+};
+static Ctlr ctlr3 = {
+	.regs = (void*)(VIRTIO + 0xA40000),
+	.irq = IRQi2c3,
+};
+static Ctlr ctlr4 = {
+	.regs = (void*)(VIRTIO + 0xA50000),
+	.irq = IRQi2c4,
+};
+
+void
+i2cimxlink(void)
+{
+	static I2Cbus i2c1 = { "i2c1", 400000, &ctlr1, init, io };
+	static I2Cbus i2c3 = { "i2c3", 400000, &ctlr3, init, io };
+	static I2Cbus i2c4 = { "i2c4", 400000, &ctlr4, init, io };
+
+	iomuxpad("pad_i2c1_sda", "i2c1_sda", "SION ~LVTTL ~HYS PUE ODE MAX 40_OHM");
+	iomuxpad("pad_i2c1_scl", "i2c1_scl", "SION ~LVTTL ~HYS PUE ODE MAX 40_OHM");
+	addi2cbus(&i2c1);
+
+	iomuxpad("pad_i2c3_sda", "i2c3_sda", "SION ~LVTTL ~HYS PUE ODE MAX 40_OHM");
+	iomuxpad("pad_i2c3_scl", "i2c3_scl", "SION ~LVTTL ~HYS PUE ODE MAX 40_OHM VSEL_0");
+	addi2cbus(&i2c3);
+
+	iomuxpad("pad_i2c4_sda", "i2c4_sda", "SION ~LVTTL ~HYS PUE ODE MAX 40_OHM");
+	iomuxpad("pad_i2c4_scl", "i2c4_scl", "SION ~LVTTL ~HYS PUE ODE MAX 40_OHM");
+	addi2cbus(&i2c4);
+}