在用户空间实现I2C设备驱动程序

通常,I2C设备是由内核驱动程序控制的。但是也可以通过/dev接口从用户空间访问适配器上的所有设备。为此,您需要加载模块i2c-dev。

每个注册的I2C适配器获得一个从0开始计数的数字。您可以检查/sys/class/i2c-dev/以查看哪个适配器对应哪个数字。或者,您可以运行“i2cdetect -l”来获取在给定时间内系统上出现的所有I2C适配器的格式化列表。I2cdetect是i2c-tools包的一部分。

I2C设备文件是字符设备文件,主设备号为89,次设备号对应于上述分配的编号。他们应该被称为 “i2c-%d”(i2c-10 i2c-0, i2c-1,…,…)。所有256个次设备号码为I2C保留。

C 例子

假设您想从C程序访问I2C适配器。首先,你需要包含这两个头文件:

#include <linux/i2c-dev.h>
#include <i2c/smbus.h>

现在,您必须决定要访问哪个适配器。你应该检查/sys/class/i2c-dev/或者运行" i2cdetect -l "来决定。适配器编号多少是动态分配的,所以您不能对它们做太多假设。他们甚至可以从启动时分配的变成另外一个。

接下来,打开设备文件,如下所示:

int file;
int adapter_nr = 2; /* probably dynamically determined */
char filename[20];

snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR);
if (file < 0) {
  /* ERROR HANDLING; you can check errno to see what went wrong */
  exit(1);
}

当你打开设备时,你必须指定你想要通信的设备地址:

int addr = 0x40; /* The I2C address */

if (ioctl(file, I2C_SLAVE, addr) < 0) {
  /* ERROR HANDLING; you can check errno to see what went wrong */
  exit(1);
}

好了,你现在都准备好了。现在可以使用SMBus命令或普通I2C与设备通信。如果设备支持SMBus命令,则首选SMBus命令。两者如下图所示:

__u8 reg = 0x10; /* Device register to access */
__s32 res;
char buf[10];

/* Using SMBus commands */
res = i2c_smbus_read_word_data(file, reg);
if (res < 0) {
  /* ERROR HANDLING: I2C transaction failed */
} else {
  /* res contains the read word */
}

/*
 * Using I2C Write, equivalent of
 * i2c_smbus_write_word_data(file, reg, 0x6543)
 */
buf[0] = reg;
buf[1] = 0x43;
buf[2] = 0x65;
if (write(file, buf, 3) != 3) {
  /* ERROR HANDLING: I2C transaction failed */
}

/* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
if (read(file, buf, 1) != 1) {
  /* ERROR HANDLING: I2C transaction failed */
} else {
  /* buf[0] contains the read byte */
}

注意,只有I2C和SMBus协议的一个子集可以通过 read() 和 write() 调用来实现。特别是,不支持所谓的组合事务(在同一个事务中混合读写消息)。由于这个原因,用户空间程序几乎从不使用这个接口。

重要提示:由于使用内联函数,在编译程序时必须使用 ' -O ' 或其他变体!

完整的接口描述

定义了以下ioctl:

  •  ioctl(file, I2C_SLAVE, long addr) 

改变slave地址。地址在参数的低7位中传递(除了10位地址,在本例中传递低10位)。

  •  ioctl(file, I2C_TENBIT, long select) 

如果select不等于0,选择10位地址,如果select等于0,选择正常的7位地址。默认为0。此请求仅在适配器具有I2C_FUNC_10BIT_ADDR时有效。

  •  ioctl(file, I2C_PEC, long select) 

如果select不等于0,则选择SMBus PEC(包错误检查)生成和验证,如果select等于0则禁用。默认为0。仅用于SMBus事务。这个请求只有在适配器具有I2C_FUNC_SMBUS_PEC时才有效果;如果没有,它仍然是安全的,只是没有任何效果。

  •  ioctl(file, I2C_FUNCS, unsigned long *funcs) 

获取适配器功能并将其放入*funcs中。

  •  ioctl(file, I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset) 

不间断地进行合并读写事务。仅当适配器具有I2C_FUNC_I2C时有效。实参是指向 struct i2c_rdwr_ioctl_data 的指针:

struct i2c_rdwr_ioctl_data {
  struct i2c_msg *msgs;  /* ptr to array of simple messages */
  int nmsgs;             /* number of messages to exchange */
}

