Linux驱动子系统之I2C

 

[概述]

I2C总线是由Philips公司开发的两线式串行总线,这两根线为时钟线(SCL)和双向数据线(SDA)。由于I2C总线仅需要两根线,因此在电路板上占用的空间更少,带来的问题是带宽较窄。I2C在标准模式下传输速率最高100Kb/s,在快速模式下最高可达400kb/s。属于半双工。

在嵌入式系统中,I2C应用非常广泛,大多数微控制器中集成了I2C总线,一般用于和RTC,EEPROM,智能电池电路,传感器,LCD以及其他类似设备之间的通信。 


[I2C总线传输时序]

 

[I2C总线的信号状态]

1、  空闲状态:SDA和SCL都是高电平;

2、  开始条件(S):SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据;

3、  结束条件(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传输数据;

4、  数据有效:在SCL的高电平期间,SDA保持稳定,数据有效。SDA的改变只能发生在SCL的低电平期间;

5、  ACK信号:数据传输的过程中,接收器件每接收一个字节数据要产生一个ACK信号,向发送器件发出特定的低电平脉冲,表示已经收到数据。

 

[从设备地址]

 


I2C总线从设备使用7位地址,最后一个为读写控制位。下图是eeprom的原理图,我们可以计算出它的地址为0x50。

 

[I2C读写方式]

多字节写的时序

 


多字节读的时序

 


具体可参考datasheet


===============

LinuxI2C子系统架构]

在内核中已经提供I2C子系统,所以在做I2C驱动之前,就必须要熟悉该子系统。


三大组成部分:

1、I2C核心(i2c-core)


I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

2、I2C总线驱动(I2Cadapter/Algo driver)

I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至集成在CPU内部(大多数微控制器都这么做)。适配器就是我们经常所说的控制器。

经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。

I2C总线驱动由i2c_adapter和i2c_algorithm来描述

3、I2C客户驱动程序(I2Cclient driver)

I2C客户驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在收CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。

I2C客户驱动程序由i2c_driver来描述

所有的I2C驱动代码位于drivers/i2c目录下

I2c-core.c     实现I2C核心的功能

I2c-dev.c      实现I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备

Chips         特定的I2C设备驱动

Busses        I2C总线的驱动

Algos         实现了一些I2C总线适配器的algorithm

I2C驱动编写的两种方法

从上面的图我们可以看到两种编写驱动方法,一种是利用系统提供的i2c-dev.c来实现一个i2c适配器的设备文件,然后通过在应用层操作I2C适配器来控制I2C从设备;另一种是为I2C从设备独立编写一个设备驱动,不需要i2c-dev.c文件,相对来说简单很多。

[重要的数据结构]

每次分析子系统免不了分析它的数据结构,OK我们先来分析一下。

 

I2c_adapter结构体代表一个物理的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;        /*algorithm 数据 */   
    
   /* data fields that are valid for all devices   */   
   struct rt_mutex bus_lock;   
    
   int timeout;            /* injiffies */   
   int retries;             /* 重试次数 */   
   struct device dev;      /* theadapter device */   
    
   int nr;   
   char name[48];         /* 适配器名字 */   
   struct completion dev_released;   /* 用于同步 */   
    
   struct mutex userspace_clients_lock;   
   struct list_head userspace_clients;   
};  

I2c_algorithm对应一套通信方法

struct i2c_algorithm {   
         /*I2C传输函数指针 */   
   int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,   
               int num);   
         /*SMBUS传输函数指针 */   
   int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,   
               unsigned short flags, charread_write,   
               u8 command, int size, unioni2c_smbus_data *data);   
         /*返回适配器支持的功能 */   
   u32 (*functionality) (struct i2c_adapter *);   
};  

//I2c_driver代表I2C从设备驱动

struct i2c_driver {   
    unsigned int class;   
   
    /* 这两个接口已经被probe和remove取代 */   
    int (*attach_adapter)(struct i2c_adapter *) __deprecated; /*依附i2c适配器函数指针*/   
    int (*detach_adapter)(struct i2c_adapter *) __deprecated;/*脱离i2c适配器函数指针*/   
   
