14.IIC核心函数与万能驱动

IIC核心函数

i2c_add_adapter()

该函数存放在内核/drivers/i2c/i2c-core-base.c文件。注册一个i2c适配器

cpp 复制代码
/*
 * 注册一个i2c适配器
 * adapter->nr:适配器的编号
 * adapter:i2c物理控制器对应的适配器
 */

// 由kernel分配适配器的编号
int i2c_add_adapter(struct i2c_adapter *adapter)
// 自己指定适配器的编号
int i2c_add_numbered_adapter(struct i2c_adapter *adapter)

/*
 * 返回值:
 *    成功:0
 *    失败:负值
 */

i2c_add_driver()宏

该函数存放在内核/include/linux/i2c.h文件。注册一个i2c驱动

cpp 复制代码
// struct i2c_driver
#define i2c_add_driver(driver) \ i2c_register_driver(THIS_MODULE, driver)

/*
 * 存放在内核/drivers/i2c/i2c-core-base.c文件
 * 注册一个i2c驱动
 * owner:一般为THIS_MODULE
 * driver:要注册的i2c_driver,对应的驱动结构体由我们自己来实现
 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/*
 * 返回值:
 *    成功:0
 *    失败:负值
 */

i2c_transfer()

该函数存放在内核/drivers/i2c/i2c-core-base.c文件。进行iic消息收发(可收可发)

该函数的核心是:adap → algo → master_xfer(adap, msgs, num),也就是适配器的具体通信方法。

cpp 复制代码
/*
 * 收发消息都要用到i2c_adapter适配器
 * adap:所使用的的I2C适配器,i2c_client会保存其对应的i2c_adapter
 * msgs:i2c要发生的一个或多个消息,每个消息对应一个消息结构体
 * num:消息数量(即msgs的数量)
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/*
 * 返回值:
 *    成功:发送的msgs的数量
 *    失败:负值
 */

/* 该结构体存放在内核/include/uapi/linux/i2c.h文件 */
struct i2c_msg
{
	__u16 addr;	    // 从机地址,发送到哪个从设备地址
	__u16 flags;    // 读写等其他特性,见下面的宏

	// 此宏表示读,0表示写
	#define I2C_M_RD		    0x0001	/* read data, from slave to master */
					                    /* I2C_M_RD is guaranteed to be 0x0001! */
	#define I2C_M_TEN		    0x0010	/* this is a ten bit chip address */
	#define I2C_M_DMA_SAFE		0x0200	/* the buffer of this message is DMA safe */
					                    /* makes only sense in kernelspace */
					                    /* userspace buffers are copied anyway */
	#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
	#define I2C_M_STOP		    0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	
	__u16 len;		// 消息数据的长度
	__u8 *buf;		// 发送或接收的消息缓冲区
};

i2c_master_send()

该函数存放在内核/include/linux/i2c.h文件。发送一个i2c消息(只能是一个)。其实最终调用的还是i2c_transfer()。

cpp 复制代码
static inline int i2c_master_send(const struct i2c_client *client,
				  const char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);
};

i2c_master_recv()

该函数存放在内核/include/linux/i2c.h文件。接收一个i2c消息(只能是一个)。其实最终调用的还是i2c_transfer()。

cpp 复制代码
static inline int i2c_master_recv(const struct i2c_client *client,
				  char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);
};

i2c_transfer_buffer_flags()

该函数存放在内核/drivers/i2c/i2c-core-base.c文件。发送一个i2c消息。其实最终调用的还是i2c_transfer()。

cpp 复制代码
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf, int count, u16 flags)
{
	int ret;
	// 根据传入参数构建消息结构体,此结构体定义详见上
	struct i2c_msg msg =
	{
		.addr = client->addr,
		.flags = flags | (client->flags & I2C_M_TEN),
		.len = count,
		.buf = buf,
	};

	ret = i2c_transfer(client->adapter, &msg, 1);

	/*
	 * If everything went ok (i.e. 1 msg transferred), return #bytes
	 * transferred, else error code.
	 */
	return (ret == 1) ? count : ret;
}

万能的i2c驱动(i2c-dev.c文件)

文件存放于内核/drivers/i2c/。

内核集成i2c_dev驱动模块,开机自动加载

为每个i2c_adapter生成一个设备文件,通过该设备文件间接使用i2c核心函数收发数据

注册i2c总线的通知函数,解决加载顺序问题

i2c_dev_init()

cpp 复制代码
#define I2C_MAJOR	89		/* Device major number		*/
#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)
#define I2C_MINORS	MINORMASK

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");
	// 申请设备号,I2C_MAJOR为89,次设备号为0,I2C_MINORS为1<<20-1,表示次设备号的数量。
	// 就是把这个主设备号对应的次设备号都申请了。
	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;
	// 创建一个同名类,在 /sys/class中可以看到
	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总线的通知函数
	// 参数2详见下
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	// 遍历i2c总线上的所有设备,每次都执行第二个参数对应的函数
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

