- 物理接线I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
- I2C总线特征I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,
我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。I2C总线上可挂接的设备数量受总线的最大电容400pF限制,如果所挂接的是相同型号的器件,则还受器件地位的限制。I2C总线数据传输速率在标准模式下可达 100kbit/s,快速模式下可达 400kbit/s,高速模式下可达3.4Mbit/s。一般通过 I2C总线接口可编程时钟来实现传输速率的调整。I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传。 - I2C总线协议1.I2C总线协议基本时序信号空闲状态:SCL和SDA都保持着高电平。起始条件:总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他2C器件无法访问总线。
① 停止条件:当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。
② 应答信号: 每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为低,则 表示一个应答信号。
③ 非应答信号:每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为高,则表示一个应答信号。
注意:起始和结束信号总是由主设备产生。
17.1 Linux下的驱动思路
在linux系统下编写I2C驱动,目前主要有两种方法:
1)把I2C设备当作一个普通的字符设备来处理;
2)利用linux下I2C驱动体系结构(子系统)来完成。
下面比较下这两种方法:
第一种方法:
优点:思路比较直接,不需要花很多时间去了解linux中复杂的I2C子系统的操作方法。
缺点:
要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器(I2C控制器)操作。
要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可移植性差。
对内核的资源无法直接使用,因为内核提供的所有I2C设备器以及设备驱动都是基于I2C子系统的格式。
第一种方法的优点就是第二种方法的缺点.
第一种方法的缺点就是第二种方法的优点。
17.2 I2C架构概述
上图完整的描述了linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂。
I2C子系统由上到下分成3层:
层名 | 描述 |
I2C设备驱动层 | 真正实现具体设备的时序的代码。使用核心层提供API接口写,有特定编写框架。 |
I2C核心层 | 提供了I2C总线驱动和设备驱动的注册、注销方法、I2C通信方法(”algorithm”),与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。 提供了设备驱动层和适配器驱动层需要API接口,以及实现收发数据管理功能。起到一个连接上下两的作用。 |
I2C适配器驱动层 | 对I2C硬件体系结构中适配器端的实现。 适配器可由CPU控制,甚至可以直接集成在CPU内部。 通俗说就是直接操作硬件上IIC控制的的驱动代码。真正的实现IIC数据收发。 |
上面三层,设备驱动层需要自己写,核心层不变,由内核提供,I2C适配器驱动层一般由芯片厂商提供。
17.3 Linux下I2C体系文件构架
在Linux内核源代码中的driver目录下包含一个i2c目录。
文件 | 功能描述 |
i2c-core.c | 这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。 |
i2c-dev.c | 实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。 I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。 |
busses文件夹 | 这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c。 |
algos文件夹 | 实现了一些I2C总线适配器的algorithm。 |
[root@WBYQ /]# ls /dev/i2c-* -l crw-rw---- 1 root root 89, 0 Jul 12 01:30 /dev/i2c-0 crw-rw---- 1 root root 89, 1 Jul 12 01:30 /dev/i2c-1 crw-rw---- 1 root root 89, 2 Jul 12 01:30 /dev/i2c-2 crw-rw---- 1 root root 89, 3 Jul 12 01:30 /dev/i2c-3 crw-rw---- 1 root root 89, 7 Jul 12 01:30 /dev/i2c-7 crw-rw---- 1 root root 89, 8 Jul 12 01:30 /dev/i2c-8 [root@WBYQ /]# |
核心层:i2c-core.c i2c-boardinfo.c i2c-smbus.c i2c-mux.c
I2C适配器驱动层:
busses文件夹下的一个C文件对应于一个物理的I2C适配器驱动程序。
比如:EXYNOS4412 的I2C 适配器驱动i2c-s3c2410.c。
现在这里的两层我们都实现不了,linux系统和芯片厂家提供。
我们主要实现设备驱动层,下面主要讲解它了。
17.4 设备驱动层(重点)
17.4.0 设备驱动层结构
由设备层代码 + 驱动层代码构成,可以类比平台设备驱动模型。
17.4.1 驱动层核心结构
该核心结构在I2c.h(include\Linux )下。内核使用 struct i2c_driver 结构描述一个I2C设备驱动,这个结构必须实现的是:probe,remove。
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, it will be removed in a 老接口,可能会消失,建议不要使用. */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; int (*detach_adapter)(struct i2c_adapter *) __deprecated; /* 新的接口,用来替代上面两个接口,必须实现,功能类型平台模型的probe,remove*/ 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; }; |
17.4.2 设备层核心结构
内核使用 struct i2c_client 来描述一个设备信息。
比如,器件地址,标志(器件地址位数:10位,7位等),所依赖的总线等。
内核定义的结构:
struct i2c_client { ; 标志,比如说设备地址10位还是7位 unsigned short addr; 低7位为芯片地址 char name[I2C_NAME_SIZE];设备名称,随便,但是要和驱动层id_table相同 struct i2c_adapter *adapter;依附的 i2c_adapter,它表示一个IIC控制器 依附的i2c_driver 设备结构体 设备所使用的中断号 链表头 }; |
最小的情况下实现flags,addr,name,adapter
17.5 API函数
17.5.1 注册iic驱动
int i2c_add_driver(struct i2c_driver *driver) |
注册I2C设备驱动,driver 是已经填充好的struct i2c_driver结构指针
一般写在模块的初始化代码。
17.5.2 注销iic驱动
void i2c_del_driver(struct i2c_driver *driver) |
注销I2C设备驱动,
一般写在模块的出口处。
17.5.3 标准的发送数据函数
以下关于内核I2C核心层提供的标准发收函数:
int i2c_master_send(struct i2c_client *client, const char *buf , 发送函数 |
功能:发送数据给真正的硬件设备。
参数:
:指针I2C设备结构的指针。
:发送的数据指针
发送的字符数量
返回发送的字节数,失败返回-1。
注意:此函数只是实现标准IIC的写协议,不代表具体器件写协议。
如:要写数据给AT24C02 ,从内部地址10开始写,应该怎么写。
方法1:把内部地址当数据写在第一个缓冲中,后面是真正的数据。
buf[0]=subaddr; //内部地址
buf[1]=? //数据
……
buf[9]=?;
i2c_master_send(client,buf ,10) ;
方法2:先单独发器件地址,再发送要写在内部地址的数据。
subaddr=subaddr; //内部地址
i2c_master_send(client,&subaddr ,1) ;
?; //数据
buf[1]=?
……
buf[9]=?;
i2c_master_send(client,buf ,10) ;
17.5.4 标准的读取数据函数
int i2c_master_recv(struct i2c_client *client, char *buf ,int count) |
功能:从硬件中读取数据
参数:
:指针I2C设备结构的指针。
:存放数据指针
要读的字节数量
注意:此函数只是实现标准IIC的读协议,不代表具体器件读协议。
比如,对24c02进行读操作,先使用 i2c_master_send发送内部地址,然后调用 i2c_master_recv 函数读数据。
17.5.5 收发一体函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) |
功能:这个函数是I2C传输函数,收发一体的函数。
参数:
adap:指针I2C适配器结构的指针,这个指针是使用 i2c_client 中的 adapter 指针。
:存放数据指针
要传输的 struct i2c_msg 数量。
内核使用struct i2c_msg 结构来描述一则消息,包含了目标地址,操作方式(读写),数据存放位置或源位置。
struct i2c_msg { __u16 addr;/* 这条消息是发送给谁*/ __u16 flags; /* 消息额外的标志,可选择的值有以下宏*/ #define I2C_M_TEN0x0010/* 表示目标器件地址是10位的 */ #define I2C_M_RD0x0001/* 在从设备中读取数据 */ /* 没有专门定义一个写的标志,默认是写,*/ //以下标志使用不到 #define I2C_M_NOSTART0x4000/* if I2C_FUNC_NOSTART */ #define I2C_M_REV_DIR_ADDR0x2000/* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK0x1000/* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK0x0800/* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN0x0400/* length will be first received byte */ __u16 len;/* 传输的数据字节数量*/ __u8 *buf;/* 接收/发送缓冲区*/ }; |
一则消息不是传递一个字节,可以传递多个字节,最大65536字节。
比如:要写数据给AT24C02 ,从内部地址10开始写,写2个,应该怎么写。
方法1:把内部地址当数据写在第一个缓冲中,后面是真正的数据。
char subaddr = 10; //内部地址 char data[100]={1,2,3};//等待发送的数据 struct i2c_msg msgs[2]={ [0]={ .addr = EEPROM_DEVICE_ADDR,//EEPROM_DEVICE_ADDR器件地址 .flags= 0 , //默认是写 .len = 1, .buf =&subaddr }, [1]={ 器件地址 .flags= 0 , .len = 2, .buf = data }, }; i2c_transfer(client->adapter,msgs,2); |
注意:24c02连续进行页写操作不能跨页写,这个要用户自己保证。
17.5.6 注册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适配器,使用静态总线编号 |
- 适配器数据结构:
struct i2c_adapter { struct module *owner; unsigned int class;允许探测的类 */ const struct i2c_algorithm *algo; /*访问总线的函数接口*/ void *algo_data; /* data fields that are valid for all devices*/ struct rt_mutex bus_lock; int timeout;/*时间节拍*/ int retries; /*重试*/ struct device dev;/* the adapter device */ int nr; /*总线编号*/ char name[48]; /*总线名称*/ struct completion dev_released; struct mutex userspace_clients_lock; struct list |
17.5.9 Exynos4412开发板内核IIC适配器注册分析
- 开发板底层的IIC驱动文件: i2c-s3c2410.c
- 在i2c-s3c2410.c的probe函数里完成了IIC适配器注册:
- 在适配器注册之前,对适配器的底层函数指针进行了赋值
- 最终IIC的数据通过CPU本身的IIC控制器写入硬件是依靠&s3c24xx_i2c_algorithm结构体赋值的函数完成,这是最底层操作IIC硬件的函数。
IIC适配器注册示例:
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> static struct i2c_adapter adapter; /* 声明我们的i2c功能 */ static u32 i2c_support(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING; } static int i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,int num) { printk("设备地址:0x%X\n",msgs[0].addr); if(num==1) //写模式 { printk("写模式:\n"); printk("寄存器起始位置:%d\n",msgs[0].buf[0]); printk("写入的数据长度:%d\n",msgs[0].len-1); int i; printk("数据如下:"); for(i=1;i<=msgs[0].len-1;i++) { printk("%d ",msgs[0].buf[i]); } printk("\n"); } else if(num==2)//读模式 { printk("读模式:\n"); printk("寄存器起始位置:%d\n",msgs[0].buf[0]); printk("读取的数据长度:%d\n",msgs[1].len); int i; //给数据赋值,模拟数据读取 for(i=0;i<msgs[1].len;i++) { msgs[1].buf[i]=i+66; } printk("\n"); } return 0; } static const struct i2c_algorithm my_i2c_algorithm = { .master_xfer= i2c_master_xfer, //底层调用函数 .functionality= i2c_support, //支持的功能声明 }; static int __init i2c_adapter_init(void) { strlcpy(adapter.name, "my_exynos_i2c", sizeof(adapter.name)); adapter.owner = THIS_MODULE; adapter.algo = &my_i2c_algorithm; /*适配器对应的功能函数*/ adapter.retries = 2; adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; if(i2c_add_adapter(&adapter)!=0) { printk("IIC适配器注册失败!\n"); return 0; } printk("适配器注册的总线编号:%d\n",adapter.nr); printk("i2c_adapter_init!!\n"); return 0; } static void __exit i2c_adapter_exit(void) { i2c_del_adapter(&adapter); printk(" i2c_adapter_exit!\n"); } module_init(i2c_adapter_init); module_exit(i2c_adapter_exit); MODULE_LICENSE("GPL"); |
17.5.7 注销IIC适配器
int i2c_del_adapter(struct i2c_adapter *adap) |
17.5.8 获取适配器
struct i2c_adapter *i2c_get_adapter(int nr) |
功能: 根据注册时绑定的总线编号,获取IIC适配器结构体
参数: nr 总线编号
返回值: IIC适配器结构
17.6 Smbus总线操作函数
以下是 smbus 总线的操作函数,smbus是系统管理总线,可以看成是IIC总线的一个子集,它的规范大部分都是基于I2C标准。所以,大部分的IIC器件也可以使用 smbus 总线来操作。非常合适操作那些有内部地址的器件。
17.6.1 单字节读函数
s32 i2c_smbus_read_byte_data(const struct i2c_client *client,u8 command): |
功能:指定地址的单字节读
参数:
client结构指针。
command:内部地址。
返回值:读到的数据,失败返回负数。
17.6.2 单字节写函数
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value) |
功能:指定地址的单字节写入数据
参数:
client结构指针。
command:内部地址
value:要写入的数据
返回值:写如的数据,失败返回负数。
17.6.3 读指定长函数
如果器件存在页写问题,建议使用这个函数,循环操作。
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values) |
功能:从指定地址开始读取指定长度的数据。
参数:
client结构指针
command:内部地址
length:要读的数据长度
value:存放数据的指针
返回值:读到的数据,失败返回负数。
17.6.4 写指定长度函数
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values) |
功能:从指定地址开始读取指定长度的数据
参数:
client结构指针
command:内部地址
length:要写的数据长度
value:源数据指针
返回值:写入的数据,失败返回负数。
17.7 编程模板
像平台设备一样,编写两层代码:设备层,驱动层。
17.7.1 驱动层代码模板
驱动层是否需要写文件操作方法根据自己需要。
//编写器件ID列表 static const struct i2c_device_id ft5x0x_id_table[] = { { "ft5x0x", 0 }, {}, }; MODULE_DEVICE_TABLE(i2c, ft5x0x_id_table); struct i2c_client *tsclient; /* 编写probe函数,针对单点 */ static int __devinit ft5x0x_probe(struct i2c_client *client, const struct i2c_device_id *id) { s32 val; 设备层传入的client结构 给用户提供访问接口 …… return 0; } /* 编写反探测函数 */ static int __devexit ft5x0x_remove(struct i2c_client *client) { return 0; } //1-驱动层核心核心结构 static struct i2c_driver ft5x0x_driver = { .driver = { 可以在 sys/bus/i2c/drivers查看到 .owner = THIS_MODULE, }, .probe = ft5x0x_probe, .remove = __devexit_p(ft5x0x_remove), .id_table = ft5x0x_id_table, }; //模块初始换函数 static __init int ft5x0x_drv_init(void) { 注册一个IIC驱动 */ i2c_add_driver(&ft5x0x_driver); return 0; } //模块卸载函数 static __exit void ft5x0x_drv_exit(void) { i2c_del_driver(&ft5x0x_driver); } module_init(ft5x0x_drv_init); module_exit(ft5x0x_drv_exit); MODULE_LICENSE("GPL"); |
17.7.2 设备层模板
1)获取 i2c_adapter 的内存地址
struct i2c_adapter *i2c_get_adapter(int nr) |
作用:1)获取 i2c_adapter 结构地址
)增加 i2c_adapter 结构的引用计数,防止使用过程中被移除。
nr:就是适配器的总线编号:
就是0
就是1
返回:指针适配器结构的首地址,失败返回NULL。
2)减少i2c_adapter引用计数
使用 i2c_get_adapter 后都需要使用这个函数。
i2c_put_adapter(struct i2c_adapter *adap) |
3)注册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 函数获取
:i2c设备的描述信息
addr_list:i2c设备可能出现的地址表,是一个数组。
probe:探测到i2c设备时候会执行回调函数,一般是NULL。
4)struct i2c_board_info结构
内核使用 struct i2c_board_info 描述一个i2c设备基本信息,通过这些可以创建一个 i2c_client 结构。
/** * struct i2c_board_info - template for device creation 用来初始化 i2c_client.name 用来初始化 i2c_client.flags 用来初始化 i2c_client.addr 用来初始化 i2c_client.dev.platform_data * @archdata: copied into i2c_client.dev.archdata * @of_node: pointer to OpenFirmware device node 用来初始化 i2c_client.irq */ struct i2c_board_info { chartype[I2C_NAME_SIZE]; unsigned shortflags; unsigned shortaddr; void*platform_data; struct dev_archdata*archdata; struct device_node *of_node; intirq; }; |
5)取消一个I2C设备注册
void i2c_unregister_device(struct i2c_client*client) |
6)设备层中地址表定义
就是传递给 i2c_new_probed_device函数的参数 addr_list,一定要以宏 I2C_CLIENT_END 结尾。
示例:
static const unsigned short ft5x0x_i2c[] = { 0x38, I2C_CLIENT_END };//地址为0x38 |
关于平台数据:
如果i2c_driver中需要使用到平台数据,可以这样取出client->dev.platform_data,这个就是struct i2c_board_info中的.platform_data成员。
关注I2C地址:I2c子系统的器件地址是纯地址,就裸机的地址表示方法 >> 1 得到。
比如:0xA0 在Linux系统中是 0xA0>>1 得到 0X50。
I2C子系统机制和平台设备不一样,虽然是通过id_table,但是还有一个条件,就是i2c器件是真实存在的。
17.8 I2C注册层方式
1)动态注册:可以自己后期安装模块注册
。
2)静态注册:把信息写板级文件中。
注意:测试时要去除内核自带驱动。
Symbol: TOUCHSCREEN_FT5X0X [=n] Type : tristate Prompt: FocalTech ft5x0x TouchScreen driver Defined at drivers/input/touchscreen/Kconfig:312 Depends on: !S390 && !UML && INPUT [=y] && INPUT_TOUCHSCREEN [=y] && MACH_TINY4412 [=y] Location: -> Device Drivers -> Input device support -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y]) -> Touchscreens (INPUT_TOUCHSCREEN [=y]) │ |
17.9 IIC设备驱动存在两种注册
17.9.1 动态注册
I2C设备的信息也是系统启动后通过安装模块形式。其写法。
#include <linux/i2c.h> #include <linux/slab.h> #include <linux/export.h> #include <linux/module.h> static struct i2c_client *ft5x0x_client; /* 传入iic的设备地址和name给iic驱动端 */ static const unsigned short ft5x0x_i2c[] = { 0x38, I2C_CLIENT_END }; static __init int ft5x0x_dev_init(void) { struct i2c_adapter *i2c_adap; struct i2c_board_info ft5x0x_info; memset(&ft5x0x_info, 0, sizeof(struct i2c_board_info)); strlcpy(ft5x0x_info.type, "ft5x0x", I2C_NAME_SIZE); i2c_adap = i2c_get_adapter(1); //根据总线号取得适配器指针 注册iic设备 */ ft5x0x_client = i2c_new_probed_device(i2c_adap, &ft5x0x_info, ft5x0x_i2c, NULL); i2c_put_adapter(i2c_adap); return 0; } static __exit void ft5x0x_dev_exit(void) { i2c_unregister_device(ft5x0x_client); } module_init(ft5x0x_dev_init); module_exit(ft5x0x_dev_exit); MODULE_LICENSE("GPL"); |
17.9.2 静态注册比较常见。
直接在mach-xxxxx.c 板级文件中的机器初始化函数通过传递struct i2c_board_info 结构,使用
int __initi2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) |
busnum :I2C设备挂接的总线号,示例,填入0,
info:已经填充好的 struct i2c_board_info 结构指针
len:代表info指针中有多少个 struct i2c_board_info 元素。(这条总线挂了多少个设备)。
只需要调用这个函数就可以创建一个i2c_client 结构,并且注册。
示例:
static struct i2c_board_info ft5x06_info[] = { //必选,实际上是填充了type和addr成员。 I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)), .platform_data = &ft5x0x_pdata, //平台数据可有可无,根据的驱动需要传递。 } #define I2C_BOARD_INFO(dev_type, dev_addr) \ .type = dev_type, .addr = (dev_addr) |
然后在机器初始化函数中调用:
i2c_register_board_info(1, ft5x06_info,1); |
这样内核会根据总线号和struct i2c_board_info信息构建一个i2c_client结构,并且注册。把该结构和总线编号对应的适配绑定起来。
注意:静态注册方式必须保证先于适配器驱动注册。一般情况下只要把注册代码写在板级文件机器初始化函数中,基本上不会出问题。
以下截图,是开发板底层静态初始化IIC信息的代码:
17.10 IIC接口ft5x06触摸屏驱动代码示例
FT5x06系列ICs是单芯片电容式触摸屏控制器IC,带有一个内置的8位微控制器单元(MCU)。采用互电容的方法,在配合的相互的电容式触摸面板,它支持真正的多点触摸功能。FT5x06具有用户友好的输入的功能,这可以应用在许多便携式设备,例如蜂窝式电话,移动互联网设备,上网本和笔记本个人电脑。FT5x06系列IC包括FT5206/FT5306/FT5406。
如果内核有自带的驱动,需要将内核自带的驱动先卸载掉!
[root@XiaoLong linux-3.5]# make menuconfig
Device Drivers ---> Input device support ---> [*] Touchscreens ---> <> FocalTech ft5x0x TouchScreen driver 开发板触摸屏芯片驱动-将*号去掉 |
17.10.1 FT5x06设备端代码
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/irq.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> static struct i2c_client *i2cClient = NULL; static unsigned short const i2c_addr_list[] = { 0x38, I2C_CLIENT_END };//地址队列 static int __init i2c_dev_init(void) { struct i2c_adapter *i2c_adap;//获取到的总线存放在这个结构体 struct i2c_board_info i2c_info;//设备描述结构体,里面存放着欲设备的名字还有地址 /*获取IIC控制器*/ i2c_adap = i2c_get_adapter(1); /*清空结构体*/ memset(&i2c_info,0,sizeof(struct i2c_board_info)); /*名称的赋值*/ strlcpy(i2c_info.type,"FT5X06",I2C_NAME_SIZE); /*获取中断号*/ i2c_info.irq=gpio_to_irq(EXYNOS4_GPX1(6)); /*创建IIC设备--客户端*/ i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); /*设置模块信息 */ 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); //增加模块计数 try_module_get(i2c_adap->owner); } module_init(i2c_dev_init); module_exit(i2c_dev_exit); MODULE_LICENSE("GPL"); |
17.10.2 FT5X06驱动端代码
以下的驱动代码单纯读取了触摸屏的坐标信息,没有加入输入子系统上报,下一章节讲了输入子系统再对触摸屏代码进行修改。
#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/workqueue.h> static struct work_struct work; static struct i2c_client *client_drv; static const struct i2c_device_id i2c_id[] = { {"FT5X06",0},//设备端的名字为"myiic",0表示不需要私有数据 {} }; //工作队列处理函数 static void work_func(struct work_struct *work) { s32 x=0,y=0,num=0; /*1. 读出按下的点数量*/ num=i2c_smbus_read_byte_data(client_drv,0x02)&0x0f; /*2. 读取X坐标*/ x=(i2c_smbus_read_byte_data(client_drv,0x03)&0x0f)<<8; //高位 x|=i2c_smbus_read_byte_data(client_drv,0x04); //低位 /*3. 读取Y坐标*/ y=(i2c_smbus_read_byte_data(client_drv,0x05)&0x0f)<<8; //高位 y|=i2c_smbus_read_byte_data(client_drv,0x06); //低位 printk("num=%d,x=%d,y=%d\n",num,x,y); } //中断服务函数 static irqreturn_t irq_handler(int irq, void *dev) { schedule_work(&work); return IRQ_NONE; } static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功时调用 { client_drv=client; printk("i2c_probe!!!\n"); printk("驱动端IIC匹配的地址=0x%x\n",client->addr); 注册触摸屏的中断*/ if(request_irq(client->irq,irq_handler,IRQ_TYPE_EDGE_BOTH,client->name,NULL)!=0) { 中断注册失败!\n"); } /*初始化工作*/ INIT_WORK(&work,work_func); return 0; } static int i2c_remove(struct i2c_client *client) { printk("i2c_remove!!!\n"); //注销中断 free_irq(client->irq,NULL); return 0; } struct i2c_driver i2c_drv = { .driver = //这个不添加会报错,实际上没作用 { .name = "XL", .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"); |
17.11 IIC接口IT7260触摸屏驱动代码示例
17.11.1 IT7260设备端代码
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> static struct i2c_client *i2cClient = NULL; static unsigned short i2c_addr_list[0xFF]={0x46,I2C_CLIENT_END}; static int __init i2c_dev_init(void) { int i; struct i2c_adapter *i2c_adap;//获取到的总线存放在这个结构体 struct i2c_board_info i2c_info;//设备描述结构体,里面存放着欲设备的名字还有地址 /*获取IIC控制器*/ i2c_adap = i2c_get_adapter(1); /*清空结构体*/ memset(&i2c_info,0,sizeof(struct i2c_board_info)); /*名称的赋值*/ strlcpy(i2c_info.type,"IT7260_touch",I2C_NAME_SIZE); /*创建IIC设备--客户端*/ i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); if(i2cClient==NULL) { printk("地址不存在\r\n"); } else { printk("地址存在0x%x\r\n",i-1); } /*设置模块信息 */ 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); } module_init(i2c_dev_init); module_exit(i2c_dev_exit); MODULE_LICENSE("GPL"); |
17.11.2 IT7260驱动端代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/pm.h> #include <linux/slab.h> #include <asm/io.h> #include <linux/i2c.h> #include <linux/timer.h> #include <linux/gpio.h> #include <linux/irq.h> #include <plat/ctouch.h> static int it7260_irq; //中断号 static struct i2c_client *it7260_client; //IIC客户端 static struct work_struct work; 工作队列 static void it7260_ts_poscheck(struct work_struct *work) { unsigned char buf[14]; unsigned short xpos, ypos; unsigned char event=0; memset(buf, 0, sizeof(buf)); /*读取触摸屏寄存器数据*/ i2c_smbus_read_i2c_block_data(it7260_client,0xE0,14,buf); /*触摸按键*/ if (buf[0] == 0x41) { if (buf[1] == 0x04)printk("stat=%d,KEY_MENU=%d\n",buf[1],!!buf[2]); else if (buf[1] == 0x03)printk("stat=%d,KEY_HOMEPAGE=%d\n",buf[1],!!buf[2]); else if (buf[1] == 0x02)printk("stat=%d,KEY_BACK=%d\n",buf[1],!!buf[2]); else if (buf[1] == 0x01)printk("stat=%d,KEY_SEARCH=%d\n",buf[1],!!buf[2]); } /* 第一个点*/ if (buf[0] & 0x01) { xpos = ((buf[3] & 0x0F) << 8) | buf[2]; ypos = ((buf[3] & 0xF0) << 4) | buf[4]; event = buf[5] & 0x0F; printk("x=%d,y=%d,ev=%d\n",xpos,ypos,event); } } static irqreturn_t it7260_ts_isr(int irq, void *dev_id) { schedule_work(&work); return IRQ_HANDLED; } /* 资源匹配函数 */ static int it7260_ts_probe(struct i2c_client *client,const struct i2c_device_id *idp) { it7260_client = client; INIT_WORK(&work, it7260_ts_poscheck); it7260_irq = gpio_to_irq(EXYNOS4_GPX1(6)); request_irq(it7260_irq, it7260_ts_isr,IRQ_TYPE_EDGE_BOTH,client->name, NULL); return 0; } /* 资源卸载函数 */ static int __devexit it7260_ts_remove(struct i2c_client *client) { free_irq(it7260_irq, NULL); return 0; } /*IIC驱动名称匹配结构*/ static const struct i2c_device_id it7260_ts_id[] = { {"IT7260_touch", 0}, {}/* should not omitted */ }; /*底层结构*/ static struct i2c_driver it7260_ts_driver = { .driver={ .name = "IT7260", }, .probe = it7260_ts_probe, /*资源探测*/ .remove = it7260_ts_remove, /*资源卸载*/ .id_table = it7260_ts_id, /*ID匹配*/ }; static int __init it7260_ts_init(void) { return i2c_add_driver(&it7260_ts_driver); } static void __exit it7260_ts_exit(void) { i2c_del_driver(&it7260_ts_driver); } module_init(it7260_ts_init); module_exit(it7260_ts_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("it7260 touchscreen driver"); |
17.12 IIC接口AT24C02驱动代码示例
17.12.1 应用层代码
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /* AT24C02 应用层测试代码 */ int main(int argc,char **argv) { int fp; char buff_write[]="AT24C02读写测试!123456789ABCDEFG"; char buff_read[100]; fp=open("/dev/Tiny4412_AT24C02",O_RDWR); if(fp<0) /*判断文件是否打开成功*/ { printf("AT24C02 driver open error!\n"); return -1; } printf("写入的数据:%s\n",buff_write); write(fp,buff_write,strlen(buff_write)+1); /*调用写函数->向驱动层传递数据*/ read(fp,buff_read,strlen(buff_write)+1); /*调用读函数->获取驱动层的数据*/ printf("读出的数据:%s\n",buff_read); close(fp); return 0; } |
17.12.2 设备端代码
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> /*获取总线*/ struct i2c_adapter *i2c_adap; //获取到的总线存放在这个结构体 static struct i2c_client *i2cClient = NULL; //AT24C02固定地址 b1010 //AT24C02硬件地址 b000 //组合:b1010000 = 0x50 //注意:IIC标准地址是7位 static unsigned short const i2c_addr_list[] = { 0x50, I2C_CLIENT_END };//地址队列 static int __init i2c_dev_init(void) { struct i2c_board_info i2c_info;//设备描述结构体,里面存放着欲设备的名字还有地址 i2c_adap = i2c_get_adapter(0); //获取0号总线 if(i2c_adap==NULL) { printk("AT24C02--II总线0 获取失败!!\n"); } memset(&i2c_info,0,sizeof(struct i2c_board_info));//把设备描述结构体清空结构体清空 strlcpy(i2c_info.type,"Tiny4412_AT24C02",I2C_NAME_SIZE);//把设备的名字赋值给i2c_info i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); if(i2cClient==NULL) { printk("AT24C02 0x%x:地址不可用!!\n",i2c_addr_list[0]); } i2c_put_adapter(i2c_adap); printk("AT24C02_dev_init初始化成功!!\n"); return 0; } static void __exit i2c_dev_exit(void)//平台设备端的出口函数 { /*注销设备*/ i2c_unregister_device(i2cClient); i2c_release_client(i2cClient); printk("AT24C02_dev_exit ok!!\n"); } module_init(i2c_dev_init); module_exit(i2c_dev_exit); MODULE_LICENSE("GPL"); |
17.12.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> 中断IO口定义*/ #include <linux/workqueue.h> /*工作队列相关*/ #include <linux/mutex.h> /*互斥信号量头文件*/ #include <linux/delay.h> #include <linux/miscdevice.h> /*杂项设备相关结构体*/ #include <linux/fs.h> /*文件操作集合头文件*/ #include <linux/uaccess.h> /*使用copy_to_user和copy_from_user*/ static struct i2c_client *at24cxx_client; /*互斥锁相关*/ /*互斥锁*/ static DEFINE_MUTEX(my_mutexname); /*相关的函数声明*/ static u8 i2c_read_data(u8 addr,u8 len,u8* str); static ssize_t at24cxx_read(struct file *my_file, char __user *buf, size_t my_size, loff_t * my_loff); static int at24cxx_release(struct inode *my_inode, struct file *my_file); static ssize_t at24cxx_write(struct file *my_file, const char __user *buf, size_t my_size, loff_t *my_loff); static int at24cxx_open(struct inode *my_inode, struct file *my_file); /*定义一个文件操作集合结构体*/ static struct file_operations ops_at24cxx={ .owner = THIS_MODULE, 读函数-被应用层read函数调用*/ 写函数-被应用层write函数调用*/ 打开函数-被应用层open函数调用*/ 释放函数*/ }; /*定义一个杂项设备结构体*/ static struct miscdevice misce_at24cxx={ .minor =MISC_DYNAMIC_MINOR, /*自动分配次设备号*/ .name = "Tiny4412_AT24C02", /*名称 在dev/目录下边可以找到*/ .fops = &ops_at24cxx, /*文件操作集合*/ }; /* IIC驱动端 */ static const struct i2c_device_id i2c_id[] = { {"Tiny4412_AT24C02",0},//设备端的名字为"my_at24c02",后面的表示需要私有数据 {} }; /*---------------------------------- 写数据函数 参数 addr:地址 len :长度 str :数据首地址 ------------------------------------*/ static u8 i2c_write_data(u8 addr,u8 len,u8* str) { u8 i,flag=0; for(i=0;i<len;i++) { /*循环写入数据*/ flag=i2c_smbus_write_byte_data(at24cxx_client,addr+i,str[i]); if(flag<0)return -1; 写延时*/ } return flag; } /*----------------------------------------- AT24C02测试 写入一个字节再读出来比较 ------------------------------------------*/ static s32 at24cxx_test(void) { s32 data; /*写入数据*/ data=i2c_smbus_write_byte_data(at24cxx_client,0,0xAA); if(data<0)printk("at24cxx write data error!\n"); msleep(2); /*写延时*/ /*读出数据*/ data=i2c_smbus_read_byte_data(at24cxx_client,0); if(data<0)printk("at24cxx read data error!\n"); if(data==0xAA) { return 0; } else { return -1; } } /*---------------------------------- 读数据函数 参数 addr:地址 len :长度 str :存放数据首地址 ------------------------------------*/ static u8 i2c_read_data(u8 addr,u8 len,u8* str) { u8 i,flag=0; for(i=0;i<len;i++) { /*循环读出数据*/ flag=i2c_smbus_read_byte_data(at24cxx_client,addr+i); if(flag<0)return -1; str[i]=flag; } return flag; } static int at24cxx_open(struct inode *my_inode, struct file *my_file) { return 0; } static u8 at24cxx_addr=0; static ssize_t at24cxx_read(struct file *my_file, char __user *buf, size_t my_len, loff_t * my_loff) { int error; u8 read_buff[100]; i2c_read_data(at24cxx_addr,my_len,read_buff); error=copy_to_user(buf,read_buff,my_len); return error; } static ssize_t at24cxx_write(struct file *my_file, const char __user *buf, size_t my_len, loff_t *my_loff) { int error; u8 write_buff[100]; unsigned char key=0; error=copy_from_user(write_buff,buf,my_len); //接收应用层的数据 error=i2c_write_data(at24cxx_addr,my_len,write_buff); return error; } static int at24cxx_release(struct inode *my_inode, struct file *my_file) { return 0; } static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功时调用 { at24cxx_client=client; s32 data=0; printk("<1>""AT24C02驱动端资源匹配成功!\n"); printk("<1>""驱动端IIC匹配的地址=0x%x\n",client->addr); mutex_lock(&my_mutexname); /*获取互斥锁*/ /* 检测适配器是否支持smbus字节读写函数 */ if(i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { printk("适配器支持smbus字节读写函数\n"); } if(at24cxx_test()<0) /*测试AT24C02是否OK*/ { printk("AT24C02 device read/write error!\n"); } else { printk("AT24C02 device read/write ok!\n"); } if(misc_register(&misce_at24cxx)==0)/*注册*/ { printk("misc AT24C02 init ok!\n"); } else { printk("misc AT24C02 init error!\n"); } mutex_unlock(&my_mutexname); /*释放互斥锁*/ return 0; } static int i2c_remove(struct i2c_client *client) { if(misc_deregister(&misce_at24cxx)==0)/*注销*/ { printk("at24cxx remove exit ok!\n"); } printk("i2c_remove!!!\n"); printk("i2c_驱动端卸载成功!!!\n"); return 0; } struct i2c_driver i2c_drv = { .driver = //这个不添加会报错,实际上没作用 { .name = "AT24C02", /*sys/bus/i2c/drivers*/ .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"); |
17.13 IIC接口PCF8591ADC芯片驱动
PCF8591是一种具有I2C总线接口的A/D转换芯片。在与CPU的信息传输过程中仅靠时钟线SCL和数据线SDA就可以实现。
17.13.1 应用层代码
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /* PCF8591 应用层测试代码 */ int main(int argc,char **argv) { unsigned char data=0; int fp; float tmp; // tmp=5.34v 0.34 int a; int b; fp=open("/dev/Tiny4412_PCF8591",O_RDWR); if(fp<0) /*判断文件是否打开成功*/ { printf("PCF8591 driver open error!\n"); return -1; } while(1) { read(fp,&data,1); write(fp,&data,1); printf("ADC1=%d\n",data); tmp=(float)data*(5.0/255); //电压= 采集的数字量*(参考电压/分辨率); a=tmp; //a=5 tmp=5.3 b=(int)((tmp-a)*1000); //b=0.34 printf("ADC1=%d.%dV\r\n",(int)a,(int)b); sleep(1); } close(fp); return 0; } |
17.13.2 设备端代码
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> /*获取总线*/ struct i2c_adapter *i2c_adap; //获取到的总线存放在这个结构体 static struct i2c_client *i2cClient = NULL; //PCF8591固定地址 b1001 //PCF8591硬件地址 b000 //组合:b1001000 = 0x48 //注意:IIC标准地址是7位 static unsigned short const i2c_addr_list[] = { 0x48, I2C_CLIENT_END };//地址队列 static int __init i2c_dev_init(void) { struct i2c_board_info i2c_info;//设备描述结构体,里面存放着欲设备的名字还有地址 i2c_adap = i2c_get_adapter(0); //获取0号总线 if(i2c_adap==NULL) { printk("PCF8591--II总线0 获取失败!!\n"); } memset(&i2c_info,0,sizeof(struct i2c_board_info));//把设备描述结构体清空结构体清空 strlcpy(i2c_info.type,"Tiny4412_PCF8591",I2C_NAME_SIZE);//把设备的名字赋值给i2c_info i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); if(i2cClient==NULL) { printk("PCF8591 0x%x:地址不可用!!\n",i2c_addr_list[0]); } i2c_put_adapter(i2c_adap); printk("PCF8591_dev_init初始化成功!!\n"); return 0; } static void __exit i2c_dev_exit(void)//平台设备端的出口函数 { /*注销设备*/ i2c_unregister_device(i2cClient); i2c_release_client(i2cClient); printk("PCF8591_dev_exit ok!!\n"); } module_init(i2c_dev_init); module_exit(i2c_dev_exit); MODULE_LICENSE("GPL"); |
17.13.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> 中断IO口定义*/ #include <linux/workqueue.h> /*工作队列相关*/ #include <linux/mutex.h> /*互斥信号量头文件*/ #include <linux/delay.h> #include <linux/miscdevice.h> /*杂项设备相关结构体*/ #include <linux/fs.h> /*文件操作集合头文件*/ #include <linux/uaccess.h> /*使用copy_to_user和copy_from_user*/ #define AIN0 0x40 #define AIN1 0x41 #define AIN2 0x42 #define AIN3 0x43 static struct i2c_client *PCF8591_client; /*IIC设备总线*/ /*读取PCF8591 ADC数据*/ unsigned char PCF8591_ReadADC(unsigned char ch) { return i2c_smbus_read_byte_data(PCF8591_client,ch); } static int PCF8591_open(struct inode *my_inode, struct file *my_file) { return 0; } static ssize_t PCF8591_read(struct file *my_file, char __user *buf, size_t my_len, loff_t * my_loff) { unsigned char data=PCF8591_ReadADC(AIN0); copy_to_user(buf,&data,1); data=PCF8591_ReadADC(AIN1); printk("1:%d\r\n",data); data=PCF8591_ReadADC(AIN2); printk("2:%d\r\n",data); data=PCF8591_ReadADC(AIN3); printk("3:%d\r\n",data); return 0; } static ssize_t PCF8591_write(struct file *my_file, const char __user *buf, size_t my_len, loff_t *my_loff) { //DAC输出 i2c_smbus_write_byte_data(PCF8591_client,0x40,100); return 0; } static int PCF8591_release(struct inode *my_inode, struct file *my_file) { return 0; } /*定义一个文件操作集合结构体*/ static struct file_operations ops_PCF8591={ .owner = THIS_MODULE, 读函数-被应用层read函数调用*/ 写函数-被应用层write函数调用*/ 打开函数-被应用层open函数调用*/ 释放函数*/ }; /*定义一个杂项设备结构体*/ static struct miscdevice misce_PCF8591={ .minor =MISC_DYNAMIC_MINOR, /*自动分配次设备号*/ .name = "Tiny4412_PCF8591", /*名称 在dev/目录下边可以找到*/ .fops = &ops_PCF8591, /*文件操作集合*/ }; static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功时调用 { PCF8591_client=client; printk("<1>""驱动端IIC匹配的地址=0x%x\n",client->addr); /* 检测适配器是否支持smbus字节读写函数 */ if(i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { printk("适配器支持smbus字节读写函数\n"); } /*注册*/ misc_register(&misce_PCF8591); return 0; } static int i2c_remove(struct i2c_client *client) { misc_deregister(&misce_PCF8591);/*注销*/ printk("i2c_驱动端卸载成功!!!\n"); return 0; } /* IIC驱动端 */ static const struct i2c_device_id i2c_id[] = { {"Tiny4412_PCF8591",0},//设备端的名字为"my_PCF8591",后面的表示需要私有数据 {} }; struct i2c_driver i2c_drv = { .driver= { .name = "PCF8591", .owner = THIS_MODULE, }, .probe = i2c_probe, .remove = i2c_remove, .id_table = i2c_id, }; 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"); |