Linux i2c驱动框架分析 (二)

news/2024/7/3 12:28:32

Linux i2c驱动框架分析 (一)
Linux i2c驱动框架分析 (二)
Linux i2c驱动框架分析 (三)
通用i2c设备驱动分析

i2c core

i2c核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,理解其中的主要函数非常关键,因为 I2C 总线驱动和设备驱动之间赖于i2c核心作为纽带。i2c核心中提供的主要函数如下。
(1)增加/删除i2c_adapter


int i2c_add_adapter(struct i2c_adapter *adapter);
void i2c_del_adapter(struct i2c_adapter *adap);

(2)增加/删除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);

#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

(3)增加/删除i2c_client

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, 
	struct i2c_board_info const *info);

struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,
		      struct i2c_board_info *info,
		      unsigned short const *addr_list,
		      int (*probe)(struct i2c_adapter *, unsigned short addr));

void i2c_unregister_device(struct i2c_client *client)

(4)i2c传输、发送和接收

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(const struct i2c_client *client, const char *buf,
			   int count);
int i2c_master_recv(const struct i2c_client *client, char *buf,
			   int count);

i2c_transfer()函数用于进行i2c适配器和i2c设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。

i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用 i2c_algorithm的master_xfer()函数真正驱动硬件流程。

在i2c_core.c中定义了一个总线类型i2c_bus_type:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

这个i2c总线结构体管理着i2c设备与i2c驱动的匹配,删除等操作,i2c总线会调用i2c_device_match函数看i2c设备和i2c驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用i2c驱动的probe函数。

i2c设备与驱动

Linux i2c设备驱动的模块加载与卸载

i2c设备驱动的模块加载函数通用的方法是进行通过I2C核心的 i2c_add_driver()函数添加 i2c_driver的工作,而在模块卸载函数中需要做相反的工作:通过i2c核心的i2c_del_driver()函数删除i2c_driver。

Linux i2c设备实例化的多种方法

这里只是介绍其中的几种方法。

  1. 通过devicetree声明I2C设备
    在i2c控制器的节点里声明子节点,每个子节点代表一个i2c设备,如下图所示:
    在这里插入图片描述

  2. 显式实例化i2c设备
    通过i2c_new_device()或者i2c_new_probed_device()函数,创建并注册i2c_client。

  3. 从用户空间实例化i2c设备
    Example:

//名字为eeprom,地址为0x50的i2c设备注册进内核,挂接在i2c控制器0上
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-0/new_device

驱动与设备的匹配

i2c驱动与设备的匹配是在注册驱动或设备时进行匹配的,下面以注册i2c驱动为例,分析匹配过程的细节。

注册i2c驱动:

#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)


int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (WARN_ON(!is_registered))
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;

	//设置所属总线为i2c_bus_type
	driver->driver.bus = &i2c_bus_type;

	INIT_LIST_HEAD(&driver->clients);

	//注册驱动
	res = driver_register(&driver->driver);
	if (res)
		return res;

	pr_debug("driver [%s] registered\n", driver->driver.name);

	/* 探测驱动的address_list列表中的设备地址是否存在,存在则注册设备 */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

下面重点分析driver_register函数:

int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	//进行一些相关判断
	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	//查询是否含有同名驱动
	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	/* 把驱动插入到bus的驱动链表,这里涉及设备驱动模型相关知识
     *  在/sys/bus/i2c/drivers/目录下创建该驱动的sysfs目录,
     *  同时遍历bus的设备链表调用__driver_attach函数
     */
	ret = bus_add_driver(drv);
	if (ret)
		return ret;

	//增添属性相关
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}