    /* Standard driver model interfaces */   
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);   
    int (*remove)(struct i2c_client *);   
   
    void (*shutdown)(struct i2c_client *);   
    int (*suspend)(struct i2c_client *, pm_message_t mesg);   
    int (*resume)(struct i2c_client *);   
    void (*alert)(struct i2c_client *, unsigned int data);   
   
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);   
   
    struct device_driver driver;   
    const struct i2c_device_id *id_table;  /* 该驱动所支持的设备ID表 */   
   
    /* 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;   
};  

//I2c_client代表I2C从设备

struct i2c_client {   
unsigned short flags;                 /*I2C_CLIENT_TEN:使用10位从地址,I2C_CLIENT_PEC:使用SMBus包错误检测*/   
         unsignedshort addr;                 /* chipaddress - NOTE: 7bit    */   
                                               /*addresses are stored in the         */   
                                               /*_LOWER_ 7 bits            */   
         charname[I2C_NAME_SIZE];   
         structi2c_adapter *adapter; /* 依附的i2c_adapter   */   
         structi2c_driver *driver;         /* 依附的i2c_driver*/   
         structdevice dev;             /* the devicestructure             */   
         intirq;                         /* irq issuedby device               */   
         structlist_head detected;   
};  

//I2c_adapter与i2c_algorithm

/*一个I2C适配器需要I2C_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。I2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg为单位。*/

struct i2c_msg {   
         __u16addr;     /* slave address                         */   
         __u16flags;    /* 读写标志*/   
         __u16len;                  /* msg length                              */   
         __u8*buf;                 /* pointer to msgdata                       */   
};  

//I2c_driver与i2c_client

//I2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同等类型的i2c_client。

 

//I2c_adapter与i2c_client

//I2c_adapter与i2c_client的关系与I2C硬件体系中适配器和从设备的关系一致,i2c_client依附在i2c_adapter。

//[核心层提供的接口函数]

//1、  增加/删除I2C适配器

int i2c_add_adapter(struct i2c_adapter *adapter)   
int i2c_del_adapter(struct i2c_adapter *adap)  

//2、  增加/删除I2C从设备驱动

int i2c_register_driver(struct module *owner, structi2c_driver *driver)   
static inline int i2c_add_driver(struct i2c_driver *driver)   
void i2c_del_driver(struct i2c_driver *driver)  

i2c_add_driver是对i2c_register_driver简单的封装

//3、  i2c传输,发送和接收

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg*msgs, int num)   
int i2c_master_send(const struct i2c_client *client, constchar *buf, int count)   
int i2c_master_recv(const struct i2c_client *client, char*buf, int count)  

//i2c_master_send和i2c_master_recv是i2c_transfer的封装

//tips:3.1.0的内核中已经没有i2c_attach_client和i2c_detach_client接口

//[I2C总线通信方法]

//我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm结构体中的两个函数:

struct i2c_algorithm {   
         int(*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,   
                               int num);   
         u32(*functionality) (struct i2c_adapter *);   
};  

//Functionality函数用于返回algorithm所支持的通信协议;

//Master_xfer函数在I2C适配器上完成数据的传输;

//Master_xfer函数实现模板

static int i2c_adapter_xxx_xfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num)   
{   
   ......   
   for (i = 0; i < num; i++) {   
       i2c_adapter_xxx_start();         /*产生起始位*/   
       if (msgs[i]->flags & 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);  /*读取len长度的数据到buf中*/   
       } else {   
           i2c_adapter_xxx_setaddr(msg->addr << 1);   
           i2c_adapter_xxx_wait_ack();   
           i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len);   
       }   
    }   
   i2c_adapter_xxx_stop(); /*产生停止位*/   
}  


 

上面调用的函数用于完成适配器的底层硬件操作,与I2C适配器和CPU的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。在内核源码中,针对不同的I2C适配器都有master_xfer的实现,风格与模板不尽相同,但是可以用该模板作为参考来看源代码,受益匪浅。

[I2C从设备驱动与I2C适配器的match]

I2C从设备驱动也就是上图中的I2C客户驱动,适配器与从设备是一对多的关系。在这里极好的体现了外设驱动和主机驱动的分离思想,带来优势是极为明显的。

