3i2c-dev
3.1 概述
之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动。不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件的打开而产生,并随着设备文件的关闭而撤销。I2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主题是”i2c_driver成员函数+字符设备驱动”。
3.2 i2c-dev.c源码分析
初始化模块
static int __init i2c_dev_init(void)
{
res= register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
i2c_dev_class= class_create(THIS_MODULE, "i2c-dev");
/*Keep track of adapters which will be added or removed later */
res= bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
/*绑定已经存在的适配器 */
i2c_for_each_dev(NULL,i2cdev_attach_adapter);
}
I2c-dev初始化函数主要做了注册名为”i2c”的字符设备文件和”i2c-dev”的类
i2cdev_read和i2cdev_write
I2c-dev.c中实现的i2cdev_read和i2cdev_write函数不具有太强的通用性,只适合下面这种单开始信号情况:
而不适合多开始信号的情况:
所以我们经常会使用i2cdev_ioctl函数的I2C_RDWR,在分析i2cdev_ioctl函数之前,我们需要了解一个结构体:
/* This is the structure as used in theI2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
structi2c_msg __user *msgs;
/* pointersto i2c_msgs */
__u32nmsgs;
/* number ofi2c_msgs */
};
Msgs表示单个开始信号传递的数据;
Nmsgs表示有多少个msgs,比如上图,单开始信号时,nmsgs等于1;多开始信号时,nmsgs等于2
struct i2c_msg {
__u16addr;
/* slave address*/
__u16flags;
/* 默认为写入 */
#define I2C_M_TEN0x0010/*this is a ten bit chip address */
#define I2C_M_RD0x0001/* read data,from slave to master */
#define I2C_M_NOSTART0x4000/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR0x2000/*if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK0x1000/*if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK0x0800/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN0x0400/* length will be first received byte */
__u16len;
/* msg length*/
__u8*buf;
/* pointer to msgdata*/
};
使用i2cdev_ioctl函数的I2C_RDWR指令会调用到i2cdev_ioctl_rdrw函数:
static noinline inti2cdev_ioctl_rdrw(struct i2c_client *client,
unsignedlong arg)
{
structi2c_rdwr_ioctl_data rdwr_arg;
structi2c_msg *rdwr_pa;
u8__user **data_ptrs;
inti, res;
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= kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL);
if(copy_from_user(rdwr_pa, rdwr_arg.msgs,
rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
kfree(rdwr_pa);
return-EFAULT;
}
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);
}
}
咋一看,还挺复杂,其实主要做了一件事情:把用户空间传递过来的i2c_rdwr_ioctl_data数据进行错误检查,然后调用i2c_transfer函数与适配器进行通信,如果是接收数据,代码会将访问到的数据传回i2c_rdwr_ioctl_data的buf中。I2c_transfer最终会调用到I2C适配器具体实现的master_xfer函数来与硬件进行通信。
3.3 eeprom实例
预备知识
使用的mini2440开发板,eeprom的地址为0x50,实验完成一个数据的读写,先看下读写时序
AT24C08任意地址字节写的时序:
AT24C08任意地址字节写的时序:
下面的代码可以按照上面的两个图来阅读:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
intfd, ret;
unsignedchar rdwr_addr = 0x42;
/* e2prom 读写地址 */
unsignedchar device_addr = 0x50;
/* e2prom 设备地址 */
unsignedchar data = https://www.it610.com/article/0x12;
/* 向e2prom写的数据 */
structi2c_rdwr_ioctl_data e2prom_data;
fd= open("/dev/i2c/0", O_RDWR);
if(fd < 0) {
perror("openerror");
exit(1);
}
e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \
sizeof(structi2c_msg));
if(e2prom_data.msgs == NULL) {
perror("mallocerror");
exit(1);
}
ioctl(fd,I2C_TIMEOUT, 1);
/* 设置超时 */
ioctl(fd,I2C_RETRIES, 2);
/* 设置重试次数 */
/*向e2prom的rdwr_addr地址写入数据data*/
e2prom_data.nmsgs= 1;
e2prom_data.msgs[0].len= 2;
e2prom_data.msgs[0].addr= device_addr;
e2prom_data.msgs[0].flags= 0;
/* write */
e2prom_data.msgs[0].buf= (unsigned char *)malloc(2);
e2prom_data.msgs[0].buf[0]= rdwr_addr;
/* write address */
e2prom_data.msgs[0].buf[1]= data;
/* write data */
ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret < 0) {
perror("writedata error");
exit(1);
}
printf("writedata: %d to address: %#x\n", data, rdwr_addr);
data= https://www.it610.com/article/0;
/* be zero*/
/*从e2prom的rdwr_addr地址读取数据存入buf*/
e2prom_data.nmsgs= 2;
e2prom_data.msgs[0].len= 1;
e2prom_data.msgs[0].addr= device_addr;
//e2prom_data.msgs[0].flags= 0;
/* write */
e2prom_data.msgs[0].buf= &rdwr_addr;
e2prom_data.msgs[1].len= 1;
e2prom_data.msgs[1].addr= device_addr;
e2prom_data.msgs[1].flags= 1;
/* read */
e2prom_data.msgs[1].buf= &data;
ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret < 0) {
perror("readerror");
exit(1);
}
printf("readdata: %d from address: %#x\n", data,rdwr_addr);
free(e2prom_data.msgs);
close(fd);
return0;
}
在mini2440开发板上已经实验成功。
【Linux|Linux驱动子系统之I2C(3)】
推荐阅读
- Linux|109 个实用 shell 脚本
- linux笔记|linux 常用命令汇总(面向面试)
- Linux|Linux--网络基础
- linux|apt update和apt upgrade命令 - 有什么区别()
- linux|2022年云原生趋势
- Go|Docker后端部署详解(Go+Nginx)
- 开源生态|GPL、MIT、Apache...开发者如何选择开源协议(一文讲清根本区别)
- GitHub|7 款可替代 top 命令的工具