linux驱动之i2c子系统(代码片段)

小嵌同学 小嵌同学     2023-01-15     486

关键词:

一、I2C基本原理

(1)三根通信线:SCL、SDA、GND

(2)同步、串行、电平、低速、近距离

(3)总线式结构,支持多个设备挂接在同一条总线上

(4)主从式结构,通信双方必须一个为主(master)一个为从(slave),主设备掌握每次通信的主动权,从设备按照主设备的节奏被动响应。每个从设备在总线中(某条工作的总线上,并不是所有的总线上都是同一个地址)有唯一的地址(slave address),主设备通过从地址找到自己要通信的从设备(本质是广播的方式)。

  收发消息都是广播,总线上的设备都可收到,通过于自己的地址对比确认是否是给自己的信息

(5)I2C主要用途就是主SoC和外围设备之间的通信,最大优势是可以在总线上扩展多个外围设备的支持。常见的各种物联网传感器芯片(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)均使用I2C接口和主SoC进行连接。

(6)电容触摸屏芯片的多个引脚构成2个接口一个接口是I2C的,负责和主SoC连接(本身作为从设备),主SoC通过该接口初始化及控制电容触摸屏芯片、芯片通过该接口向SoC汇报触摸事件的信息(触摸坐标等),我们使用电容触摸屏时重点关注的是这个接口;另一个接口是电容触摸板的管理接口,电容触摸屏芯片通过该接口来控制触摸板硬件。该接口是电容触摸屏公司关心的,他们的触摸屏芯片内部固件编程要处理这部分,我们使用电容触摸屏的人并不关心这里。

二、linux内核的I2C子系统详解

1、linux内核的I2C驱动框架总览

(1)I2C驱动框架的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件

(2)源码中I2C相关的驱动均位于:drivers/i2c目录下。

(3)linux系统提供2种I2C驱动实现方法

  第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2C master,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实现对硬件的操作(操控寄存器进行初始化等),因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做**“应用层驱动”**,这种方式并不主流,它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。这种驱动在驱动层很简单(就是i2c-dev.c)我们就不分析了。

  第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。这种是我们后续分析的重点。

2、I2C子系统的4个关键结构体(kernel/include/linux/i2c.h)

210有多个iic接口,每个接口由多个寄存器操控,代表了iic控制器
(1)struct i2c_adapter:用来描述主机的iic控制器(适配器),主控驱动,芯片换了这块代码就要变

struct i2c_adapter 
	struct module *owner;
	unsigned int id;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
									//通过函数指针可以使用不同的算法	
	void *algo_data;

	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */

	int nr;
	char name[48];
	struct completion dev_released;

	struct list_head userspace_clients;
;

(2)struct i2c_algorithm:用来描述I2C算法,即主从机的通信时序,其被包含在struct i2c_adapter中,同一个主控soc可以有不同的算法,比如从设备是一个标准的iic设备传感器,一个不是标准的iic设备

struct i2c_algorithm 
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);
;

(3)struct i2c_client:描述I2C(从机)设备信息

