当前位置:网站首页>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;
}
边栏推荐
- kaili不能输入中文怎么办???
- 上网成瘾改变大脑结构:语言功能受影响,让人话都说不利索
- 【Go ~ 0到1 】 第六天 文件的读写与创建
- Analysis of abnormal frequency of minor GC in container environment
- 一加10 Pro和iPhone 13怎么选?
- Learn more about the basic situation of 2022pmp examination
- 整理混乱的头文件,我用include what you use
- Spark 中的 Rebalance 操作以及与Repartition操作的区别
- 防火墙基础透明模式部署和双机热备
- 你应该懂些CI/CD
猜你喜欢
[Huawei HCIA continuous update] SDN and FVC
就在今天丨汇丰4位专家齐聚,共讨银行核心系统改造、迁移、重构难题
PingCode 性能测试之负载测试实践
Vscode modification indentation failed, indent four spaces as soon as it is saved
Kunming Third Ring Road Closure project will pass through these places. Is there one near your home?
Internet addiction changes brain structure: language function is affected, making people unable to speak neatly
Perfectly integrated into win11 style, Microsoft's new onedrive client is the first to see
上网成瘾改变大脑结构:语言功能受影响,让人话都说不利索
Analysis of abnormal frequency of minor GC in container environment
It's too convenient. You can complete the code release and approval by nailing it!
随机推荐
Solve the El input input box For number number input problem, this method can also be used to replace the problem of removing the arrow after type= "number"
Display opencv drawn pictures on MFC picture control control
La 18e Conférence internationale de l'IET sur le transport d'électricité en courant alternatif et en courant continu (acdc2022) s'est tenue avec succès en ligne.
To sort out messy header files, I use include what you use
【HCIA持续更新】网络管理与运维
Rainfall warning broadcast automatic data platform bwii broadcast warning monitor
图像检索(image retrieval)
大规模服务异常日志检索
补能的争议路线:快充会走向大一统吗?
PingCode 性能测试之负载测试实践
中信证券网上开户安全吗 开户收费吗
To sort out messy header files, I use include what you use
The Ministry of human resources and Social Security announced the new construction occupation
Implementation of super large-scale warehouse clusters in large commercial banks
detectron2安装方法
To sort out messy header files, I use include what you use
[Huawei HCIA continuous update] SDN and FVC
超标量处理器设计 姚永斌 第7章 寄存器重命名 摘录
51 single chip microcomputer temperature alarm based on WiFi control
Firewall basic transparent mode deployment and dual machine hot standby