如果使用的I2C控制器具有slave功能,那么Linux也可以成为I2C slave控制器。为此,需要总线驱动程序中的slave支持以及提供实际功能的独立于硬件的软件后端。后者的一个例子是slave-eeprom驱动程序,它充当双内存驱动程序。总线上的另一个I2C master程序可以像普通EEPROM一样访问它,而Linux I2C slave程序可以通过sysfs访问内容并根据需要处理数据。后端驱动程序和I2C总线驱动程序通过事件进行通信。下面是一个显示数据流和数据传输方式的小图表。虚线只标记了一个示例。后端也可以使用字符设备,只在内核中,或完全不同的东西:
e.g. sysfs I2C slave events I/O registers
+-----------+ v +---------+ v +--------+ v +------------+
| Userspace +........+ Backend +-----------+ Driver +-----+ Controller |
+-----------+ +---------+ +--------+ +------------+
| |
----------------------------------------------------------------+-- I2C
--------------------------------------------------------------+---- Bus
注意:技术上,在后端和驱动程序之间还有I2C核心。然而,在撰写本文时,该层是透明的。
使用手册
I2C slave后端行为类似于标准I2C clients。所以,你可以像文档' instantiating-devices '中描述的那样实例化它们。唯一的区别是i2c slave后端有自己的地址空间。因此,您必须向最初请求的地址添加0x1000。在总线1上的7位地址0x64处实例化从用户空间的slave-eeprom驱动程序的示例:
# echo slave-24c02 0x1064 > /sys/bus/i2c/devices/i2c-1/new_device
每个后端都应该有单独的文档来描述其特定的行为和设置。
开发人员手册
首先,总线驱动程序和后端所使用的事件将被详细描述。在此之后,将给出一些扩展总线驱动程序和编写后端程序的实现提示。
I2C slave events
总线驱动程序使用以下函数向后端发送一个事件(events):
ret = i2c_slave_event(client, event, &val)
' client '描述I2C slave设备。' event '是下面描述的特殊事件类型之一。 val '为要读/写的数据字节保存一个u8值,因此是双向的。即使val不用于事件,也必须始终提供指向val的指针,即不要在这里使用NULL。' ret '是后端返回的值。强制性事件必须由总线驱动程序提供,并且必须由后端驱动程序检查。
event类型:
- I2C_SLAVE_WRITE_REQUESTED(mandatory)
‘val’: 未使用
‘ret’: 总是 0
另一个I2C master想要向我们写入数据。一旦检测到我们自己的地址和写位,就应该发送这个事件。数据还没有到达,所以没有需要处理或返回的内容。不过,可能需要进行唤醒或初始化。
- I2C_SLAVE_READ_REQUESTED(mandatory)
' val ':后端返回要发送的第一个字节
‘ret’: 总是 0
另一个I2C master想从我们这里读取数据。一旦检测到我们自己的地址和读位,就应该发送此事件。返回后,总线驱动程序应该发送第一个字节。
- I2C_SLAVE_WRITE_RECEIVED(mandatory)
' val ':总线驱动发送接收的字节
' ret ': 0表示该字节被ACK,errno表示该字节被NACK
另一个I2C master发送了一个字节给我们,需要在' val '中设置。如果' ret '为零,总线驱动程序应该ack这个字节。如果' ret '是errno,则该字节应该被删除。
- I2C_SLAVE_READ_PROCESSED(mandatory)
' val ':后端返回要发送的下一个字节
‘ret’: 总是 0
总线驱动请求将下一个字节以' val '的形式发送给另一个I2C master。重要:这并不意味着前一个字节已经被ack了,它只是意味着前一个字节被移到了总线上! 为了确保无缝传输,大多数硬件在前一个字节被移出时请求下一个字节。 如果master发送NACK并在当前字节移出后停止读取,则此处请求的字节永远不会被使用。它很可能需要在下一个I2C_SLAVE_READ_REQUEST上再次发送,这取决于您的后端。
- I2C_SLAVE_STOP(mandatory)
“val”:未使用的
‘ret’: 总是0
接收到停止条件。这可以随时发生,后端应该为I2C传输重置它的状态机,以便能够接收新的请求。
Software backends
如果你想编写一个软件后端(software backend):
- 使用标准的 i2c_driver 及其匹配机制
- 写slave_callback来处理上面的从事件(最好使用状态机)
- 通过i2c_slave_register()注册这个回调
以i2c-slave-eeprom驱动为例。
Bus driver support
如果你想给总线驱动添加 slave 支持:
- 实现注册/注销 slave 的调用,并将这些调用添加到结构体i2c_algorithm中。在注册时,您可能需要设置I2C slave 地址并启用 slave 中断。如果你使用运行时pm,你应该使用pm_runtime_get_sync(),因为你的设备通常需要一直开机才能检测到它的 slave 地址。当取消注册时,执行与上面相反的操作。
- 捕获 slave 中断并将适当的i2c_slave_events发送到后端。
注意,大多数硬件支持在同一总线上成为master 和 slave。所以,如果你扩展一个总线驱动程序,请确保驱动程序也支持它。在几乎所有情况下,slave 支持不需要禁用 master 功能。
以i2c-rcar驱动为例进行说明。
关于 ACK/NACK
总是对地址阶段进行ACK是一种良好的行为,这样 master 就知道设备是基本存在还是神秘消失了。使用NACK来表示忙是很麻烦的。SMBus要求始终对地址阶段进行ACK,而I2C规范在这方面更宽松。大多数I2C控制器在检测到它们的从属地址时也会自动进行ACK,所以没有NACK选项。由于这些原因,这个API在地址阶段不支持NACK。
目前,如果 master 在读取数据时进行了ACK或NACK,则没有slave事件来报告。如果有需要,我们可以将此设置为可选事件。然而,这种情况应该是极其罕见的,因为master被期望在那之后发送STOP,而我们有一个针对它的事件。另外,请记住,并非所有I2C控制器都有可能报告该事件。
关于 buffers
在开发这个API期间,出现了使用缓冲区而不仅仅是字节的问题。这样的扩展可能是可能的,但在撰写本文时还不清楚是否有用。使用缓冲区时要记住以下几点:
- 缓冲区应该是可选的,而后端驱动程序总是必须支持基于字节的事务,因为无论如何,这是大多数HW工作的方式。
- 对于模拟硬件寄存器的后端,缓冲区在很大程度上没有帮助,因为在每个字节写入后,应该立即触发一个操作。对于读取,如果后端只是因为内部处理而更新了一个寄存器,那么保存在缓冲区中的数据可能会变得陈旧。
- master 可以在任何时候发送STOP。对于部分传输的缓冲区,这意味着需要额外的代码来处理此异常。这样的代码容易出错。
这个后端在连接的I2C总线上模拟一个EEPROM。它的内存内容可以通过位于sysfs中的文件从用户空间访问:
/sys/bus/i2c/devices/<device-directory>/slave-eeprom
有24c02、24c32、24c64和24c512。还支持只读变体。实例化所需的名称形式为' slave-<type>[ro] '。例子:
24c02, read/write, address 0x64:
# echo slave-24c02 0x1064 > /sys/bus/i2c/devices/i2c-1/new_device
24c512, read-only, address 0x42:
# echo slave-24c512ro 0x1042 > /sys/bus/i2c/devices/i2c-1/new_device
如果一个名为“firmware-name”的设备属性包含一个有效的文件名(只包含DT或ACPI),你也可以在引导期间预加载数据。
截至2015年,Linux不支持对二进制sysfs文件进行检测,所以当另一个master文件更改内容时不会有通知。
Linux I2C slave testunit backend这个后端可以用来触发I2C总线 master 程序的测试用例,它需要具有某些功能的远程设备(通常不容易获得)。示例包括 multi-master 测试和 SMBus Host Notify测试。对于某些测试,I2C slave控制器必须能够在master模式和slave模式之间切换,因为它也需要发送数据。
注意,这是一个用于测试和调试的设备。它不应该在生产构建中启用。虽然有一些版本控制,并且我们努力保持向后兼容性,但并不能保证稳定的ABI !
实例化设备是常规的。例如总线0,地址0x30:
# echo “slave-testunit 0x1030” > /sys/bus/i2c/devices/i2c-0/new_device
在此之后,您将有一个write-only设备侦听。read只会返回测试单元的8位版本号。当写入时,设备由4个8位寄存器组成,除了一些“部分”命令,所有寄存器都必须被写入以启动一个测试用例,也就是说,你通常向设备写入4个字节。寄存器是:
0x00 CMD - 触发哪个测试;
0x01 DATAL - 配置字节1;
0x02 DATAH - 配置字节2;
0x03 DELAY - 延迟n * 10ms,直到测试开始。
使用i2c-tools包中的' i2cset ',通用命令看起来像:
# i2cset -y <bus_num> <testunit_address> <CMD> <DATAL> <DATAH> <DELAY> i
DELAY是一个通用参数,用于在CMD中延迟测试的执行。当一个命令正在运行时(包括延迟),新的命令将不会被确认(即NACK)。你需要等待,直到旧的完成。
下面的部分将描述这些命令。无效的命令将导致传输不被确认(NACK)。
Commands
0x00 NOOP(预留将来使用)
0x01 READ_BYTES (也需要master模式)
DATAL - 读取数据的地址(低7位,最高位目前未使用);
DATAH -读取的字节数
对于测试总线master驱动程序是否正确地处理multi-master非常有用。您可以触发测试单元从总线上的另一个设备读取字节。如果被测试的总线master也想同时访问总线,总线将会繁忙。从设备0x50读取128字节,延迟50ms的例子:
# i2cset -y 0 0x30 0x01 0x50 0x80 0x05 i
0x02 SMBUS_HOST_NOTIFY (也需要master模式)
DATAL - 要发送的状态字的低字节;
DATAH - 要发送的状态字的高字节
此测试将向主机发送SMBUS_HOST_NOTIFY消息。注意,在Linux内核中,status字目前被忽略。在10ms后发送通知的例子:
# i2cset -y 0 0x30 0x02 0x42 0x64 0x01 i
0x03 SMBUS_BLOCK_PROC_CALL(部分命令)
DATAL - 必须为' 1 ',即再写一个字节;
DATAH - 返回的字节数;
DELAY - 不适用,部分命令!
这个测试将响应SMBus规范定义的块进程调用。写入的一个数据字节指定在接下来的读传输中将被送回多少字节。注意,在这个读传输中,testunit将为后面的字节长度加上前缀。因此,如果您的master总线驱动程序像大多数程序一样模拟SMBus调用,那么它需要支持i2c_msg的I2C_M_RECV_LEN标志。这是一个很好的测试用例。返回的数据首先包含长度,然后是length-1到0的字节数组。下面是一个使用i2ctransfer(你需要i2c-tools v4.2或更高版本)模拟i2c_smbus_block_process_call()的示例:
# i2ctransfer -y 0 w3@0x30 0x03 0x01 0x10 r?
0x10 0x0f 0x0e 0x0d 0x0c 0x0b 0x0a 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01 0x00
返回的第一个字节0x10代表接下来要返回的总字节数,即16个字节:0x0f 0x0e 0x0d 0x0c 0x0b 0x0a 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01 0x00