写点什么

Linux 下 IIC 驱动编写,介绍 IIC 子系统框架的使用

作者:DS小龙哥
  • 2022-10-19
    重庆
  • 本文字数:9668 字

    阅读完需:约 1 分钟

一、IIC 协议介绍

说起 IIC,搞单片机,嵌入式的那肯定是接触的比较多的。串口、IIC、SPI 这 3 个协议在单片机阶段应该是用比较多的,很多的外设模块,芯片都是串口、IIC、SPI 等协议与主控芯片进行通信,完成逻辑开发。 在 Linux 系统驱动层使用 IIC 其实本质上与单片机没什么差别的,最终反正是和芯片进行交互通信,本身 IIC 协议并不难,但是在 Linux 下为了标准化,加了很多框架导致理解上就觉得复杂,听起来 xxx 子系统就很神秘高大上,其实这是因为对 Linux 驱动框架不熟悉,只要把框架流程能梳理清楚,那么整体就变得简单了。


IIC 协议在物理连接上比较简单,只有两条线: SDA(串行数据线)和 SCL(串行时钟线)  ,比较省 IO 口。  其中的 SDA 数据线是双向的,根据时序发送数据和接收数据时主机会对应的切换自身的输入输出模式。 SDA 数据线是传输数据信号的,时钟线是用来控制什么时候发送数据信号,搞懂规则两边设备就可以通过 IIC 协议进行通讯。 从连线上可以得知,IIC 属于串行协议,每一次的时钟信号只能单方面发送一个字节数据。


IIC 协议又称为 IIC 总线。   为什么叫总线? 这个可不是随便乱叫的。 除了 IIC 总线外,常见的总线还有 SPI 总线,USB 总线等等。  如果对 IIC,SPI 不了解,可能对总线这个概念不清楚,但 USB 大家应该是都见过的,如果电脑的 USB 口不够用,我们可以买集线器扩展 USB 接口,将集线器插在电脑 USB 口之后,集线器上面可以插入多个 USB 设备:比如,USB 鼠标,USB 键盘,U 盘,USB-网卡等等。 对于电脑而言,它怎么知道自己在和哪一个 USB 设备通讯?如何区分的?这就是依靠地址,每个设备都有自己的设备地址进行匹配。  所以,从这里可以看出, 可以挂载多个设备的协议才能叫总线, 通过总线就可以省下不少的 IO 口。


IIC 协议既然叫 IIC 总线上,所以 IIC 总线上也可以挂载多个设备,区分设备的办法也是依靠地址;发起 IIC 协议的单片机称为主机,把挂接在总线上的其他设备都作为从设备。


I2C 总线数据传输速率在标准模式下可达 100kbit/s,快速模式下可达 400kbit/s,高速模式下可达 3.4Mbit/s。一般通过 I2C 总线接口可编程时钟来实现传输速率的调整。I2C 总线上的主设备与从设备之间以字节(8 位)为单位进行双向的数据传。

二、IIC 总线协议

学习 IIC 总线主要是要搞清楚几个信号:  起始信号,停止信号,应答信号,非应答信号。


在空闲状态下(默认情况下):SCL 和 SDA 都保持着高电平。


起始信号: 总线在空闲状态时,SCL 和 SDA 都保持着高电平,当 SCL 为高电平而 SDA 由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他 2C 器件无法访问总线。


停止信号: 当 SCL 为高而 SDA 由低到高的跳变,表示产生一个停止条件。


应答信号: 每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为低,则 表示一个应


答信号。


非应答信号: 每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为高,则表示一个应


答信号。


注意: 起始和结束信号总是由主设备产生。


IIC 总线的数据传输时序图:



数据位传输时序图:



应答信号时序图:



开始信号与停止信号时序图:



根据上面的时序,可以通过 IO 口模拟出 IIC 时序,下面贴出 IIC 模拟时序代码:


 
复制代码

三、IIC 子系统

3.1 框架介绍

在 Linux 下为了方便驱动的规范,驱动的维护,移植等多种原因,Linux 内核设计一套 IIC 子系统框架,方便大家按照规范编写驱动,只要是按照标准框架写的驱动,在任何 Linux 系统上都可以跑(只要底层适配好了)。 这对驱动的移植而言就很方便。