struct i2c_client 
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct i2c_driver *driver;	/* and our access routines	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
;

(4)struct i2c_driver:描述I2C(从机)设备驱动

struct i2c_driver 
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this if you can, it will probably
	 * be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *);
	int (*detach_adapter)(struct i2c_adapter *);

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
;

  i2c_driver 与 i2c_client在驱动中会进行匹配,当二者匹配成功时,i2c_client就会将自己的硬件信息交给i2c_driver,类似于平台总线的驱动和设备进行匹配。

3、关键文件

(1)kernel/drivers/i2c/i2c-core.c
  iic核心文件,属于内核开发者实现的那部分,与具体硬件无关,属于纯软件。但其内部间接性地调用了许多和硬件操作相关的函数,通过结构体与函数指针实现。

(2)busses目录(kernel/drivers/i2c/)
  放了许多主控芯片的iic控制器相关程序,我们要关注的是i2c-s3c2410.c,2410和210的iic控制器这部分的实现相同

(3)kernel/drivers/i2c/algos,实现的一些算法操作,我们暂时不需要去关注

(4)此外还会涉及到mach-x210.c、kernel/drivers/i2c/i2c-boardinfo.c

4、i2c-core.c初步分析(从后向前看)

(1)smbus代码略过我们涉及不到(1534行之后的代码)
 emsp;其是应用于移动PC和桌面PC系统中的低速率通讯。希望通过一条廉价并且功能强大的总线(由两条线组成),来控制主板上的设备并收集相应的信息。

(2)1179行代码

postcore_initcall(i2c_init);
module_exit(i2c_exit);

总线在内核中也是一个模块,是需要去注册的。

struct bus_type i2c_bus_type = 
	.name		= "i2c",
	.match		= i2c_device_match,//用于driver和device进行匹配
	.probe		= i2c_device_probe,//当driver和device匹配上之后就会执行该函数
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
;
static int __init i2c_init(void)

	int retval;

	retval = bus_register(&i2c_bus_type);//注册iic,注册后就可以在/sys/bus/目录下看到iic
	if (retval)
		return retval;
#ifdef CONFIG_I2C_COMPAT//这个宏应该是没有的
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
	if (!i2c_adapter_compat_class) 
		retval = -ENOMEM;
		goto bus_err;
	
#endif
	retval = i2c_add_driver(&dummy_driver);//dummy_driver是一个空的驱动
	if (retval)
		goto class_err;
	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	bus_unregister(&i2c_bus_type);
	return retval;

当新注册driver/device和device/driver匹配上后就会执行XX_probe函数进行初始化,否则就没有驱动。

static void __exit i2c_exit(void)

	i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
#endif
	bus_unregister(&i2c_bus_type);

5、I2C总线的匹配机制(i2c-core.c)

(1)总线的match函数i2c_device_match

#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)

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;

	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;

变量成员组成及类型解析:driver->id_table//一个数组
struct i2c_driver	*driver;
	const struct i2c_device_id *id_table;
		struct i2c_device_id 
			char name[I2C_NAME_SIZE];
			kernel_ulong_t driver_data	/* Data private to the driver */
					__attribute__((aligned(sizeof(kernel_ulong_t))));
		;

变量成员组成及类型解析:client
struct i2c_client
	char name[I2C_NAME_SIZE];
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)//字符串比较进行driver和device匹配
			return id;
		id++;
	
	return NULL;

  让device和driver进行匹配,不同的总线有它自己的match函数。一般都是通过名字进行匹配的,当二者匹配上时会执行i2c_device_probe中的。总线的probe函数会去调driver的probe函数。

(2)总线的probe函数i2c_device_probe

static int i2c_device_probe(struct device *dev)

	struct i2c_client	*client = i2c_verify_client(dev);//i2c_client就是device
	struct i2c_driver	*driver;//i2c_driver就是driver
	int status;

	if (!client)
		return 0;

	driver = to_i2c_driver(dev->driver);//找到驱动
	if (!driver->probe || !driver->id_table)
		return -ENODEV;
	client->driver = driver;
	if (!device_can_wakeup(&client->dev))
		device_init_wakeup(&client->dev,
					client->flags & I2C_CLIENT_WAKE);
	dev_dbg(dev, "probe\\n");

	//当driver和device匹配上之后会去执行driver中的probe函数
	status = driver->probe(client, i2c_match_id(driver->id_table, client));
	if (status) 
		client->driver = NULL;
		i2c_set_clientdata(client, NULL);
	
	return status;

总结:I2C总线上有2条分支:i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,I2C总线都会调用match函数去对client.name和driver.id_table.name进行循环匹配。如果driver.id_table中所有的id都匹配不上则说明client并没有找到一个对应的driver,没了;如果匹配上了则标明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。

6、核心层开放给其他部分的注册接口

(1)i2c_add_adapter/i2c_add_numbered_adapter:注册adapter(iic适配器,主机控制器)的

int i2c_add_adapter(struct i2c_adapter *adapter)

	int	id, res = 0;

retry:
	if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
		return -ENOMEM;

	mutex_lock(&core_lock);
	/* "above" here means "above or equal to", sigh */
	res = idr_get_new_above(&i2c_adapter_idr, adapter,
				__i2c_first_dynamic_bus_num, &id);
	mutex_unlock(&core_lock);

	if (res < 0) 
		if (res == -EAGAIN)
			goto retry;
		return res;
	

	adapter->nr = id;
	return i2c_register_adapter(adapter);

int i2c_add_numbered_adapter(struct i2c_adapter *adap)

	int	id;
	int	status;

	if (adap->nr & ~MAX_ID_MASK)
		return -EINVAL;

