当前位置:网站首页>I2C子系统之适配器的设备接口分析(i2c-dev.c文件分析)
I2C子系统之适配器的设备接口分析(i2c-dev.c文件分析)
2022-07-04 15:58:00 【正在起飞的蜗牛】
1、驱动实现的原理
(1)驱动实现的思路:将I2C控制器的操作方法直接提供给应用,驱动本身只实现最简单的I2C总线收发数据的方法,具体I2C接口硬件的操作时序由应用去控制;
(2)驱动只做了最基本的收发数据操作,驱动代码开发的难度降低了,但是应用开发的难度上升了,并且应用需要感知硬件的差异,但是我们大部分在写程序时都是需要对应用屏蔽硬件的差异,所以这套驱动框架并不常用;
2、驱动实现的效果
(1)在/dev目录下创建"i2c-n(n=0,1,2······)"设备节点,每一个设备节点代表Soc的一个I2C控制器;
(2)应用通过open、read、write、ioctl等函数,去于挂载在相应I2C总线上的I2C设备进行通信;
补充:一个I2C控制器就等同于一条I2C总线;I2C控制器在I2C子系统中也叫适配器;
3、驱动的加载:i2c_dev_init( )
//设备节点的操作方法
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
//I2C驱动
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
};
#define I2C_MAJOR 89 /* Device major number */
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
//注册设备号是89,次设备号范围是0-255、文件操作集合是i2cdev_fops的字符设备
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;
//注册名字是i2c-dev的设备类
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
//注册i2c适配器设备驱动i2cdev_driver
res = i2c_add_driver(&i2cdev_driver);
if (res)
goto out_unreg_class;
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
(1)注册设备号是89,次设备号范围是0-255、文件操作集合是i2cdev_fops的字符设备;
(2)注册名字是i2c-dev的设备类;
(3)注册i2c适配器设备驱动i2cdev_driver,将来内核注册的每个I2C适配器都会被该驱动以设备节点的方式暴露给应用;
总结:I2C驱动都同属于i2c-dev设备类,共用主设备号89;将来在"/sys/class/i2c-dev"目录下可以看到注册的I2C驱动;
4、打开设备节点
4.1、i2cdev_open()函数
static int i2cdev_open(struct inode *inode, struct file *file)
{
//通过inode获取次设备号
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
//根据次设备号从链表i2c_dev_list中获得对应的i2c_dev对象
i2c_dev = i2c_dev_get_by_minor(minor);
if (!i2c_dev)
return -ENODEV;
//获得ID为i2c_dev->adap->nr的适配器对象
adap = i2c_get_adapter(i2c_dev->adap->nr);
if (!adap)
return -ENODEV;
/* This creates an anonymous i2c_client, which may later be * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE. * * This client is ** NEVER REGISTERED ** with the driver model * or I2C core code!! It just holds private copies of addressing * information and maybe a PEC flag. */
//创建一个i2c_client对象并初始化
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->driver = &i2cdev_driver;
client->adapter = adap;
//保存到struct file结构体的私有数据,后续会用到
file->private_data = client;
return 0;
}
(1)从inode中获取次设备号,然后根据次设备号从链表i2c_dev_list中获得对应的i2c_dev对象;
(2)获得ID为i2c_dev->adap->nr的适配器对象,适配器对象就是在内核中用于表示I2C控制器的;
(3)创建一个i2c_client对象并初始化,将adap、i2cdev_driver、名字都保存到client中;
(4)最后将i2c_client对象作为file的私有数据供后续其他操作使用;
5、从I2C设备读取数据
5.1、函数调用
i2cdev_read()
i2c_master_recv()
i2c_transfer()
adap->algo->master_xfer() //adap适配器收发数据的方法
copy_to_user()
5.2、i2cdev_read()函数
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
char *tmp;
int ret;
//解析处open时构建的struct i2c_client结构体
struct i2c_client *client = file->private_data;
if (count > 8192)
count = 8192;
tmp = kmalloc(count, GFP_KERNEL);
if (tmp == NULL)
return -ENOMEM;
pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
iminor(file->f_path.dentry->d_inode), count);
//接收数据,然后再把数据拷贝到用户空间缓存区
ret = i2c_master_recv(client, tmp, count);
if (ret >= 0)
ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
kfree(tmp);
return ret;
}
(1)解析处open时构建的struct i2c_client结构体;
(2)调用i2c_master_recv()函数接收数据,然后再把数据拷贝到用户空间缓存区;
5.3、i2c_master_recv()函数
int i2c_master_recv(struct i2c_client *client, char *buf, int count)
{
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
int ret;
//在open()方法中并没有看到对client->addr赋值,因此打开设备后并不能直接调用read()方法
msg.addr = client->addr; //从设备在I2C总线上的地址
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;
//i2c_transfer()通过调用适配器通信方法master_xfer将消息发出
ret = i2c_transfer(adap, &msg, 1);
/* If everything went ok (i.e. 1 msg transmitted), return #bytes transmitted, else error code. */
return (ret == 1) ? count : ret;
}
(1)i2c_master_recv()是通过填充i2c_msg结构体,然后调用i2c_transfer()函数来实现传输;
(2)i2c_transfer()则通过调用适配器通信方法master_xfer()将消息发出;
补充:在填充msg时赋值了从设备在I2C总线上的地址,但是在open时并没有对addr进行赋值,所以是不能open后直接进行read操作的,需要先调用ioctl函数设置从设备地址addr,表明要操作I2C总线上的哪个设备;
6、往I2C设备写数据
6.1、函数调用关系
i2cdev_write()
copy_from_user()
i2c_master_send()
i2c_transfer()
adap->algo->master_xfer()
6.2、i2cdev_write()函数
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp;
struct i2c_client *client = file->private_data;
if (count > 8192)
count = 8192;
tmp = kmalloc(count, GFP_KERNEL);
if (tmp == NULL)
return -ENOMEM;
//把要发送的数据从用户空间拷贝到内核空间
if (copy_from_user(tmp, buf, count)) {
kfree(tmp);
return -EFAULT;
}
pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
iminor(file->f_path.dentry->d_inode), count);
ret = i2c_master_send(client, tmp, count);
kfree(tmp);
return ret;
}
(1)解析处open时构建的struct i2c_client结构体;
(2)调用i2c_master_send()函数发送数据,最终都是调用适配器通信方法master_xfer(),和读数据的过程基本一致;
7、ioctl函数:i2cdev_ioctl()
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
//解析处open()时创建的client
struct i2c_client *client = file->private_data;
unsigned long funcs;
dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);
switch (cmd) {
case I2C_SLAVE: //分配从设备地址,如果地址已经分配,则返回EBUSY错误
case I2C_SLAVE_FORCE: //不管分配与否,强制设置该地址
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
client->addr = arg;
return 0;
case I2C_TENBIT: //设置I2C_M_TEN标识.启用10bit地址模式
if (arg)
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return 0;
case I2C_PEC: //启动SMBus的包错误检查
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS: //读取适配器支持的功能
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);
case I2C_RDWR: //读取、写入I2C消息
return i2cdev_ioctl_rdrw(client, arg);
case I2C_SMBUS: //处理SMBus消息传输
return i2cdev_ioctl_smbus(client, arg);
case I2C_RETRIES: //设置重试次数
client->adapter->retries = arg;
break;
case I2C_TIMEOUT: //设置超时时间
client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:
return -ENOTTY;
}
return 0;
}
8、设备节点是何时创建
8.1、函数调用关系
i2c_dev_init()
i2c_add_driver()
i2c_register_driver()
bus_for_each_dev()
__process_new_driver()
driver->attach_adapter()
i2cdev_attach_adapter()
device_create()
device_create_file()
8.2、i2cdev_attach_adapter()函数
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
struct i2c_dev *i2c_dev;
int res;
//分配一个i2c_dev对象,并添加到i2c_dev_list链表中
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
/* 创建设备对象并在sysfs中注册,在/dev目录下创建设备号为MKDEV(I2C_MAJOR, adap->nr), 名称为"i2c-%d"的字符设备节点*/
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
//创建"/sys/class/i2c-dev/"i2c-%d"/name"文件
res = device_create_file(i2c_dev->dev, &dev_attr_name);
if (res)
goto error_destroy;
pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
error_destroy:
device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
return_i2c_dev(i2c_dev);
return res;
}
边栏推荐
- 解决el-input输入框.number数字输入问题,去掉type=“number“后面箭头问题也可以用这种方法代替
- 缓存穿透、缓存击穿、缓存雪崩分别是什么
- Zebras are recognized as dogs, and the reason for AI's mistakes is found by Stanford
- 图像检索(image retrieval)
- Internet addiction changes brain structure: language function is affected, making people unable to speak neatly
- 防火墙基础透明模式部署和双机热备
- detectron2安装方法
- What grade does Anxin securities belong to? Is it safe to open an account
- Datakit -- the real unified observability agent
- Interpretation of data security governance capability evaluation framework 2.0, the fourth batch of DSG evaluation collection
猜你喜欢

