I2C协议的介绍可以参考《I2C协议》,这里就不做太多介绍了。
看了很多书籍和博文,先把linux I2C的驱动架构讲解了一遍,但是linux的I2C驱动架构真的挺复杂的,但是仅看驱动的话,东西也不多,因为有很多东西内核已经帮我们实现了。所以我决定换一种方法,先看驱动代码,再看linux的I2C架构,先简单后复杂。
我们在fs6618开发板上,通过MMA8451a来看看I2C的程序,I2C驱动相关的我用+++++++来表明了一下,mma8451寄存器的操作相关的现在先不用关心,我们先看I2C驱动怎么来搭建起来的
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/i2c.h> #include <asm/uaccess.h> #include <linux/fs.h> #include <linux/cdev.h> #include "mma8451q_data.h" /*****mma8541q的寄存器定义**********/ #define WHO_AM_I 0X0D #define DEVICE_ID 0X1A #define XYZ_DATA_RANGE 0X0E #define CTRL_CFG1 0X2A #define CTRL_CFG2 0X2B #define ACC_X_H 0X01 #define ACC_X_L 0X02 #define ACC_Y_H 0X03 #define ACC_Y_L 0X04 #define ACC_Z_H 0X05 #define ACC_Z_L 0X06 /***************************************/ int major = 0; struct mma8451q_data data; struct i2c_client *mma8451q_client; /****************************** 功能:封装一个向mma8451q寄存器写数据函数 参数: client I2C设备对象+++++++++++++++++++++ reg:寄存器地址 data:数据 返回值:无 *******************************/ void mma8451q_write_con( struct i2c_client *client ,char reg ,char data) { int ret = 0; char tx[] = {reg,data}; /*++++++++++++++++++++++++++++++ I2C 传输信息结构体的变量填充 +++++++++++++++++++++++++++++++*/ struct i2c_msg msgs[] = { {.addr = client->addr, .flags = 0 , .len = sizeof(tx) , .buf = tx}, }; ret = i2c_transfer(client->adapter,msgs,ARRAY_SIZE(msgs)); if(ret < 0){ printk(KERN_INFO"i2c_transfer fail..%s,%d\n",__func__,__LINE__); } } /****************************** 功能:封装一个向mma8451q寄存器读数据函数 参数: client I2C设备对象 reg:寄存器地址 data:数据 返回值:无 *******************************/ char mma8451q_read_reg(struct i2c_client *client ,char reg ) { int ret = 0; char tx = reg; char rx = 0; /*++++++++++++++++++++++++++++++ I2C 传输信息结构体的变量填充 +++++++++++++++++++++++++++++++*/ struct i2c_msg msgs[] = { {.addr = client->addr , .flags = 0 , .len = sizeof(tx) , .buf = &tx}, {.addr = client->addr , .flags = 1 , .len = sizeof(rx) , .buf = &rx}, }; ret = i2c_transfer(client->adapter,msgs,ARRAY_SIZE(msgs)); if(ret < 0){ printk(KERN_INFO"i2c_transfer fail..%s,%d\n",__func__,__LINE__); } return rx; } /************************************* 功能:file_operations 的open实现函数 **************************************/ int mma8451q_open(struct inode *inode, struct file *file) { printk(KERN_INFO"%s,%d\n",__func__,__LINE__); return 0; } /************************************* 功能:file_operations 的close实现函数 **************************************/ int mma8451q_release(struct inode *inode, struct file *file) { printk(KERN_INFO"%s,%d\n",__func__,__LINE__); return 0; } /************************************* 功能:file_operations 的read实现函数 **************************************/ ssize_t mma8451q_read(struct file *file, char __user *user, size_t size, loff_t *loff) { data.accx = mma8451q_read_reg(mma8451q_client ,ACC_X_H)<<8; data.accx |= mma8451q_read_reg(mma8451q_client ,ACC_X_L); data.accy = mma8451q_read_reg(mma8451q_client ,ACC_Y_H)<<8; data.accy |= mma8451q_read_reg(mma8451q_client ,ACC_Y_L); data.accz = mma8451q_read_reg(mma8451q_client ,ACC_Z_H)<<8; data.accz |= mma8451q_read_reg(mma8451q_client ,ACC_Z_L); if(copy_to_user(user,&data,sizeof(data))){ printk(KERN_INFO"copy_to_user fail..%s,%d\n",__func__,__LINE__); return -EAGAIN; } return 0; } struct file_operations f_ops = { .owner = THIS_MODULE, .read = mma8451q_read, .open = mma8451q_open, .release = mma8451q_release, }; /****************************************** 功能:i2c_driver 的probe实现函数 和当初的platform_driver中的 一样,linux匹配上I2C设备 和I2C driver后会执行这个函数 *******************************************/ int mma8415q_probe(struct i2c_client *client , const struct i2c_device_id *id) { mma8451q_client = client;//地址信息中断号都在这个里面 if(mma8451q_read_reg(client,WHO_AM_I) != DEVICE_ID){ printk(KERN_INFO"THIS DEVICE IS NOT MMA8451Q ....%s,%d\n",__func__,__LINE__); return -EINVAL; } mma8451q_write_con(client,XYZ_DATA_RANGE,(mma8451q_read_reg(client,XYZ_DATA_RANGE)&~(0X3<<0))|(0X1<<1)); mma8451q_write_con(client,CTRL_CFG1,mma8451q_read_reg(client,CTRL_CFG1)|(0X1<<0)); mma8451q_write_con(client,CTRL_CFG2,(mma8451q_read_reg(client,CTRL_CFG2)&~(0X3<<0))|(0X1<<1)); major = register_chrdev(0,"mma8451q",&f_ops); if(major < 0){ printk(KERN_INFO"register_chrdev ...%s,%d\n",__func__,__LINE__); return -EINVAL; } printk(KERN_INFO"major :%d %s,%d\n",major,__func__,__LINE__); return 0; } /****************************************** 功能:i2c_driver 的probe实现函数 和当初的platform_driver中的 一样,linux监测到有I2C设备和 驱动脱离时后会执行这个 函数 *******************************************/ int mma8415q_remove(struct i2c_client *client) { unregister_chrdev(major,"mma8451q"); printk(KERN_INFO"%s,%d\n",__func__,__LINE__); return 0; } struct i2c_device_id id_tables[] = { {.name = "mma8451q"}, {} }; /*++++++++++++++++++++++++++++++ 创建一个I2C driver对象 类比一下我们之前学的 platform_driver *++++++++++++++++++++++++++++++*/ struct i2c_driver mma8451q_drv = { .probe = mma8415q_probe, .remove = mma8415q_remove, .driver = { .name = "mma8451q", .owner = THIS_MODULE, }, .id_table = id_tables, }; /*驱动模块初始化*/ static int __init mma8451q_init(void) { /*++++++++++++++++++++ 添加I2C 设备驱动 ++++++++++++++++++++*/ i2c_add_driver(&mma8451q_drv); printk(KERN_INFO"%s,%d\n",__func__,__LINE__); return 0; } /*驱动模块卸载*/ static void __exit mma8451q_exit(void) { /*++++++++++++++++++++ 删除I2C设备驱动 +++++++++++++++++++++*/ i2c_del_driver(&mma8451q_drv); printk(KERN_INFO"%s,%d\n",__func__,__LINE__); } module_init(mma8451q_init); module_exit(mma8451q_exit); MODULE_LICENSE("GPL");
之前在学习platform设备时,我们从三方面来分析的,总线,驱动,设备。总线用于匹配驱动和设备。I2C中我们也是可以笼统的这么区分的,上面的代码是驱动代码,内核会提供好总线部分,设备有厂商提供,当然我们也是可以自己写的。我们还是先来看看驱动把。
platform设备对应有platform_drvier,描述I2C设备对象的就有i2c_driver
struct i2c_driver { unsigned int class; /* 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 *); struct device_driver driver; const struct i2c_device_id *id_table; struct list_head clients; .................. };
和之前的platform_driver是很类似的,probe函数设备与驱动匹配上后要执行的函数,remove是断开时执行的函数。
继承了驱动基类device_driver
idtable是为了与对应的I2C设备及逆行匹配的,填充name与I2C设备文件中或设备树中的一致。
struct i2c_device_id { char name[I2C_NAME_SIZE]; kernel_ulong_t driver_data /* Data private to the driver */ __attribute__((aligned(sizeof(kernel_ulong_t)))); };
对应i2c_drvier的API操作函数
1、添加I2C设备驱动
函数原型:static inline int i2c_add_driver(struct i2c_driver *driver)
功能:添加I2C设备驱动
参数:驱动对象
2、删除I2C设备对象
函数原型:void i2c_del_driver(struct i2c_driver *driver)
功能:删除I2C设备
代码中还有个东东是i2c_client,这是个啥呢?这个起始就是对应的物理I2C设备,就相当于platform_device
struct i2c_client { unsigned short flags; /* 标志位 */ unsigned short addr; /* 设备地址,低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; };
读写位1bit | 器件类型4bit | 自定义3bit |
最低位是读写位,0位写,1位读,中间4bit是器件类型,芯片厂商定义,自定义由用户自己设置。
i2c_client是设备信息,是由设备文件或设备树来完成填充的。
上述几项和platform中都差不多,下面看看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_PROTOCOL_MANGLING */ #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 */ };
内核为我们封装了I2C消息的结构体
addr:发送地址
flags:标志位,0表示是写,I2C_M_RD表示读,I2C_M_TEN表示从设备的地址为10bit
len:长度
buf:数据缓冲区指针
如何将这个消息发送出去呢?
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
功能:发送I2C消息
参数:
adap 适配器,一会儿再细讲
msgs:消息结构体指针
num:发送消息数目
这基本就是驱动中要干的事情,看着也不难哈,那我们现在来看看内核中处理I2C到底是咋整滴。
上图呢就是I2C的驱动层次架构,我们先从下往上看。
硬件上,首先最下是I2C的设备,就比如我们的mpu6050,或者是EEPROM,挂接到了芯片的I2C模块上,I2C模块被称为I2C Adaptor,一个adaptor上可以挂接多个device。
软件上,硬件有adapter和device,那么软件上就需要来实现它,在那里呢?访问抽象层处实现了adapter,而驱动层实现的就是device相关的,而且看对应关系上,仍然是一个adapter可以对应多个client/driver。图形上看有点对称的意思。而在驱动层和访问抽象层中间还夹着一个I2C_CORE,它起到了桥梁的作用,向驱动层提供了驱动注册注销的方法和统一通信API接口。有了I2C_CORE,驱动层就不用管adapter的区别了。
说一些比较正式的,在Linux系统中,I2C驱动由3部分组成,即I2C核心,I2C总线驱动,I2C设备驱动。
a -- IIC核心---驱动层与总线层的桥梁
IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法,IIC通信方法(即“algorithm””)上层的、与具体adapter无关的代码以及探测设备、检测设备地址的上层代码等。一个adapter就需要一个通信方法algorithm,设备的驱动如何做到与适配器adapter和算法无关呢?I2C_CORE来完成。这也是为什么在上面的驱动代码中,看起来很简洁,并没有与算法相关的东西的原因。
在我们的Linux驱动的i2c文件夹下有algos,busses,chips三个文件夹,另外还有i2c-core.c和i2c-dev.c两个文件。
i2c-core.c文件实现了I2Ccore框架,是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2Cdriver结构和I2Cadapter结构。I2Ccore提供接口函数,允许一个I2Cadatper,I2Cdriver和I2Cclient初始化时在I2Ccore中进行注册,以及退出时进行注销。同时还提供了I2C总线读写访问的一般接口,主要应用在I2C设备驱动中。
b -- IIC总线驱动--通信方法algorithm的具体实现
IIC总线驱动是对IIC硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至直接集成在CPU内部。总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在那里,等待设备驱动调用其函数。
IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter、IIC适配器的algorithm数据结构i2c_algorithm和控制IIC适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
Busses文件夹下的i2c-mpc.c文件实现了PowerPC下I2C总线适配器驱动,定义描述了具体的I2C总线适配器的i2c_adapter数据结构,实现比较底层的对I2C总线访问的具体方法。I2Cadapter 构造一个对I2Ccore层接口的数据结构,并通过接口函数向I2Ccore注册一个控制器。I2Cadapter主要实现对I2C总线访问的算法,iic_xfer() 函数就是I2Cadapter底层对I2C总线读写方法的实现。同时I2Cadpter 中还实现了对I2C控制器中断的处理函数。
c -- IIC设备驱动----实现一个设备操作集合的各种方法
IIC设备驱动是对IIC硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的IIC适配器上,通过IIC适配器与CPU交换数据。设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。
IIC设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
i2c-dev.c文件中实现了I2Cdriver,提供了一个通用的I2C设备的驱动程序,实现了字符类型设备的访问接口,实现了对用户应用层的接口,提供用户程序访问I2C设备的接口,包括实现open,release,read,write以及最重要的ioctl等标准文件操作的接口函数。我们可以通过open函数打开 I2C的设备文件,通过ioctl函数设定要访问从设备的地址,然后就可以通过 read和write函数完成对I2C设备的读写操作。
通过I2Cdriver提供的通用方法可以访问任何一个I2C的设备,但是其中实现的read,write及ioctl等功能完全是基于一般设备的实现,所有的操作数据都是基于字节流,没有明确的格式和意义。为了更方便和有效地使用I2C设备,我们可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。
在i2c_transfer中没有讲结构体adapter,现在我们算是对adapter这个东西有了解了,我们来看看它的真面目
struct i2c_adapter { struct module *owner; //模块计数 unsigned int id; //alogorithm的类型,定义于i2c_id.h中 unsigned int class; //允许探测的驱动类型 const struct i2c_algorithm *algo; //指向适配器的驱动程序 void *algo_data; //指向适配器的私有数据,根据不同的情况使用方法不同 int (*client_register)(struct i2c_client *); //设备client注册时调用 int (*client_unregister(struct i2c_client *); //设备client注销时调用 u8 level; struct mutex bus_lock; //对总线进行操作时,将获得总线锁 struct mutex clist_lock ; //链表操作的互斥锁 int timeout; //超时 int retries; //重试次数 struct device dev; //指向 适配器的设备结构体 int nr ; struct list_head clients; //连接总线上的设备的链表 char name[48]; //适配器名称 struct completion dev_released; //用于同步的完成量 };
最重要的呢就是const struct i2c_algorithm *algo; ,实现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 *); };
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
就是要实现的通信算法,adapter驱动的大部分工作都在于此。i2c_transfer函数起始呢就是通过这里的master_xfer进行传输的。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { unsigned long orig_jiffies; int ret, try; if (adap->algo->master_xfer) { ......... } }
在linux内核中,I2C的总线驱动一般都已经实现了,所以通信算法这些东西我们就不用实现了,直接传递正确的参数就可以了。我们需要做的主要还是驱动层的代码。
好了,驱动这三个部分基本就理顺了。