IIC 总线框架是基于平台总线驱动模型设计的,将驱动端、设备端分开。 设备端完成总线信息配置,设备信息配置;而驱动端完成与底层 IIC 设备通信,完成数据交互。


从整体框架来讲,IIC 子系统最底层的是 IIC 适配器框架,IIC 适配器一般是由厂家提供,一般 CPU 支持几个 IIC 总线接口,就注册几个适配器;这个适配器框架里,提供了 CPU 的 IIC 总线寄存器操作代码,也就是适配器提供最底层的函数(读写字节)。  用户拿到 Linux 内核之后,需要针对某一个芯片(例如:eeprom-AT24C02)编写驱动时,首先要根据 AT24C02 与 CPU 上的 IIC 总线接线情况,在通过适配器接口获取这个总线的指针(这个指针就是指向这个 IIC 总线的适配器结构),然后注册设备端,当 IIC 驱动端也注册成功之后,设备端的信息会传递给驱动端,驱动就得到了这个 IIC 总线对应的适配器结构。 接下来在 IIC 驱动层调用了 Linux 内核提供的 IIC 读写函数与设备通信时,这些函数的参数里就要传入适配器指针。  (因为通过这个适配器指针可以调用到 IIC 总线底层的读写函数,最终完成设备通信)。


大致框图如下:



下面介绍 IIC 总线提供的一些函数。



3.2 设备层相关函数

设备层相关函数:1)获取 i2c_adapter 的内存地址struct i2c_adapter *i2c_get_adapter(int nr)作用:1)获取 i2c_adapter 结构地址
2)增加 i2c_adapter 结构的引用计数,防止使用过程中被移除。 nr:就是适配器的总线编号: i2c0, --- 就是 0 i2c1, --- 就是 1返回:指针适配器结构的首地址,失败返回 NULL。
3)减少 i2c_adapter 引用计数使用 i2c_get_adapter 后都需要使用这个函数。i2c_put_adapter(struct i2c_adapter *adap)
4)注册 I2C 设备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))功能:向内核注册一个 I2C 设备参数: adap :i2c_client 所依赖的适配器指针,adap 则通过 i2c_get_adapter 函数获取 info:i2c 设备的描述信息addr_list:i2c 设备可能出现的地址表,是一个数组。probe:探测到 i2c 设备时候会执行回调函数,一般是 NULL。
复制代码

3.3 驱动层相关函数




注册 iic 驱动int i2c_add_driver(struct i2c_driver *driver) 注册 I2C 设备驱动,driver 是已经填充好的 struct i2c_driver 结构指针一般写在模块的初始化代码。
注销 iic 驱动void i2c_del_driver(struct i2c_driver *driver)注销 I2C 设备驱动,一般写在模块的出口处。



I2C 核心层提供的标准发收函数:int i2c_master_send(struct i2c_client *client, const char *buf , int count) //发送函数功能:发送数据给真正的硬件设备。 参数: client:指针 I2C 设备结构的指针。 buf:发送的数据指针 count:发送的字符数量返回发送的字节数,失败返回-1。注意:此函数只是实现标准 IIC 的写协议,不代表具体器件写协议。如:要写数据给 AT24C02 ,从内部地址 10 开始写,应该怎么写。


标准的读取数据函数 int i2c_master_recv(struct i2c_client *client, char *buf ,int count) 功能:从硬件中读取数据参数: client:指针 I2C 设备结构的指针。 buf:存放数据指针 count:要读的字节数量 注意:此函数只是实现标准 IIC 的读协议,不代表具体器件读协议。比如,对 24c02 进行读操作,先使用 i2c_master_send 发送内部地址,然后调用 i2c_master_recv 函数读数据。

收发一体函数int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)功能:这个函数是 I2C 传输函数,收发一体的函数。参数:adap:指针 I2C 适配器结构的指针,这个指针是使用 i2c_client 中的 adapter 指针。 msgs:存放数据指针 num: 要传输的 struct i2c_msg 数量。


