驱动篇:Linux 的 I2C设备驱动(四)(摘录)
发布日期:2021-06-29 11:34:55 浏览次数:2 分类:技术文章

本文共 10885 字,大约阅读时间需要 36 分钟。

驱动篇:Linux 的 I2C设备驱动(四)(摘录)

I 2 C 设备驱动

I 2 C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数。i2c_client 一般被包含在设备的私有信息结构体 yyy_data 中,而 i2c_driver 则适合被定义为全局变量并初始化
已被初始化的 i2c_driver

static struct i2c_driver yyy_driver = {
.driver ={
.name = "yyy",} ,.attach_adapter = yyy_attach_adapter,.detach_client = yyy_detach_client,.command = yyy_command, };

I 2 C 设备驱动的模块加载与卸载

I 2 C 设备驱动的模块加载函数通用的方法是在 I 2 C 设备驱动的模块加载函数中完成两件事。
(1)通过 register_chrdev()函数将 I 2 C 设备注册为一个字符设备。
(2)通过 I 2 C 核心的 i2c_add_driver()函数添加 i2c_driver。
在模块卸载函数中需要做相反的两件事。
(1)通过 I 2 C 核心的 i2c_del_driver()函数删除 i2c_driver。
(2)通过 unregister_chrdev()函数注销字符设备。
代码清单 15.14 所示为 I 2 C 设备驱动的加载与卸载函数模板。

static int _ _init yyy_init(void) {
int res;/*注册字符设备*/res = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老内核接口if (res)goto out;/*添加 i2c_driver*/res = i2c_add_driver(&yyy_driver); if (res)goto out_unreg_class; return 0; out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c"); out: printk(KERN_ERR "%s: Driver Initialisation failed\n", _ _FILE__);return res;}static void _ _exit yyy_exit(void){
i2c_del_driver(&i2cdev_driver);unregister_chrdev(YYY_MAJOR, "yyy");}

I 2 C 设备驱动的 i2c_driver 成员函数

i2c_add_driver (&yyy_driver)的执行会引发 i2c_driver 结构体中 yyy_attach_adapter()函数的执行,我们可以在 yyy_attach_adapter()函数里探测物理设备。为了实现探测,yyy_attach_adapter()函数里面也只需简单地调用 I 2 C 核心的 i2c_probe()函数
i2c_attach_adapter 函数模板

static int yyy_attach_adapter(struct i2c_adapter *adapter) {
return i2c_probe(adapter, &addr_data, yyy_detect); } 代码第 3 行传递给 i2c_probe()函数的第 1 个参数是 i2c_adapter 指针,第 2 个参数是要探测的地址数据,第 3 个参数是具体的探测函数。要探测的地址实际列表在一个16 位无符号整型数组中,这个数组以 I2C_CLIENT_END 为最后一个元素。

i2c_probe()函数会引发 yyy_detect()函数的调用, 可以在 yyy_detect()函数中初始化i2c_client

I 2 C 设备驱动的 detect 函数模板

static int yyy_detect(struct i2c_adapter *adapter, int address, int kind) {
struct i2c_client *new_client; struct yyy_data *data; int err = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_XXX)goto exit; if (!(data = kzalloc(sizeof(struct yyy_data), GFP_KERNEL))) {
err = - ENOMEM;goto exit; } new_client = &data->client; new_client->addr = address; new_client->adapter = adapter; new_client->driver = &yyy_driver; new_client->flags = 0; /* 新的 client 将依附于 adapter */ if ((err = i2c_attach_client(new_client)))goto exit_kfree; yyy_init_client(new_client); return 0; exit_kfree: kfree(data); exit: return err;}代码第 10 行分配私有信息结构体的内存,i2c_client 也被创建。第 16~20 行对新创建的 i2c_client 进行初始化。第 23 行调用内核的 i2c_attach_client()知会 I 2 C 核心系统中包含了一个新的 I 2 C 设备。第 26 行代码初始化 i2c_client 对应的 I 2 C 设备,这个函数是硬件相关的

如图 15.4 所示为当 I 2 C 设备驱动的模块加载函数被调用的时候引发的连锁反应的流程。I 2 C 设备驱动的卸载函数进行 i2c_del_driver(&yyy_driver)调用后,会引发与yyy_driver 关联的每个 i2c_client 与之解除关联, 即 yyy_detach_client()函数将因此而被调用,代码清单 15.17 所示为函数 yyy_detach_client()的设计。