__driver_attach:

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;
	int ret;


	/* 调用bus里的match函数进行匹配,对于i2c bus对应函数i2c_device_match
     * 匹配成功返回1
     */
	ret = driver_match_device(drv, dev);
	if (ret == 0) {
		/* no match */
		return 0;
	} else if (ret == -EPROBE_DEFER) {
		dev_dbg(dev, "Device match requests probe deferral\n");
		driver_deferred_probe_add(dev);
	} else if (ret < 0) {
		dev_dbg(dev, "Bus failed to match device: %d", ret);
		return ret;
	} /* ret > 0 means positive match */

	......
	
	//判断该设备是否已匹配了驱动
	if (!dev->driver)
		//会调到bus里的probe函数,对于i2c bus对应函数为i2c_device_probe(如果不存在则调用驱动的probe函数)
		driver_probe_device(drv, dev);
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);

	return 0;
}

分析i2c_device_match函数,看看具体怎么匹配的:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	//设备树匹配 
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	//id表匹配
	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table) 
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

有多种匹配方式,这里分析一下id表匹配:

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0) //比较name
			return id;
		id++;
	}
	return NULL;
}

如果匹配成功,driver_match_device函数返回1,之后会调到bus里的probe函数,对于i2c bus对应函数为i2c_device_probe:

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
	int status;

	if (!client)
		return 0;

	......

	driver = to_i2c_driver(dev->driver);
	if (!driver->probe || !driver->id_table)
		return -ENODEV;

	......

	//调用驱动的probe函数
	status = driver->probe(client, i2c_match_id(driver->id_table, client));
	......
}

i2c驱动的探测功能

i2c驱动含有一个设备地址列表address_list:

struct i2c_driver {
	unsigned int class;
	......
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

在注册i2c驱动时,通过适配器,探测address_list中对应地址的设备是否存在,如果存在则注册i2c设备。

在注册i2c驱动时,会遍历i2c bus上的适配器,调用__process_new_driver函数:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	.....
	
	//可以得知i2c适配器也被当做一种设备插入到i2c bus的设备链表上
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

__process_new_driver:

static int __process_new_driver(struct device *dev, void *data)
{
	//判断是否为i2c适配器
	if (dev->type != &i2c_adapter_type)
		return 0;
	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

i2c_do_add_adapter:

static int i2c_do_add_adapter(struct i2c_driver *driver,
			      struct i2c_adapter *adap)
{
	/* 探测 */
	i2c_detect(adap, driver);

	/* 这是老式的探测方法,已不推荐使用 */
	if (driver->attach_adapter) {
		dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
			 driver->driver.name);
		dev_warn(&adap->dev,
			 "Please use another way to instantiate your i2c_client\n");
		/* We ignore the return code; if it fails, too bad */
		driver->attach_adapter(adap);
	}
	return 0;
}

i2c_detect:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);

	address_list = driver->address_list;

	//如果驱动未提供detect函数和address_list,直接返回
	if (!driver->detect || !address_list)
		return 0;

	/* Warn that the adapter lost class based instantiation */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
		dev_dbg(&adapter->dev,
			"This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
			"If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",
			driver->driver.name);
		return 0;
	}

	/* Stop here if the classes do not match */
	if (!(adapter->class & driver->class))
		return 0;

	/* 创建一个临时的i2c_client,用于帮助探测 */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev,
			"found normal entry for adapter %d, addr 0x%02x\n",
			adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		//设置好临时的i2c_client后,探测设备地址
		err = i2c_detect_address(temp_client, driver);
		if (unlikely(err))
			break;
	}

	kfree(temp_client);
	return err;
}

i2c_detect_address:

