i2c子系统

i2c 硬件协议

i2c 子系统中的重要结构体

i2c硬件框架

i2c_adapter

如上图为i2c 硬件框架图,一个soc 中可能有多个i2c控制器,每个控制器下可能挂有多个i2c 从设备(用scl、sda两条线相连)。
那么在内核中我们怎么来描述一个i2c 控制器呢?用struct i2c_adapter
i2c_adapter 中比较重要的成员:
nr : 表示它在soc 中是第几个i2c 控制器(或者说第几条i2c总线)。
algo: 算法,它表示我们如何使用这条i2c总线 (向i2c总线上发送/接收数据)。在struct i2c_algorithm 中有个master_xfer 回调函数,它用来实现向总线发送、接收数据(这个函数应该由i2c 控制器驱动实现)。

struct i2c_adapter {
	struct module *owner;
	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 mutex userspace_clients_lock;
	struct list_head userspace_clients;

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;
};

struct i2c_algorithm {
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,		//i2c 总线传输函数
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,		//smbus 协议:基于i2c 协议的另外一种协议,与i2c协议基本类似 
			   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 *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};
i2c_client

知道了如何描述i2c 控制器,那么怎么描述挂在i2c 总线下的从设备呢? 用 struct i2c_client
与一个i2c 从设备进行通信,我们需要确定以下几点信息:

  1. 从设备挂在哪条i2c 总线上。
  2. 从设备的器件地址是多少。

所以i2c_client 中比较重要的成员是:
adapter: 指向一个i2c_adapter 结构体地址,它表示这个从设备挂在这个控制器下。
addr: 器件地址,7位有效。
flags: 一些重要的标志。比如I2C_CLIENT_TEN,一般的i2c器件地址都是7位数据,所以默认按7位处理,但是使能了这个标志就表示我的地址是10位有效。

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
#define I2C_CLIENT_PEC		0x04	/* Use Packet Error Checking */
#define I2C_CLIENT_TEN		0x10	/* we have a ten bit chip address */
					/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE	0x20	/* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY	0x40	/* We want to use I2C host notify */
#define I2C_CLIENT_WAKE		0x80	/* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB		0x9000	/* Use Omnivision SCCB protocol */
					/* Must match I2C_M_STOP|IGNORE_NAK */
	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 device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};
i2c_msg

知道了如何描述 i2c控制器 和i2c从设备,我们还需要知道怎么描述总线上的数据。用struct i2c_msg 来描述。
addr: 从设备器件地址。
flags: 一些重要的标志。比如flags 的bit0 表示传输方向,bit 0等于I2C_M_RD表示读,bit 0等于0表示写。
len: 要写或是要读的数据长度。
buf: 用于存储数据的buffer。

struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#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;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

写操作
写入寄存器只需要一步操作就可以完成,所以写操作只需要构建一个i2c_msg 来描述:
发送器件地址+方向,接着发送reg地址 和 数据。
在这里插入图片描述

u8 reg_addr = 0x10;
unsigned char data_buf[10];
struct i2c_msg msgs[1];

data_buf[0] = reg_addr;			//将buf的第一个字节设置为要写的寄存器地址
memcpy(data_buf,data,len)		//后面的buf 存入要写入的寄存器的数据

msgs[0].addr   = 0x50;		//器件地址
msgs[0].flags  = 0;			//写
msgs[0].len    = len+1;		//数据长度+寄存器地址长度(1字节)
msgs[0].buf    = &data_buf;	//设置存放数据的buf

读操作
而读取寄存器则需要两步操作来完成,所以读需要用两个i2c_msg 来描述:
第一步:发送要读取的寄存器地址。(这是写方向,由第一个msg来描述 设备地址+方向 +reg_addr)
第二步:再次向总线发送器件地址+方向,然后将sda 信号线交由从设备处理,等待数据的返回。(读方向,由第二个msg 来描述)
在这里插入图片描述

u8 data_addr = 0x10;
i8 data;
struct i2c_msg msgs[2];

msgs[0].addr   = 0x50;		//器件地址
msgs[0].flags  = 0;			//写
msgs[0].len    = 1;
msgs[0].buf    = &data_addr;	//寄存器地址

msgs[1].addr   = 0x50;
msgs[1].flags  = I2C_M_RD;	//读
msgs[1].len    = 1;
msgs[1].buf    = &data;		//设置一个空的、承载数据的buf,读取到的数据会放入buf 中。

