Linux设备驱动之——I2C总线


2  I2C子系统

2.1 LinuxI2C子系统架构

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


2.2 三大组成部分

1、I2C核心(i2c-core)

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

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

I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。

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

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

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

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

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

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

I2c-dev.c     通用的从设备驱动

Chips       特定的I2C设备驱动

Busses      I2C适配器的驱动

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

2.4 I2C驱动编写的两种方法

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

2.5 重要的数据结构

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

I2c_adapter结构体代表I2C总线控制器


  1. struct i2c_adapter { 
  2.    struct module *owner; 
  3.    unsigned int class;       /*classes to allow probing for */ 
  4.     const struct i2c_algorithm*algo; /* 总线上数据传输的算法*/ 
  5.    void *algo_data;              /* algorithm 数据 */ 
  6.   
  7.    int timeout;            /* injiffies */ 
  8.    int retries;             /* 重试次数 */ 
  9.     struct device dev;      /* the adapter device */ 
  10.   
  11.    int nr; 
  12.    char name[48];                 /* 适配器名字 */ 
  13.    struct completion dev_released;   /* 用于同步 */ 
  14. }; 

I2c_algorithm对应一套通信方法

  1. struct i2c_algorithm { 
  2.     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, intnum); 
  3.    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, 
  4.                unsigned short flags, charread_write, 
  5.                u8 command, int size, unioni2c_smbus_data *data); 
  6.     u32 (*functionality) (structi2c_adapter *); 
  7. }; 

Functionality 函数用于返回algorithm所支持的通信协议,比如I2C_FUNC_I2C,I2C_FUNC_10BIT_ADDR等。

Master_xfer   函数实现总线上数据传输,与具体的适配器有关

Master_xfer函数实现模板

  1. static int i2c_adapter_xxx_xfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num) 
  2.    ...... 
  3.    for (i = 0; i < num; i++) { 
  4.        i2c_adapter_xxx_start();         /*产生起始位*/ 
  5.        if (msgs[i]->flags & I2C_M_RD) {    /*读取*/ 
  6.            i2c_adapter_xxx_setaddr((msg->addr << 1) | 1);  /*发送从设备地址*/ 
  7.            i2c_adapter_xxx_wait_ack();   /*获得从设备的ACK*/ 
  8. i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);  /*读取len长度的数据到buf中*/ 
  9.        } else
  10.            i2c_adapter_xxx_setaddr(msg->addr << 1); 
  11.            i2c_adapter_xxx_wait_ack(); 
  12.            i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); 
  13.        } 
  14.     } 
  15.    i2c_adapter_xxx_stop(); /*产生停止位*/ 

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

I2c_driver代表I2C从设备驱动

  1. struct i2c_driver { 
  2.          unsignedint class
  3.   
  4.          int(*attach_adapter)(struct i2c_adapter *) __deprecated; /*依附i2c适配器函数指针*/ 
  5.          int(*detach_adapter)(struct i2c_adapter *) __deprecated;/*脱离i2c适配器函数指针*/ 
  6.   
  7.          int (*probe)(struct i2c_client*, const struct i2c_device_id *); 
  8.          int (*remove)(struct i2c_client*); 
  9.   
  10.          int(*suspend)(struct i2c_client *, pm_message_t mesg); 
  11.          int(*resume)(struct i2c_client *); 
  12.          void(*alert)(struct i2c_client *, unsigned int data); 
  13.          int(*command)(struct i2c_client *client, unsigned int cmd, void *arg); 
  14.   
  15.          struct device_driver driver; 
  16.          const struct i2c_device_id*id_table;  /* 该驱动所支持的设备ID表 */ 
  17.   
  18.          /*Device detection callback for automatic device creation */ 
  19.          int(*detect)(struct i2c_client *, struct i2c_board_info *); 
  20.          constunsigned short *address_list; 
  21.          structlist_head clients; 
  22. }; 

