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

why repeated start based i2c operation are not supported in linux?

我想从需要多次启动操作才能读取其寄存器值的 i2c 从机读取。

由于在某种程度上我在 Linux 内核 3.18.21 中跟踪了 I2C 驱动程序,我发现它不支持多启动操作并且我无法从这个 I2C 从机读取(Power Over以太网管理器 PD69104B1).

如果此 i2c 从设备或任何其他需要,我仍在寻找扩展驱动程序的方法。

我使用 i2c-tools 3.2.1。 我试着

$ i2cdump -y 0 0x20 

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

$ i2cget -y 0 0x20 0x12

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

本slave支持两种读操作:

我尝试了所有可能的方法:

但大多数时候 i2c 总线会进入超时错误和挂起情况。

有人知道如何在 Linux 中实现吗?

更新0:

此 I2C 从设备需要唯一的读取周期:

这里从 i2c 从站返回的最后一个值不期望 ACK。

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

这个 POE I2C 从机在 SCL 上有 14ms 的 i2c 超时,这有点怀疑。这看起来像 i2c 非标准,因为 i2c 可以在 0HZ 上工作,即 SCL 可以由主机根据需要拉伸。 Linux 绝对不是实时的 OS 所以不能保证达到这个超时并且 i2c 从 SCL 超时重置可能发生。这就是我目前的结论!

使用的 I2C 消息符号来自: https://www.kernel.org/doc/Documentation/i2c/i2c-protocol

why repeated start based i2c operation are not supported in linux?

事实上,它们是支持的。

如果您正在寻找一种在 user-space 中执行重复启动条件的方法,您可能需要 ioctl() 和 [=14] =]请求,就像描述的那样here (see last code snippet in original question) and here(有问题的代码)。

下面介绍在kernel-space.

中执行重复启动的方法

在 Linux 内核中,repeated start condition 的 I2C 读取操作默认执行 combined (write/read) 消息。

下面是一个如何执行组合 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 控制器(或其驱动程序)不支持组合消息中的重复启动,您仍然可以使用 I2C 的 bit-bang 实现,即 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 所述:

If you set the I2C_M_NOSTART variable for the first partial message, we do not generate Addr, but we do generate the startbit S.


参考文献:

[1] I2C on STLinux