static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
	struct i2c_board_info info;
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* 检查addr是否有效 */
	err = i2c_check_7bit_addr_validity_strict(addr);
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}

	/* 检查addr是否与挂在适配器上的设备的地址相同 */
	if (i2c_check_addr_busy(adapter, addr))
		return 0;

	/* 向地址为addr的设备发送信号,可是否有回应 */
	if (!i2c_default_probe(adapter, addr))
		return 0;

	/* Finally call the custom detection function */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;

	/* 程序执行到这里,说明存在地址为addr的设备,调用驱动的detect函数
   * 在驱动的detect函数中,要初始化i2c_board_info->type,即i2c设备的name
   */
	err = driver->detect(temp_client, &info);
	if (err) {
		/* -ENODEV is returned if the detection fails. We catch it
		   here as this isn't an error. */
		return err == -ENODEV ? 0 : err;
	}

	/* 检查name是否初始化 */
	if (info.type[0] == '\0') {
		dev_err(&adapter->dev,
			"%s detection function provided no name for 0x%x\n",
			driver->driver.name, addr);
	} else {
		struct i2c_client *client;

		/* Detection succeeded, instantiate the device */
		if (adapter->class & I2C_CLASS_DEPRECATED)
			dev_warn(&adapter->dev,
				"This adapter will soon drop class based instantiation of devices. "
				"Please make sure client 0x%02x gets instantiated by other means. "
				"Check 'Documentation/i2c/instantiating-devices' for details.\n",
				info.addr);

		dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",
			info.type, info.addr);

		//增添新的i2c设备
		client = i2c_new_device(adapter, &info);
		if (client)
			//把i2c设备插入驱动的clients链表
			list_add_tail(&client->detected, &driver->clients);
		else
			dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
				info.type, info.addr);
	}
	return 0;
}

http://www.niftyadmin.cn/n/3657312.html

相关文章

BOOT,FAT16结构

以下资料仅供参考:----------------------------------------------------------------------------------------目录项(Directory Entries)文件属性字节(File attribute byte)FAT16结构(FAT16 structure)磁盘引导记录结构(BOOT record layout)目录项(Directory Entries)offset…

Tip - SQL报表打印的空白页问题

SQL报表中一个常见问题是&#xff1a;在HTML格式中报表看起来还不错&#xff0c;但是打印出来&#xff08;或者在PDF格式中&#xff09;却发现每一个页面后面都跟着一个空白页。这是因为报表的设计尺寸超过了打印页面的物理尺寸。那么如何设置报表的尺寸适合打印呢&#xff1f;…

Linux i2c驱动框架分析 (三)

Linux i2c驱动框架分析 &#xff08;一&#xff09; Linux i2c驱动框架分析 &#xff08;二&#xff09; Linux i2c驱动框架分析 &#xff08;三&#xff09; 通用i2c设备驱动分析 i2c适配器驱动 i2c适配器驱动加载与卸载 i2c总线驱动模块的加载函数要完成两个工作。 初始化…

.NET 格式字符串速查

.NET编程及SQL报表中常用到数字或者日期的格式转化&#xff0c;那些格式字符总是记不住&#xff0c;索性列出来备查&#xff1a;Standard numeric format strings: http://msdn2.microsoft.com/en-us/library/aa720653.aspxCustom numeric format strings: http://msdn2.micros…

通用i2c设备驱动分析

Linux i2c驱动框架分析 &#xff08;一&#xff09; Linux i2c驱动框架分析 &#xff08;二&#xff09; Linux i2c驱动框架分析 &#xff08;三&#xff09; 通用i2c设备驱动分析 内核提供了一个通用的i2c驱动&#xff0c;这个驱动程序为每个适配器提供了设备文件的功能&…

Linux spi驱动框架分析(一)

本次的spi专题&#xff0c;主要参考&#xff1a;这位大佬的博客 感觉大佬们的无私奉献&#xff0c;而我写博客的初衷除了记录自己的学习经历之外&#xff0c;同时也分享给大家&#xff0c;分享知识。 系列文章&#xff1a; Linux spi驱动框架分析&#xff08;一&#xff09; L…

软件架构师

软件企业中有一个角色叫做软件架构师&#xff0c;不同公司或者不同的环境下&#xff0c;对该职位的定位可能不尽相同。微软首席架构师Ray Ozzie 对自己职位的一些看法&#xff0c;倒是给人很多启发&#xff1a;1. 不管是设计一座桥梁还是一幢大厦&#xff0c;你是在特定的情况下…

Linux spi驱动框架分析(二)

系列文章&#xff1a; Linux spi驱动框架分析&#xff08;一&#xff09; Linux spi驱动框架分析&#xff08;二&#xff09; Linux spi驱动框架分析&#xff08;三&#xff09; Linux spi驱动框架分析&#xff08;四&#xff09; spi core spi核心&#xff08;dervers/spi/s…