知道了怎么描述要传输的数据,那么发送、接收时就在把msg交给传输函数来完成。它就是i2c_adapter ->algo->master_xfer
类型如下:
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
那么每次传输数据都要调用i2c_adapter ->algo->master_xfer这么深,岂不是很烦,所以内核封装了一个函数i2c_transfer:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
它会取出 i2c_adapter ->algo->master_xfer,然后调用这个回调函数来传输 msg。

i2c_driver

与其它驱动一样,i2c 从设备驱动也要按照分离原则设计,i2c_driver 就是用来描述一个i2c 从设备驱动。
//include\linux\i2c.h
在这里插入图片描述
i2c 从设备驱动使用i2c 总线驱动模型来管理,当i2c_client 与i2c_driver 匹配时就会调用i2c_driver->probe 来执行从设备的驱动代码。

I2C驱动程序的层次

在这里插入图片描述
i2c 驱动主要分为i2c 控制器驱动(包括i2c 控制器驱动 + i2c 核心层)、i2c 从机驱动 和i2c 字符设备驱动。
i2c控制器驱动: 初始化i2c 控制器硬件,实现i2c控制器的传输功能(master_xfer 函数),根据核心层提供的方法注册i2c 控制器(i2c_adapter)。
i2c 核心层: 提供下层i2c控制器的管理方法(i2c_adapter 注册方法),向上层给出统一的i2c 传输方法(i2c_transfer)(Linux需要兼容多个平台的i2c 控制驱动,所以由核心层来管理它们,给出统一的传输方法);核心层还需要注册i2c_bus_type 总线模型,负责i2c_driver 与i2c_client 的匹配工作。
i2c 从机驱动: 调用i2c_transfer 实现从设备功能。(从设备:rtc、eeprom、switch…)。
i2c 字符设备驱动: 给每个i2c 控制器创建一个字符设备和设备节点,向应用层提供i2c 控制器的读写方法(通过read/write、ioctl 中调用i2c_transfer 实现)。

i2c控制器驱动 (i2c_adapter 注册流程)

(除了soc 自带的硬件i2c 控制器外,还可以使用gpio模拟i2c,内核提供了通用的gpio 模拟i2c 驱动——drivers\i2c\busses\i2c-gpio.c (除了硬件函数不同,软件框架(i2c_adapter) 与普通的硬件i2c控制器驱动没有任何区别))
一个soc 上可能会有多个i2c 控制器,可以用设备树节点来描述一个i2c 控制器的硬件信息。
以imx6ull 为例:
在这里插入图片描述
在这里插入图片描述
设备树节点会由内核解析为一个platform_device 并注册到内核中,在i2c 控制器驱动中会注册platform_driver,当它们匹配时就会调用platform_driver->probe。(i2c 控制器驱动是由platform总线管理的,而非i2c总线)
在probe 函数中会获取设备树节点中i2c 控制器的硬件信息(irq、寄存器地址范围、时钟…),然后设置i2c 控制器硬件寄存器、注册中断等等;
另外在内核中i2c 控制器使用一个i2c_adapter 来描述,所以在probe 函数中需要创建、设置、注册i2c_adapter (只介绍i2c 框架,其它硬件设置代码忽略)。

如下为imx6ull 的i2c 控制器驱动 drivers\i2c\busses\i2c-imx.c:
创建一个struct imx_i2c_struct (nxp i2c驱动自己定义的结构体,其中包含i2c_adapter)
在这里插入图片描述
在这里插入图片描述
设置i2c_adapter 中的各个成员(nr、algo 为关键,每条i2c 总线都有一个唯一的number 来标识):
其中i2c_adapter->algo (struct i2c_algorithm)是最关键的,algo->master_xfer 就是i2c 控制器的传输函数。
在这里插入图片描述
可以使用master_xfer 向i2c 总线传输数据;
functionality 函数可以确定i2c 控制器驱动支持的功能。
在这里插入图片描述
I2C_FUNC_I2C:表示支持i2c 典型的传输功能;
I2C_FUNC_SMBUS_EMUL、I2C_FUNC_SMBUS_READ_BLOCK_DATA:smbus 总线相关的读写功能。
在这里插入图片描述
可以使用i2c_set_adapdata 来设置驱动数据。(i2c_adapter->dev->driver_data = i2c_imx(随便任何常用的数据结构),类似于platform_set_drvdata)
在这里插入图片描述
最后调用i2c_add_numbered_adapter 或i2c_add_adapter 向注册一个i2c_adapter。
(i2c_add_numbered_adapter 与i2c_add_adapter 的区别是,前者为驱动指定好了i2c_adapter->nr,后者指定nr = -1 由内核来分配合适的number)
在这里插入图片描述

