总结在linux环境下四种读写I2C设备的方法:
一:读写/dev/i2c-x设备结点
/dev/i2c-x设备结点对应的驱动文件为内核目录drivers/i2c下自带的i2c-dev.c文件,通读此文件可以发现它的工作流程。
1、通过register_chrdev注册字符设备结点
2、通过class_create在sysfs下创建对应的class结点
3、利用bus_register_nofifier对I2C总线添加通知链,在有适配器加入或删除时调用相应的处理函数
4、通过I2C核心函数i2c_for_each_dev循环遍历I2C总线上的设备,每找到一个设备便执行i2cdev_attach_adapter函数
5、在i2cdev_attach_adapter函数中会首先判断此设备是否为适配器,如果是,则通过device_create在/dev目录下生成相应的结点,名称为i2c-x,其中x为第几个适配器
6、在用户空间利用open("/dev/i2c-x",O_RDWR)操作相应结点时,会为操作此适配器的每一个用户创建一个i2c_client,i2c_client会通过client->adapter = adapter和file->private_data = client关联对应的适配器和操作文件
7、则在用户空间通过read/write函数操作设备时驱动通过struct i2c_client *client = file->private_data;获取到适配器结构,继而通过调用I2C核心函数i2c_mater_send/i2c_master_recv,最终调用到具体的适配器发送/接收数据函数
通过此方法操作I2C设备时需注意在通过open打开设备结点后,需通过ioctl I2C_SLAVE函数设置设备地址,之后才可通过read/write函数对设备进行操作
二:自定义I2C设备,并通过/dev下对应结点操作
驱动文件可按如下编写:
1、通过module_i2c_driver注册对应的I2C驱动,此时内核会遍历挂载在I2C总线上的设备,通过总线的match方法对驱动和设备进行匹配,匹配原则可以是1、基于设备树的of_driver_match_device方法,对应的结构为定义的驱动结构体是否含有of_match_table成员 2、基于id_table的匹配,对应为驱动是否定义id_table成员 3、基于名字的匹配,匹配原则为驱动的name是否和设备的name相同 4、基于ACPI风格的匹配,使用较少
2、当驱动和设备匹配后会先调用I2C总线的probe函数先做一些处理,之后便会调用我们定义的I2C驱动的probe函数
3、在probe函数中保存client指针到我们自定义的设备结构体中,如自定义为:
struct xxx{
struct i2c_client client;
...
};
4、通过register_chrdev注册字符设备,通过class_create和device_create创建/sys/和/dev/下结点(或通过mknod手工创建)
5、在open函数中将自定义的驱动结构设置为file->private_data,方便在读写函数中获取
6、在用户空间通过open函数打开我们注册的/dev/目录下结点,read/write时便会调用对应的读写函数,在读写函数中会通过file->private_data获取到自定义驱动结构,进而取到i2c_client,最终通过i2c_mater_send/i2c_master_recv或直接自己构建i2c_msg通过i2c_transfer进行发送
三:通过DEVICE_ATTR在对应的sys设备目录下创建读写文件
1、还是先通过module_i2c_driver注册对应的I2C驱动,在找到设备后调用的probe函数中,只需要通过sysfs_create_files创建一组读写函数或sysfs_create_group创建多组操作函数
2、编写读写函数并通过DEVICE_ATTR宏进行声明,将函数组织为struct attribute,多组struct attribute组织为struct attribute_group并作为刚刚sysfs函数的参数
3、用户空间不需要通过open/read/write方法,而只需要通过echo/cat对/sys/class/设备名称目录下的刚刚定义的函数进行读写就可以了
四:将驱动注册为regmap,在对应的读写函数中使用regmap的读写方法
regmap是内核3.1之后加入的特性,用于减少慢速I/O驱动上的重复逻辑,可以使驱动代码量减少。
这种方法个人觉得最大的好处是那些既支持I2C又支持SPI的设备,而在实际使用中只会使用其中一种接口,所以公共部分的读写函数不再通过i2c_transfer或者spi_sync_transfer这些特定于具体总线的操作函数,而是通过regmap提供的regmap_read/regmap_write公共函数,此函数会根据之前设备的注册自动选择为是I2C或是SPI的操作,所以我们需要做的就是:
1、通过module_i2c_driver或是module_spi_driver注册设备,内核会根据i2c_borad_info或者spi_board_info或者设备树中设备所挂载在的总线自动进行I2C或者SPI的probe操作
2、在I2C的probe函数中通过devm_regmap_init_i2c,在SPI的probe函数中通过devm_regmap_init_spi将设备注册为regmap的对应接口,函数的第二个参数为regmap_config结构,指示了寄存器宽度和数据宽度
3、可通过上方的二方法将设备注册为字符设备或三方法在/sys目录下创建对应的操作函数
4、在操作函数的实现中只需要通过regmap_read/regmap_write对数据进行处理就可以了
欢迎补充!