提示基于之前I2C 基础知识了解设备树配置、配置GPIO、I2C驱动框架的了解这里在此基础上进行I2C 通信。文章目录前言一、参考资料二、知识点分析搞清楚需求-IIC驱动程序IIC 用到的apii2c_master_send / i2c_master_recvi2c_transfer三大函数核心区别对照结构体-i2c_msg-i2c_clientIIC 读操作IIC 写操作IIC 调用三、驱动源码实现-实现IIC读写四、驱动验证总结前言在之前I2C基础上进行I2C 通信打通通信知识点。一、参考资料之前基础笔记驱动-I2C-客户端代码编写-编写设备树Linux驱动-i2c 驱动框架编写Linux驱动-IIC完善FT5X06设备节点和驱动关联内容Linux驱动开发—内核I2C驱动详解IIC驱动二、知识点分析搞清楚需求-IIC驱动程序如上我们最终目的是什么就是在之前IIC 驱动框架的基础上进行IIC通讯让IIC工作起来。那么如何验证在之前程序基础上进行了IIC 读写操作并调用读写来验证IIC 。IIC 用到的api这里仅从IIC 通讯案例中涉及到的api 来进行讲解。i2c_master_send / i2c_master_recv路径kernel/include/linux/i2c.h具体源码如下/** * i2c_master_send - issue a single I2C message in master transmit mode * client: Handle to slave device * buf: Data that will be written to the slave * count: How many bytes to write, must be less than 64k since msg.len is u16 * * Returns negative errno, or else the number of bytes written. */staticinlineinti2c_master_send(conststruct i2c_client*client,constchar*buf,intcount){returni2c_transfer_buffer_flags(client,(char*)buf,count,0);};/** * i2c_master_recv - issue a single I2C message in master receive mode * client: Handle to slave device * buf: Where to store data read from slave * count: How many bytes to read, must be less than 64k since msg.len is u16 * * Returns negative errno, or else the number of bytes read. */staticinlineinti2c_master_recv(conststruct i2c_client*client,char*buf,intcount){returni2c_transfer_buffer_flags(client,buf,count,I2C_M_RD);};其实就是单收发报文然后都调用了i2c_transfer_buffer_flagsi2c_transfer如上分析i2c_master_send / i2c_master_recv都是指向i2c_transfer_buffer_flags那么我们看看i2c_transfer_buffer_flags方法函数源码如下路径kernel/drivers/i2c/i2c-core-base.c/** * i2c_transfer_buffer_flags - issue a single I2C message transferring data * to/from a buffer * client: Handle to slave device * buf: Where the data is stored * count: How many bytes to transfer, must be less than 64k since msg.len is u16 * flags: The flags to be used for the message, e.g. I2C_M_RD for reads * * Returns negative errno, or else the number of bytes transferred. */inti2c_transfer_buffer_flags(conststruct i2c_client*client,char*buf,intcount,u16 flags){intret;struct i2c_msg msg{.addrclient-addr,.flagsflags|(client-flagsI2C_M_TEN),.lencount,.bufbuf,};reti2c_transfer(client-adapter,msg,1);/* * If everything went ok (i.e. 1 msg transferred), return #bytes * transferred, else error code. */return(ret1)?count:ret;}EXPORT_SYMBOL(i2c_transfer_buffer_flags);继续看i2c_transfer源码如下/** * i2c_transfer - execute a single or combined I2C message * adap: Handle to I2C bus * msgs: One or more messages to execute before STOP is issued to * terminate the operation; each message begins with a START. * num: Number of messages to be executed. * * Returns negative errno, else the number of messages executed. * * Note that there is no requirement that each message be sent to * the same slave address, although that is the most common model. */inti2c_transfer(struct i2c_adapter*adap,struct i2c_msg*msgs,intnum){intret;/* REVISIT the fault reporting model here is weak: * * - When we get an error after receiving N bytes from a slave, * there is no way to report N. * * - When we get a NAK after transmitting N bytes to a slave, * there is no way to report N ... or to let the master * continue executing the rest of this combined message, if * thats the appropriate response. * * - When for example num is two and we successfully complete * the first message but get an error part way through the * second, its unclear whether that should be reported as * one (discarding status on the second message) or errno * (discarding status on the first one). */if(adap-algo-master_xfer){#ifdefDEBUGfor(ret0;retnum;ret){dev_dbg(adap-dev,master_xfer[%d] %c, addr0x%02x, len%d%s\n,ret,(msgs[ret].flagsI2C_M_RD)?R:W,msgs[ret].addr,msgs[ret].len,(msgs[ret].flagsI2C_M_RECV_LEN)?:);}#endifif(in_atomic()||irqs_disabled()){reti2c_trylock_bus(adap,I2C_LOCK_SEGMENT);if(!ret)/* I2C activity is ongoing. */return-EAGAIN;}else{i2c_lock_bus(adap,I2C_LOCK_SEGMENT);}ret__i2c_transfer(adap,msgs,num);i2c_unlock_bus(adap,I2C_LOCK_SEGMENT);returnret;}else{dev_dbg(adap-dev,I2C level transfers not supported\n);return-EOPNOTSUPP;}}EXPORT_SYMBOL(i2c_transfer);三大函数核心区别对照综合上面源码看注释就可以明白区别这里直接对照表如下对比维度i2c_master_send / i2c_master_recvi2c_transfer支持消息数量仅单条 i2c_msg支持多条连续 msg 数组总线时序单次传输结束必发 STOP断开总线多条消息中间无 STOP连续占用总线传入句柄struct i2c_client设备struct i2c_adapter硬件总线读写控制固定纯写 / 纯读无法混合每条 msg 可独立配置读写标志使用门槛简单新手友好稍复杂需要手动构造 i2c_msg寄存器读取❌ 无法实现✅ 唯一能实现先写后读时序典型场景简单单发、单纯只读触摸 / 传感器寄存器读写、多段连续 I2C 报文所以 大多数场景我们用的是i2c_transfer方法只是需要自己去拼接i2c_msg结构体。struct i2c_msg msg{.addrclient-addr,.flagsflags|(client-flagsI2C_M_TEN),.lencount,.bufbuf,};结构体-i2c_msg-i2c_client路径include/uapi/linux/i2c.hi2c_msg这个是永远传输i2c 数据读写中会用到可以理解为通信介质。struct i2c_msg{__u16 addr;/* slave address */__u16 flags;#defineI2C_M_RD0x0001/* read data, from slave to master *//* I2C_M_RD is guaranteed to be 0x0001! */#defineI2C_M_TEN0x0010/* this is a ten bit chip address */#defineI2C_M_DMA_SAFE0x0200/* the buffer of this message is DMA safe *//* makes only sense in kernelspace *//* userspace buffers are copied anyway */#defineI2C_M_RECV_LEN0x0400/* length will be first received byte */#defineI2C_M_NO_RD_ACK0x0800/* if I2C_FUNC_PROTOCOL_MANGLING */#defineI2C_M_IGNORE_NAK0x1000/* if I2C_FUNC_PROTOCOL_MANGLING */#defineI2C_M_REV_DIR_ADDR0x2000/* if I2C_FUNC_PROTOCOL_MANGLING */#defineI2C_M_NOSTART0x4000/* if I2C_FUNC_NOSTART */#defineI2C_M_STOP0x8000/* if I2C_FUNC_PROTOCOL_MANGLING */__u16 len;/* msg length */__u8*buf;/* pointer to msg data */};具体核心参数说明如下struct i2c_msg{__u16 addr;// I2C从设备7位地址__u16 flags;// 传输控制标志位多个宏按位或组合__u16 len;// 当前这条消息要传输的字节数量__u8*buf;// 数据缓冲区指针存放收发的字节数据};i2c_client结构体定义如下struct i2c_client{unsignedshortflags;/* div., see below */unsignedshortaddr;/* chip address - NOTE: 7bit *//* addresses are stored in the *//* _LOWER_ 7 bits */charname[I2C_NAME_SIZE];struct i2c_adapter*adapter;/* the adapter we sit on */struct device dev;/* the device structure */intinit_irq;/* irq set at initialization */intirq;/* irq issued by device */struct list_head detected;#ifIS_ENABLED(CONFIG_I2C_SLAVE)i2c_slave_cb_t slave_cb;/* callback for slave mode */#endif};IIC 读操作//i2c 读函数intft5x06_read_reg(u8 reg_addr){u8 data;//i2c 通讯以最小8位为最小单位// i2c_transfer标准读写流程先写寄存器地址再读数据struct i2c_msg msgs[2]{[0]{.addrft5x06_client-addr,// I2C从机地址0x38.flags0,// 标志0 I2C写操作.lensizeof(reg_addr),// 长度1字节寄存器地址.bufreg_addr,// 缓冲区要读取的寄存器号},[1]{.addrft5x06_client-addr,.flagsI2C_M_RD,// I2C_M_RD 读操作标志.lensizeof(data),// 读取1字节返回值.bufdata,// 读到的数据存入data},};// i2c_transfer发起一组I2C消息返回成功执行的msg数量// ARRAY_SIZE(msgs)2必须两条消息都执行成功才算读取正常if(i2c_transfer(ft5x06_client-adapter,msgs,ARRAY_SIZE(msgs))!ARRAY_SIZE(msgs)){return-EIO;// 读写失败返回IO错误码}returndata;// 返回寄存器读到的值}那么构造从机地址 addr从哪里来 在 方法int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)也就是 probe 函数中的i2c_client指针变量中去取所以在程序中定义全局变量struct i2c_client *ft5x06_client;然后赋值intft5x06_probe(struct i2c_client*client,conststruct i2c_device_id*id){.....ft5x06_clientclient;.....IIC 写操作写操作其实跟简单对比读操作来说voidft5x06_write_reg(u8 reg_addr,u8 data,u16 len){u8 buff[256];// 本地缓冲区最大255字节数据1字节寄存器地址struct i2c_msg msgs[]{[0]{.addrft5x06_client-addr,.flags0,// 纯写操作.lenlen1,// 总长度 寄存器地址1字节 有效数据长度.bufbuff,},};buff[0]reg_addr;// 缓冲区首字节寄存器地址memcpy(buff[1],data,len);// 后续空间拷贝待写入数据// 发起I2C写传输if(i2c_transfer(ft5x06_client-adapter,msgs,ARRAY_SIZE(msgs))!ARRAY_SIZE(msgs)){return;// 写入失败直接退出无错误返回、无打印}}IIC 调用接下来就开始调用了如下intft5x06_probe(struct i2c_client*client,conststruct i2c_device_id*id){intret0;intvalue0;ft5x06_clientclient;....................ft5x06_write_reg(0x80,0x4b,1);valueft5x06_read_reg(0x80);printk(reg 0x80 is %#x\n,value);return0;}有人会问未删除传递寄存器地址0x80哪里来的 那当然是数据手册里面找的不是瞎写的。三、驱动源码实现-实现IIC读写#includelinux/init.h#includelinux/module.h#includelinux/i2c.h#includelinux/of_device.h#includelinux/gpio/consumer.h#includelinux/delay.h#includelinux/interrupt.hstruct gpio_desc*reset_gpio;struct gpio_desc*irq_gpio;struct i2c_client*ft5x06_client;//i2c 读函数intft5x06_read_reg(u8 reg_addr){u8 data;//i2c 通讯以最小8位为最小单位// i2c_transfer标准读写流程先写寄存器地址再读数据struct i2c_msg msgs[2]{[0]{.addrft5x06_client-addr,// I2C从机地址0x38.flags0,// 标志0 I2C写操作.lensizeof(reg_addr),// 长度1字节寄存器地址.bufreg_addr,// 缓冲区要读取的寄存器号},[1]{.addrft5x06_client-addr,.flagsI2C_M_RD,// I2C_M_RD 读操作标志.lensizeof(data),// 读取1字节返回值.bufdata,// 读到的数据存入data},};// i2c_transfer发起一组I2C消息返回成功执行的msg数量// ARRAY_SIZE(msgs)2必须两条消息都执行成功才算读取正常if(i2c_transfer(ft5x06_client-adapter,msgs,ARRAY_SIZE(msgs))!ARRAY_SIZE(msgs)){return-EIO;// 读写失败返回IO错误码}returndata;// 返回寄存器读到的值}// // 为什么一个是u8 类型一个是u16类型因为u8是用来i2c通讯的u16是用來賦值給i2c_msg 的voidft5x06_write_reg(u8 reg_addr,u8 data,u16 len){u8 buff[256];// 本地缓冲区最大255字节数据1字节寄存器地址就是要struct i2c_msg msgs[]{[0]{.addrft5x06_client-addr,.flags0,// 纯写操作.lenlen1,// 总长度 寄存器地址1字节 有效数据长度.bufbuff,},};buff[0]reg_addr;// 缓冲区首字节寄存器地址memcpy(buff[1],data,len);// 后续空间拷贝待写入数据// 发起I2C写传输if(i2c_transfer(ft5x06_client-adapter,msgs,ARRAY_SIZE(msgs))!ARRAY_SIZE(msgs)){return;// 写入失败直接退出无错误返回、无打印}}irqreturn_tft5x06_handler(intirq,void*args){printk(ft5x06_handler\n);returnIRQ_RETVAL(IRQ_HANDLED);}intft5x06_probe(struct i2c_client*client,conststruct i2c_device_id*id){intret0;intvalue0;ft5x06_clientclient;printk(KERN_INFO###### This is ft5x06 probe ######\n);// 获取复位GPIO描述符reset_gpiogpiod_get_optional(client-dev,reset,0);// 第二个参数是属性reset-gpios前缀resetif(reset_gpioNULL){printk(gpiod_get_optional reset_gpio error\n);return-1;}// 获取中断GPIO描述符irq_gpiogpiod_get_optional(client-dev,interrupts,0);// 第二个参数是属性interrupts-gpio前缀interruptsif(irq_gpioNULL){printk(gpiod_get_optional irq_gpio error\n);return-1;}gpiod_direction_output(reset_gpio,0);ssleep(5);gpiod_direction_output(reset_gpio,1);retrequest_irq(client-irq,ft5x06_handler,IRQF_TRIGGER_FALLING|IRQF_ONESHOT,ft5x06_irq,NULL);if(ret0){printk(request_irq request error\n);return-1;}ft5x06_write_reg(0x80,0x4b,1);valueft5x06_read_reg(0x80);printk(reg 0x80 is %#x\n,value);return0;}intft5x06_remove(struct i2c_client*client){printk(KERN_INFO###### This is ft5x06_remove ######\n);return0;}staticconststruct of_device_id ft5x06_id[]{{.compatiblemy-ft5x06},{/* Sentinel */},};// 关键补充MODULE_DEVICE_TABLE(of,ft5x06_id);staticstruct i2c_driver ft5x06_driver{.driver{.ownerTHIS_MODULE,.namemy-ft5x06,.of_match_tableft5x06_id,},.probeft5x06_probe,.removeft5x06_remove,};staticint__initft5x06_driver_init(void){intret;reti2c_add_driver(ft5x06_driver);if(ret0){printk(KERN_ERRi2c_add_driver failed ret%d\n,ret);returnret;}printk(KERN_INFO###### ft5x06 driver register success ######\n);return0;}staticvoid__exitft5x06_driver_exit(void){i2c_del_driver(ft5x06_driver);printk(KERN_INFO###### ft5x06 driver unregister ######\n);}module_init(ft5x06_driver_init);module_exit(ft5x06_driver_exit);MODULE_LICENSE(GPL);四、驱动验证编译后make 编译.ko 文件然后加载驱动验证结果如下总结这里其实就是一个简单的IIC通讯还没有涉及到实际的功能就是验证IIC通不通读写寄存器这里重点还是理解思路、对于读写操作和api 操作要熟悉。