注销 IIC 适配器int i2c_del_adapter(struct i2c_adapter *adap)
获取适配器struct i2c_adapter *i2c_get_adapter(int nr)功能: 根据注册时绑定的总线编号,获取 IIC 适配器结构体参数: nr 总线编号返回值: IIC 适配器结构

static int i2c_register_adapter(struct i2c_adapter *adap) //注册 IIC 适配器,该函数在下面两个函数里已经调用int i2c_add_adapter(struct i2c_adapter *adapter) //声明并注册 i2c 适配器,使用动态总线编号int i2c_add_numbered_adapter(struct i2c_adapter *adap) //声明并注册 i2c 适配器,使用静态总线编号




以下是 smbus 总线的操作函数,smbus 是系统管理总线,可以看成是 IIC 总线的一个子集,它的规范大部分都是基于 I2C 标准。所以,大部分的 IIC 器件也可以使用 smbus 总线来操作。非常合适操作那些有内部地址的器件。
单字节读函数s32 i2c_smbus_read_byte_data(const struct i2c_client *client,u8 command):功能:指定地址的单字节读参数:client 结构指针。command:内部地址。返回值:读到的数据,失败返回负数。

单字节写函数s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)功能:指定地址的单字节写入数据参数:client 结构指针。command:内部地址value:要写入的数据返回值:写如的数据,失败返回负数。

读指定长函数如果器件存在页写问题,建议使用这个函数,循环操作。s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values)功能:从指定地址开始读取指定长度的数据。参数:client 结构指针command:内部地址length:要读的数据长度value:存放数据的指针返回值:读到的数据,失败返回负数。

写指定长度函数s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values)功能:从指定地址开始读取指定长度的数据参数:client 结构指针command:内部地址length:要写的数据长度value:源数据指针返回值:写入的数据,失败返回负数。
复制代码

四、驱动代码案例

4.1  IIC 适配器注册案例代码

介绍如何自己注册 IIC 适配器。


#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/i2c.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>#include <linux/workqueue.h>#include <linux/i2c.h>#include <linux/delay.h>#include <linux/io.h>
/*--------------------------IIC底层时序--------------------------------*/
/*定义IIC需要使用的寄存器*/static volatile unsigned int *GPD1_CON=NULL;static volatile unsigned int *GPD1_DAT=NULL;
#define IIC_OUTPUT_MODE_SET() {*GPD1_CON&=~(0xF<<4*2);*GPD1_CON|=0x1<<4*2;} #define IIC_INPUT_MODE_SET() {*GPD1_CON&=~(0xF<<4*2);}
#define IIC_SCL(x) if(x){*GPD1_DAT|=1<<3;}else{*GPD1_DAT&=~(1<<3);}#define IIC_SDA_OUT(x) if(x){*GPD1_DAT|=1<<2;}else{*GPD1_DAT&=~(1<<2);}#define IIC_SDA_IN (*GPD1_DAT&1<<2)
/*函数功能: IIC总线初始化硬件连接: SCL-GPD1_3 SDA-GPD1_2*/static void IIC_Init(void){ /*1. 将物理地址转换为虚拟地址*/ GPD1_CON=ioremap(0x114000C0,4); GPD1_DAT=ioremap(0x114000C4,4); if(GPD1_CON==NULL||GPD1_DAT==NULL) { printk("物理地址转换为虚拟地址出现问题!\n"); } /*2. 配置寄存器*/ *GPD1_CON&=~(0xF<<4*3); *GPD1_CON|=0x1<<4*3; *GPD1_CON&=~(0xF<<4*2); *GPD1_CON|=0x1<<4*2; *GPD1_DAT|=1<<2; *GPD1_DAT|=1<<3;}