我们来大致分析一下匹配的过程:

添加一个I2C从设备驱动(i2c_driver)

当调用i2c_add_driver函数向I2C总线(i2c-core.c文件中注册的”i2c”总线)增加一个i2c_driver时,会遍历总线中的所有i2c_client,调用总线注册的match函数I2C适配器上是否有与i2c_driver匹配的i2c_client,如果匹配会调用I2C注册的probe函数,然后再调用i2c_driver定义的probe来进行关联和初始化工作。

如果对Linux设备驱动模型有了解,对上面的分析应该不难理解。

 

=========================================
 

 

[概述]

之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动。不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件的打开而产生,并随着设备文件的关闭而撤销。I2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主题是”i2c_driver成员函数+字符设备驱动”。


[i2c-dev.c源码分析]

I2c-dev初始化函数主要做了注册名为”i2c”的字符设备文件和”i2c-dev”的类。

I2c-dev.c中实现的i2cdev_read和i2cdev_write函数不具有太强的通用性,只适合下面这种单开始信号情况:

 


而不适合多开始信号的情况:

 


所以我们经常会使用i2cdev_ioctl函数的I2C_RDWR,在分析i2cdev_ioctl函数之前,我们需要了解一个结构体:

/* This is the structure as used in theI2C_RDWR ioctl call */  
struct i2c_rdwr_ioctl_data {  
         structi2c_msg __user *msgs;         /* pointersto i2c_msgs */  
         __u32nmsgs;                    /* number ofi2c_msgs */  
}; 

Msgs     表示单个开始信号传递的数据;

Nmsgs     表示有多少个msgs,比如上图,单开始信号时,nmsgs等于1;多开始信号时,nmsgs等于2

struct i2c_msg {  
         __u16addr;     /* slave address                         */  
         __u16flags;  /* 默认为写入 */  
#define I2C_M_TEN                  0x0010     /*this is a ten bit chip address */   
#define I2C_M_RD           0x0001     /* readdata, 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 */   
         __u16len;                  /* msg length                              */  
         __u8*buf;                 /* pointer to msgdata                       */  
}; 

使用i2cdev_ioctl函数的I2C_RDWR指令会调用到i2cdev_ioctl_rdrw函数:

