diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2022-06-18 12:48:26 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2022-06-18 12:48:26 +0000 |
commit | a23e9ac65d85d31ebc6155050070546fa3a26df6 (patch) | |
tree | d723c589c94bd9c567b13a9077d8d5b30436510e /sys/src/9/imx8/i2cimx.c | |
parent | 476e7341d52f8d893d942d14343033edc0781e12 (diff) |
imx8: add i2c bus driver
Diffstat (limited to 'sys/src/9/imx8/i2cimx.c')
-rw-r--r-- | sys/src/9/imx8/i2cimx.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/sys/src/9/imx8/i2cimx.c b/sys/src/9/imx8/i2cimx.c new file mode 100644 index 000000000..41e297697 --- /dev/null +++ b/sys/src/9/imx8/i2cimx.c @@ -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); +} |