资源描述
linux内核I2C总线驱动实现
谈到在linux系统下编写I2C驱动,目前主要有两种方式,一种是把I2C设备当作一个普通的字符设备来处理,另一种是利用linux I2C驱动体系结构来完成。下面比较下这两种驱动。
第一种方法的好处(对应第二种方法的劣势)有:
● 思路比较直接,不需要花时间去了解linux内核中复杂的I2C子系统的操作方法。
第一种方法问题(对应第二种方法的好处)有:
● 要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器操作;
● 要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可移植性差;
● 对内核的资源无法直接使用。因为内核提供的所有I2C设备器及设备驱动都是基于I2C子系统的格式。I2C适配器的操作简单还好,如果遇到复杂的I2C适配器(如:基于PCI的I2C适配器),工作量就会大很多。
本文针对的对象是熟悉I2C协议,并且想使用linux内核子系统的开发人员。网络和一些书籍上有介绍I2C子系统的源码结构。但发现很多开发人员看了这些文章后,还是不清楚自己究竟该做些什么。究其原因还是没弄清楚I2C子系统为我们做了些什么,以及我们怎样利用I2C子系统。本文首先要解决是如何利用现有内核支持的I2C适配器,完成对I2C设备的操作,然后再过度到适配器代码的编写。本文主要从解决问题的角度去写,不会涉及特别详细的代码跟踪。
I2C的通信肯定至少要有2个芯片完成,所以它的驱动是由2大部分组成:主芯片的i2c的驱动、从芯片的i2c的驱动。
注:万一选的都不支持怎么办?(只能2个芯片的驱动都得实现了,不过过程差不多)
1 分析linux内核中I2C驱动框架
1 .1主芯片的I2C的驱动
首先要查看linux内核是否支持主芯片中i2c驱动器,如果支持就配置一下就ok了,否则要编写主控芯片的i2c驱动器。
编写方法:
(1)要有i2c总线驱动(首先要查查内核i2c文件是否支持这种总线驱动,一般都有支持,如果没有只好自己写了)
(2)i2c设备驱动(主控芯片的地址等等信息)
这个过程都是差不多的,以后在分析。一般的主控芯片的i2c控制器linux内核基本上支持的很好,如:2410的i2c驱动器的支持。
1.2从芯片的I2C的驱动
下面主要分析从芯片的I2C驱动(也有2种方式,第一个是利用内核提供的i2c-dev.c来构建,另一个是自己写)
主要分析第一种方式:
利用系统给我们提供的i2c-dev.c来实现一个i2c适配器的设备文件。然后通过在应用层操作i2c适配器来控制i2c设备。
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的i2c设备的存储空间或寄存器,并控制I2C设备的工作方式。
需要特别注意的是:i2c-dev.c的read()、write()方法都只适合于如下方式的数据格式(可查看内核相关源码)
图1 单开始信号时序
所以不具有太强的通用性,如下面这种情况就不适用(通常出现在读目标时)。
图2 多开始信号时序
但是read和write方法适用性有限,只适用用于适配器支持i2c算法的情况,如:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
而不适合适配器只支持smbus算法的情况,如:
static const struct i2c_algorithm smbus_algorithm = {
.smbus_xfer = i801_access,
.functionality = i801_func,
};
基于以上原因,一般都不会使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以实现上面所有的情况(两种数据格式、以及I2C算法和smbus算法)。
针对i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。
struct i2c_rdwr_ioctl_data
{
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};
struct i2c_msg {
_ _u16 addr; /* slave address */
_ _u16 flags; /* 标志(读、写) */
_ _u16 len; /* msg length */
_ _u8 *buf; /* pointer to msg data */
};
针对smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。对于smbus算法,不需要考虑“多开始信号时序”问题。
struct i2c_smbus_ioctl_data {
__u8 read_write; //读、写
__u8 command; //命令
__u32 size; //数据长度标识
union i2c_smbus_data __user *data; //数据
};
下面以一个实例讲解操作的具体过程。通过S3C2410操作AT24C02 e2prom。实现在AT24C02中任意位置的读、写功能。
首先在内核中已经包含了对s3c2410 中的i2c控制器(总线驱动)驱动的支持。提供了i2c算法(非smbus类型的,所以后面的ioctl的命令是I2C_RDWR)
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
另外一方面需要确定为了实现对AT24C02 e2prom的操作,需要确定从机芯片的地址及读写访问时序。
● AT24C02地址的确定
原理图上将A2、A1、A0都接地了,所以地址是0x50。
● AT24C02任意地址字节写的时序
可见此时序符合前面提到的“单开始信号时序”
● AT24C02任意地址字节读的时序
可见此时序符合前面提到的“多开始信号时序”
下面开始具体代码的分析(代码在2.6.22内核上测试通过):
/*i2c_test.c
* hongtao_liu <lht@>
*/
#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#define I2C_RETRIES 0x0701
#define I2C_TIMEOUT 0x0702
#define I2C_RDWR 0x0707
/*********定义struct i2c_rdwr_ioctl_data和struct i2c_msg,要和内核一致*******/
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
struct i2c_msg
{
unsigned short addr;
unsigned short flags;
unsigned short len;
unsigned char *buf;
};
struct i2c_rdwr_ioctl_data
{
struct i2c_msg *msgs;
int nmsgs;
/* nmsgs这个数量决定了有多少开始信号,对于“单开始时序”,取1*/
};
/***********主程序***********/
int main()
{
int fd,ret;
struct i2c_rdwr_ioctl_data e2prom_data;
fd=open("/dev/i2c-0",O_RDWR); /* 为什么是i2c-0呢??那就要到内核里看啦,等会再说。
open底层调用了i2c_get_adapter(int id)函数,这个函数很重要,他可以识别占用了哪个i2c总线使用地0个i2c控制器 /dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c(这里说啦上面的为什么) 的方式,就没有,也不需要这个节,i2c_driver结构体中有attach_adapter方法:里面用device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d",adap->nr);I2C_MAJOR=89,即i2c-dev.c针对每个i2c适配器生成一个主设备号位89的设备文件,次设备要自己定义。
/dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c 的方式,就没有,也不需要这个节点。
*/
if(fd<0)
{
perror("open error");
}
e2prom_data.nmsgs=2;
/*
*因为操作时序中,最多是用到2个开始信号(字节读操作中),所以此将
*e2prom_data.nmsgs配置为2
*/
e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
if(!e2prom_data.msgs)
{
perror("malloc error");
exit(1);
}
ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
ioctl(fd,I2C_RETRIES,2);/*重复次数*/
/***write data to e2prom**/
/**/
e2prom_data.nmsgs=1;
(e2prom_data.msgs[0]).len=2; //1个 e2prom 写入目标的地址和1个数据
(e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
(e2prom_data.msgs[0]).flags=0; //write
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
(e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址
(e2prom_data.msgs[0]).buf[1]=0x58;//the data to write
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
if(ret<0)
{
perror("ioctl error1");
}
sleep(1);
/******read data from e2prom*******/
e2prom_data.nmsgs=2;
(e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
(e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
(e2prom_data.msgs[0]).flags=0;//write
(e2prom_data.msgs[0]).buf[0]=0x10;//e2prom数据地址
(e2prom_data.msgs[1]).len=1;//读出的数据
(e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址
(e2prom_data.msgs[1]).flags=I2C_M_RD;//read
(e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
(e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
if(ret<0)
{
perror("ioctl error2");
}
printf("buff[0]=%x/n",(e2prom_data.msgs[1]).buf[0]);
/***打印读出的值,没错的话,就应该是前面写的0x58了***/
close(fd);
i2c_put_adapter(client->adapter);//释放i2c总线
return 0;
}
以上讲述了一种比较常用的利用i2c-dev.c操作i2c设备的方法,这种方法可以说是在应用层完成了对具体i2c设备的驱动工作。
计划下一篇总结以下几点:
(1)在内核里写i2c设备驱动的两种方式:
● Probe方式(new style),如:
static struct i2c_driver pca953x_driver = {
.driver = {
.name = "pca953x",
},
.probe = pca953x_probe,
.remove = pca953x_remove,
.id_table = pca953x_id,
};
● Adapter方式(LEGACY),如:
static struct i2c_driver pcf8575_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "pcf8575",
},
.attach_adapter = pcf8575_attach_adapter,
.detach_client = pcf8575_detach_client,
};
(2)适配器驱动编写方法
(3)分享一些项目中遇到的问题
希望大家多提意见,多多交流。
2 I2C驱动编写
下面具体分析如何写第一部分:
2.1 主芯片的I2C驱动编写
主控芯片的i2c驱动分为2个步骤:
2.1.1写总线驱动
选了个主控芯片,比如:S3C8900(自己瞎选的芯片),在driver/i2c/busses/i2c-s3c2410.c中没有找到这个芯片的I2C支持(总线驱动支持)。(倒霉了,没有选好芯片,也可能是最新型号的,linux内核没跟上)。
在此之前先分析i2c-s3c2410.c中完成的工作(总线驱动):
l 设计对应于i2c_adapter_xxx_init()模板的s3c8900的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。
l 设计对应于i2c_adapter_xxx_xfer()模板的 s3c8900适配器的通信方法函数,针对 s3c24xx、64xx、s5pc1XX、s5p64xx处理器functionality()函数s3c24xx_i2c-func()只需简单的返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能
话说没找到总线驱动支持,(这倒霉孩子)那就得编写个类似的i2c-s3c8900.c的总线驱动支持,嘿嘿,照着上面的功能写吧,反正是总线驱动。
Ø I2C适配器驱动加载与卸载
1. 初始化i2c适配器所使用的硬件资源,如申请I/O地址、中断号等;
2. 通过i2c_add_adapter添加i2c_adapter数据结构,当然这个数据结构的成员已经被xxx适配器的相应的函数指针所初始化;
3. i2c总线卸载模块与装载相反,是否i2c适配器使用的硬件资源,通过i2c_del_adapter删除i2c_adapter的数据结构。
模板如下:
static int __init i2c_adapter_xxx_init(void)
{
xxx_adapter_hw_init();//初始化硬件资源
i2c_add_adapter(&xxx_adapter);
}
static void __init i2c_adapter_xxx_exit(void)
{
xxx_adapter_hw_free();//释放硬件资源
i2c_del_adapter(&xxx_adapter);
}
具体CPU具体分析,有的用platform做的,可以参考6410的做法
2.1.2 I2C总线的通信方法
我们需要为特定的i2c适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。functionality函数很简单,用于返回algorithm所支持的通信协议,如:I2C_FUCN-_I2C, I2C_FUNC_10BIT_ADDR,I2C_FUNC_SMBUS_READ_BYTE,I2C_FUNC_SMBUS_write_byte等
master_xfer函数在i2c适配器上完成传递给他的i2c_msg数组中的每个i2c消息。
模板如下:
static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
......
for(i = 0; i<num ; i++){
i2c_adapter_xxx_start(); //产生开始位
//如果是读消息
if(msg[i]->falgs &I2C_M_RD){
i2c_adapter_xxx_setaddr((msg->addr<<1)|1); //发送从设备读地址
i2c_adapter_xxx_wait_ack();//获取从设备的ack信息
i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);//读取msg[i]->len长的数据到msg[i]->buf里
} else{ //是写消息
i2c_adapter_xxx_setaddr((msg->addr<<1)|1); //发送从设备写地址
i2c_adapter_xxx_wait_ack();//获取从设备的ack信息
i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);//读取msg[i]->len长的数据到msg[i]->buf里
}
}
i2c_adapter_xxx_stop(); //产生停止位
}
好啦,完成了装载和卸载,又完成了通信方法这两个重要的东东,那么总线驱动结构已经完成啦,累死了!
2.2 写设备驱动
四部曲:
Ø 构建i2c_driver
Ø 注册i2c_driver
Ø 构建i2c_client ( 第一种方法:注册字符设备驱动、第二种方法:通过板文件的i2c_board_info填充,然后注册)
Ø 注销i2c_driver
具体如下:
2.2.1构建i2c_driver
static struct i2c_driver pca953x_driver = {
.driver = {
.name= "pca953x", //名称
},
.id= ID_PCA9555,//id号
.attach_adapter= pca953x_attach_adapter, //调用适配器连接设备
.detach_client= pca953x_detach_client,//让设备脱离适配器
};
2.2.2注册i2c_driver
static int __init pca953x_init(void)
{
return i2c_add_driver(&pca953x_driver);
}
module_init(pca953x_init);
执行i2c_add_driver(&pca953x_driver)后,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:
static int pca953x_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, pca953x_detect);
/*
adapter:适配器
addr_data:地址信息
pca953x_detect:探测到设备后调用的函数
*/
}
地址信息addr_data是由下面代码指定的。
/* Addresses to scan */
static unsigned short normal_i2c[] = {0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END};
I2C_CLIENT_INSMOD;
注意:normal_i2c里的地址必须是你i2c芯片的地址。否则将无法正确探测到设备。而I2C_ CLIENT_INSMOD是一个宏,它会利用normal_i2c构建addr_data。
2.2.3构建i2c_client,并注册字符设备驱动
i2c_probe在探测到目标设备后,后调用pca953x_detect,并把当时的探测地址address作为参数传入。
static int pca953x_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *new_client;
struct pca953x_chip *chip; //设备结构体
int err = 0,result;
dev_t pca953x_dev=MKDEV(pca953x_major,0);//构建设备号,根据具体情况设定,这里我只考虑了normal_i2c中只有一个地址匹配的情况。主次设备号来源
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_WORD_DATA))//判定适配器能力
goto exit;
if (!(chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL))) {
err = -ENOMEM;
goto exit;
}
/****构建i2c-client****/
chip->client=kzalloc(sizeof(struct i2c_client),GFP_KERNEL);
new_client = chip->client;
i2c_set_clientdata(new_client, chip);
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &pca953x_driver;
new_client->flags = 0;
strlcpy(new_client->name, "pca953x", I2C_NAME_SIZE);
if ((err = i2c_attach_client(new_client)))//注册i2c_client
goto exit_kfree;
if (err)
goto exit_detach;
if(pca953x_major)
{
result=register_chrdev_region(pca953x_dev,1,"pca953x");
}
else{
result=alloc_chrdev_region(&pca953x_dev,0,1,"pca953x");
pca953x_major=MAJOR(pca953x_dev);
}
if (result < 0) {
printk(KERN_NOTICE "Unable to get pca953x region, error %d/n", result);
return result;
}
pca953x_setup_cdev(chip,0); //注册字符设备,此处不详解
return 0;
exit_detach:
i2c_detach_client(new_client);
exit_kfree:
kfree(chip);
exit:
return err;
}
i2c_check_functionality用来判定设配器的能力,这一点非常重要。你也可以直接查看对应设配器的能力,如
static const struct i2c_algorithm smbus_algorithm = {
.smbus_xfer= i801_access,
.functionality= i801_func,
};
static u32 i801_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK|(isich4 ? I2C_FUNC_SMBUS_HWPEC
展开阅读全文