retry:
	if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
		return -ENOMEM;

	mutex_lock(&core_lock);
	/* "above" here means "above or equal to", sigh;
	 * we need the "equal to" result to force the result
	 */
	status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
	if (status == 0 && id != adap->nr) 
		status = -EBUSY;
		idr_remove(&i2c_adapter_idr, id);
	
	mutex_unlock(&core_lock);
	if (status == -EAGAIN)
		goto retry;

	if (status == 0)
		status = i2c_register_adapter(adap);
	return status;

(2)i2c_add_driver:注册driver的

static inline int i2c_add_driver(struct i2c_driver *driver)

	return 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 (unlikely(WARN_ON(!i2c_bus_type.p)))
		return -EAGAIN;

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

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

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

	INIT_LIST_HEAD(&driver->clients);
	/* Walk the adapters that are already present */
	mutex_lock(&core_lock);
	bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver);
	mutex_unlock(&core_lock);

	return 0;

(3)i2c_new_device:注册client的

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

	struct i2c_client	*client;
	int			status;

	client = kzalloc(sizeof *client, GFP_KERNEL);
	if (!client)
		return NULL;

	client->adapter = adap;

	client->dev.platform_data = info->platform_data;

	if (info->archdata)
		client->dev.archdata = *info->archdata;

	client->flags = info->flags;
	client->addr = info->addr;
	client->irq = info->irq;

	strlcpy(client->name, info->type, sizeof(client->name));

	/* Check for address validity */
	status = i2c_check_client_addr_validity(client);
	if (status) 
		dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\\n",
			client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
		goto out_err_silent;
	

	/* Check for address business */
	status = i2c_check_addr_busy(adap, client->addr);
	if (status)
		goto out_err;

	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type;
	client->dev.type = &i2c_client_type;
#ifdef CONFIG_OF
	client->dev.of_node = info->of_node;
#endif

	dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
		     client->addr);
	status = device_register(&client->dev);
	if (status)
		goto out_err;

	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\\n",
		client->name, dev_name(&client->dev));

	return client;

out_err:
	dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
		"(%d)\\n", client->name, client->addr, status);
out_err_silent:
	kfree(client);
	returnlinux驱动开发19-i2c子系统之客户驱动分析与移植

I2C子系统_设备驱动移植  用户程序通过I2C设备驱动程序访问步骤是这样的... 用户的操作函数---/sys/bus/i2c/devices/....->设备驱动的操作函数---->最终回调i2c-core的函数实现数据的通信。 AT24.C驱动追踪:(Linux内核已经... 查看详情

i2c系统驱动程序模型(代码片段)

I2C系统驱动程序模型参考资料:Linux内核文档:Documentation\\i2c\\instantiating-devices.rstDocumentation\\i2c\\writing-clients.rstLinux内核驱动程序示例:drivers/eeprom/at24.c1.I2C驱动程序的层次I2CCore就是I2C核心层,它的作用:提 查看详情

linux驱动:i2c设备总线驱动(代码片段)

前言linux下设备跟驱动是分开的,他们通过总线进行匹配,设备由设备树负责,在设备树中添加相应的结点,系统会自动向总线注册相应的设备,而驱动开发需要负责的主要就是驱动的编写,向总线注册驱... 查看详情

linux驱动开发i2c(代码片段)

Linux内核将I2C驱动分成两部分I2C总线驱动:SOC的I2C控制器驱动,也称为I2C适配器驱动。半导体厂商编写。I2C设备驱动:具体I2C设备的驱动。SOC使用者编写。I2C总线驱动Linux内核将SOC的I2C适配器抽象为i2c_adapter,include... 查看详情

驱动开发之i2c总线(代码片段)

驱动开发之I2C总线:I2C:数据线和时钟线。  起始信号:时钟线为高电平,数据线由高到低跳变。  结束信号:时钟线为高电平,数据线由低到高跳变。  应答信号:第九个时钟周期,时钟线保持为高电平,数据线为低电平,... 查看详情

编写设备驱动之i2c_client(代码片段)

编写设备驱动之i2c_client参考资料:Linux内核文档:Documentation\\i2c\\instantiating-devices.rstDocumentation\\i2c\\writing-clients.rstLinux内核驱动程序示例:drivers/eeprom/at24.c本节代码:GIT仓库中IMX6ULL\\source\\04_I2C\ 查看详情

十i2c子系统(代码片段)

 由于之后的触摸屏驱动分析中使用到了GPIO子系统和i2c子系统,因此在分析触摸屏驱动之前我准备把这两个子系统进行简单分析。 在读者学习本章以及后续i2c相关章节之前,最好了解i2c通信方式,可以参考:i2c编程。 ... 查看详情

linux应用开发第十二章i2c编程应用开发(代码片段)

...linux系统下操作I2C总线的外设12.2.1概述12.2.2简述I2C的linux驱动1)I2C核心层:2)I2C总线驱动层:3)I2C总线驱动层:12.3在linux应用层使用I2C12.3.1如何使用I2Ctools测试I2C外设1)I2Ctools概述:2)下载I2Ctools源码:3)编译I2Ctools... 查看详情