static noinline inti2cdev_ioctl_rdrw(struct i2c_client *client,  
                   unsignedlong arg)  
{  
         structi2c_rdwr_ioctl_data rdwr_arg;  
         structi2c_msg *rdwr_pa;  
         u8__user **data_ptrs;  
         inti, res;  
   
         if(copy_from_user(&rdwr_arg,  
                               (struct i2c_rdwr_ioctl_data __user *)arg,  
                               sizeof(rdwr_arg)))  
                   return-EFAULT;  
   
         /*Put an arbitrary limit on the number of messages that can 
          * be sent at once */  
         if(rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)  
                   return-EINVAL;  
   
         rdwr_pa= kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL);  
         if(!rdwr_pa)  
                   return-ENOMEM;  
   
         if(copy_from_user(rdwr_pa, rdwr_arg.msgs,  
                               rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {  
                   kfree(rdwr_pa);  
                   return-EFAULT;  
         }  
   
         data_ptrs= kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);  
         if(data_ptrs == NULL) {  
                   kfree(rdwr_pa);  
                   return-ENOMEM;  
         }  
   
         res= 0;  
         for(i = 0; i < rdwr_arg.nmsgs; i++) {  
                   /*Limit the size of the message to a sane amount; 
                    * and don't let length change either. */  
                   if((rdwr_pa[i].len > 8192) ||  
                       (rdwr_pa[i].flags & I2C_M_RECV_LEN)) {  
                            res= -EINVAL;  
                            break;  
                   }  
                   data_ptrs[i]= (u8 __user *)rdwr_pa[i].buf;  
                   rdwr_pa[i].buf= memdup_user(data_ptrs[i], rdwr_pa[i].len);  
                   if(IS_ERR(rdwr_pa[i].buf)) {  
                            res= PTR_ERR(rdwr_pa[i].buf);  
                            break;  
                   }  
         }  
         if(res < 0) {  
                   intj;  
                   for(j = 0; j < i; ++j)  
                            kfree(rdwr_pa[j].buf);  
                   kfree(data_ptrs);  
                   kfree(rdwr_pa);  
                   returnres;  
         }  
   
         res= i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);  
         while(i-- > 0) {  
                   if(res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {  
                            if(copy_to_user(data_ptrs[i], rdwr_pa[i].buf,  
                                                rdwr_pa[i].len))  
                                     res= -EFAULT;  
                   }  
                   kfree(rdwr_pa[i].buf);  
         }  
         kfree(data_ptrs);  
         kfree(rdwr_pa);  
         returnres;  

咋一看,还挺复杂,其实主要做了一件事情:把用户空间传递过来的i2c_rdwr_ioctl_data数据进行错误检查,www.linuxidc.com 然后调用i2c_transfer函数与适配器进行通信,如果是接收数据,代码会将访问到的数据传回i2c_rdwr_ioctl_data的buf中。I2c_transfer最终会调用到I2C适配器具体实现的master_xfer函数来与硬件进行通信。

[eeprom实例]

预备知识

使用的mini2440开发板,eeprom的地址为0x50,实验完成一个数据的读写,先看下读写时序

AT24C08任意地址字节写的时序:

 


AT24C08任意地址字节写的时序:

 


下面的代码可以按照上面的两个图来阅读:

#include <stdio.h>   
#include <linux/types.h>   
#include <fcntl.h>   
#include <unistd.h>   
#include <stdlib.h>   
#include <sys/types.h>   
#include <sys/ioctl.h>   
#include <errno.h>   
#include <assert.h>   
#include <string.h>   
#include <linux/i2c.h>   
#include <linux/i2c-dev.h>   
   
int main()  
{  
         intfd, ret;  
         unsignedchar rdwr_addr = 0x42;   /* e2prom 读写地址 */  
         unsignedchar device_addr = 0x50; /* e2prom 设备地址 */  
         unsignedchar data = 0x12;  /* 向e2prom写的数据 */  
         structi2c_rdwr_ioctl_data e2prom_data;  
   
         fd= open("/dev/i2c/0", O_RDWR);  
         if(fd < 0) {  
                   perror("openerror");  
                   exit(1);  
         }  
   
         e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \  
                                               sizeof(structi2c_msg));  
         if(e2prom_data.msgs == NULL) {  
                   perror("mallocerror");  
                   exit(1);  
         }  
   
         ioctl(fd,I2C_TIMEOUT, 1); /* 设置超时 */  
         ioctl(fd,I2C_RETRIES, 2); /* 设置重试次数 */  
   
          
         /*向e2prom的rdwr_addr地址写入数据data*/  
         e2prom_data.nmsgs= 1;  
         e2prom_data.msgs[0].len= 2;  
         e2prom_data.msgs[0].addr= device_addr;  
         e2prom_data.msgs[0].flags= 0;     /* write */  
   
          
         e2prom_data.msgs[0].buf= (unsigned char *)malloc(2);  
         e2prom_data.msgs[0].buf[0]= rdwr_addr;    /* write address */  
         e2prom_data.msgs[0].buf[1]= data;      /* write data */  
   
         ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);  
         if(ret < 0) {  
                   perror("writedata error");  
                   exit(1);  
         }  
         printf("writedata: %d to address: %#x\n", data, rdwr_addr);  
         data= 0;  /* be zero*/  
   
   
         /*从e2prom的rdwr_addr地址读取数据存入buf*/  
         e2prom_data.nmsgs= 2;  
         e2prom_data.msgs[0].len= 1;  
         e2prom_data.msgs[0].addr= device_addr;  
//      e2prom_data.msgs[0].flags= 0;     /* write */   
         e2prom_data.msgs[0].buf= &rdwr_addr;  
   
         e2prom_data.msgs[1].len= 1;  
         e2prom_data.msgs[1].addr= device_addr;  
         e2prom_data.msgs[1].flags= 1;     /* read */  
         e2prom_data.msgs[1].buf= &data;  
   
         ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);  
         if(ret < 0) {  
                   perror("readerror");  
                   exit(1);  
         }  
         printf("read  data: %d from address: %#x\n", data,rdwr_addr);  
          
         free(e2prom_data.msgs);  
         close(fd);  
   
         return0;  