在新内核中,attach_adapter和detach_adapter已经被probe和remove取代

Id_table用于i2c_driver和i2c_client的匹配

I2c_client代表I2C从设备

  1. struct i2c_client { 
  2. unsigned short flags;                 /*I2C_CLIENT_TEN:使用10位从地址,I2C_CLIENT_PEC:使用SMBus包错误检测*/ 
  3.          unsignedshort addr;                 /* chipaddress - NOTE: 7bit    */ 
  4.          charname[I2C_NAME_SIZE]; 
  5.          struct i2c_adapter *adapter; /* 依附的i2c_adapter   */ 
  6.          struct i2c_driver *driver;         /* 依附的i2c_driver*/ 
  7.          structdevice dev;             /* the devicestructure             */ 
  8.          intirq;                         /* irq issuedby device               */ 
  9.          structlist_head detected; 
  10. }; 


2.6 核心层提供的接口函数

1、  增加/删除I2C适配器

int i2c_add_adapter(struct i2c_adapter *adapter)

int i2c_del_adapter(struct i2c_adapter *adap)

  1.   
  2. static int i2c_register_adapter(struct i2c_adapter *adap) 
  3. res = device_register(&adap->dev); 
  4.   
  5. if (adap->nr <__i2c_first_dynamic_bus_num) 
  6.            i2c_scan_static_board_info(adap); 
  7.   
  8. bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); 

Device_register(&adap->dev)  向I2C总线注册一个adapter设备

i2c_scan_static_board_info(adap)   注册所有已知的i2c_client

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

  1. int i2c_add_driver(struct i2c_driver *driver) 
  2. void i2c_del_driver(struct i2c_driver *driver) 
  3.   
  4. inti2c_register_driver(struct module *owner, struct i2c_driver *driver) 
  5.   
  6. /* add the driver to the list of i2c drivers inthe driver core */ 
  7. driver->driver.owner = owner; 
  8. driver->driver.bus = &i2c_bus_type; 
  9.   
  10. res = driver_register(&driver->driver); 
  11.   
  12. /* Walk the adapters that are already present*/ 
  13. i2c_for_each_dev(driver, __process_new_driver); 

driver_register(&driver->driver)   向I2C总线注册一个i2c_driver

3、  i2c传输,发送和接收

  1. int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg*msgs, int num) 
  2. int i2c_master_send(const struct i2c_client *client, constchar *buf, int count) 
  3. int i2c_master_recv(const struct i2c_client *client, char*buf, int count) 
  4.   
  5. int i2c_transfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num) 
  6. if (adap->algo->master_xfer) { 
  7.            for (ret = 0, try = 0; try <=adap->retries; try++) { 
  8.                     ret = adap->algo->master_xfer(adap, msgs,num); 
  9.            } 

最终会调用到适配器实现的master_xfer函数来完成数据传输工作

3  i2c-dev

3.1 概述

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

3.2 i2c-dev.c源码分析

初始化模块

  1. static int __init i2c_dev_init(void
  2.          res= register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); 
  3.   
  4.          i2c_dev_class= class_create(THIS_MODULE, "i2c-dev"); 
  5.   
  6.          /*Keep track of adapters which will be added or removed later */ 
  7.          res= bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); 
  8.   
  9.          /*绑定已经存在的适配器 */ 
  10.          i2c_for_each_dev(NULL,i2cdev_attach_adapter); 

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