九i2c设备驱动(代码片段)

一、前言前面第二篇文章中,我总结了Linux系统下i2c驱动中的适配器驱动,但是一个完整的总线-设备驱动模型应该包含总线驱动和设备驱动,总线驱动也就是前面所总结的i2c适配器驱动,现在再来总结一下i2c设备驱动的具体实现... 查看详情

i2c子系统之适配器的设备接口分析(i2c-dev.c文件分析)(代码片段)

...f1a;一个I2C控制器就等同于一条I2C总线;I2C控制器在I2C子系统中也叫适配器;3、驱动的加载:i2c_dev_init()//设备节点的操作方法staticconststructfile_operationsi2cdev_fops= .owner =THIS_MODULE, 查看详情

基于瑞芯微rv1109linux触摸屏gt911驱动调试心得(代码片段)

1、确定I2C地址1.1、使用i2cdetect工具查看系统i2c节点的情况很明显这里可以看到系统已经配置了i2c-0、i2c-1、i2c-3、i2c-4、i2c-5,我们可以看下原厂在设备树里面的支持情况:gedit kernel/arch/arm/boot/dts/rv1126.dtsi &打开后我们可... 查看详情

我的内核学习笔记12:linuxi2c-gpio驱动应用实例(代码片段)

...I2C协议的驱动,只需要配置2根GPIO即可使用。Linux的I2C子系统比较复杂,笔者暂时还没有研究。本着“实用”的目的,介绍一下如何使用这个驱动及一些注意事项。一、概述Linux内核很多驱动都使用到I2C子系统。如EEPROM... 查看详情

基于瑞芯微rv1109linux触摸屏gt911驱动调试心得(代码片段)

点击上方「嵌入式云IOT技术圈」,选择「置顶公众号」第一时间查看嵌入式笔记!1、确定I2C地址1.1、使用i2cdetect工具查看系统i2c节点的情况很明显这里可以看到系统已经配置了i2c-0、i2c-1、i2c-3、i2c-4、i2c-5,我们可以... 查看详情

编写设备驱动之i2c_driver(代码片段)

编写设备驱动之i2c_driver参考资料:Linux内核文档:Documentation\\i2c\\instantiating-devices.rstDocumentation\\i2c\\writing-clients.rstLinux内核驱动程序示例:drivers/eeprom/at24.c本节代码:GIT仓库中框架:IMX6ULL\\sourc 查看详情

i2c系统的重要结构体(代码片段)

I2C系统的重要结构体参考资料:Linux驱动程序:drivers/i2c/i2c-dev.cI2CTools:https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/1.I2C硬件框架2.I2C传输协议写操作读操作3.Linux软件框架4.重要结构体使用一句话概括I2C传输:APP通过I2CContro 查看详情

linux学习资料整理

...ff08;驱动)Linux设备驱动模型-KobjectLinux驱动之input输入子系统linux设备驱动之I2C驱动框架linux内核之leds子系统linux驱动底层深入理解Linux设备驱动之platform平台总线**学习交流群:**943552345 查看详情

rt-thread设备驱动i2c浅析及使用(代码片段)

由于I2C可以控制多从机的属性,设备驱动模型分为 I2C总线设备(类似与Linux里面的I2C适配器)+I2C从设备;系统I2C设备驱动主要实现 I2C总线设备驱动,而具体的I2C从设备的实现则调用I2C总线设备ops访问I2C总线设备一般情况... 查看详情

linux设备驱动之iio子系统——triggeredbuffersupport触发缓冲支持(代码片段)

Triggeredbuffersupport触发缓冲支持  在许多数据分析应用中,能够基于某些外部信号(触发器)捕获数据是比较有用的。这些触发器可能是:数据就绪信号连接到某个外部系统的IRQ线路(GPIO或其他)处理器周期性中断用户空间在s... 查看详情