在这里插入图片描述I 2 C 设备驱动的 i2c_detach_client 函数模板

static int yyy_detach_client(struct i2c_client *client) {
int err;struct yyy_data *data = i2c_get_clientdata(client);if ((err = i2c_detach_client(client)))return err;kfree(data); return 0; }上述函数中第 4 行的 i2c_get_clientdata()函数用于从 yyy_data 私有信息结构中的i2c_client 的指针获取 yyy_data 的指针。第 6 行调用 I 2 C 核心函数 i2c_detach_client(),这个函数会引发 i2c_adapter 的 client_ unregister()函数被调用。第 9 行代码释放 yyy_data的内存

图 15.5 所示为当 I 2 C 设备驱动的模块卸载函数被调用的时候引发的连锁反应的流程

在这里插入图片描述下面讲解 i2c_driver 中重要函数 yyy_command()的实现,它实现了针对设备的控制命令。具体的控制命令是设备相关的,如对于实时钟而言,命令将是设置时间和获取时间,而对于视频 AD 设备而言,命令会是设置采样方式、选择通道等。假设 yyy 设备接受两类命令 YYY_CMD1 和 YYY_CMD2,而处理这两个命令的函数分别为 yyy_cmd1()和 yyy_cmd2(),代码清单 15.18 所示为 yyy_command()函数的设计
I 2 C 设备驱动的 command 函数模板

static int yyy_command(struct i2c_client *client, unsigned int cmd,void *arg) {
switch (cmd) {
case YYY_CMD1:return yyy_cmd1(client, arg);case YYY_CMD2:return yyy_cmd2(client, arg);default:return - EINVAL; }}

具体命令的实现是通过组件 i2c_msg 消息数组,并调用 I 2 C 核心的传输、发送和接收函数,由 I 2 C 核心的传输、发送和接收函数调用 I 2 C 适配器对应的 algorithm 相关函数来完成的

I 2 C 设备具体命令处理函数模板

static int yyy_cmd1(struct i2c_client *client, struct rtc_time *dt) {
struct i2c_msg msg[2]; /*第一条消息是写消息*/ msg[0].addr = client->addr; msg[0].flags = 0; msg[0].len = 1; msg[0].buf = &offs; /*第二条消息是读消息*/ msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].len = sizeof(buf); msg[1].buf = &buf[0];i2c_transfer(client->adapter, msg, 2);... }

I 2 C 设备驱动的文件操作接口

作为一种字符类设备, Linux I 2 C 设备驱动的文件操作接口与普通的设备驱动是完全一致的,但是在其中要使用 i2c_client、i2c_driver、i2c_adapter 和 i2c_algorithm 结构体和 I 2 C 核心, 并且对设备的读写和控制需要借助体系结构中各组成部分的协同合作。
I 2 C 设备文件的接口写函数范例