static struct notifier_block i2cdev_notifier =
{
	// 详见下
	.notifier_call = i2cdev_notifier_call,
};

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
			 void *data)
{
	struct device *dev = data;
	// 发生的事件类型
	switch (action) {
	// 此i2c总线下发生添加设备事件
	case BUS_NOTIFY_ADD_DEVICE:
		// 创建设备文件之类的操作
		return i2cdev_attach_adapter(dev, NULL);
	// 此i2c总线下发生删除设备事件
	case BUS_NOTIFY_DEL_DEVICE:
		return i2cdev_detach_adapter(dev, NULL);
	}

	return 0;
}

int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{
	int res;

	mutex_lock(&core_lock);
	res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
	mutex_unlock(&core_lock);

	return res;
}

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while (!error && (dev = next_device(&i)))
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}

i2cdev_attach_adapter()

i2c总线添加设备后回调执行的函数

cpp 复制代码
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;
	// 若设备类型不是i2c适配器,直接返回
	// 也有可能是 i2c_client 设备
	if (dev->type != &i2c_adapter_type)
		return 0;
	// 从i2c设备结构体中获取i2c适配器结构体
	adap = to_i2c_adapter(dev);
	// 分配内存
	i2c_dev = get_free_i2c_dev(adap);
	if (IS_ERR(i2c_dev))
		return PTR_ERR(i2c_dev);
	// 设置文件操作接口
	cdev_init(&i2c_dev->cdev, &i2cdev_fops);
	i2c_dev->cdev.owner = THIS_MODULE;
	// 注意次设备号为适配器对应的编号,可以自己指定,也可以有系统分配
	// 参数3为次设备号的数量,此fops只对应次设备号的文件
	res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
	if (res)
		goto error_cdev;

	/* register this i2c device with the driver core */
	// 在/sys下,在i2c_dev_class类(参数1)下创建一个新的目录, "i2c-适配器编号"就是目录名
	// 然后会通知用户空间的uevent守护进程,此守护进程会创建一个同参数4名的设备文件
	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;
	}

	pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
		 adap->name, adap->nr);
	return 0;
error:
	cdev_del(&i2c_dev->cdev);
error_cdev:
	put_i2c_dev(i2c_dev);
	return res;
}

i2cdev_fops结构体

cpp 复制代码
static const struct file_operations i2cdev_fops =
{
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,

	.read		= i2cdev_read,
	.write		= i2cdev_write,
 
	.unlocked_ioctl	= i2cdev_ioctl,
	.compat_ioctl	= compat_i2cdev_ioctl,

	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

i2cdev_open()

cpp 复制代码
static int i2cdev_open(struct inode *inode, struct file *file)
{
	// 从inode获取次设备号,就是适配器的编号
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;

	// 根据次设备号从i2c总线获取对应的适配器
	adap = i2c_get_adapter(minor);
	if (!adap)
		return -ENODEV;

	// client表示某个具体的i2c设备,为其分配内存
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}

	// 设置具体的i2c设备的名字
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
	// 设置具体的i2c设备归属的适配器
	client->adapter = adap;
	// 以后可以通过file的此成员获取client指针
	file->private_data = client;

	return 0;
}

i2cdev_read()

cpp 复制代码
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;

	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_inode(file)), count);
	
    // 核心函数,接受一个i2c消息
	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;

	kfree(tmp);
	return ret;
}

i2cdev_write()

cpp 复制代码
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;

	把用户空间的buf拷贝内核空间的tmp
	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n", iminor(file_inode(file)), count);
	
    // 核心函数,发送一个i2c消息
	ret = i2c_master_send(client, tmp, count);

	kfree(tmp);
	return ret;
}
相关推荐
热爱嵌入式的小许1 小时前
Linux基础项目开发1:量产工具——显示系统
linux·运维·服务器·韦东山量产工具
韩楚风5 小时前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
陈苏同学5 小时前
4. 将pycharm本地项目同步到(Linux)服务器上——深度学习·科研实践·从0到1
linux·服务器·ide·人工智能·python·深度学习·pycharm
Ambition_LAO5 小时前
解决:进入 WSL(Windows Subsystem for Linux)以及将 PyCharm 2024 连接到 WSL
linux·pycharm
Pythonliu75 小时前
茴香豆 + Qwen-7B-Chat-Int8
linux·运维·服务器
你疯了抱抱我5 小时前
【RockyLinux 9.4】安装 NVIDIA 驱动,改变分辨率,避坑版本。(CentOS 系列也能用)
linux·运维·centos
追风赶月、5 小时前
【Linux】进程地址空间(初步了解)
linux
栎栎学编程5 小时前
Linux中环境变量
linux
挥剑决浮云 -6 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记
小O_好好学7 小时前
CentOS 7文件系统
linux·运维·centos