i2cdev_read和i2cdev_write

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


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


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

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

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

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

  1. struct i2c_msg { 
  2.          __u16addr;     /* slave address                         */ 
  3.          __u16flags;  /* 默认为写入 */ 
  4. #define I2C_M_TEN                  0x0010     /*this is a ten bit chip address */ 
  5. #define I2C_M_RD           0x0001     /* read data,from slave to master */ 
  6. #define I2C_M_NOSTART                  0x4000     /* if I2C_FUNC_PROTOCOL_MANGLING */ 
  7. #define I2C_M_REV_DIR_ADDR     0x2000     /*if I2C_FUNC_PROTOCOL_MANGLING */ 
  8. #define I2C_M_IGNORE_NAK          0x1000     /*if I2C_FUNC_PROTOCOL_MANGLING */ 
  9. #define I2C_M_NO_RD_ACK           0x0800     /* if I2C_FUNC_PROTOCOL_MANGLING */ 
  10. #define I2C_M_RECV_LEN               0x0400     /* length will be first received byte */ 
  11.          __u16len;                  /* msg length                              */ 
  12.          __u8*buf;                 /* pointer to msgdata                       */ 
  13. }; 

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

  1. static noinline inti2cdev_ioctl_rdrw(struct i2c_client *client, 
  2.                    unsignedlong arg) 
  3.          structi2c_rdwr_ioctl_data rdwr_arg; 
  4.          structi2c_msg *rdwr_pa; 
  5.          u8__user **data_ptrs; 
  6.          inti, res; 
  7.   
  8.          if(copy_from_user(&rdwr_arg, 
  9.                                (struct i2c_rdwr_ioctl_data __user *)arg, 
  10.                                sizeof(rdwr_arg))) 
  11.                    return-EFAULT; 
  12.   
  13.          if(rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS) 
  14.                    return-EINVAL; 
  15.   
  16.          rdwr_pa= kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL); 
  17.   
  18.          if(copy_from_user(rdwr_pa, rdwr_arg.msgs, 
  19.                                rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { 
  20.                    kfree(rdwr_pa); 
  21.                    return-EFAULT; 
  22.          } 
  23.   
  24.          res= i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); 
  25.          while(i-- > 0) { 
  26.                    if(res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) { 
  27.                             if(copy_to_user(data_ptrs[i], rdwr_pa[i].buf, 
  28.                                                 rdwr_pa[i].len)) 
  29.                                      res= -EFAULT; 
  30.                    } 
  31.                    kfree(rdwr_pa[i].buf); 
  32.          } 

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

3.3 eeprom实例

预备知识

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

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


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


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

  1. #include <stdio.h> 
  2. #include <linux/types.h> 
  3. #include <fcntl.h> 
  4. #include <unistd.h> 
  5. #include <stdlib.h> 
  6. #include <sys/types.h> 
  7. #include <sys/ioctl.h> 
  8. #include <errno.h> 
  9. #include <assert.h> 
  10. #include <string.h> 
  11. #include <linux/i2c.h> 
  12. #include <linux/i2c-dev.h> 
  13.   
  14. int main() 
  15.          intfd, ret; 
  16.          unsignedchar rdwr_addr = 0x42;   /* e2prom 读写地址 */ 
  17.          unsignedchar device_addr = 0x50; /* e2prom 设备地址 */ 
  18.          unsignedchar data = 0x12;  /* 向e2prom写的数据 */ 
  19.          structi2c_rdwr_ioctl_data e2prom_data; 
  20.   
  21.          fd= open("/dev/i2c/0", O_RDWR); 
  22.          if(fd < 0) { 
  23.                    perror("openerror"); 
  24.                    exit(1); 
  25.          } 
  26.   
  27.          e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \ 
  28.                                                sizeof(structi2c_msg)); 
  29.          if(e2prom_data.msgs == NULL) { 
  30.                    perror("mallocerror"); 
  31.                    exit(1); 
  32.          } 
  33.   
  34.          ioctl(fd,I2C_TIMEOUT, 1); /* 设置超时 */ 
  35.          ioctl(fd,I2C_RETRIES, 2); /* 设置重试次数 */ 
  36.   
  37.          
  38.          /*向e2prom的rdwr_addr地址写入数据data*/ 
  39.          e2prom_data.nmsgs= 1; 
  40.          e2prom_data.msgs[0].len= 2; 
  41.          e2prom_data.msgs[0].addr= device_addr; 
  42.          e2prom_data.msgs[0].flags= 0;     /* write */ 
  43.   
  44.          
  45.          e2prom_data.msgs[0].buf= (unsigned char *)malloc(2); 
  46.          e2prom_data.msgs[0].buf[0]= rdwr_addr;    /* write address */ 
  47.          e2prom_data.msgs[0].buf[1]= data;      /* write data */ 
  48.   
  49.          ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data); 
  50.          if(ret < 0) { 
  51.                    perror("writedata error"); 
  52.                    exit(1); 
  53.          } 
  54.          printf("writedata: %d to address: %#x\n", data, rdwr_addr); 
  55.          data= 0;  /* be zero*/ 
  56.   
  57.   
  58.          /*从e2prom的rdwr_addr地址读取数据存入buf*/ 
  59.          e2prom_data.nmsgs= 2; 
  60.          e2prom_data.msgs[0].len= 1; 
  61.          e2prom_data.msgs[0].addr= device_addr; 
  62. //      e2prom_data.msgs[0].flags= 0;     /* write */ 
  63.          e2prom_data.msgs[0].buf= &rdwr_addr; 
  64.   
  65.          e2prom_data.msgs[1].len= 1; 
  66.          e2prom_data.msgs[1].addr= device_addr; 
  67.          e2prom_data.msgs[1].flags= 1;     /* read */ 
  68.          e2prom_data.msgs[1].buf= &data; 
  69.   
  70.          ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data); 
  71.          if(ret < 0) { 
  72.                    perror("readerror"); 
  73.                    exit(1); 
  74.          } 
  75.          printf("read  data: %d from address: %#x\n", data,rdwr_addr); 
  76.          
  77.          free(e2prom_data.msgs); 
  78.          close(fd); 
  79.   
  80.          return0; 

