目录
博客说明
开发环境
1. Linux I2C 体系结构
1.1 Linux I2C 核心
1.1.1 增加 / 删除 i2c_adapter
1.1.2 增加 / 删除 i2c_driver
1.1.3 I2C 传输, 发送和接收
1.2 Linux I2C 适配器驱动
1.2.1 I2C 适配器驱动的注册与注销
1.2.2 I2C 总线的通信方法
1.3 Linux I2C 设备驱动
附录
@(Linux I2C 驱动学习)
博客说明
撰写日期 | 2019.11.20 |
---|---|
完稿日期 | 2019.11.21 |
最近维护 | 暂无 |
本文作者 | multimicro |
联系方式 | [email protected] |
资料链接 | 本文无附件资料 |
GitHub | https://github.com/wifialan/drivers |
原文链接 |
开发环境
环境说明 | 详细信息 | 备注信息 |
---|---|---|
操作系统 | Ubunut 18.04 | |
开发板 | JZ2440-V3 | |
u-boot | uboot-2012.04.01 | |
busybox | busybox-1.22.1 | |
u-boot 和 busybox 编译器 | arm-linux-gcc (4.4.3) | |
Linux 内核 | linux-4.19-rc3 | |
Linux 内核编译器 | arm-linux-gnueabi-gcc (4.9.4) |
1. Linux I2C 体系结构
i2c 的编程应用流程请参考我的上一篇文章 Linux 设备树学习 -- 基于 i2c 总线分析, 这篇文章我是基于 i2c 总线而写的, 里面包含注册和匹配方式的介绍, 和相应的模板.
参考宋宝华 《Linux 设备驱动开发详解》 第 15 章内容.
Linux 的 I2C 体系结构分为 3 个组成部分:
I2C 核心
I2C 总线驱动(适配器驱动)
I2C 设备驱动
1.1 Linux I2C 核心
借鉴 宋宝华 《Linux 设备驱动开发详解》 第 15 章第 2 节内容.
I2C 核心 (drivers/i2c/i2c-core-base.c) 中提供了一组不依赖于硬件平台的接口函数, 这个文件一般不需要被工程师修改, 但是理解其中的主要函数非常关键, 因为 I2C 总线驱动和设备驱动之间以 I2C 核心作为纽带. I2C 核心中的主要函数如下.
1.1.1 增加 / 删除 i2c_adapter
- int i2c_add_adapter(struct i2c_adapter *adap);
- void i2c_del_adapter(struct i2c_adapter *adap);
在有设备树版本中, 内部都封装了从设备树中获取 i2c 设备并注册为 i2c client 的方法, 如下:
/i2c 节点一般表示 i2c 控制器, 它会被转换为 platform_device, 在内核中有对应的 platform_driver;
platform_driver 的 probe 函数中会调用 i2c_add_numbered_adapter:
- i2c_add_numbered_adapter// drivers/i2c/i2c-core-base.c
- __i2c_add_numbered_adapter
- i2c_register_adapter
- of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c
- for_each_available_child_of_node(bus, node) {
- client = of_i2c_register_device(adap, node);
- client = i2c_new_device(adap, &info); // 设备树中的 i2c 子节点被转换为 i2c_client
- }
上述第一个函数 i2c_add_numbered_adapter 内容如下, 通过 Source Insight 软件, 可以一步一步定位分析, 可以很好的帮助理解设备树汇总的 i2c 子设备节点是如何转换为 i2c client 的.
- int i2c_add_numbered_adapter(struct i2c_adapter *adap)
- {
- if (adap->nr == -1) /* -1 means dynamically assign bus id */
- return i2c_add_adapter(adap);
- return __i2c_add_numbered_adapter(adap);
- }
1.1.2 增加 / 删除 i2c_driver
- int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
- void i2c_del_driver(struct i2c_driver *driver);
- #define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
这些函数一般用在编写 i2c 设备 driver 中, 如
- drivers/char/at24c256.c
- static int __init at24_init(void)
- {
- return i2c_add_driver(&at24c256_driver);
- }
- static void __exit at24_exit(void)
- {
- i2c_del_driver(&at24c256_driver);
- printk(DRV_NAME "\tRemove i2c driver success\n");
- }
- module_init(at24_init);
- module_exit(at24_exit);
1.1.3 I2C 传输, 发送和接收
- int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
- int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
- int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
这三个函数的区别在于:
i2c_transfer 一次可以传输多个 i2c_msg
i2c_master_send 一次只能发送一个 i2c_msg
i2c_master_recv 一次只能接收一个 i2c_msg
在开发比较复杂的 i2c 时序时, 采用最多的是 i2c_transfer. 参考 Code 1
- Code 1
- int ret;
- struct i2c_msg msg[2];
- char buffer_data[100];
- memset(buffer_data,0,sizeof(buffer_data));
- buffer_data[0] = (char)0x00;
- buffer_data[1] = (char)0x00;
- buffer_data[2] = (char)0xAA;
- buffer_data[3] = (char)0xAB;
- buffer_data[4] = (char)0xAC;
- /* 1st write data */
- msg[0].addr = at24_dev->client->addr | 0x01; // 这个是 i2c 从设备的地址
- msg[0].flags = at24_dev->client->flags & 0; // 这个是对 i2c 从设备的读写标志位, 和上面的 addr 组成 i2c 的第一个 8bit 数据
- msg[0].buf = &buffer_data[0]; // 这个是 i2c 从设备地址后的数据, 数组中, 每一个数据都是一个 8bit 数据
- msg[0].len = 5; // 指定发送除地址位外的多少个数据, 上述 buffer_data 中, 前两个是 at24c256 的从设备地址, 后 3 个是写入的数据
- /* 2nd write data */
- msg[1].addr = at24_dev->client->addr | 0x01;
- msg[1].flags = I2C_M_RD;
- msg[1].buf = &buffer_data[5]; // 指定接受的数据存放位置, 这里从 buffer_data 中的第 6 个位置中开始存储读到的数据,
- msg[1].len = 3; // 指定读的数据个数
- // 开始 i2c 传输, 发送成功后, 会返回发送的次数, 由最后一个参数为 2 可以, 需要开启两次传输, 那么发送成功后就会返回 2
- ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 2);
- tmp = (ret == 2) ? msg[1].len : ret;
i2c_transfer 发送失败会返回特定的负数, 详情参考 Linux i2c 的通信函数 i2c_transfer 在什么情况下出现错误 https://www.cnblogs.com/x_wukong/p/9518670.html
1.2 Linux I2C 适配器驱动
1.2.1 I2C 适配器驱动的注册与注销
借鉴 宋宝华 《Linux 设备驱动开发详解》 第 15 章第 3 节内容.
由于 I2C 总线控制器通常是在内存上的, 所以它本身也连接在 platform 总线上, 要通过 platform_driver 和 platform_device 的匹配来执行. 因此尽管 I2C 适配器给别人提供了总线, 它自己也被认为是接在 platform 总线上的一个客户. Linux 的总线, 设备和驱动模型实际上是一个树形结构, 每个节点虽然可能成为别的总线控制器, 但是自己也被认为是从上一级总线枚举出来的.
也就是说, I2C 适配器的初始化, 需要在 platform_driver 的 probe()函数中完成. 这部分大多由芯片厂商提供好了, 自己不需要编写.
1.2.2 I2C 总线的通信方法
只有 I2C 适配器驱动是没有灵魂的, 需要给它注入一个通信算法 i2c_algorithm, 才能然 I2C 运作起来.
可以说 I2C 适配器驱动是来配置 I2C 硬件, 而 i2c_algorithm 是来操作 I2C 硬件产生对应的 I2C 时序波形, 完成基于 I2C 总线的数据传输.
我在开发时, 没有自己编写通信方法, 但是也可以实现通信, 猜测这个也是芯片厂商给写好的, 直接调用接口
1.3 Linux I2C 设备驱动
这方面主要是完成 I2C 设备驱动模块的加载与卸载, 并实现数据传输, 模块的加载与卸载就是经常编写的驱动开发流程, 详情参考附录中 GitHub 代码.
在编写 i2c 设备 driver 中的 i2c 设备读写函数中, 如下, 我将 AT24C256 这个 i2c 设备注册成了字符设备, 调用了 file_operations 结构体里面的读写方法. 参考 Code 2 和 Code 3
- drivers/char/at24c256.c
- Code 2
- static ssize_t at24_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
- {
- int ret,tmp;
- char i;
- struct i2c_msg msg;
- char buffer_data[100];
- copy_from_user(buffer_data, buffer, 10);
- memset(buffer_data,0,sizeof(buffer_data));
- buffer_data[0] = (char)0x00;
- buffer_data[1] = (char)0x00;
- for (i = 0; i <64; ++i)
- {
- buffer_data[i+2] = i;
- }
- msg.addr = at24_dev->client->addr | 0x01; // 这个是 i2c 从设备的地址
- msg.flags = at24_dev->client->flags & 0;
- msg.buf = &buffer_data[0]; // 这个是 i2c 从设备地址后的数据
- msg.len = 66;
- ret = i2c_transfer(at24_dev->client->adapter,&msg,1);
- tmp = (ret == 1) ? msg.len : ret;
- printk("i2c code: %d return code: %d addr: 0x%02x%02x", ret,tmp,buffer_data[0],buffer_data[1]);
- for (i = 0; i <64; ++i)
- {
- printk("Write Data: 0x%02x",buffer_data[i+2]);
- }
- return 0;
- }
- Code 3
- static ssize_t at24_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
- {
- int ret,tmp;
- unsigned long i;
- struct i2c_msg msg[3];
- char buffer_data[100];
- memset(buffer_data,0,sizeof(buffer_data));
- msg[0].addr = at24_dev->client->addr | 0x01;
- msg[0].flags = I2C_M_RD;
- msg[0].buf = &buffer_data[2];
- msg[0].len = 64;
- ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 1);
- tmp = (ret == 1) ? msg[0].len : ret;
- printk("i2c code: %d return code: %d addr: 0x%02x%02x", ret,tmp,buffer_data[0],buffer_data[1]);
- for (i = 0; i < 64; ++i)
- {
- printk("Read Data: 0x%02x",buffer_data[i+2]);
- }
- return 0;
- }
附录
GitHub 代码:
来源: http://www.bubuko.com/infodetail-3297541.html