static ssize_t yyy_write(struct file *file, char *buf, size_t count,loff_t off) {
struct i2c_client *client=(struct i2c_client*)file->private_data;i2c_msg msg[1];char *tmp;int ret;tmp = kmalloc(count, GFP_KERNEL);if (tmp == NULL)return - ENOMEM; if (copy_from_user(tmp, buf, count)) {
kfree(tmp);return - EFAULT; } msg[0].addr = client->addr;//地址 msg[0].flags = 0;//0 为写 msg[0].len = count;//要写的字节数 msg[0].buf = tmp;//要写的数据 ret = i2c_transfer(client->adapter, msg, 1); //传输 I 2 C 消息 return (ret == 1) ? count : ret; }

上述程序给出的仅仅是一个写函数的例子,具体的写操作与设备密切相关。下面详细讲解 I 2 C 设备读写过程中数据的流向和函数的调用关系

(1)从用户空间到字符设备驱动写函数接口,写函数构造 I 2 C 消息数组。

(2)写函数把构造的 I 2 C 消息数组传递给 I 2 C 核心的传输函数 i2c_transfer()。
(3)I 2 C 核心的传输函数 i2c_transfer()找到对应适配器 algorithm 的通信方法函数 master_xfer()去最终完成 I 2 C 消息的处理。
在这里插入图片描述通常, 如果 I 2 C 设备不是一个输入/输出设备或存储设备,就并不需要给 I 2 C 设备提供读写函数。许多 I 2 C 设备只是需要被设置以某种方式工作,而不是被读写。另外,I 2 C 设备驱动的文件操作接口也不是必需的,甚至极少被需要。大多数情况下,我们只需要通过 i2c-dev.c 文件提供的 I 2 C 适配器设备文件接口就可完成对 I 2 C 设备的读写。

i2c-dev.c 文件分析

i2c-dev.c 文件完全可以被看作一个 I 2 C 设备驱动, 其结构与 15.4.1~15.4.3 小节的描述是基本一致的,不过,它实现的一个 i2c_client 是虚拟、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到 i2c_adapter 的 clients链表中。i2c-dev.c 针对每个 I 2 C 适配器生成一个主设备号为 89 的设备文件,实现了i2c_driver 的成员函数以及文件操作接口,所以 i2c-dev.c 的主体是“i2c_driver 成员函数 + 字符设备驱动”
i2c-dev.c 中提供 i2cdev_read()、 i2cdev_write()函数来对应用户空间要使用的 read()和 write() 文件 操作 接 口, 这两 个函 数分 别 调用 I 2 C 核 心的 i2c_master_recv() 和i2c_master_send()函数来构造一条 I 2 C 消息并引发适配器 algorithm 通信函数的调用,完成消息的传输,对应于如图 15.7 所示的时序。但是,很遗憾,大多数稍微复杂一点I 2 C 设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期(即如图 15.8 所示的重复开始位 RepStart 模式),这种情况下,在应用层仍然调用 read()、write()文件 API 来读写 I 2 C 设备,将不能正确地读写。许多工程师碰到过类似的问题,往往经过相当长时间的调试都没法解决 I 2 C 设备的读写,连错误的原因也无法找到,显然是对 i2cdev_read()和 i2cdev_write()函数的作用有所误解。
在这里插入图片描述鉴于上述原因,i2c-dev.c 中 i2cdev_read()和 i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非 RepStart 模式的情况。对于两条以上消息组成的读写,在用户空间需要组织 i2c_msg 消息数组并调用 I2C_RDWR IOCTL 命令。代码清单 15.21 所示为 i2cdev_ioctl()函数的框架, 其中详细列出了 I2C_RDWR 命令的处理过程。
i2c-dev.c 中的 i2cdev_ioctl 函数

static int i2cdev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg){
struct i2c_client *client=(structi2c_client*)file->private_data;... switch ( cmd ) {
case I2C_SLAVE: case I2C_SLAVE_FORCE:...case I2C_TENBIT:...case I2C_PEC:...case I2C_FUNCS:/*设置从设备地址*/...case I2C_RDWR:if (copy_from_user(&rdwr_arg,(struct i2c_rdwr_ioctl_data _ _user *)arg, sizeof(rdwr_arg)))return -EFAULT; /* 一次传入的消息太多 */ if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)return -EINVAL; /*获得用户空间传入的消息数组*/ rdwr_pa = (struct i2c_msg *)kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL); if (rdwr_pa == NULL) return -ENOMEM; if (copy_from_user(rdwr_pa, rdwr_arg.msgs,rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
kfree(rdwr_pa); return -EFAULT; } data_ptrs = kmalloc(rdwr_arg.nmsgs *sizeof(u8 _ _user *),GFP_KERNEL);if (data_ptrs == NULL) {
kfree(rdwr_pa); return -ENOMEM; } res = 0; for( i=0; i
8192) {
res = -EINVAL; break; } data_ptrs[i] = (u8 _ _user *)rdwr_pa[i].buf; rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL); if(rdwr_pa[i].buf == NULL) {
res = -ENOMEM; break; } if(copy_from_user(rdwr_pa[i].buf, data_ptrs[i], rdwr_pa[i].len)) {
++i; /* Needs to be kfreed too */ res = -EFAULT;break;} } if (res < 0) {
int j; for (j = 0; j < i; ++j)kfree(rdwr_pa[j].buf); kfree(data_ptrs); kfree(rdwr_pa); return res; } /*把这些消息交给通信方法去处理*/ res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);while(i-- > 0) {
/*如果是读消息,把值复制到用户空间*/if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {
if(copy_to_user( data_ptrs[i], rdwr_pa[i].buf, rdwr_pa[i].len)) {
res = -EFAULT;} } kfree(rdwr_pa[i].buf); } kfree(data_ptrs); kfree(rdwr_pa);