/*函数功能: 起始信号*/static void IIC_START(void){ IIC_OUTPUT_MODE_SET(); //配置输出模式 IIC_SCL(1); IIC_SDA_OUT(1); udelay(2); IIC_SDA_OUT(0); udelay(2); IIC_SCL(0);//告诉从机,通信开始(主机将要给从机发送数据)。}
/*函数功能: 停止信号*/static void IIC_STOP(void){ IIC_OUTPUT_MODE_SET(); //配置输出模式 IIC_SCL(0); IIC_SDA_OUT(0); udelay(2); IIC_SCL(1); udelay(2); IIC_SDA_OUT(1);}
/*函数功能: 获取从机发给主机的应答返回值 : 0表示获取成功,1表示获取失败目的: 读取总线上一位数据的值。这一位数据的正确值0*/static u8 IIC_GetACK(void){ u8 cnt=0; IIC_INPUT_MODE_SET(); //输入模式 IIC_SDA_OUT(1); //上拉 IIC_SCL(0); //告诉从机主机需要数据 udelay(2); IIC_SCL(1); //告诉从机主机正在读数据 while(IIC_SDA_IN) //等待应答 { cnt++; udelay(1); if(cnt>=250)return 1; } IIC_SCL(0); //告诉从机,主机准备发送数据 return 0;}

/*函数功能: 主机给从机发送应答函数参数: 1(非应答) 0(应答)目的: 发送一位数据*/static void IIC_SendAck(u8 ack){ IIC_OUTPUT_MODE_SET(); IIC_SCL(0); //告诉从机,主机将要发送数据 if(ack){IIC_SDA_OUT(1);} else {IIC_SDA_OUT(0);} udelay(2); IIC_SCL(1); udelay(2); IIC_SCL(0);}

/*函数功能: 发送一个字节数据函数参数: data将要发送数据*/static void IIC_WriteOneByte(u8 data){ u8 i; IIC_OUTPUT_MODE_SET(); for(i=0;i<8;i++) { IIC_SCL(0); if(data&0x80){IIC_SDA_OUT(1);} else {IIC_SDA_OUT(0);} udelay(2); IIC_SCL(1); udelay(2); data<<=1; } IIC_SCL(0);}
/*函数功能: 读取一个字节数据返回值 : 读取成功的数据*/static u8 IIC_ReadOneByte(void){ u8 data=0,i=0; IIC_INPUT_MODE_SET(); for(i=0;i<8;i++) { IIC_SCL(0); udelay(2); IIC_SCL(1); data<<=1; if(IIC_SDA_IN)data|=0x01; udelay(2); } IIC_SCL(0); return data;}
/*函数功能: iic_adapter写一个字节函数参数: data:写入的字节数据 addr:存放位置(0~255)*/static void iic_adapter_WriteOneByte(u8 dev_addr,u8 data,u8 addr){ IIC_START(); IIC_WriteOneByte(dev_addr<<1|0x0); //发送设备地址 IIC_GetACK(); //获取应答 IIC_WriteOneByte(addr); //发送存放数据的地址 IIC_GetACK(); //获取应答 IIC_WriteOneByte(data); //发送实际要存放的数据 IIC_GetACK(); //获取应答 IIC_STOP(); //发送停止信号}
/*函数功能: 指定位置读取指定数量的数据函数参数: addr: 从哪里开始读取数据 len : 读取多长的数据 *p : 存放数据的缓冲区 0x38<<1*/static void iic_adapter_ReadData(u8 dev_addr,u8 addr,u8 len,u8 *p){ u16 i; IIC_START(); IIC_WriteOneByte(dev_addr<<1|0x0); //发送设备地址(写) IIC_GetACK(); //获取应答 IIC_WriteOneByte(addr); //发送存放数据的地址(即将读取数据的地址) IIC_GetACK(); //获取应答 IIC_START(); IIC_WriteOneByte(dev_addr<<1|0x1); //发送设备地址(读) IIC_GetACK(); //获取应答 for(i=0;i<len;i++) { p[i]=IIC_ReadOneByte(); //接收数据 IIC_SendAck(0); //给从机发送应答 } IIC_SendAck(1); //给从机发送非应答 IIC_STOP(); //停止信号}

/*函数功能: 指定位置写入指定数量的数据函数参数: addr: 从哪里开始写数据 len : 写入多长的数据 *p : 存放数据的缓冲区*/static void iic_adapter_WriteData(u8 dev_addr,u8 addr,u8 len,u8 *p){ u16 i=0; for(i=0;i<len;i++) { iic_adapter_WriteOneByte(dev_addr,p[i],addr+i); }}
/*----------------------------------------------------------------------*/

/*定义一个IIC适配器结构*/static struct i2c_adapter adap;
/* s3c24xx_i2c_xfer**当消息需要时,来自i2c总线代码的第一个呼叫端口*通过i2c总线传输。*/static int tiny4412_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,int num){/* printk("num=%d\n",num); printk("请求发送数据的总线编号:%d\n",adap->nr); printk("从机的设备地址:0x%X\n",msgs[0].addr); printk("寄存器的起始地址:%d\n",msgs[0].buf[0]);*/ int i; if(num==1) /*写*/ { if(msgs[0].len==0)return 0; iic_adapter_ReadData(msgs[0].addr,msgs[0].buf[0],msgs[0].len-1,&msgs[0].buf[1]); /* printk("发送的数据长度:%d\n",msgs[0].len-1); printk("实际发送数据:"); for(i=1;i<msgs[0].len;i++) { printk("%d ",msgs[0].buf[i]); } printk("\n"); */ } else if(num==2) /*读*/ { /* printk("读取的数据长度:%d\n",msgs[1].len); for(i=0;i<msgs[1].len;i++) { msgs[1].buf[i]=66+i; } printk("\n"); */ if(msgs[1].len==0)return 0; iic_adapter_ReadData(msgs[0].addr,msgs[0].buf[0],msgs[1].len,msgs[1].buf); } /*增加与硬件交互的代码*/ return 0;}
/* 声明我们的i2c功能 */static u32 tiny4412_i2c_func(struct i2c_adapter *adap){ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING;}
/* i2c总线注册信息 */static const struct i2c_algorithm tiny4412_i2c_algorithm = { .master_xfer = tiny4412_i2c_xfer, .functionality = tiny4412_i2c_func,};

static int __init i2c_adapter_init(void){ IIC_Init(); strlcpy(adap.name, "tiny4412-i2c", sizeof(adap.name)); adap.owner = THIS_MODULE; adap.algo = &tiny4412_i2c_algorithm; /*适配器对应的功能函数*/ adap.retries = 2; /*数据发送失败之后重复的次数*/ adap.nr=-1; /*表示动态分配总线编号*/ adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; if(i2c_add_adapter(&adap)!=0) /*注册IIC适配器,并且动态分配新的总线编号*/ { printk("适配器注册失败!\n"); return -1; } printk("适配器注册成功,新的总线编号为:%d\n",adap.nr); return 0;}
static void __exit i2c_adapter_exit(void){ /*注销IIC适配器*/ i2c_del_adapter(&adap); /*解除虚拟地址映射关系*/ iounmap(GPD1_CON); iounmap(GPD1_DAT);}
module_init(i2c_adapter_init);module_exit(i2c_adapter_exit);MODULE_LICENSE("GPL");
复制代码

4.2  IIC 设备端代码

介绍如何获取注册适配器结构,完成设备端信息填充,完成注册。  这里代码是以触摸屏的驱动为例。


#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/i2c.h>#include <linux/slab.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>
static struct i2c_client *i2cClient = NULL;
static unsigned short i2c_addr_list[0xFF]= { 0x38,0x46,I2C_CLIENT_END};//地址队列
/*IIC标准地址是7位
011 :厂家提供的地址1000:厂家提供的源码里分析获取的
*/
/* *把什么设备信息告诉驱动? *1. 告诉驱动我是哪条总线 *2. 告诉驱动:我的设备地址是多少 *3. 申明一个名字用来找到驱动端(完成匹配) */static int __init i2c_dev_init(void){ struct i2c_adapter *i2c_adap;//获取到的总线存放在这个结构体 struct i2c_board_info i2c_info;//设备描述结构体,里面存放着欲设备的名字还有地址
/*1. 根据总线编号获取IIC控制器*/ i2c_adap = i2c_get_adapter(9); /*要使用IIC_1号总线*/
/*2. 清空结构体*/ memset(&i2c_info,0,sizeof(struct i2c_board_info));
/*3. 名称的赋值*/ strlcpy(i2c_info.type,"touch_name",I2C_NAME_SIZE);
/*触摸屏的中断信号检测IO口编号*/ i2c_info.irq=EXYNOS4_GPX1(6); /*4. 创建IIC设备--客户端*/ i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); if(i2cClient==NULL) { printk("地址匹配出现错误!\n"); return 0; }
/*5. 设置adapter\增加使用计数*/ i2c_put_adapter(i2c_adap); printk("i2c_dev_init!!\n"); return 0;}
static void __exit i2c_dev_exit(void)//平台设备端的出口函数{ printk(" i2c_dev_exit ok!!\n");
/*注销设备*/ i2c_unregister_device(i2cClient);
/*释放设备,减少使用计数*/ i2c_release_client(i2cClient);
/*释放结构体空间*/ kfree(i2cClient);}
module_init(i2c_dev_init);module_exit(i2c_dev_exit);MODULE_LICENSE("GPL");
复制代码

4.3 驱动层代码

#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/i2c.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>#include <linux/workqueue.h>
static const struct i2c_device_id i2c_id[] ={ {"touch_name",0},//设备端的名字,0表示不需要私有数据};
static struct i2c_client *touch_client=NULL;
/*工作队列处理的结构体*/static struct work_struct tiny4412_touch_work;

/*工作队列处理函数*/static void touch_work_func(struct work_struct *work){ /*1. 读取触摸屏信息*/ u8 buff[7]; i2c_smbus_read_i2c_block_data(touch_client,0x00,7,buff); if(buff[2]&0xF) { printk("按下的点数量:%d\n",buff[2]&0xF); printk("X_0坐标:%d\n",(buff[3]&0xF)<<8|buff[4]); printk("Y_0坐标:%d\n",(buff[5]&0xF)<<8|buff[6]); } else { printk("触摸屏松开!\n"); }}
/*触摸屏中断处理函数*/irqreturn_t irq_handler_touch(int irq, void *dev){ /*调度工作(计划让CPU执行这个工作)*/ schedule_work(&tiny4412_touch_work); /*正常情况下: 是在中断服务函数里面*/ return IRQ_HANDLED; /*表示中断已经处理过了*/}
static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功时调用{ printk("驱动端IIC匹配的地址=0x%x\n",client->addr); touch_client=client; /*保存地址*/ /*1. 读取厂商ID*/ u8 ID; i2c_smbus_read_i2c_block_data(client,0xA3,1,&ID); printk("厂商ID:%d\n",ID);
/*2. 读取电源寄存器状态*/ u8 State; i2c_smbus_read_i2c_block_data(client,0xA5,1,&State); printk("电源寄存器状态:%d\n",State);
/*3. 初始化工作*/ INIT_WORK(&tiny4412_touch_work,touch_work_func); /*4. 完成中断的注册*/ int err=request_irq(gpio_to_irq(client->irq),irq_handler_touch,IRQ_TYPE_EDGE_BOTH,client->name,NULL); if(err!=0) { printk("中断注册失败!\n"); return -1; } printk("提示: 触摸屏中断注册成功!\n"); return 0;}
static int i2c_remove(struct i2c_client *client){ printk("触摸屏驱动端资源卸载成功!\n"); /*1. 释放中断号*/ free_irq(gpio_to_irq(client->irq),NULL); return 0;}
struct i2c_driver i2c_drv ={ .driver = { .name = "i2c_drv", .owner = THIS_MODULE, }, .probe = i2c_probe, //探测函数 .remove = i2c_remove, //资源卸载 .id_table = i2c_id, //里面有一个名字的参数用来匹配设备端名字};
/*iic驱动端*/static int __init i2c_drv_init(void){ i2c_add_driver(&i2c_drv);//向iic总线注册一个驱动 return 0;}
static void __exit i2c_drv_exit(void)//平台设备端的出口函数{ i2c_del_driver(&i2c_drv);}
module_init(i2c_drv_init);module_exit(i2c_drv_exit);MODULE_LICENSE("GPL");
复制代码


发布于: 刚刚阅读数: 3
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022-01-06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux下IIC驱动编写,介绍IIC子系统框架的使用_10月月更_DS小龙哥_InfoQ写作社区