为什么重复启动基于i2c操作不支持在Linux?

我想从需要多次启动操作的i2c slave读取它的寄存器值。

在某种程度上,我已经在Linux内核3.18.21中追踪了I2C驱动程序,发现它不支持多重启动操作,我无法从这个I2C从设备(以太网供电pipe理器PD69104B1)读取数据。

我仍然可以find扩展驱动程序的方式,如果需要这个i2c slave或其他需要的东西的话。

我用的是i2c-tools 3.2.1。 我试着

$ i2cdump -y 0 0x20 

但我可以看到相同的值,这意味着它每次都先读取寄存器。

 $ i2cget -y 0 0x20 0x12 

或者任何其他寄存器地址返回与第一个寄存器相同的值。

该从站支持两个读取操作:

  • 字节读写地址得到它的值,但是这需要多重启动
  • 块读取 – 开始读取和i2c从属将按顺序给寄存器值,如0x00 0x01 ….(第一寄存器,第二,第三,第四….等)

我尝试一切可能的方法:

  • i2c_smbus_access()
  • i2c_smbus_write_byte()
  • i2c_smbus_read_block_data()
  • write()
  • read()

但是大部分时间,i2c总线进入超时错误和挂起的情况。

任何人有想法如何在Linux中实现这一点?

Update0:

该I2C从机需要独特的读周期:

  • 方向改变:S Addr Wr [A] RegAddress [A] S Addr Rd [A] [RegValue] P

  • 简短阅读:S Addr Rd [A] [RegValue] P

这里从i2c slave返回的最后一个值不要期待ACK。

我尝试使用I2C_M_NO_RD_ACK,但没有太多的帮助。 我读了一些价值,然后得到FF。

这个POE I2C从器件在SCL上有14ms的时间,这有点怀疑。 这看起来像i2c非标准,因为i2c可以在0HZ上工作,即只要需要,SCL可以由主设备拉伸。 Linux绝对不是实时操作系统,所以实现这个时间不能保证和i2c从属SCL超时重置可能发生。 这是我目前的结论!

所使用的I2C消息表示法来自: https : //www.kernel.org/doc/Documentation/i2c/i2c-protocol

为什么重复启动基于i2c操作不支持在Linux?

事实上,他们是支持的。

如果您正在寻找在用户空间中执行重复启动条件的方法,则可能需要使用I2C_RDWR请求执行ioctl()I2C_RDWR所述(请参阅原始问题中的最后一个代码片段)和此处的代码(有问题的代码)。

下面描述了在内核空间中执行重复启动的方式。


在Linux内核中,具有重复启动条件的 I2C读取操作默认为组合 (写入/读取)消息。

以下是如何执行组合I2C传输的示例:

 /** * Read set of registers via I2C using "repeated start" condition. * * Two I2C messages are being sent by this function: * 1. I2C write operation (write register address) with no STOP bit in the end * 2. I2C read operation * * @client: I2C client structure * @reg: register address (subaddress) * @len: bytes count to read * @buf: buffer which will contain read data * * Returns 0 on success or negative value on error. */ static int i2c_read_regs(struct i2c_client *client, u8 reg, u8 len, u8 *buf) { int ret; struct i2c_msg msg[2] = { { .addr = client->addr, .len = 1, .buf = &reg, }, { .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = buf, } }; ret = i2c_transfer(client->adapter, msg, 2); if (ret < 0) { dev_err(&client->dev, "I2C read failed\n"); return ret; } return 0; } 

要只读取1个字节(单个寄存器值),可以使用下一个辅助函数:

 /** * Read one register via I2C using "repeated start" condition. * * @client: I2C client structure * @reg: register address (subaddress) * @val: variable to store read value * * Returns 0 on success or negative value on error. */ static int i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val) { return i2c_read_regs(client, reg, 1, val); } 

以下是i2c_read_regs(client, reg, 1, val)调用的说明:

  • 设备地址是client->addr
  • 注册地址是reg
  • 1意味着我们要读取1个字节的数据(图片上的粉红色矩形)
  • 读取的数据将驻留在val

在这里输入图像描述


注意:如果您的I2C控制器(或其驱动程序)不支持在组合消息中重复启动,则仍然可以使用bit-bang实现I2C(即i2c-gpio驱动程序)。


如果什么都行不通,你可以尝试下一个最后的手段。 出于某种原因我不记得,为了重复开始工作,我需要添加I2C_M_NOSTART到第一个消息的.flags ,如下所示:

 struct i2c_msg msg[2] = { { .addr = client->addr, .flags = I2C_M_NOSTART, .len = 1, .buf = &reg, }, { .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = buf, } }; 

Documentation/i2c/i2c-protocol

如果为第一个部分消息设置I2C_M_NOSTART变量,我们不会生成Addr ,但是我们生成了startbit S


参考文献:

[1] STLinux上的I2C