Today I make some progress on i2c, because I want to use VoCore2 to driver a IR camera, its type is MLX90640, but i2c-gpio spends too much CPU and old hardware driver i2c-mt7621 can not get correct data.
i2c-gpio spends a lot of CPU time because it is using busy wait between every bit, so if I want to get MLX90640 at 8fps, it will need 800byte x 8bit x 8 = 51.2KHz, emm, currently i2c-gpio max speed is 250KHz, so it will take over 20% CPU…not a good choice.
i2c-mt7621 driver is using auto mode of mt7628’s i2c hardware, but that ‘auto’ mode only allow send byte one by one. (PS: what stupid auto mode…not auto at all…maybe something wrong?), it is much faster and less cpu taken than i2c-gpio mode, but between two bytes it has a big gap, around 50us, it is not acceptable by MLX90640, so if using this driver, I will never get 90640 correct data.
I have to find some new way… When I look into datasheet, I find mt7628 i2c also support a “sequence” mode which is able to send up to 8 bytes one time, and also get every byte status bit (status bit is NACK, ACK), this is perfect.
After several tries, I make it work successfully.
Here is the core part of code, raw patch uploaded to github.com/vonger/vocore2.git, replace old 0045 patch in openwrt.
static int mtk_i2c_trigger(struct mtk_i2c *i2c, u32 mode, u32 c)
{
u32 val, reg = 0;
val = (mode << SM0_MODE_SHIFT) | ((c - 1) << PGLEN_SHIFT) | SM0_TRI_BUSY;
iowrite32(val, i2c->base + REG_SM0CTL1);
usleep_range(i2c->delay * c, i2c->delay * c + 50);
if (mode == SM0_MODE_WRITE || mode == SM0_MODE_READ_ACK) {
reg = ioread32(i2c->base + REG_SM0CTL1);
val = (reg >> ACK_SHIFT) & ACK_MASK;
if (val == 0)
return -1;
}
return 0;
}
static int mtk_i2c_master_read(struct mtk_i2c *i2c, struct i2c_msg *cur)
{
int used = 0;
iowrite32(cur->addr * 2 + 1, i2c->base + REG_SM0D0);
if (mtk_i2c_trigger(i2c, SM0_MODE_WRITE, 1) < 0)
return -1;
while (used < cur->len) {
int size = min(cur->len - used, 8);
if (used + size == cur->len) /* last page, send NAK */
mtk_i2c_trigger(i2c, SM0_MODE_READ_NACK, size);
else
mtk_i2c_trigger(i2c, SM0_MODE_READ_ACK, size);
memcpy(cur->buf + used, i2c->base + REG_SM0D0, size);
used += size;
}
return used;
}
static int mtk_i2c_master_write(struct mtk_i2c *i2c, struct i2c_msg *cur)
{
int used = 0;
iowrite32(cur->addr * 2, i2c->base + REG_SM0D0);
if (mtk_i2c_trigger(i2c, SM0_MODE_WRITE, 1) < 0)
return -1;
while (used < cur->len) {
int size = min(cur->len - used, 8);
memcpy(i2c->base + REG_SM0D0, cur->buf + used, size);
mtk_i2c_trigger(i2c, SM0_MODE_WRITE, size);
used += size;
}
return used;
}
static int mtk_i2c_master_xfer(struct i2c_adapter *a, struct i2c_msg *m, int c)
{
struct mtk_i2c *i2c = i2c_get_adapdata(a);
int i;
for (i = 0; i < c; i++) {
if (m[i].flags & I2C_M_TEN)
return -EINVAL;
mtk_i2c_trigger(i2c, SM0_MODE_START, 1);
if ((m + i)->flags & I2C_M_RD) {
if (mtk_i2c_master_read(i2c, m + i) < 0)
break;
} else {
if (mtk_i2c_master_write(i2c, m + i) < 0)
break;
}
}
mtk_i2c_trigger(i2c, SM0_MODE_STOP, 1);
/* can not access one or more address in the i2c_msg */
if (i != c)
return -ENODEV;
/* the return value is number of executed messages */
return i;
}
Finally it is hardware driver now, I have set i2c bus to 400KHz in VOCORE2.dts, do not waste it 🙂 Also this driver fixed i2c-mt7621 can not detect device by i2cdetect, now i2cdetect -r/-q both works well.