在mini2440开发板上已经实验成功。


==============================

[概述]

I2C总线驱动是对I2C硬件体系结构中适配器的实现,主要提供了数据传输的算法和各种信号(起始,停止,ACK等)实现的函数,总线驱动由i2c_adapter和i2c_algorithm来描述。

[s3c2440I2C总线驱动分析]

S3c2440I2C控制器的硬件描述


S3c2440处理器内部集成了一个I2C控制器,通过四个寄存器来进行控制:

IICCON     I2C控制寄存器

IICSTAT     I2C状态寄存器

IICDS       I2C收发数据移位寄存器

IICADD     I2C地址寄存器


本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2011-11/47651p4.htm

 

 

通过IICCON,IICDS,IICADD寄存器操作,www.linuxidc.com 可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器来获取。

[i2c-s3c2410代码分析]

I2C总线驱动代码在drivers/i2c/busses/i2c-s3c2410.c,这个代码同样支持s3c2410,s3c6410,s5pc110等Samsung 系列的芯片。

初始化模块和卸载模块

static int __init i2c_adap_s3c_init(void)  
{  
         returnplatform_driver_register(&s3c24xx_i2c_driver);  
}  
   
static void __exit i2c_adap_s3c_exit(void)  
{  
         platform_driver_unregister(&s3c24xx_i2c_driver);  

总线驱动是基于platform来实现的,很符合设备驱动模型的思想。

static struct platform_drivers3c24xx_i2c_driver = {  
         .probe                = s3c24xx_i2c_probe,  
         .remove            = s3c24xx_i2c_remove,  
         .id_table  = s3c24xx_driver_ids,  
         .driver                = {  
                   .owner     = THIS_MODULE,  
                   .name       = "s3c-i2c",  
                   .pm  = S3C24XX_DEV_PM_OPS,  
                   .of_match_table= s3c24xx_i2c_match,  
         },  
}; 
s3c24xx_i2c_probe函数

当调用platform_driver_register函数注册platform_driver结构体时,如果platformdevice 和 platform driver匹配成功后,会调用probe函数,来初始化适配器硬件。

static int s3c24xx_i2c_probe(structplatform_device *pdev)  
{  
         ……  
         /*初始化适配器信息 */  
         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;  
   
         /*初始化自旋锁和等待队列头 */  
         spin_lock_init(&i2c->lock);  
         init_waitqueue_head(&i2c->wait);  
   
         /*find the clock and enable it */  
         i2c->dev= &pdev->dev;  
         i2c->clk= clk_get(&pdev->dev, "i2c");  
         clk_enable(i2c->clk);  
   
         /*映射寄存器 */  
         res= platform_get_resource(pdev, IORESOURCE_MEM, 0);  
         i2c->ioarea= request_mem_region(res->start, resource_size(res),  
                                                pdev->name);  
         i2c->regs= ioremap(res->start, resource_size(res));  
   
         /*设置I2C核心需要的信息 */  
         i2c->adap.algo_data= i2c;  
         i2c->adap.dev.parent= &pdev->dev;  
   
         /*初始化I2C控制器 */  
         ret= s3c24xx_i2c_init(i2c);  
   
         /*申请中断 */  
         i2c->irq= ret = platform_get_irq(pdev, 0);  
         ret= request_irq(i2c->irq, s3c24xx_i2c_irq, 0,  
                              dev_name(&pdev->dev), i2c);  
   
         ret= s3c24xx_i2c_register_cpufreq(i2c);  
         i2c->adap.nr= i2c->pdata->bus_num;  
         i2c->adap.dev.of_node= pdev->dev.of_node;  
   
         ret= i2c_add_numbered_adapter(&i2c->adap);  
          
         /*注册I2C适配器 */  
         of_i2c_register_devices(&i2c->adap);  
         platform_set_drvdata(pdev,i2c);  
   
         dev_info(&pdev->dev,"%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));  
         clk_disable(i2c->clk);  
         return0;  
         ……  

Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。当处理器包含多个I2C控制器时,我们通过板文件定义的platform数据中的bus_num来区分。

I2C总线通信方法

static const struct i2c_algorithms3c24xx_i2c_algorithm = {  
         .master_xfer             = s3c24xx_i2c_xfer,  
         .functionality             = s3c24xx_i2c_func,  
}; 
s3c24xx_i2c_xfer函数是总线通信方式的具体实现;

s3c24xx_i2c_func函数返回适配器所支持的通信功能。

完整的通信过程还需要借助中断s3c24xx_i2c_irq,具体分析可以参考《Linux设备驱动开发详解》

[适配器的设备资源]

S3c2440的I2C总线驱动是基于platform来实现,前面我们分析了platformdriver部分,再来看下platform device部分。

在arch/arm/plat-samsung/dev-i2c0.c文件中定义了platform_device结构体以及I2C控制器的资源信息:

static struct resource s3c_i2c_resource[] ={  
         [0]= {  
                   .start= S3C_PA_IIC,  
                   .end   = S3C_PA_IIC + SZ_4K - 1,  
                   .flags= IORESOURCE_MEM,  
         },  
         [1]= {  
                   .start= IRQ_IIC,  
                   .end   = IRQ_IIC,  
                   .flags= IORESOURCE_IRQ,  
         },  
};  
   
struct platform_device s3c_device_i2c0 = {  
         .name                 = "s3c2410-i2c",   /* 设备名 */  
#ifdef CONFIG_S3C_DEV_I2C1   
         .id               = 0,  
#else   
         .id               = -1,  
#endif   
         .num_resources         =ARRAY_SIZE(s3c_i2c_resource),  
         .resource   =s3c_i2c_resource,  
};  
   
struct s3c2410_platform_i2cdefault_i2c_data __initdata = {  
         .flags                  = 0,  
         .slave_addr      = 0x10,  /* I2C适配器的地址 */  
         .frequency        = 100*1000,  /* 总线频率 */  
         .sda_delay        = 100,   /* SDA边沿延迟时间ns */  
};  
   
void __init s3c_i2c0_set_platdata(structs3c2410_platform_i2c *pd)  
{  
         structs3c2410_platform_i2c *npd;  
   
         if(!pd)  
                   pd= &default_i2c_data;  
   
         npd= s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),  
                                   &s3c_device_i2c0);  
   
         if(!npd->cfg_gpio)  
                   npd->cfg_gpio= s3c_i2c0_cfg_gpio;  

在板文件中把platform_device注册进内核:

static struct platform_device*mini2440_devices[] __initdata = {  
         ……  
         &s3c_device_i2c0,  
……  
}; 

调用s3c_i2c0_set_platdata 函数把适配器具体的数据赋值给dev.platform_data:

static void __init mini2440_init(void)  
{  
         ……  
s3c_i2c0_set_platdata(NULL);  

I2C总线驱动就分析到这里。

5 客户驱动

5.1 概述

I2C客户驱动是对I2C从设备的实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。

I2C客户驱动程序主要由i2c_driver和i2c_client来描述。 


5.2 实例源码分析

好了,我们来深入了解客户驱动代码的实现,drivers/misc/eeprom/at24.c文件支持大多数I2C接口的eeprom

I2c_driver实现

static struct i2c_driver at24_driver = {  
         .driver= {  
                   .name= "at24",  
                   .owner= THIS_MODULE,  
         },  
         .probe= at24_probe,  /* 当i2c_client和i2c_driver匹配时调用 */  
         .remove= __devexit_p(at24_remove), /* 注销时调用 */  
         .id_table= at24_ids,   /* i2c_driver支持的i2c_client类型 */  
}; 

初始化和卸载

static int __init at24_init(void)  
{  
         returni2c_add_driver(&at24_driver);  
}  
   
static void __exit at24_exit(void)  
{  
         i2c_del_driver(&at24_driver);  

At24_Probe函数

static int at24_probe(struct i2c_client*client, const struct i2c_device_id *id)  
{  
         ……  
          
         /* 
          * Export the EEPROM bytes through sysfs, sincethat's convenient. 
          * By default, only root should see the data(maybe passwords etc) 
          */  
         sysfs_bin_attr_init(&at24->bin);  
         at24->bin.attr.name= "eeprom";  
         at24->bin.attr.mode= chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;  
         at24->bin.read= at24_bin_read;  
         at24->bin.size= chip.byte_len;  
   
         at24->macc.read= at24_macc_read;  
        writable = !(chip.flags &AT24_FLAG_READONLY);  
         if(writable) {  
                   if(!use_smbus || i2c_check_functionality(client->adapter,  
                                     I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)){  
   
                            unsignedwrite_max = chip.page_size;  
   
                            at24->macc.write= at24_macc_write;  
   
                            at24->bin.write= at24_bin_write;  
                            at24->bin.attr.mode|= S_IWUSR;  
                            ……  
         }  
……  
          
err = sysfs_create_bin_file(&client->dev.kobj,&at24->bin);  
         if(err)  
                   gotoerr_clients;  
   
         i2c_set_clientdata(client,at24);  
         ……  

Probe函数主要的工作是在sys目录下创建bin节点文件,用户可以同此节点文件来操作eeprom,并提供操作方法(read,write)

5.3  I2c_client实现

At24c不依赖于具体的CPU和I2C控制器硬件特性,因此如果电路板包含该外设,只需要添加对应的i2c_board_info,下面是at24c08 i2c_client在板文件中的实现:

static struct at24_platform_data at24c08 ={  
         .byte_len = SZ_8K / 8,   /* eeprom的存储大小B */  
         .page_size        = 16,      /* 页大小 */  
};  
   
static struct i2c_board_infomini2440_i2c_devs[] __initdata = {  
         {  
                   I2C_BOARD_INFO("24c08",0x50),          /* 24c08设备名,0x50设备地址 */  
                   .platform_data= &at24c08,  
         },  
};  
static void __init mini2440_init(void)  
{  
         ……  
         i2c_register_board_info(0,mini2440_i2c_devs,  
                                     ARRAY_SIZE(mini2440_i2c_devs));  
         ……  

I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,在调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client。

I2c_client的构建

我们调用I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,但是还没有构建出一个i2c_client结构体,也没有注册进I2C总线。我们来分析一下构造的过程,调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。

5.4  I2c_driver和i2c_client的match

在调用i2c_add_driver注册i2c_driver和构建i2c_client时,都会调用i2c bus中注册的i2c_device_match()->i2c_match_id()函数通过i2c_driver->id_table->name和client->name来匹配

static const struct i2c_device_id*i2c_match_id(const struct i2c_device_id *id,  
                                                        conststruct i2c_client *client)  
{  
         while(id->name[0]) {  
                   if(strcmp(client->name, id->name) == 0)  
                            returnid;  
                   id++;  
         }  
         returnNULL;  

5.5 测试

已在mini2440上实验成功,在/sys/bus/i2c/devices/0-0050/目录下(50代表从设备地址)会产生一个eeprom文件,这个文件相当于是硬件设备eeprom的映射,我们可以像普通文件一样对eeprom文件进行操作,实质上就是就硬件eeprom的操作。重启开发板,你会发现对eeprom文件修改过的内容不会改变,这就证明实验成功了,要知道sys文件系统是无法对数据保存的。


本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2011-11/47651p5.htm

===============

6 总结

下图根据之前的分析丰富的架构图

 


Tips:I2C适配器驱动不一定是基于platform实现,这里是以s3c-i2c为例。


 

I2c_driver、i2c_client与i2c_adapter

I2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同等类型的i2c_client。调用i2c_add_driver函数将I2c_driver注册到I2C总线上,调用i2c_register_board_info函数将i2c_client注册到全局链表__i2c_board_list。当调用i2c_add_adapter注册适配器时,遍历__i2c_board_list链表,i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()会构建i2c_client结构。当调用i2c_add_driver时,会先注册i2c_driver到I2C总线上,然后调用I2C BUS注册的match函数进行匹配,如果匹配成功,则先调用I2C BUS中注册的probe函数,在调用i2c_driver中实现的probe函数,完成相应的工作。

 


====================

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值