陵水网站建设装修设计公司,wordpress 文章打赏,网络营销主要传播渠道,网易企业邮箱收件服务器主机名i2c 硬件协议
Linux 应用层读写i2c 数据
在Linux系统上#xff0c;不仅可以在内核中使用 i2c 总线发送、接收数据#xff0c;同时也支持应用层使用i2c 总线发送、接收。 如果在内核中使能了drivers/i2c/i2c-dev.c 配置#xff0c;内核就会为每一个i2c 控制器生成一个/dev/…i2c 硬件协议
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 器件写数据
**参数fdi2c器件对应I2C控制器设备节点句柄
** dev_addri2c从设备地址
** 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 len1;/* 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_addri2c从设备地址
** 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 子系统中的重要结构体 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 从设备进行通信我们需要确定以下几点信息
从设备挂在哪条i2c 总线上。从设备的器件地址是多少。
所以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 len1; //数据长度器件地址长度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-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 0count 是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-nrdevice-name 也按照i2c-%d——i2c_adapter-nr 来定义
i2c-dev open
了解了i2c_dev.c 的注册过程已经注册好了字符设备(cdev) 以及设备节点 (device)。 现在就可以用open、read、write… 来访问底层的file_operations 啦那么就来看看底层的open、read、write… 是如何实现的。 每一个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清除私有数据。
I2C驱动程序的层次 i2c 驱动主要分为i2c 控制器驱动包括i2c 控制器驱动 i2c 核心层、i2c 从机驱动 和i2c 字符设备驱动。 i2c控制器驱动 初始化i2c 控制器硬件实现i2c控制器的传输功能(master_xfer 函数)根据核心层提供的方法注册i2c 控制器(i2c_adapter)。 i2c 核心层 提供下层i2c控制器的管理方法i2c_adapter 注册方法向上层给出统一的i2c 传输方法i2c_transferLinux需要兼容多个平台的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_DATAsmbus 总线相关的读写功能。 可以使用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.1drivers\i2c\i2c-core.c kernel v5.4drivers\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 别名为i2c0nr 就是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.1drivers\i2c\i2c-core.c kernel v5.4drivers\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)