Linux I2C 从设备接口描述¶
作者:Wolfram Sang <wsa@sang-engineering.com>,2014-15 年
如果使用的 I2C 控制器具有从设备功能,Linux 也可以充当 I2C 从设备。为此,需要在总线驱动程序中提供从设备支持,以及提供实际功能的硬件独立的软件后端。后者的一个例子是从设备 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
总线驱动程序请求下一个要发送给另一个 I2C 主设备的字节,该字节位于“val”中。重要提示:这并不意味着先前的字节已被确认,它仅表示先前的字节已移出到总线!为了确保无缝传输,大多数硬件在先前的字节仍在移出时请求下一个字节。如果主设备发送 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,则应使用 pm_runtime_get_sync(),因为您的设备通常需要始终上电才能检测到其从设备地址。注销时,执行上述操作的逆操作。捕获从设备中断并将适当的 i2c_slave_events 发送到后端。
请注意,大多数硬件都支持在同一总线上同时作为主设备 _和_ 从设备。因此,如果您扩展总线驱动程序,请确保该驱动程序也支持这一点。在几乎所有情况下,从设备支持都不需要禁用主设备功能。
检查 i2c-rcar 驱动程序作为示例。
关于 ACK/NACK¶
始终 ACK 地址阶段是一种良好的行为,因此主设备可以知道设备基本上是否存在,或者它是否神秘地消失了。使用 NACK 来声明正忙很麻烦。SMBus 要求始终 ACK 地址阶段,而 I2C 规范对此更为宽松。大多数 I2C 控制器也会在检测到其从设备地址时自动 ACK,因此没有选择 NACK 它们。由于这些原因,此 API 不支持地址阶段中的 NACK。
目前,没有从设备事件来报告主设备在从我们这里读取时是否 ACK 或 NACK 了某个字节。如果需要,我们可以将其设置为可选事件。但是,这种情况应该非常罕见,因为主设备预计之后会发送 STOP,并且我们有一个事件用于此。另外,请记住并非所有 I2C 控制器都可以报告该事件。
关于缓冲区¶
在此 API 的开发过程中,提出了使用缓冲区而不是仅使用字节的问题。这种扩展可能是可能的,但在撰写本文时,其有用性尚不清楚。使用缓冲区时需要记住的一些要点
缓冲区应该是可选的,并且后端驱动程序将始终必须支持基于字节的事务作为最终的回退,因为这是大多数硬件的工作方式。
对于模拟硬件寄存器的后端,缓冲区在很大程度上没有帮助,因为在写入每个字节后,应立即触发一个操作。对于读取,如果后端由于内部处理而刚更新了寄存器,则缓冲区中保存的数据可能会过时。
主设备可以随时发送 STOP。对于部分传输的缓冲区,这意味着需要额外的代码来处理此异常。此类代码往往容易出错。