I2C/SMBus 功能

引言

因为并非每个 I2C 或 SMBus 适配器都实现了 I2C 规范中的所有功能,所以客户端不能相信在被允许连接到适配器时,它所需的一切都已实现:客户端需要一种方法来检查适配器是否具有所需的功能。

功能常量

要获取最新功能常量列表,请查阅 <uapi/linux/i2c.h>!

I2C_FUNC_I2C

纯 I2C 级别命令(纯 SMBus 适配器通常无法执行这些命令)

I2C_FUNC_10BIT_ADDR

处理 10 位地址扩展

I2C_FUNC_PROTOCOL_MANGLING

了解 I2C_M_IGNORE_NAK、I2C_M_REV_DIR_ADDR 和 I2C_M_NO_RD_ACK 标志(它们会修改 I2C 协议!)

I2C_FUNC_NOSTART

可以跳过重复启动序列

I2C_FUNC_SMBUS_QUICK

处理 SMBus write_quick 命令

I2C_FUNC_SMBUS_READ_BYTE

处理 SMBus read_byte 命令

I2C_FUNC_SMBUS_WRITE_BYTE

处理 SMBus write_byte 命令

I2C_FUNC_SMBUS_READ_BYTE_DATA

处理 SMBus read_byte_data 命令

I2C_FUNC_SMBUS_WRITE_BYTE_DATA

处理 SMBus write_byte_data 命令

I2C_FUNC_SMBUS_READ_WORD_DATA

处理 SMBus read_word_data 命令

I2C_FUNC_SMBUS_WRITE_WORD_DATA

处理 SMBus write_byte_data 命令

I2C_FUNC_SMBUS_PROC_CALL

处理 SMBus process_call 命令

I2C_FUNC_SMBUS_READ_BLOCK_DATA

处理 SMBus read_block_data 命令

I2C_FUNC_SMBUS_WRITE_BLOCK_DATA

处理 SMBus write_block_data 命令

I2C_FUNC_SMBUS_READ_I2C_BLOCK

处理 SMBus read_i2c_block_data 命令

I2C_FUNC_SMBUS_WRITE_I2C_BLOCK

处理 SMBus write_i2c_block_data 命令

为方便起见,还定义了上述标志的一些组合

I2C_FUNC_SMBUS_BYTE

处理 SMBus read_byte 和 write_byte 命令

I2C_FUNC_SMBUS_BYTE_DATA

处理 SMBus read_byte_data 和 write_byte_data 命令

I2C_FUNC_SMBUS_WORD_DATA

处理 SMBus read_word_data 和 write_word_data 命令

I2C_FUNC_SMBUS_BLOCK_DATA

处理 SMBus read_block_data 和 write_block_data 命令

I2C_FUNC_SMBUS_I2C_BLOCK

处理 SMBus read_i2c_block_data 和 write_i2c_block_data 命令

I2C_FUNC_SMBUS_EMUL

处理所有可由真实 I2C 适配器模拟的 SMBus 命令(使用透明模拟层)

在 3.5 版本之前的内核中,I2C_FUNC_NOSTART 是作为 I2C_FUNC_PROTOCOL_MANGLING 的一部分实现的。

适配器实现

当你编写新的适配器驱动时,你必须实现一个函数回调 functionality。典型的实现如下所示。

一个典型的纯 SMBus 适配器会列出它支持的所有 SMBus 事务。这个例子来自 i2c-piix4 驱动

static u32 piix4_func(struct i2c_adapter *adapter)
{
      return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
             I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
             I2C_FUNC_SMBUS_BLOCK_DATA;
}

一个典型的完整 I2C 适配器会使用以下内容(来自 i2c-pxa 驱动)

static u32 i2c_pxa_functionality(struct i2c_adapter *adap)
{
      return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

I2C_FUNC_SMBUS_EMUL 包含了 i2c-core 能够使用 I2C_FUNC_I2C 模拟的所有 SMBus 事务(以及 I2C 块事务),无需适配器驱动的任何帮助。这样做的目的是让客户端驱动检查 SMBus 函数的支持情况,而无需关心这些函数是由适配器硬件实现,还是由 i2c-core 在 I2C 适配器之上软件模拟的。

客户端检查

在客户端尝试连接到适配器,甚至进行测试以检查其支持的设备是否在适配器上存在之前,它应该检查所需的功能是否存在。典型的方法是(来自 lm75 驱动)

static int lm75_detect(...)
{
      (...)
      if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
                                   I2C_FUNC_SMBUS_WORD_DATA))
              goto exit;
      (...)
}

在这里,lm75 驱动程序检查适配器是否可以执行 SMBus 字节数据和 SMBus 字数据事务。如果不能,那么该驱动程序将无法在此适配器上工作,继续下去没有意义。如果上述检查成功,那么驱动程序就知道它可以调用以下函数:i2c_smbus_read_byte_data()i2c_smbus_write_byte_data()i2c_smbus_read_word_data()i2c_smbus_write_word_data()。经验法则是,你使用 i2c_check_functionality() 测试的功能常量应与你的驱动程序调用的 i2c_smbus_* 函数完全匹配。

请注意,上述检查并未说明这些功能是由底层适配器在硬件中实现,还是由 i2c-core 在软件中模拟的。客户端驱动程序无需关心这一点,因为 i2c-core 会在 I2C 适配器之上透明地实现 SMBus 事务。

通过 /DEV 检查

如果你尝试从用户空间程序访问适配器,你将不得不使用 /dev 接口。当然,你仍然需要检查所需的功能是否受支持。这通过使用 I2C_FUNCS ioctl 来完成。下面是一个示例,改编自 i2cdetect 程序

int file;
if (file = open("/dev/i2c-0", O_RDWR) < 0) {
      /* Some kind of error handling */
      exit(1);
}
if (ioctl(file, I2C_FUNCS, &funcs) < 0) {
      /* Some kind of error handling */
      exit(1);
}
if (!(funcs & I2C_FUNC_SMBUS_QUICK)) {
      /* Oops, the needed functionality (SMBus write_quick function) is
         not available! */
      exit(1);
}
/* Now it is safe to use the SMBus write_quick command */