msgs[]本身包含进一步指向数据缓冲区的指针。该函数将根据是否在特定消息中设置了I2C_M_RD标志,向缓冲区写入或从缓冲区读取数据。从地址和是否使用10位地址模式必须在每个消息中设置,覆盖上面ioctl的值设置。

  •  ioctl(file, I2C_SMBUS, struct i2c_smbus_ioctl_data *args) 

如果可能的话,使用下面描述的i2c_smbus_*方法,而不是直接发出ioctls。

您可以通过使用read(2)和write(2)调用来执行普通的I2C事务。你不需要传递地址字节;相反,在尝试访问设备之前,通过ioctl I2C_SLAVE设置它。

你可以通过以下函数执行SMBus级别的事务(详细信息请参见文档文件smbus-protocol):

__s32 i2c_smbus_write_quick(int file, __u8 value);
__s32 i2c_smbus_read_byte(int file);
__s32 i2c_smbus_write_byte(int file, __u8 value);
__s32 i2c_smbus_read_byte_data(int file, __u8 command);
__s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value);
__s32 i2c_smbus_read_word_data(int file, __u8 command);
__s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value);
__s32 i2c_smbus_process_call(int file, __u8 command, __u16 value);
__s32 i2c_smbus_block_process_call(int file, __u8 command, __u8 length,
                                   __u8 *values);
__s32 i2c_smbus_read_block_data(int file, __u8 command, __u8 *values);
__s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length,
                                 __u8 *values);

所有这些事务在失败时返回-1;您可以读取errno来查看发生了什么。“写”事务成功返回0;' read '事务返回读的值,read_block则返回读的值的数量。读写的块缓冲区不需要超过32个字节。

以上函数可以通过链接到libi2c库来使用,libi2c库是由i2c-tools项目提供的。参见:https://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git/

实现细节

如果你感兴趣,下面是当你使用/dev接口I2C时内核内部发生的代码流:

  1. 您的程序打开/dev/i2c-N并在其上调用ioctl(),如上面的“C示例”部分所述。
  2. 这些 open() 和 ioctl() 调用是由 i2c-dev 内核驱动程序处理的:分别参见 i2c-dev.c:i2cdev_open() 和 i2c-dev.c:i2cdev_ioctl()。您可以将 i2c-dev 视为可以从用户空间编程的通用I2C芯片驱动程序。
  3. 一些 ioctl() 调用用于管理任务,由 i2c-dev 直接处理。示例包括I2C_SLAVE(设置要访问的设备的地址)和I2C_PEC(在未来事务中启用或禁用SMBus错误检查)。
  4. 其他 ioctl() 调用被i2c-dev转换为内核函数调用。示例包括I2C_FUNCS,它使用 i2c.h:i2c_get_functionality() 查询I2C适配器功能,以及I2C_SMBUS,它使用i2c-core-smbus.c:i2c_smbus_xfer()执行SMBus事务。i2c-dev 驱动程序负责检查来自用户空间的所有参数的有效性。在此之后,这些通过 i2c-dev 从用户空间发出的调用与由内核I2C芯片驱动程序直接执行的调用之间就没有区别了。这意味着I2C总线驱动程序不需要实现任何特殊的东西来支持来自用户空间的访问。
  5. 这些 i2c.h 函数是对I2C总线驱动程序的实际实现的包装。每个适配器都必须声明实现这些标准调用的回调函数。I2c.h: i2c_get_functions() 调用 i2c_adapter.algo->functional(),而 i2c-core-smbus.c:i2c_smbus_xfer() 如果实现了,则调用 adapter.algo->smbus_xfer(),如果没有实现,则调用 i2c-core-smbus.c:i2c_smbus_xfer_emulated(),后者反过来调用 i2c_adapter.algo->master_xfer()。

在您的I2C总线驱动程序处理了这些请求之后,沿着调用链向上执行,几乎不做任何处理,除了通过 i2c-dev 将返回的数据打包成适合 ioctl 的格式。

本文来自博客园,作者:王楼小子,转载请注明原文链接:https://www.cnblogs.com/wanglouxiaozi/p/15158246.html

原文地址:https://www.cnblogs.com/wanglouxiaozi/p/15158246.html