Linux I2C驱动

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.ci2c-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_adapterIIC适配器的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的总线驱动一般都已经实现了,所以通信算法这些东西我们就不用实现了,直接传递正确的参数就可以了。我们需要做的主要还是驱动层的代码。

好了,驱动这三个部分基本就理顺了。









猜你喜欢

转载自blog.csdn.net/u012142460/article/details/79282506