i2c-core (i2c核心层)

i2c_add_adapter (i2c_adapter 注册函数)

kernel v4.1:drivers\i2c\i2c-core.c
kernel v5.4:drivers\i2c\i2c-core-base.c
i2c_add_adapter 会做如下操作:
1、注册i2c_adapter 会注册i2c_adapter->dev
2、遍历i2c 控制器节点下的所有子节点,为每个子节点创建一个i2c_client,并注册i2c_client。

(所谓的注册i2c_client 其实就是注册i2c_client->dev (device),将i2c_client->dev 添加到bus->p->klist_devices,注册过程中会查找bus->p->klist_drivers 中的device_driver 是否与其匹配,匹配则调用bus->probe,最终调用到i2c_driver->probe)
device 与device_driver 注册与匹配流程

of_alias_get_id 可以获取到设备树别名上的序号,使用别名序号作为i2c_adapter->nr。
如图中的节点 i2c1 别名为i2c0,nr 就是0。
在这里插入图片描述
nr 获取成功后调用__i2c_add_numbered_adapter 注册i2c_adapter。
在这里插入图片描述

__i2c_add_numbered_adapter
->i2c_register_adapter
i2c_register_adapter 首先检测i2c_bus_type 是否有效,判断i2c_adapter->name、i2c_adapter->algo 是否有效,无效直接返回失败。
在这里插入图片描述
设置i2c_adapter->dev.bus 为i2c_bus_type,注册i2c_adapter->dev。
在这里插入图片描述
重点关注of_i2c_register_devices(adap) ,它会遍历i2c 控制器节点下的所有子节点,根据节点生成i2c_client 并注册i2c_client->dev
在这里插入图片描述
i2c_adapter->dev->of_node 在前面的probe 中已经被设为了platform_device->dev->of_node,即i2c 控制器的设备树节点。
所以for_each_available_child_of_node 是遍历i2c 控制器的所有子节点。
在这里插入图片描述
在这里插入图片描述
i2c_new_device 以i2c_board_info 生成一个i2c_client,并注册它
在这里插入图片描述
在这里插入图片描述

i2c_transfer (i2c 传输函数)

为了给从机驱动 和i2c-dev 等驱动更容易的使用i2c 总线传输数据,i2c 核心层封装了一个所有i2c 控制器通用的i2c 传输函数——i2c_transfer。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
它其实就是调用的i2c 控制器驱动中实现的master_xfer 函数,i2c_adapter->algo->master_xfer。
i2c_transfer
->__i2c_transfer
在这里插入图片描述

i2c 设备-驱动-总线 模型

kernel v4.1:drivers\i2c\i2c-core.c
kernel v5.4:drivers\i2c\i2c-core-base.c

为了实现i2c 从设备的设备信息与驱动分离,Linux 内核引入了i2c 总线模型。
在i2c-core.c 的i2c_init 函数中注册了i2c_bus_type 总线模型。(postcore_initcall 在内核启动过程中会较早的调用,在注册i2c_driver、i2c_client时,总线模型已经被注册好了)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
i2c总线模型和platform 总线其实都是类似的,它们都基于device、device_driver 设备驱动模型
判断匹配的方式也是类似的,主要关注设备树匹配方式(比较compatible 属性) 和传统的id_table 匹配方式(比较i2c_device_id->name 与i2c_client->name 是否相等)。
在这里插入图片描述

不同的是i2c_bus_type 提供了probe 函数,会优先调用到bus_type->probe 即i2c_device_probe。
(platform_bus_type 没有提供bus_type->probe,调用的是device_driver->probe)
在i2c_device_probe 最终调用i2c_driver->probe 也就是从设备驱动中的probe 函数。
(这里获取了从设备节点的irq,一般从设备不需要使用irq,所以在从设备节点中没有描述irq,这里返回失败client->irq 被设为0)
在这里插入图片描述

i2c_add_driver (i2c_driver 注册函数)