常用的 IOCTL 包括 I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备 ACK 情况下的重试次数,默认为 1)、I2C_TIMEOU(超时)以及 I2C_RDWR

应用程序需要通过 i2c_rdwr_ioctl_data 结构体来给内核传递 I 2 C 消息,这个结构体定义如代码清单 15.22 所示,i2c_msg 数组指针及消息数量就被包含在 i2c_rdwr_ ioctl_data 结构体中

i2c_rdwr_ioctl_data 结构体

struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* I 2 C 消息指针 */ __u32 nmsgs; /* I 2 C 消息数量 */};

直接通过 read()/write()读写 I 2 C 设备

#include 
#include
#include
#include
#include
#include
#include
#define I2C_RETRIES 0x0701 #define I2C_TIMEOUT 0x0702 #define I2C_SLAVE 0x0703 int main(int argc, char **argv) { unsigned int fd; unsigned short mem_addr; unsigned short size; unsigned short idx; #define BUFF_SIZE char buf[BUFF_SIZE]; char cswap; union { unsigned short addr; char bytes[2];} tmp; if (argc < 3) { printf("Use:\n%s /dev/i2c-x mem_addr size\n", argv[0]); return 0; } sscanf(argv[2], "%d", &mem_addr); sscanf(argv[3], "%d", &size); if (size > BUFF_SIZE) size = BUFF_SIZE;fd = open(argv[1], O_RDWR); if (!fd) { printf("Error on opening the device file\n");return 0;} ioctl(fd, I2C_SLAVE, 0x50); /* 设置 E 2 PROM 地址 */ ioctl(fd, I2C_TIMEOUT, 1); /* 设置超时 */ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */for (idx = 0; idx < size; ++idx, ++mem_addr){ tmp.addr = mem_addr;cswap = tmp.bytes[0]; tmp.bytes[0] = tmp.bytes[1]; tmp.bytes[1] = cswap; write(fd, &tmp.addr, 2); read(fd, &buf[idx], 1); } buf[size] = 0; close(fd); printf("Read %d char: %s\n", size, buf); return 0; }

通过 O_RDWR IOCTL 读写 I 2 C 设备

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_I2C_MSG 2#define I2C_RETRIES 0x0701#define I2C_TIMEOUT 0x0702#define I2C_RDWR 0x0707struct i2c_msg{ __u16 addr; /* 从地址 */__u16 flags;#define I2C_M_RD 0x01__u8 *buf; /* 消息数据指针 */};struct i2c_rdwr_ioctl_data{ struct i2c_msg *msgs; /* i2c_msg[]指针 */int nmsgs; /* i2c_msg 数量 */};int main(int argc, char **argv){ struct i2c_rdwr_ioctl_data work_queue;unsigned int idx;unsigned int fd;unsigned short start_address;int ret;if (argc < 4){ printf("Usage:\n%s /dev/i2c-x start_addr\n", argv[0]);return 0; } fd = open(argv[1], O_RDWR); if (!fd) { printf("Error on opening the device file\n");return 0; } sscanf(argv[2], "%x", &start_address); work_queue.nmsgs = MAX_I2C_MSG; /* 消息数量 */work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs*sizeof(struct i2c_msg)); if (!work_queue.msgs) { printf("Memory alloc error\n");close(fd);return 0;} for (idx = 0; idx < work_queue.nmsgs; ++idx) { (work_queue.msgs[idx]).len = 0;(work_queue.msgs[idx]).addr = start_address + idx;(work_queue.msgs[idx]).buf = NULL;} ioctl(fd, I2C_TIMEOUT, 2); /* 设置超时 */ ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */ ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue); if (ret < 0) { printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);} close(fd); return ; }

转载地址:https://blog.csdn.net/zytgg123456/article/details/110142013 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:驱动篇:I 2 C 总线驱动实例(一)(摘录)
下一篇:驱动篇:Linux 的 I2C设备驱动(三)(摘录)

发表评论

最新留言

很好
[***.229.124.182]2024年04月30日 08时07分30秒