I2C总线知识
I2C子系统框架
I2C
是一个不是很复杂的通信协议,在裸机上能够轻易实现,但是linux
系统下的I2C
系统框架复杂的,简单控制IO
电平输出不行,这是因为与裸机相比,linux
是一个多进程系统,在进行I2C
通信过程中很可能被其他进程打断,这样就会造成数据丢失,为了避免此种情况,便有了现在的I2C
子系统,除了保证数据不丢失,可移植性也大大增强。
I2C核心
I2C
核心提供了I2C
总线驱动和设备驱动的注册、注销方法(),相关数据结构,I2C
通信方法(algorithm)
上层的、与具体适配器无关的的代码以及探测设备、检测设备地址的上层代码等
I2C总线驱动(I2C adapter/algorithm driver)
I2C
总线驱动是对I2C
硬件控制驱动代码的实现,实现I2C
基本时序的产生以及数据的传输,是真正的硬件数据收到实现的地方,这层一般都是芯片厂家实现的,一般是使用平台模型实现,做二次开发不需要实现这一层代码,顶多是移植。
I2C客户驱动程序
I2C
客户驱动程序对系统中I2C
设备操作具体实现代码,使用两个结构体来描述:
i2c_client:用来表示i2c
设备信息的,提供给i2c设备驱动使用(与platform_device相似
)
i2c_driver:用来表示i2c
设备的具体驱动代码实现(与platform_driver相似
)
要实现一个i2c
设备的设备驱动就是分别实现i2c_client
,i2c_driver
结构,并且使用核心层提供的函数分别注册。但是实际编写过程,并不需要自己去创建i2c_client
,而是给内核提供i2c_client
所需要的信息,然后内核通过i2c_board_info
结构传递i2c_client
所需要的信息给内核动态创建注册i2c_client
结构。这一点和平台设备模型有点区别。
这一层是做二次开发的驱动工程师必须掌握的的编程
I2C相关重要数据结构
struct i2c_client –I2C设备结构
路径:i2c.h linux-3.5\include\linux
作用:用来描述一个i2c
设备的基本信息:器件地址、所绑定的适配器、使用到的中断编号、需要传递给i2c_driver
使用的平台数据等
struct i2c_client {
unsigned short flags; /* div., see below */
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 i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
重要成员:
flags:地址长度,如是10
位还是7
位地址,默认是7
位地址。如果是10
位地址器件,则设置为I2C_CLIENT_TEN
addr:具体I2C
器件如(at24c02
),设备地址
name:设备名,用于和i2c_driver
层匹配使用的,可以和平台模型中的平台设备层platform_driver
中的name
作用是一样的。
adapter:本设备所绑定的适配器结构(CPU
有很多I2C
适配器,类似单片机有串口1
、串口2
等等,在linux
中每个适配器都用一个结构描述)
driver:指向匹配的i2c_driver
结构,不需要自己填充,匹配上后内核会完成这个赋值操作
dev:内嵌的设备模型,可以使用其中的platform_data
成员传递给任何数据给i2c_driver
使用。
irq:设备需要使用到中断时,把中断编号传递给i2c_driver
进行注册中断,如果没有就不需要填充。(有的I2C
器件有中断引脚编号,与CPU
相连)
i2c_driver–I2C驱动结构
路径:i2c.h linux-3.5\include\linux
作用:内核使用这个结构来描述一个i2c
设备驱动层,要编写设备驱动层代码,核心就是实现该结构,并且使用核心层提供注册函数进行注册
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
* near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
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;
};
重要成员:
attach_adapter:在i2c_client
与i2c_driver
匹配后执行该函数,但这个接口不久后会消失,不建议使用这个接口
detach_adapter:在取消i2c_client
与i2c_driver
匹配绑定后后执行该函数,但这个接口不久后会消失,不建议使用这个接口
attach_adapter
,detach_adapter
是一对函数,功能相反
probe:新版本内核接口,用来代替attach_adapter
,在i2c_client
与i2c_driver
匹配后执行该函数
remove:新版本内核接口,用来代替 detach_adapter
,在取消i2c_client
与i2c_driver
匹配绑定后后执行该函数
driver:这个成员类型在平台设备驱动层中也有,而且使用其中的name
成员来实现平台设备匹配,但是i2c
子系统中不使用其中的name
进行匹配,这也是i2c
设备驱动模型和平台设备模型匹配方法的一点区别
id_table:用来实现i2c_client
与i2c_driver
匹配绑定,当i2c_client
中的name
成员和i2c_driver
中id_table
中name
成员相同的时候,就匹配上了。
补充:i2c_client
与i2c_driver
匹配问题
- i2c_client
中的name
成员和i2c_driver
中id_table
中name
成员相同的时候
- i2c_client
指定的信息在物理上真实存放对应的硬件,并且工作是正常的才会绑定上,并执行其中的probe
接口函数
这第二点要求和平台模型匹配有区别,平台模型不要求设备层指定信息在物理上真实存在就能匹配
struct i2c_adapter适配器结构
路径:i2c.h linux-3.5\include\linux
作用:内核使用该结构来描述一个物理上的i2c
总线控制器(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;
};
重要成员
algo:通信算法结构,其中包含了真正指向硬件收发函数的指针
algo_data:通信算法私有数据,自定义
timeout:超时时间,单位是jiffies
当发送数据或接收数据超过该时间就会认定失败
retries:传输失败后,再尝试的次数
dev:内嵌的设备模型,可以使用其中的platform_data
成员保存平台设备层传递下来的参数(一般情况下适配器驱动都是以平台模型实现的)。
nr:芯片集成的I2C
控制器一般有好几个,在软件层面的每个控制器都用一个结构体描述,系统中他们靠链表衔接在一起,区分每个适配器就是靠查找控制器链表成员的nr
来区分,物理上i2c
总线编号,I2C0
对应填0
,I2C1
对应填1
······
struct i2c_algorithm
路径:i2c.h linux-3.5\include\linux
作用:该结构属于struct i2c_adapter
结构成员,内核使用该结构来描述适配器使用什么样方式实现数据收发
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
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 *);
};
重要成员
master_xfer:标准i2c
接口真正的收发函数,必须实现
smbus_xfer:标准smbus
接口真正的收发函数,根据需求
补充:
smbus
和i2c
协议非常类似,就在i2c
协议的基础上衍生而来的,大部分场合是兼容的,所以linux
系统的i2c
子系统中把smbus
总线和i2c
总线合在一起
struct i2c_msg
路径:i2c.h linux-3.5\include\linux
作用:该结构属于struct i2c_algorithm
中master_xfer
函数的参数,内核使用该结构来传递一则消息给适配器驱动层,适配器根据该结构中的信息,执行相应的代码操作i2c
硬件控制器的寄存器,实现对数据的收发。
要发送的数据或者要接受的数据信息,操作设备的地址就包含在这个结构中。
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 */
#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 */
__u8 *buf; /* pointer to msg data */
};
重要成员:
addr:要读写的设备地址
flags:表示地址的长度,读写功能。如果是10
位地址必须设置I2C_M_TEN
,如果是读操作必须设置有I2C_M_TEN
······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度
i2c_board_info
该结构表示一个i2c
设备信息,系统初始化期间,通过函数i2c_register_board_info
将设备信息添加到全局链表__i2c_board_list
中,在适配器注册时,从__i2c_board_list
取出该结构生成的i2c
设备(struct i2c_client
)
注册该结构使用的方法是使用下面的函数进行注册
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len)
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
int irq;
};
重要成员
type:用来初始化i2c_client
结构中的name
成员
flags:用来初始化i2c_client
结构中的flags
成员
addr:用来初始化i2c_client
结构中的addr
成员
platform_data:用来初始化i2c_client
结构中的.dev.platform_data
成员
archdata:用来初始化i2c_client
结构中的.dev.archdata
成员
irq:用来初始化i2c_client
结构中的irq
成员
核心就是记住该结构和i2c_client
结构成员的对应关系。在i2c
子系统不直接创建i2c_client
结构,只是提供struct i2c_board_info
结构信息,让子系统动态创建,并且注册。
注意
对比i2c_board_info
与i2c_client
结构的必须成员,i2c_board_info
中还缺少一个很重要的成员adapter
,adapter
成员可以通过注册函数:
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len)
内部通过busnum
来查找对应编号的i2c_adapter
结构
I2C子系统常用API
I2C适配器驱动层使用的API
EXYNOS4412芯片I2C驱动代码:i2c-s3c2410.c linux-3.5\drivers\i2c\busses
s3c2410
是三星公司arm9芯片,EXYNOS4412
是三星A9
芯片,I2C
作为一个外设,只是简单的从ARM9
芯片上移动到A9
芯片,三星公司的I2C
驱动代码可以同时支持本公司ARM9
,ARM11
,A8
,A9
类芯片
i2c-s3c2410.c
使用平台名实现,所以,你要想使用I2C
总线,则需要根据这个驱动移植,完成平台设备层代码,配套的内核源码已经在mach-tiny4412.c
中移植好
适配器简要说明:
驱动开发人员自定义了一个数据结构,其中内嵌了struct i2c_adapter
结构,
struct s3c24xx_i2c {
········
struct i2c_adapter adap;
······
};
注册适配器
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
·······
//分配自定义数据结构空间,其中适配器结构在这里面分配
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
//通信算法
i2c->adap.algo = &s3c24xx_i2c_algorithm;
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
·······
/* Note, previous versions of the driver used i2c_add_adapter()
* to add the bus at any number. We now pass the bus number via
* the platform data, so if unset it will now default to always
* being bus 0.
*/
i2c->adap.nr = i2c->pdata->bus_num;
i2c->adap.dev.of_node = pdev->dev.of_node;
//注册适配器核心结构,相当于实现了一个适配器
ret = i2c_add_numbered_adapter(&i2c->adap);
··········
}
其中:通信算法
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
上面probe
接口函数中最后使用 i2c_add_numbered_adapter
已经实现i2c_adapter
结构
注销适配器;
int i2c_del_adapter(struct i2c_adapter *adap)
I2C设备驱动层使用的API
增加/删除i2c_driver
路径:i2c.h linux-3.5\include\linux
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
/*
* An i2c_driver is used with one or more i2c_client (device) nodes to access
* i2c slave chips, on a bus instance associated with some i2c_adapter.
*/
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
功能:注册一个i2c_driver
结构
返回值:0
成功,负数 失败
注销i2c_driver
void i2c_del_driver(struct i2c_driver *driver);
功能:注销一个i2c_driver
结构
返回值:无
获得/释放 i2c_adapter
路径:i2c-core.c linux-3.5\drivers\i2c
struct i2c_adapter *i2c_get_adapter(int nr)
功能:通过i2c
总线编号获得内核中的i2c_adapter
结构地址,然后用户可以使用这个结构地址就可以给i2c_client
结构使用,从而实现i2c_client
进行总线绑定,从而增加适配器引用计数。
返回值:
NULL:没有找到指定总线编号适配器结构
非NULL:指定nr
的适配器结构内存地址
减少引用计数:当使用·i2c_get_adapter·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)
void i2c_put_adapter(struct i2c_adapter *adap)
创建/删除 i2c_client
创建i2c_client
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))
功能:根据参数adap
,info
,addr
,addr_list
动态创建i2c_client
并且进行注册
参数:
adap:i2c_client
所依附的适配器结构地址
info:i2c_client
基本信息
addt_list: i2c_client
的地址(地址定义形式是固定的,一般是定义一个数组,数组必须以I2C_CLIENT_END
结束,示例:unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
probe:回调函数指针,当创建好i2c_client
后,会调用该函数,一般没有什么特殊需求传递NULL
。
返回值:
非NULL:创建成功,返回创建好的i2c_client
结构地址
NULL:创建失败
示例:
struct i2c_adapter *ad;
struct i2c_board_info info=[0];
unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
//假设设备挂在i2c-2总线上
ad=i2c_get_adapter(2);
//自己填充board_info
strcpy(inf.type,"ft5x0x");
info.flags=0;
//动态创建i2c_client并且注册
i2c_new_probed_device(ad,info,ft5x0x_i2c,NULL);
I2C传输、发送接收
路径:i2c-core.c linux-3.5\drivers\i2c
I2C读取数据函数
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
功能:实现标准的I2C
读时序,数据可以是N
个数据,这个函数调用时候默认已经包含发送从机地址+读方向这一环节了
参数:
client:设备结构
buf:读取数据存放缓冲区
count:读取数据大小 不大于64k
返回值:
失败:负数
成功:成功读取的字节数
I2C发送数据函数
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
功能:实现标准的I2C
写时序,数据可以是N
个数据,这个函数调用时候默认已经包含发送从机地址+写方向这一环节了
参数:
client:设备结构地址
buf:发送数据存放缓冲区
count:发送数据大小 不大于64k
返回值:
失败:负数
成功:成功发送的字节数
i2c收发一体化函数,收还是发由参数msgs的内存决定
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
功能:根据msgs
进行手法控制
参数:
adap:使用哪一个适配器发送信息,一般是取i2c_client
结构中的adapter
指针作为参数
msgs:具体发送消息指针,一般情况下是一个数组
num:表示前一个参数msgs
数组有多少个消息要发送的
返回值:
负数:失败
> 0
表示成功发送i2c_msg
数量
补充i2c_msg结构:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
重要成员:
addr:要读写的设备地址
flags:表示地址的长度,读写功能。如果是10
位地址必须设置I2C_M_TEN
,如果是读操作必须设置有I2C_M_TEN
······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度
SMBus相关的API
SMBus
是intel
发明的,可以看做是i2c
总线的一个子集,很多场合下都可以使用SMBus
函数控制i2c
设备,他们是兼容的。
SMBus
最高速度:100kbit/s
i2c
: 100kbit/s
400kbit/s
3.4Mbit/s
内核提供给SMBus
相当丰富的API
接口
···············
这里不做介绍
I2C子系统编程
I2C子系统下设备驱动有两种模式;
用户模式设备驱动编程:这种方式依赖
I2C
子系统中的i2c-dev.c
这个总线字符设备驱动,这个驱动主设备号是89
,这个驱动并没有绑定任何一个从设备,可以使用他控制任何设备,通过ioctl
设置要控制的设备地址,然后通过read
,write
方式操作i2c
总线设备。
这种方式对应用程序编写者对硬件认识要求高,必须掌握该设备的控制逻辑才可以正确和设备进行通信,实际开发中都是应用和驱动分别作的,写应用程序的人可能完全不懂硬件。按照前面的框架图,自己去实现一个
i2c_client
和i2c_driver
结构,使用响应函数进行注册。这种方式是比较好的方式,把设备实际控制逻辑封装在驱动层,应用编程序只需要简单调用read
,write
函数就可以实现和设备进行数据交互,对硬件只是没什么要求。
常规i2c设备驱动
i2c_client层的实现
有两种方法实现:静态注册和动态注册
- 静态注册方式(实际工作中最常用的方法)
把i2c_client
结构所需的信息及注册直接编译在zImage
中,不能以模块方式进行编写,对应的i2c_driver
也不能通过编译成模块,后期加载。这种方式在调试驱动时候每修改一次代码就需要重新编译内核,然后下载内核测试,所以这种方式比较麻烦,一般是调试好驱动程序后,产品发布的时候在使用这种方式修改内核源码实现i2c
设备驱动 - 动态注册方式:
可以把i2c_client
和i2c_driver
分别编写成独立的ko
文件,系统启动使用insmod
命令安装,这种发放在开发阶段会方便一些。
静态注册
使用的一个注册函数int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len)
功能:i2c_register_board_info
将设备信息添加到全局链表__i2c_board_list
中,这个函数并不负责创建i2c_client
结构,i2c-client
结构是在,在适配器注册时,从__i2c_board_list
取出该结构生成的i2c
设备(struct i2c_client
)
参数:
busnum:i2c
设备所挂的总线编号,根据自己的硬件设备而定
i2c_board_info:存放i2c
物理设备基本信息的i2c_board_info
结构指针,一般情况下是一个数组,这样,这个函数就可以一次性注册多个物理上的i2c
设备到__i2c_board_list
链表上去了。
len:表明数组上多少要注册的i2c_board_info
结构
返回值:
0:注册成功
负数:注册失败
注意:这个函数实现i2c-boardinfo.c linux-3.5\drivers\i2c
中并没有使用EXPORT_SYMBOL
或EXPORT_SYMBOL_GPL
导出,其他模块文件不能使用,也就是你不能在一个模块文件中调用这个函数,(不能把包含有该函数调用的文件编译为ko
)
在tiny4412
开发板中的,触摸屏采用的芯片是(ft5x0x
),在板级启动文件中已经有了i2c
的驱动,具体文件在mach-tiny4412.c linux-3.5\arch\arm\mach-exynos
中
我们可以看到在static void __init smdk4x12_machine_init(void)
开发板启动函数中已经有了i2c_register_board_info
驱动注册,具体定义的触摸芯片的borard_info
定义为
static struct i2c_board_info smdk4x12_i2c_devs1[] __initdata = {
#ifdef CONFIG_TOUCHSCREEN_FT5X0X
{
I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),
//平台数据 定义的一些与芯片相关的信息
.platform_data = &ft5x0x_pdata,
},
#endif
#ifdef CONFIG_TOUCHSCREEN_IT7260
{
I2C_BOARD_INFO("IT7260", (0x8C>>1)),
.irq = TS_INT_GPIO, /* do gpio_to_irq() in driver */
·························
补充内容
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
补充内容
static struct ft5x0x_i2c_platform_data ft5x0x_pdata = {
.gpio_irq = EXYNOS4_GPX1(6),
.irq_cfg = S3C_GPIO_SFN(0xf),
.screen_max_x = 800,
.screen_max_y = 1280,
.pressure_max = 255,
};
我们可看到已经定义了针对ft5x0x
的board_info
结构,填充了一些信息名称,地址,设备信息等,根据smdk4x12_i2c_devs1[]
命名可以看出这是挂接在适配器1
上的设备,由于适配器1
上的i2c
设备有很多,上面定义的结构以宏来区分,改结构定义很长只截取了一小段。
所以看出源码中已经有了对ft5x0x
的定义以及注册,
static void __init smdk4x12_machine_init(void)
{
······
s3c_i2c1_set_platdata(&tiny4412_i2c1_data);
i2c_register_board_info(1, smdk4x12_i2c_devs1,
ARRAY_SIZE(smdk4x12_i2c_devs1));
·················
}
内核已经实现了对i2c
设备的注册,驱动其实在源码中也已经写好,具体是在ft5x06_ts.c linux-3.5\drivers\input\touchscreen
,我们可以利用源码中自带的i2c
平台设备驱动,在自己写一份自己的驱动,添加到内核,把系统自带的排除出去。试验一下效果。
具体做法:
//my_ft5x0x.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>
//定义本驱动支持的设备列表,这里的name和client.name进行比较,相同表示支持
const struct i2c_device_id ft5x0x_id_table[]={
{"ft5x0x_ts,0"}, //名字与设备层相同 匹配使用
{} //空成员 表示结束
};
int ft5x0x_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
printk("我写的驱动已经挂接上了");
printk("client->name:%s client->addr:0x:%x\r\n",client->name,client->addr);
}
int ft5x0x_remove(struct i2c_client *client)
{
printk("我写的驱动已经移除了");
}
//定义i2c_driver 结构并初始化成员
static struct i2c_driver ft5x0x_driver={
.probe = ft5x0x_probe,
.remove = ft5x0x_remove,
.id_table = ft5x0x_id_table,
.driver ={
.name="my_i2c", //名字不做匹配使用
.owner=THIS_MODULE,
},
};
//驱动初始化
static int __init ft5x0x_dev_init(void)
{
int ret;
ret=i2c_add_driver(&ft5x0x_driver);
if(ret<0)
{
printk("驱动注册失败\r\n");
}
return 0;
}
//驱动卸载
static void __exit ft5x0x_dev_exit(void)
{
}
module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");
把自己写的驱动my_ft5x0x.c
复制到linux-3.5\drivers\input\touchscreen
,修改此目录下的Makefile
将源码中的触摸屏芯片的驱动ft5x06_ts.c
注释掉,添加自己的驱动my_ft5x0x.c
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
#obj-$(CONFIG_TOUCHSCREEN_FT5X0X) += ft5x06_ts.o
obj-$(CONFIG_TOUCHSCREEN_FT5X0X) += my_ft5x0x.o
回到linux-3.5
根目录重新编译内核,进行内核烧写,启动开发板,查看开发板运行log
日志,是否将自己写的i2c
驱动模板成功进行挂接。
[ 2.000000] usbcore: registered new interface driver btusb
[ 2.000000] cpuidle: using governor ladder
[ 1.920000] 我写的驱动已经挂接上了
[ 2.020000]client->name:ft5x0x_ts client->addr:0x:38
[ 3.025000] usb 1-2: New USB device strings: Mfr=0, Product=0, SerialNumber=0
在驱动代码中实现对触摸屏触控芯片固件数据的读取
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>
//设置全局变量
struct i2c_client *clt;
void test_and_set(struct i2c_client *clt)
{
char subaddr = 0xA6;
s32 val;
struct i2c_msg msg[2]={0};
msg[0].addr = clt->addr; //器件地址
msg[0].flags = 0; //地址长度七位
msg[0].len = 1; //数据长度 1位
msg[0].buf = &subaddr; //发送数据缓冲区
msg[1].addr = clt->addr; //器件地址
msg[1].flags = I2C_M_RD; //地址长度七位
msg[1].len = 1; //数据长度 1位
msg[1].buf = &val; //发送数据缓冲区
//第一种方式 利用smbus读取函数
//读取器件ID 寄存器地址0XA6
val = i2c_smbus_read_byte(clt);
printk("firmware ID:%d\r\n",val);
//第二种方式
i2c_master_send(clt,&subaddr,1 );
i2c_master_recv(clt, (char *) val,1);
printk("firmware ID:%d\r\n",val);
//第三种方式
i2c_transfer(clt->adapter,msg,2);
printk("firmware ID:%d\r\n",val);
}
//定义本驱动支持的设备列表,这里的name和client.name进行比较,相同表示支持
const struct i2c_device_id ft5x0x_id_table[]={
{"ft5x0x_ts",(0x70) >> 1}, //名字与设备层相同 匹配使用
{} //空成员 表示结束
};
int ft5x0x_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
printk("我写的驱动已经挂接上了\r\n");
printk("client->name:%s client->addr:0x:%x\r\n",client->name,client->addr);
test_and_set(client);
}
int ft5x0x_remove(struct i2c_client *client)
{
printk("我写的驱动已经移除了");
}
//定义i2c_driver 结构并初始化成员
static struct i2c_driver ft5x0x_driver={
.probe = ft5x0x_probe,
.remove = ft5x0x_remove,
.id_table = ft5x0x_id_table,
.driver ={
.name="my_i2c", //名字不做匹配使用
.owner=THIS_MODULE,
},
};
//驱动初始化
static int __init ft5x0x_dev_init(void)
{
int ret;
ret=i2c_add_driver(&ft5x0x_driver);
if(ret<0)
{
printk("驱动注册失败\r\n");
}
return 0;
}
//驱动卸载
static void __exit ft5x0x_dev_exit(void)
{
i2c_del_driver(&ft5x0x_driver);
}
module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");
重新编译内核开发板运行效果
[ 1.920000] 我写的驱动已经挂接上了
[ 1.920000] client->name:ft5x0x_ts client->addr:0x:38
[ 1.920000] firmware ID:24
[ 1.920000] firmware ID:24
[ 1.920000] firmware ID:24
读取坐标
根据手册自己尝试,注意,此款触摸芯片,触发一次产生中断,可以读取数据信息,但是在中断函数中不要使用i2c
读取函数,因为,这些函数会造成系统休眠,在中断函数中使用,会造成内核崩溃。最好采用工作队列、tasklet
等方式。
动态注册
前面讲的方法是通过静态方式注册,自己并没有编写设备信息,只是简单的写了设备驱动层代码,而且只能编译进内核,不能以模块的方式添加进内核,所以在调试的时候会很麻烦,不断地烧写内核,编译内核,虽然I2C
设备常用的驱动方式就是编译进内核的方式,但是在调试的时候可以采用以模块方式安装的办法(动态注册),方便调试,驱动模块成熟以后在编译进内核(修改为静态注册的方式)。
具体步骤
- 系统源码中已经自带了触摸屏驱动,需要去除掉其驱动支持,在
linux
内核执行make menuconfig
-》设备驱动-》触摸屏驱动-》ft5x0x
将选项勾掉,重新编译内核,烧写内核。
这是什么原理呢?我们在源码中可以看到,添加驱动是依赖宏来添加的,内核裁剪的时候就是将其对应的宏取消掉,这样它就不会出现在适配器队列中,也就不会创建对应的i2c_client
,就是说内核找不到这个设备的信息,驱动也就失效了,我们就可以添加自己的触摸屏驱动了。
编写i2c_client
代码
设备代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>
struct i2c_client *clt;
unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
//驱动初始化
static int __init ft5x0x_client_init(void)
{
int ret=0;
//定义适配器结构体
struct i2c_adapter *ad;
struct i2c_board_info info={0};
ad=i2c_get_adapter(1);
//自己填充board_info
strcpy(info.type,"ft5x0x_ts");
info.flags=0;
//动态创建i2c_client并且注册
clt=i2c_new_probed_device(ad,&info,ft5x0x_i2c,NULL);
if(clt==NULL)
{
printk("creat is fail\n");
}
i2c_put_adapter(ad);
}
//驱动卸载
static void __exit ft5x0x_client_exit(void)
{
i2c_unregister_device(clt);
//i2c_del_adapter(ad);
}
module_init(ft5x0x_client_init);
module_exit(ft5x0x_client_exit);
MODULE_LICENSE("GPL");
驱动代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>
//设置全局变量
void test_and_set(struct i2c_client *clt)
{
char subaddr = 0xA6;
s32 val;
struct i2c_msg msg[2]={0};
msg[0].addr = clt->addr; //器件地址
msg[0].flags = 0; //地址长度七位
msg[0].len = 1; //数据长度 1位
msg[0].buf = &subaddr; //发送数据缓冲区
msg[1].addr = clt->addr; //器件地址
msg[1].flags = I2C_M_RD; //地址长度七位
msg[1].len = 1; //数据长度 1位
msg[1].buf = &val; //发送数据缓冲区
//第一种方式 利用smbus读取函数
//读取器件ID 寄存器地址0XA6
val = i2c_smbus_read_byte(clt);
printk("firmware ID:%d\r\n",val);
//第二种方式
i2c_master_send(clt,&subaddr,1 );
i2c_master_recv(clt, (char *)&val,1);
printk("firmware ID:%d\r\n",val);
//第三种方式
i2c_transfer(clt->adapter,msg,2);
printk("firmware ID:%d\r\n",val);
}
//定义本驱动支持的设备列表,这里的name和client.name进行比较,相同表示支持
const struct i2c_device_id ft5x0x_id_table[]={
{"ft5x0x_ts",(0x70) >> 1}, //名字与设备层相同 匹配使用
{} //空成员 表示结束
};
int ft5x0x_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
printk("我写的驱动已经挂接上了\r\n");
printk("client->name:%s client->addr:0x:%x\r\n",client->name,client->addr);
test_and_set(client);
}
int ft5x0x_remove(struct i2c_client *client)
{
printk("我写的驱动已经移除了");
}
//定义i2c_driver 结构并初始化成员
static struct i2c_driver ft5x0x_driver={
.probe = ft5x0x_probe,
.remove = ft5x0x_remove,
.id_table = ft5x0x_id_table,
.driver ={
.name="my_i2c", //名字不做匹配使用
.owner=THIS_MODULE,
},
};
//驱动初始化
static int __init ft5x0x_dev_init(void)
{
int ret;
ret=i2c_add_driver(&ft5x0x_driver);
if(ret<0)
{
printk("驱动注册失败\r\n");
}
return 0;
}
//驱动卸载
static void __exit ft5x0x_dev_exit(void)
{
i2c_del_driver(&ft5x0x_driver);
}
module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");
Makefile
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += my_i2c_client.o
obj-m += my_i2c_driver.o
开发板运行效果
[ 58.510000] 我写的驱动已经挂接上了
[ 58.510000] client->name:ft5x0x_ts client->addr:0x:38
[ 58.510000] firmware ID:24
[ 58.510000] firmware ID:24
[ 58.510000] firmware ID:24