内核中用一个i2c_driver 来表示一个i2c从设备驱动,用i2c_client 来描述一个i2c 从设备的硬件信息。
使用i2c_add_driver 可以注册一个struct i2c_driver,当它与i2c_client 匹配时就会调用i2c_driver->probe,那么看看它的注册过程是怎么样的。
i2c_add_driver 只是一个宏,实则调用的是i2c_register_driver。
在这里插入图片描述
在i2c_register_driver 中最关键的就是设置 i2c_driver->driver->bus 的总线类型,然后注册i2c_driver->driver(这里就是device 与device_driver 那一套注册过程了,可以参考device、device_driver 注册、匹配过程)。
在i2c 控制器驱动中调用i2c_add_adapter 注册i2c_adapter时,会搜索i2c 控制器dtb 节点下的所有子节点(每一个子节点都是一个从设备),为每个子节点创建一个i2c_client,并且注册i2c_client->dev (struct device)。
所以当i2c_driver 注册时,假如i2c_driver->driver 与某个i2c_client->dev 匹配完成,就会调用i2c_driver->probe 来执行从设备的驱动代码。
在这里插入图片描述
i2c_for_each_dev 会遍历所有存在的i2c_adapter,并未每个adapter 调用__process_new_driver (其实这一步没啥用,可以直接忽略)
__process_new_driver
->i2c_do_add_adapter
->i2c_detect
__process_new_driver 最终会调用到i2c_detect,它检查i2c_driver->detect 和i2c_driver->address_list 如果为空直接返回,观察很多从设备驱动中这两个成员都没有设置,所以这里直接返回(对于这些驱动来说i2c_for_each_dev(driver, __process_new_driver); 这句代码没啥用,可以直接忽略)。
在这里插入图片描述

i2c 从设备驱动框架

什么是i2c 从设备驱动:比如i2c 接口的rtc芯片、加密芯片、phy芯片等等,它们都需要通过i2c 总线来设置寄存器,所以i2c 从设备驱动主要就是调用i2c 读写函数来实现功能。

i2c 从设备驱动框架如下:
i2c 从设备驱动使用一个i2c_driver 来描述,所以在驱动中需要构建一个i2c_driver,并使用i2c_add_driver 注册它。
i2c 从设备的硬件信息使用一个i2c_client 来描述,在kernel v3.x 以上的版本会用设备树来描述从设备的硬件信息,最终会由内核解析成一个i2c_client 并向内核注册。
当i2c_client 与i2c_driver 匹配时,就会调用i2c_driver->probe,在probe函数中可以编写从设备驱动代码实现功能(调用i2c_transfer 等等函数设置寄存器,你也可以做其它事情任何事情,自由发挥)。

在这里插入图片描述
在这里插入图片描述

static const struct of_device_id of_match_ids_ap3216c[] = {
	{ .compatible = "lite-on,ap3216c",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id ap3216c_ids[] = {
	{ "ap3216c",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
	return 0;
}

static struct i2c_driver i2c_ap3216c_driver = {
	.driver = {
		.name = "ap3216c",
		.of_match_table = of_match_ids_ap3216c,
	},
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.id_table = ap3216c_ids,
};

static int __init i2c_driver_ap3216c_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return i2c_add_driver(&i2c_ap3216c_driver);
}


static void __exit i2c_driver_ap3216c_exit(void)
{
	i2c_del_driver(&i2c_ap3216c_driver);
}

module_init(i2c_driver_ap3216c_init);
module_exit(i2c_driver_ap3216c_exit);
MODULE_LICENSE("GPL");

比较好用的寄存器读写函数
在从设备驱动中你肯定需要读写从设备的寄存器,除了i2c_transfer 外还有其它的读写函数:
读取i2c 从设备寄存器1个字节,command 就是寄存器地址,返回寄存器1 个字节的值 (u8)
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
向寄存器写入1个字节的值。
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)
读取寄存器4个字节,返回2个字节 (u16)。
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
写入寄存器2个字节。
s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command,u16 value)
读取寄存器一块数据,返回数据的首地址(u8*)。
s32 i2c_smbus_read_block_data(const struct i2c_client *client, u8 command, u8 *values)
写入寄存器一块数据。
s32 i2c_smbus_write_block_data(const struct i2c_client *client, u8 command,u8 length, const u8 *values)

Linux 应用层读写i2c 数据

