Linux I2C 从设备接口描述

作者:Wolfram Sang <wsa@sang-engineering.com>,2014-15

如果使用的 I2C 控制器具有从设备功能,Linux 也可以充当 I2C 从设备。为了实现这一点,需要在总线驱动程序中支持从设备,以及一个提供实际功能的独立于硬件的软件后端。后者的一个例子是 slave-eeprom 驱动程序,它充当双内存驱动程序。虽然总线上的另一个 I2C 主设备可以像访问普通 EEPROM 一样访问它,但 Linux I2C 从设备可以通过 sysfs 访问内容并根据需要处理数据。后端驱动程序和 I2C 总线驱动程序通过事件进行通信。这是一个小图,可视化了数据流和数据传输的方式。虚线仅标记一个示例。后端也可以使用字符设备,仅在内核中,或完全不同的东西

            e.g. sysfs        I2C slave events        I/O registers
+-----------+   v    +---------+     v     +--------+  v  +------------+
| Userspace +........+ Backend +-----------+ Driver +-----+ Controller |
+-----------+        +---------+           +--------+     +------------+
                                                              | |
----------------------------------------------------------------+--  I2C
--------------------------------------------------------------+----  Bus

注意:从技术上讲,后端和驱动程序之间也存在 I2C 核心。但是,在撰写本文时,该层是透明的。

用户手册

I2C 从设备后端行为类似于标准 I2C 客户端。因此,您可以按照文档 如何实例化 I2C 设备 中所述的方式实例化它们。唯一的区别是 I2C 从设备后端有自己的地址空间。因此,您必须在您最初请求的地址上加 0x1000。以下示例说明如何在总线 1 上以 7 位地址 0x64 从用户空间实例化 slave-eeprom 驱动程序

# echo slave-24c02 0x1064 > /sys/bus/i2c/devices/i2c-1/new_device

每个后端都应附带单独的文档,以描述其特定行为和设置。

开发者手册

首先,将详细描述总线驱动程序和后端使用的事件。之后,将给出一些扩展总线驱动程序和编写后端的实现提示。

I2C 从设备事件

总线驱动程序使用以下函数向后端发送事件

ret = i2c_slave_event(client, event, &val)

“client” 描述 I2C 从设备。“event” 是下文描述的特殊事件类型之一。“val” 保存一个 u8 值,用于读取/写入的数据字节,因此是双向的。即使某个事件未使用 val,也必须始终提供指向 val 的指针,即不要在此处使用 NULL。“ret” 是来自后端的返回值。强制事件必须由总线驱动程序提供,并且必须由后端驱动程序检查。

事件类型

  • I2C_SLAVE_WRITE_REQUESTED(强制)

    “val”:未使用

    “ret”:如果后端已准备就绪,则为 0,否则为一些 errno

另一个 I2C 主设备想要向我们写入数据。当检测到我们自己的地址和写入位时,应发送此事件。数据尚未到达,因此无需处理或返回任何内容。返回后,总线驱动程序必须始终确认地址阶段。如果“ret”为零,则完成后端初始化或唤醒,并且可能会收到更多数据。如果“ret”是 errno,则总线驱动程序应否认所有传入字节,直到下一个停止条件,以强制重试传输。

  • I2C_SLAVE_READ_REQUESTED(强制)

    “val”:后端返回要发送的第一个字节

    “ret”:始终为 0

另一个 I2C 主设备想要从我们读取数据。当检测到我们自己的地址和读取位时,应发送此事件。返回后,总线驱动程序应传输第一个字节。

  • I2C_SLAVE_WRITE_RECEIVED(强制)

    “val”:总线驱动程序传递接收到的字节

    “ret”:如果应确认该字节,则为 0,如果应否认该字节,则为一些 errno

另一个 I2C 主设备已向我们发送了一个字节,需要在“val”中设置。如果“ret”为零,则总线驱动程序应确认该字节。如果“ret”是 errno,则应否认该字节。

  • I2C_SLAVE_READ_PROCESSED(强制)

    “val”:后端返回要发送的下一个字节

    “ret”:始终为 0

总线驱动程序请求在“val”中发送给另一个 I2C 主设备的下一个字节。重要提示:这并不意味着已确认前一个字节,而仅意味着前一个字节已移出到总线!为确保无缝传输,大多数硬件在仍移出前一个字节时请求下一个字节。如果主设备在当前移出的字节之后发送 NACK 并停止读取,则此处请求的字节永远不会被使用。不过,这很可能需要在下一个 I2C_SLAVE_READ_REQUEST 上再次发送,具体取决于你的后端。

  • I2C_SLAVE_STOP(强制)

    “val”:未使用

    “ret”:始终为 0

接收到停止条件。这可能随时发生,后端应重置其 I2C 传输状态机,以便能够接收新请求。

软件后端

如果要编写软件后端

  • 使用标准的 i2c_driver 及其匹配机制

  • 编写处理上述从设备事件的 slave_callback(最好使用状态机)

  • 通过 i2c_slave_register() 注册此回调

请查看 i2c-slave-eeprom 驱动程序作为示例。

总线驱动程序支持

如果要向总线驱动程序添加从设备支持

  • 实现用于注册/注销从设备的调用,并将它们添加到 struct i2c_algorithm 中。注册时,可能需要设置 I2C 从设备地址并启用特定于从设备的 中断。如果使用运行时电源管理,则应使用 pm_runtime_get_sync(),因为您的设备通常需要始终通电才能检测到其从设备地址。注销时,执行上述操作的相反操作。

  • 捕获从设备中断,并将相应的 i2c_slave_events 发送到后端。

请注意,大多数硬件都支持在同一总线上同时充当主设备 _和_ 从设备。因此,如果您扩展总线驱动程序,请确保驱动程序也支持这一点。在几乎所有情况下,从设备支持都不需要禁用主设备功能。

请查看 i2c-rcar 驱动程序作为示例。

关于 ACK/NACK

始终确认地址阶段是一种良好行为,因此主设备知道设备是否基本存在,或者是否神秘地消失了。使用 NACK 表示忙碌会很麻烦。SMBus 要求始终确认地址阶段,而 I2C 规范对此更加宽松。大多数 I2C 控制器在检测到其从设备地址时也会自动确认,因此无法对其进行 NACK。出于这些原因,此 API 不支持地址阶段中的 NACK。

目前,没有从设备事件来报告主设备在从我们读取时是否确认或否认了某个字节。如果需要,我们可以使之成为可选事件。但是,这种情况应该非常罕见,因为主设备应该在此之后发送 STOP,并且我们有一个用于该操作的事件。此外,请记住,并非所有 I2C 控制器都有可能报告该事件。

关于缓冲区

在此 API 的开发过程中,出现了使用缓冲区而不是仅使用字节的问题。这种扩展可能可行,但在撰写本文时,其用途尚不明确。以下是使用缓冲区时需要记住的一些要点

  • 缓冲区应该是可选的,并且后端驱动程序始终必须支持基于字节的事务作为最终回退,因为这是大多数硬件的工作方式。

  • 对于模拟硬件寄存器的后端,缓冲区在很大程度上没有帮助,因为在写入每个字节后应立即触发一个操作。对于读取,如果后端仅由于内部处理而更新了寄存器,则缓冲区中保存的数据可能会过时。

  • 主设备可以随时发送 STOP。对于部分传输的缓冲区,这意味着处理此异常的额外代码。此类代码容易出错。