4 总线驱动

4.1 概述

I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,比如起始,停止,应答信号和master_xfer的实现函数。

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

4.2 S3c2440I2C控制器的硬件描述

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

IICCON     I2C控制寄存器

IICSTAT     I2C状态寄存器

IICDS       I2C收发数据移位寄存器

IICADD     I2C地址寄存器

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

4.3 i2c-s3c2410总线驱动分析(platform_driver)

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

初始化模块和卸载模块

  1. static int __init i2c_adap_s3c_init(void
  2.          returnplatform_driver_register(&s3c24xx_i2c_driver); 
  3.   
  4. static void __exit i2c_adap_s3c_exit(void
  5.          platform_driver_unregister(&s3c24xx_i2c_driver); 

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

  1. static struct platform_drivers3c24xx_i2c_driver = { 
  2.          .probe                = s3c24xx_i2c_probe, 
  3.          .remove            = s3c24xx_i2c_remove, 
  4.          .id_table  = s3c24xx_driver_ids, 
  5.          .driver                = { 
  6.                    .owner     = THIS_MODULE, 
  7.                    .name       = "s3c-i2c"
  8.                    .pm  = S3C24XX_DEV_PM_OPS, 
  9.                    .of_match_table= s3c24xx_i2c_match, 
  10.          }, 
  11. }; 

s3c24xx_i2c_probe函数

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

  1. static int s3c24xx_i2c_probe(structplatform_device *pdev) 
  2.          …… 
  3.          /*初始化适配器信息 */ 
  4.          strlcpy(i2c->adap.name,"s3c2410-i2c", sizeof(i2c->adap.name)); 
  5.          i2c->adap.owner   = THIS_MODULE; 
  6.          i2c->adap.algo    = &s3c24xx_i2c_algorithm; 
  7.          i2c->adap.retries= 2; 
  8.          i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD; 
  9.          i2c->tx_setup     = 50; 
  10.   
  11.          /*初始化自旋锁和等待队列头 */ 
  12.          spin_lock_init(&i2c->lock); 
  13.          init_waitqueue_head(&i2c->wait); 
  14.   
  15.          /*映射寄存器 */ 
  16.          res= platform_get_resource(pdev, IORESOURCE_MEM, 0); 
  17.          i2c->ioarea= request_mem_region(res->start, resource_size(res), 
  18.                                                 pdev->name); 
  19.          i2c->regs= ioremap(res->start, resource_size(res)); 
  20.   
  21.          /*设置I2C核心需要的信息 */ 
  22.          i2c->adap.algo_data= i2c; 
  23.          i2c->adap.dev.parent= &pdev->dev; 
  24.   
  25.          /*初始化I2C控制器 */ 
  26.          ret= s3c24xx_i2c_init(i2c); 
  27.   
  28.          /*申请中断 */ 
  29.          i2c->irq= ret = platform_get_irq(pdev, 0); 
  30.          ret= request_irq(i2c->irq, s3c24xx_i2c_irq, 0, 
  31.                               dev_name(&pdev->dev), i2c); 
  32.   
  33.    /* 注册I2C适配器 */ 
  34.          ret= i2c_add_numbered_adapter(&i2c->adap); 
  35.          …… 

Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。I2c_adapter注册过程i2c_add_numbered_adapter->i2c_register_adapter

I2C总线通信方法

  1. static const struct i2c_algorithms3c24xx_i2c_algorithm = { 
  2.          .master_xfer             = s3c24xx_i2c_xfer, 
  3.          .functionality             = s3c24xx_i2c_func, 
  4. }; 

s3c24xx_i2c_xfer函数是总线通信方式的具体实现,依赖于s3c24xx_i2c_doxfer和s3c24xx_i2c_message_start两个函数;

  1. static int s3c24xx_i2c_doxfer(structs3c24xx_i2c *i2c, 
  2.                                   struct i2c_msg *msgs, int num) 
  3.          ret =s3c24xx_i2c_set_master(i2c); 
  4.   
  5.          i2c->msg     = msgs; 
  6.          i2c->msg_num= num; 
  7.          i2c->msg_ptr= 0; 
  8.          i2c->msg_idx= 0; 
  9.          i2c->state   = STATE_START; 
  10.   
  11.          s3c24xx_i2c_message_start(i2c,msgs); 

首先设置s3c I2C设备器为主设备,然后调用s3c24xx_i2c_message_start函数启动I2C消息传输。

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

4.4 适配器的设备资源(platform_device)

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

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

  1. static struct resource s3c_i2c_resource[] ={ 
  2.          [0]= { 
  3.                    .start= S3C_PA_IIC, 
  4.                    .end   = S3C_PA_IIC + SZ_4K - 1, 
  5.                    .flags= IORESOURCE_MEM, 
  6.          }, 
  7.          [1]= { 
  8.                    .start= IRQ_IIC, 
  9.                   .end  = IRQ_IIC, 
  10.                    .flags= IORESOURCE_IRQ, 
  11.          }, 
  12. }; 
  13.   
  14. struct platform_device s3c_device_i2c0 = { 
  15.          .name                 = "s3c2410-i2c",   /* 设备名 */ 
  16. #ifdef CONFIG_S3C_DEV_I2C1 
  17.          .id               = 0, 
  18. #else 
  19.          .id               = -1, 
  20. #endif 
  21.          .num_resources         =ARRAY_SIZE(s3c_i2c_resource), 
  22.          .resource   =s3c_i2c_resource, 
  23. }; 
  24.   
  25. struct s3c2410_platform_i2cdefault_i2c_data __initdata = { 
  26.          .flags                  = 0, 
  27.          .slave_addr      = 0x10,  /* I2C适配器的地址 */ 
  28.          .frequency        = 100*1000,  /* 总线频率 */ 
  29.          .sda_delay        = 100,   /* SDA边沿延迟时间ns */ 
  30. }; 
  31.   
  32. void __init s3c_i2c0_set_platdata(structs3c2410_platform_i2c *pd) 
  33.          structs3c2410_platform_i2c *npd; 
  34.   
  35.          if(!pd) 
  36.                    pd= &default_i2c_data; 
  37.   
  38.          npd= s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c), 
  39.                                    &s3c_device_i2c0); 
  40.   
  41.          if(!npd->cfg_gpio) 
  42.                    npd->cfg_gpio= s3c_i2c0_cfg_gpio; 

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

  1. static struct platform_device*mini2440_devices[] __initdata = { 
  2.          …… 
  3.          &s3c_device_i2c0, 
  4. …… 
  5. }; 

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

  1. static void __init mini2440_init(void
  2.          …… 
  3. 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实现

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

初始化和卸载

  1. static int __init at24_init(void
  2.          returni2c_add_driver(&at24_driver); 
  3.   
  4. static void __exit at24_exit(void
  5.          i2c_del_driver(&at24_driver); 

At24_Probe函数

  1. static int at24_probe(struct i2c_client*client, const struct i2c_device_id *id) 
  2.          …… 
  3.          
  4.          /*
  5.           * Export the EEPROM bytes through sysfs, sincethat's convenient.
  6.           * By default, only root should see the data(maybe passwords etc)
  7.           */ 
  8.          sysfs_bin_attr_init(&at24->bin); 
  9.          at24->bin.attr.name= "eeprom"
  10.          at24->bin.attr.mode= chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR; 
  11.          at24->bin.read= at24_bin_read; 
  12.          at24->bin.size= chip.byte_len; 
  13.   
  14.          at24->macc.read= at24_macc_read; 
  15.         writable = !(chip.flags &AT24_FLAG_READONLY); 
  16.          if(writable) { 
  17.                    if(!use_smbus || i2c_check_functionality(client->adapter, 
  18.                                      I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)){ 
  19.   
  20.                             unsignedwrite_max = chip.page_size; 
  21.   
  22.                             at24->macc.write= at24_macc_write; 
  23.   
  24.                             at24->bin.write= at24_bin_write; 
  25.                             at24->bin.attr.mode|= S_IWUSR; 
  26.                             …… 
  27.          } 
  28. …… 
  29.          
  30. err = sysfs_create_bin_file(&client->dev.kobj,&at24->bin); 
  31.          if(err) 
  32.                    gotoerr_clients; 
  33.   
  34.          i2c_set_clientdata(client,at24); 
  35.          …… 

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

5.3  I2c_client实现

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

  1. static struct at24_platform_data at24c08 ={ 
  2.          .byte_len = SZ_8K / 8,   /* eeprom的存储大小,单位Byte */ 
  3.          .page_size        = 16,      /* 页大小 Byte */ 
  4. }; 
  5.   
  6. static struct i2c_board_infomini2440_i2c_devs[] __initdata = { 
  7.          { 
  8.                    I2C_BOARD_INFO("24c08",0x50),          /* 24c08设备名,0x50设备地址 */ 
  9.                    .platform_data= &at24c08,              /* 赋值给client->dev->platform_data */ 
  10.          }, 
  11. }; 
  12. static void __init mini2440_init(void
  13.          …… 
  14.          i2c_register_board_info(0,mini2440_i2c_devs,    /* busnum = 0,busnum是适配器编号,用来识别从设备使用的哪个适配器 */ 
  15.                                      ARRAY_SIZE(mini2440_i2c_devs)); 
  16.          …… 

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来匹配

  1. static const struct i2c_device_id*i2c_match_id(const struct i2c_device_id *id, 
  2.                                                         conststruct i2c_client *client) 
  3.          while(id->name[0]) { 
  4.                    if(strcmp(client->name, id->name) == 0) 
  5.                             returnid; 
  6.                    id++; 
  7.          } 
  8.          returnNULL; 

5.5 测试

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

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函数,完成相应的工作。


转载:http://blog.csdn.net/column/details/i2c-subsystem.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值