超标量处理器设计 姚永斌 第7章 寄存器重命名 摘录

Vscode modification indentation failed, indent four spaces as soon as it is saved
![[HCIA continuous update] overview of WLAN workflow](/img/0a/b3986307589a9f7379fe1dd707b9f8.png)
[HCIA continuous update] overview of WLAN workflow

离线、开源版的 Notion—— 笔记软件Anytype 综合评测

整理混乱的头文件,我用include what you use

Zebras are recognized as dogs, and the reason for AI's mistakes is found by Stanford

简单易用的地图可视化

防火墙基础透明模式部署和双机热备

居家打工年入800多万,一共五份全职工作,他还有时间打游戏

The test experience "tortured" by the PMP test is worth your review
随机推荐
KS007基于JSP实现人个人博客系统
detectron2安装方法
【HCIA持续更新】WLAN概述与基本概念
Face_recognition人脸识别之考勤统计
Is it safe for Great Wall Securities to open an account? How to open a securities account
Summary of tx.origin security issues
Great Wall Securities security does not open a securities account
OPPO小布推出预训练大模型OBERT,晋升KgCLUE榜首
Implementation of super large-scale warehouse clusters in large commercial banks
将Opencv绘制图片显示在MFC Picture Control控件上
Internet addiction changes brain structure: language function is affected, making people unable to speak neatly
什么是低代码开发?
Chow Tai Fook fulfills the "centenary commitment" and sincerely serves to promote green environmental protection
Blood spitting finishing nanny level series tutorial - play Fiddler bag grabbing tutorial (2) - first meet fiddler, let you have a rational understanding
Zhijieyun - meta universe comprehensive solution service provider
R语言plotly可视化:plotly可视化多分类变量小提琴图(multiple variable violin plot in R with plotly)
安信证券属于什么档次 开户安全吗
【HCIA持续更新】WLAN工作流程概述
Using win10 scheduling task program to automatically run jar package at fixed time
MVC模式和三层架构