在Linux系统上,不仅可以在内核中使用 i2c 总线发送、接收数据,同时也支持应用层使用i2c 总线发送、接收。
如果在内核中使能了drivers/i2c/i2c-dev.c 配置,内核就会为每一个i2c 控制器生成一个/dev/i2c-x 的字符设备节点,使用文件IO向设备节点读写数据就可以完成i2c 数据的发送和接收。
在这里插入图片描述
在这里插入图片描述
应用层读写i2c 方法有两种:1、利用read、write函数读写;2、利用ioctl 函数读写。参考I2C编程应用开发-韦东山

ioctl的方法是使用数据包(struct i2c_msg)的方式来描述数据,使用时需要填充i2c_msg 各个成员,将i2c_msg 添加到i2c_rdwr_ioctl_data,然后调用ioctl 将i2c_rdwr_ioctl_data传入即可。
ioctl(fd, I2C_RDWR, &ioctl_data)

//读数据:需要构建两个i2c_msg,第一个msg 发送要写入的reg地址(写方向),第二个msg 读取reg中的值(读方向)
//写数据:只需要一个i2c_msg, msg->buf[0]=reg地址,msg->buf 其余空间存放要写入reg的数据
//include/linux/i2c-dev.h
struct i2c_rdwr_ioctl_data {
	struct i2c_msg __user *msgs;	/* pointers to i2c_msgs */
	__u32 nmsgs;			/* number of i2c_msgs */
};
//include/linux/i2c.h
struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */	//读标志位,写标志为0
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/		//要写和读的数据长度(buf长度)
	__u8 *buf;		/* pointer to msg data			*/
};

如下代码为应用层i2c总线读、写 i2c数据的方法(ioctl 方法)。

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>		//支持uint16_t 类型
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>


#define help	printf("Usage: \n read:./i2c_reg dev slave_addr reg_addr\n write:./i2c_reg dev slave_addr reg_addr value\n")

