总结:linux驱动之I2C至少四种读写方法

    总结在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对数据进行处理就可以了


欢迎补充!

  • 8
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Linux中,可以通过编写TP驱动程序来进行I2C设备的读写操作。下面是一个简单的示例代码,演示了如何在TP驱动读写I2C设备: ```c #include <linux/i2c.h> #include <linux/i2c-dev.h> #include <linux/module.h> static struct i2c_client *tp_client; static int tp_read_reg(struct i2c_client *client, u8 reg, u8 *value) { struct i2c_msg msgs[2]; u8 buf[2]; int ret; buf[0] = reg; msgs[0].addr = client->addr; msgs[0].flags = 0; msgs[0].buf = buf; msgs[0].len = 1; msgs[1].addr = client->addr; msgs[1].flags = I2C_M_RD; msgs[1].buf = value; msgs[1].len = 1; ret = i2c_transfer(client->adapter, msgs, 2); if (ret != 2) { pr_err("Failed to read register 0x%02x\n", reg); return ret < 0 ? ret : -EIO; } return 0; } static int tp_write_reg(struct i2c_client *client, u8 reg, u8 value) { struct i2c_msg msg; u8 buf[2]; int ret; buf[0] = reg; buf[1] = value; msg.addr = client->addr; msg.flags = 0; msg.buf = buf; msg.len = 2; ret = i2c_transfer(client->adapter, &msg, 1); if (ret != 1) { pr_err("Failed to write register 0x%02x\n", reg); return ret < 0 ? ret : -EIO; } return 0; } static int __init tp_driver_init(void) { struct i2c_adapter *adapter; struct i2c_board_info board_info; struct i2c_client *client; int ret; // 获取I2C适配器 adapter = i2c_get_adapter(0); if (!adapter) { pr_err("Failed to get I2C adapter\n"); return -ENODEV; } // 填充设备信息 memset(&board_info, 0, sizeof(struct i2c_board_info)); strlcpy(board_info.type, "my_i2c_device", I2C_NAME_SIZE); // 注册I2C设备 client = i2c_new_device(adapter, &board_info); if (!client) { pr_err("Failed to register I2C device\n"); i2c_put_adapter(adapter); return -ENODEV; } // 保存设备指针 tp_client = client; // 读取寄存器示例 u8 reg_value; ret = tp_read_reg(tp_client, 0x00, &reg_value); if (ret) { pr_err("Failed to read register\n"); i2c_unregister_device(tp_client); i2c_put_adapter(adapter); return ret; } // 写入寄存器示例 ret = tp_write_reg(tp_client, 0x01, 0xFF); if (ret) { pr_err("Failed to write register\n"); i2c_unregister_device(tp_client); i2c_put_adapter(adapter); return ret; } return 0; } static void __exit tp_driver_exit(void) { if (tp_client) { i2c_unregister_device(tp_client); i2c_put_adapter(tp_client->adapter); } } module_init(tp_driver_init); module_exit(tp_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("TP Driver"); ``` 在上述示例代码中,我们首先通过`i2c_get_adapter()`函数获取I2C适配器,然后填充设备信息并注册I2C设备。接着,我们可以使用`tp_read_reg()`函数和`tp_write_reg()`函数来读取和写入I2C设备的寄存器。最后,在驱动程序的初始化函数中,我们进行了读取和写入寄存器的示例操作。 需要注意的是,上述代码仅为示例,实际应用中需要根据具体的硬件和需求进行修改和适配。同时,还需要确保相关的I2C驱动程序已经加载并正确配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值