/*函数名:reg_write
**功能:向i2c 器件写数据
**参数:fd:i2c器件对应I2C控制器设备节点句柄
**		dev_addr:i2c从设备地址
**		reg_addr:要写入的寄存器地址
**		data_buf:要写入的数据buf
**		len:要写多少个字节。
**返回值:负数表示操作失败,其他为成功
*/
int reg_write(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
{
	int ret;
	unsigned char msg_buf[9];
	struct i2c_rdwr_ioctl_data data;
	struct i2c_msg messages;


	/* 1. 构建msg_buf*/
	/* 1.1. 将要操作的寄存器首地址赋给要进行I2C数据通信的首字节数据 */
	msg_buf[0] = reg_addr;
	/* 1.2. 将要向从机写的数据放入buf 中 */
    memcpy((void *) &msg_buf[1], data_buf, len);  //第1位之后是数据

	/* 2. 构建 struct i2c_msg messages */
	/* 2.1. 赋值从设备地址 */
	messages.addr = dev_addr;  

	/* 2.2. 赋值flags为本次I2C通信完成写功能 */
	messages.flags = 0;    

	/* 2.3. 赋值len为数据buf的长度 + 寄存器地址的数据长度 */
	messages.len = len+1;

	/* 2.4. 构建消息包的数据buf*/
	messages.buf = msg_buf;  

	/* 3. 构建struct i2c_rdwr_ioctl_data data */
	/* 3.1. 将准备好的消息包赋值给i2c_rdwr_ioctl_data中的msgs消息*/
	data.msgs = &messages;

	/* 3.2. 由于本次I2C通信只有写动作,所以消息数为1次 */
	data.nmsgs = 1;

	/* 4. 调用驱动层的读写组合的I2C数据传输 */
	if(ioctl(fd, I2C_RDWR, &data) < 0)
	{
		printf("I2C_RDWR err \n");
		return -1;
	}


	return 0;
}

/*函数名:reg_read
**功能:从i2c 器件读取数据
**参数:fd:从设备对应的I2C控制器设备节点的文件句柄
**		dev_addr:i2c从设备地址
**		reg_addr:要读取的寄存器地址
**		data_buf: 读数据的buf
**		len:要读多少个字节。
**返回值:负数表示操作失败,其他为成功
*/
int reg_read(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
{
	int ret;

	unsigned char msg_buf[9];
	struct i2c_rdwr_ioctl_data data;

	struct i2c_msg messages[2];

	/* 1. 构建 struct i2c_msg messages */
	/* 1.1. 构建第一条消息 messages[0] */
	/* 1.1.1. 赋值I2C从设备地址 */
 	messages[0].addr = dev_addr;  
 
 	/* 1.1.2. 赋值flags为本次I2C通信完成写动作 */
 	messages[0].flags = 0;    
 
 	/* 1.1.3. 赋值len为eeprom寄存器地址的数据长度是1 */
 	messages[0].len = 1;
 
 	/* 1.1.4. 本次写动作的数据是要读读取的寄存器首地址*/
 	messages[0].buf = &reg_addr;  
 	
 	/* 1.2. 构建第二条消息 messages[1] */
 	/* 1.2.1. 赋值I2C从设备地址 */
 	messages[1].addr = dev_addr;  
 
 	/* 1.1.2. 赋值flags为本次I2C通信完成读动作 */
 	messages[1].flags = I2C_M_RD;    
 
 	/* 1.1.3. 赋值len为要读取寄存器数据长度len */
 	messages[1].len = len;
 
 	/* 1.1.4. 本次读动作的数据要存放的buf位置*/
 	messages[1].buf = data_buf; 
 
 	/* 2. 构建struct i2c_rdwr_ioctl_data data */
 	/* 2.1. 将准备好的消息包赋值给i2c_rdwr_ioctl_data中的msgs消息*/
 	data.msgs = messages;
 
 	/* 2.2. 由于本次I2C通信既有写动作也有读动作,所以消息数为2次 */
 	data.nmsgs = 2;
 
 	/* 3. 调用驱动层的读写组合的I2C数据传输 */
 	if(ioctl(fd, I2C_RDWR, &data) < 0)
 	{
 		printf("I2C_RDWR err \n");
 		return -1;
 	}
 
 	return 0;
}



int main(int argc,char *argv[])
{
	int fd,ret;
	uint16_t slave_addr,reg_addr,value;
	struct i2c_rdwr_ioctl_data data;
	struct i2c_msg messages;
	unsigned char data_buf[1];

	if(argc < 4  || !strcmp(argv[1],"-h"))
	{
		help;
		return -1;
	}	

	slave_addr = (uint16_t)strtoul(argv[2], NULL, 0);
	reg_addr = (uint16_t)strtoul(argv[3], NULL, 0);
	if(argc == 5)
		value = (uint16_t)strtoul(argv[4], NULL, 0);


	fd = open(argv[1],O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	
	if(argc ==4)
	{
		ret = reg_read(fd,slave_addr,reg_addr,data_buf,1);	
		if(ret < 0)
			printf("read error\n");
		printf("reg 0x%x value 0x%x\n",reg_addr,data_buf[0]);
	}else{
		data_buf[0] = value & 0xff;
		ret = reg_write(fd,slave_addr,reg_addr,data_buf,1);
		if(ret < 0)
			printf("write error\n");
		printf("write succeed\n");
	}
	

	close(fd);
	return 0;
}

i2c-dev 驱动分析

driver/i2c/i2c-dev.c 即我们在应用层使用文件IO 访问/dev/i2c-x 设备节点,完成i2c 通信的底层代码。
在这里插入图片描述
观察/dev/i2c-x 的特性,毫无疑问的是它们都是字符设备,拥有相同的主设备号、依次递增的次设备号。每一个设备节点对应一个i2c 控制器。
那么猜测在i2c-dev.c 中就会为每个i2c 控制器注册一个字符设备、创建设备节点,实现file_operations。在应用层调用open、read、write… 时自然就会调用到file_operations 中的open、read、write… 。

应用层可以调用read、write、ioctl 来进行i2c 总线数据传输—— 所以在底层read、write、ioctl 就会实现i2c 总线的数据传输,将应用层下发的数据进行硬件的发送操作。

接下来实际看看i2c-dev.c 到底做了什么。

i2c-dev 注册

Linux 字符设备详解
首先从入口函数开始看
调用register_chrdev_region 申请了一批设备号。(major = I2C_MAJOR(89),baseminor = 0,count 是I2C_MINORS)
创建类 struct class。 (i2c-x 设备节点对应的所有struct device 都属于同一个类,i2c_dev_class)
i2c_for_each_dev 会为每个存在的 i2c_adapter,注册一个cdev 和 device(设备节点)。
在这里插入图片描述
i2c_for_each_dev
->bus_for_each_dev
在bus_for_each_dev 函数中会循环的调用传入的回调函数fn,即i2cdev_attach_adapter。
在这里插入图片描述
i2cdev_attach_adapter 会创建cdev (保存在i2c_adapter->i2c_dev->cdev)、device(保存在i2c_adapter->i2c_dev->dev),并初始化它们(cdev->ops、device->dev_t、device->class(这几个设备节点都属于同一类)),最后向内核注册cdev 和device。
完成了字符设备的注册和设备节点创建。
(minor 来自i2c_adapter->nr,device->name 也按照i2c-%d——i2c_adapter->nr 来定义)
在这里插入图片描述
在这里插入图片描述

i2c-dev open

了解了i2c_dev.c 的注册过程,已经注册好了字符设备(cdev) 以及设备节点 (device)。
现在就可以用open、read、write… 来访问底层的file_operations 啦,那么就来看看底层的open、read、write… 是如何实现的。
i2c-dev file_operations
每一个i2c-x 节点都对应一个i2c控制器,打开一个节点就是指定了要使用的控制器。
在这里插入图片描述
那么open 是怎么找对应的i2c_adapter 呢?
利用minor,前面注册的过程中讲过minor 和i2c_adapter->nr 是相等的,而每个i2c_adapter 的nr都是不同的。所以可以利用nr找到 i2c_adapter。
调用i2c_get_adapter(minor) 找到对应的i2c_adapter 。

传输数据,还要确定一个从设备,给谁发啊?
但是对于应用层的i2c 设备驱动来说,底层是不知道从设备是谁的。所以在open中新创建一个i2c_client——管他是谁,反正都用这个来描述。
然后将i2c_client->adapter 设置为获取到的i2c_adapter,表示i2c_client 挂在这个控制器下面(绑定好了i2c_client 与i2c_adapter)。
最后设置i2c_client 为file的私有数据,之后的read、write… 函数都可以从私有数据获取到client。
在这里插入图片描述
利用nr 从i2c_adapter_idr 中找到对应的i2c_adapter。(可以把i2c_adapter_idr 想象成一个链表,里面包含了内核中所有的i2c_adapter,并且它们的nr都是唯一的)
在这里插入图片描述

ioctl

在前面的应用示例程序中,用ioctl 来读写i2c 总线。
其实它不仅可以用ioctl 来读写,还可以用read/write 来读写,但是在read/write之前需要设置i2c_client->addr(从设备地址),在open中注册的这个空的,并没有设置地址。
设置从设备地址也是调用ioctl 来完成的ioctl(fd,I2C_SLAVE,slave_addr)ioctl(fd,I2C_SLAVE_FORCE,slave_addr)
在这里插入图片描述
ioclt 读数据、写数据的实现ioctl(fd,I2C_RDWR,data)
应用层传入的i2c_rdwr_ioctl_data 从应用空间拷贝至内核空间。同时data->msgs 这里只是一个指针,真正的i2c_msg数据还在应用空间内存里,所以需要再一次将i2c_msg 数据从应用空间拷贝至内核空间(memdup_user)。
在这里插入图片描述
memdup_user 就是申请一段内存空间,然后调用copy_from_user 将数据拷贝到内核内存中。
在这里插入图片描述
继续跟踪i2cdev_ioctl_rdwr,它调用i2c_transfer 向i2c总线传输数据。如果是读的话,还要把读到的数据返回给应用层。
(前面说过i2c_transfer 是调用i2c_adapter->algo->master_xfer)
在这里插入图片描述

read

read 是调用i2c_master_recv 来读取数据,其实它的实现也是调用了i2c_transfer()。

在这里插入图片描述

i2c_transfer_buffer_flags 中调用了i2c_transfer 向i2c总线传输数据。
为什么这里只要一个msg呢?很简单,read只用来读,要读的reg_addr 会调用write 先一步发送出去,所以这里只需要接收数据的msg就够了。
在这里插入图片描述

write

write 与read 基本类似,只不过读改成写,调用i2c_master_send 向i2c 总线写数据
在这里插入图片描述
依旧调用i2c_transfer_buffer_flags ,与接收不同的是,flag 改为写。
在这里插入图片描述

close

close 对应的底层函数是release,在release中与open的操作相反——释放open 创建的i2c_client,清除私有数据。
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值