1.I2C体系结构分析

1.1首先进入linux内核的driver/i2c目录下,如下图所示:

其中重要的文件介绍如下:

1)algos文件夹(algorithms)

里面保存I2C的通信方面的算法

2)busses文件夹

里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。

3) chips文件夹

里面保存I2C设备驱动相关的文件,如下图所示,比如m41t00,就是RTC实时钟
在这里插入图片描述
4) i2c-core.c
这个文件实现了I2C核心的功能(I2C总线的初始化、注册和适配器添加和注销等相关工作)以及/proc/bus/i2c*接口。
5) i2c-dev.c
提供了通用的read( ) 、 write( ) 和ioctl( ) 等接口,实现了I2C适配器设备文件的功能,其中I2C设备的主设备号都为89, 次设备号为0~255。
应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器, 并控制I2C设备的工作方式

显然,它和前几次驱动类似, I2C也分为总线驱动和设备驱动,总线就是协议相关的,它知道如何收发数据,但不知道数据含义,设备驱动却知道数据含义

1.2 I2C驱动架构,如下图所示:

在这里插入图片描述
如上图所示,每一条I2C对应一个adapter适配器,在kernel中, adapter适配器是通过struct adapter结构体定义,主要是通过i2c core层将i2c设备与i2c adapter关联起来.

在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.

对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.

2.接下来便来分析I2C总线驱动

参考 drivers/i2c/busses/i2c-s3c2410.c
先进入init入口函数,如下图所示:
在这里插入图片描述
在init函数中,注册了一个 “s3c2440-i2c”的platform_driver平台驱动,我们来看看probe函数做了些什么

3.进入s3c24xx_i2c_probe函数

struct i2c_adapter  adap;static int s3c24xx_i2c_probe(struct platform_device *pdev){ struct s3c24xx_i2c *i2c = &s3c24xx_i2c;   ... ...   /*获取,使能I2C时钟*/   i2c->clk = clk_get(&pdev->dev, "i2c");               //获取i2c时钟   clk_enable(i2c->clk);                                         //使能i2c时钟   ... ....   /*获取资源*/   res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   i2c->regs = ioremap(res->start, (res->end-res->start)+1);   ... ....   /*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/ i2c->adap.algo_data = i2c;          //i2c_adapter适配器指向s3c24xx_i2c;   i2c->adap.dev.parent = &pdev->dev;
  /* initialise the i2c controller */   /*初始化2440的I2C相关的寄存器*/   ret = s3c24xx_i2c_init(i2c);   if (ret != 0)  goto err_iomap;   ... ...   /*注册中断服务函数*/   ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);   ... ...   /*注册i2c_adapter适配器结构体*/   ret = i2c_add_adapter(&i2c->adap);   ... ...}

其中i2c_adapter结构体是放在s3c24xx_i2c->adap下,如下图所示:
在这里插入图片描述

4.接下来我们进入i2c_add_adapter()函数看看,到底如何注册的

int i2c_add_adapter(struct i2c_adapter *adapter){   int   id, res = 0;retry:   if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) //调用idr_pre_get()为i2c_adapter预留内存空间  return -ENOMEM;   mutex_lock(&core_lists);   /* "above" here means "above or equal to", sigh */   res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);   //调用idr_get_new_above()将结构插入i2c_adapter_idr中,并将插入的位置赋给id,以后可以通过id在i2c_adapter_idr中找到相应的i2c_adapter结构体   mutex_unlock(&core_lists);   if (res < 0) {  if (res == -EAGAIN)goto retry;  return res;   }   adapter->nr = id;   return i2c_register_adapter(adapter);  //调用i2c_register_adapter()函数进一步来注册.}

其中i2c_register_adapter()函数代码如下所示:

static int i2c_register_adapter(struct i2c_adapter *adap){   struct list_head  *item;               //链表头,用来存放i2c_driver结构体的表头   struct i2c_driver *driver;                     //i2c_driver,用来描述一个IIC设备驱动list_add_tail(&adap->list, &adapters);       //添加到内核的adapter链表中... ...   list_for_each(item,&drivers) {        //for循环,从drivers链表里找到i2c_driver结构体的表头  driver = list_entry(item, struct i2c_driver, list); //通过list_head表头,找到i2c_driver结构体  if (driver->attach_adapter)  
                     /* We ignore the return code; if it fails, too bad */ driver->attach_adapter(adap);     //调用i2c_driver的attach_adapter函数来看看,这个新注册的设配器是否支持i2c_driver
 }}

在i2c_register_adapter()函数里主要执行以下几步:

①将adapter放入i2c_bus_type的adapter链表
②将所有的i2c设备调出来,执行i2c_driver设备的attach_adapter函数来匹配

其中, i2c_driver结构体会在后面讲述到

而i2c_adapter适配器结构体的成员结构,如下所示:

struct i2c_adapter {  
 struct module *owner; //所属模块  
 unsigned int id; //algorithm的类型,定义于i2c-id.h,  
 unsigned int class;      
 const struct i2c_algorithm *algo;  //总线通信方法结构体指针  
 void *algo_data;    //algorithm数据  
 struct rt_mutex bus_lock; //控制并发访问的自旋锁  
 int timeout;     
 int retries; //重试次数  
 struct device dev;  //适配器设备   
 int nr;                   //存放在i2c_adapter_idr里的位置号
 char name[48]; //适配器名称  
 struct completion dev_released; //用于同步  
 struct list_head userspace_clients;   //client链表头  };

i2c_adapter表示物理上的一个i2C设备(适配器), 在i2c-s3c2410.c中,是存放在s3c24xx_i2c结构体下的(struct i2c_adapter adap)成员中

5.其中s3c24xx_i2c的结构体成员如下所示

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {            
       .master_xfer          = s3c24xx_i2c_xfer,  //主机传输   .functionality          = s3c24xx_i2c_func,                    };static struct s3c24xx_i2c s3c24xx_i2c = {   .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),   .wait              = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),   .tx_setup = 50,                        //用来延时,等待SCL被释放   .adap             = {                                             // i2c_adapter适配器结构体  .name                   = "s3c2410-i2c",  .owner                  = THIS_MODULE,  .algo                     = &s3c24xx_i2c_algorithm,           //存放i2c_algorithm算法结构体  .retries           = 2,                                       //重试次数  .class                    = I2C_CLASS_HWMON,   },};

显然这里是直接设置了i2c_adapter结构体,所以在s3c24xx_i2c_probe ()函数中没有分配i2c_adapter适配器结构体,

其中, i2c_adapter结构体的名称等于"s3c2410-i2c",它的通信方式等于s3c24xx_i2c_algorithm,重试次数等于2

PS:如果缺少i2c_algorithm的i2c_adapter什么也做不了,就只是个I2C设备,而没有通信方式

s3c24xx_i2c_algorithm中的关键函数master_xfer()就是用于产生i2c访问周期需要的start stop ack等信号

比如,在s3c24xx_i2c_algorithm中的关键函数master_xfer()里,调用了:

s3c24xx_i2c_xfer -> s3c24xx_i2c_doxfer()->s3c24xx_i2c_message_start()

来启动传输message信息, 其中s3c24xx_i2c_message_start()函数代码如下:

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg){
 unsigned int addr = (msg->addr & 0x7f) << 1;              //IIC从设备地址的最低位为读写标志位   ... ...   stat = 0;   stat |=  S3C2410_IICSTAT_TXRXEN;     //设置标志位启动IIC收发使能   if (msg->flags & I2C_M_RD) {                     //判断是读,还是写  stat |= S3C2410_IICSTAT_MASTER_RX;       
              addr |= 1;                                          //设置从IIC设备地址为读标志   } else  stat |= S3C2410_IICSTAT_MASTER_TX;   s3c24xx_i2c_enable_ack(i2c);                //使能ACK信号 iiccon = readl(i2c->regs + S3C2410_IICCON);    //读出IICCON寄存器   writel(stat, i2c->regs + S3C2410_IICSTAT);   //写入IICSTAT寄存器,使能IIC的读或写标志   dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);   writeb(addr, i2c->regs + S3C2410_IICDS);  //将IIC从设备地址写入IICDS寄存器   /* delay here to ensure the data byte has gotten onto the bus
        * before the transaction is started */   ndelay(i2c->tx_setup);         //延时,等待SCL被释放,下面便可以发送起始信号+IIC设备地址值   dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);   writel(iiccon, i2c->regs + S3C2410_IICCON);            
       stat |=  S3C2410_IICSTAT_START;              
       writel(stat, i2c->regs + S3C2410_IICSTAT);  //设置IICSTAT寄存器的bit5=1,开始发送起始信号+IIC从设备地址值,并回应ACK}

通过上面的代码和注释,发现主要是写入IIC从设备地址,然后发送起始信号+IIC从设备地址值,并回应ACK

显然IIC总线驱动i2c-s3c2410.c,主要设置适配器adapter,里面帮我们做好了IIC通信的架构,就是不知道发什么内容

我们进入driver/i2c/chips中,看看eeprom设备驱动是如何写的

参考: driver/i2c/chips/eeprom.c

6.还是首先来看它的init入口函数:

在这里插入图片描述
其中struct i2c_driver eeprom_driver的成员如下:

static struct i2c_driver eeprom_driver = {   .driver = {  .name     = "eeprom",                        //名称},   .id           = I2C_DRIVERID_EEPROM,           //IIC设备标识ID   .attach_adapter     = eeprom_attach_adapter,  //用来与总线驱动的适配器匹配,匹配成功添加到适配器adapter中   .detach_client = eeprom_detach_client,      //与总线驱动的适配器解绑,分离这个IIC从设备};

如下图所示, eeprom_driver结构体的ID成员在i2c-id.h中,里面还定义了大部分常用I2C设备驱动的设备ID
在这里插入图片描述
显然,在init函数中通过i2c_add_driver()注册i2c_driver结构体,然后通过i2c_driver ->attach_adapter来匹配内核中的各个总线驱动的适配器, 发送这个设备地址,若有ACK响应,表示匹配成功

7.接下来,我们进入i2c_add_driver()来看看是不是这样的

int i2c_add_driver(struct module *owner, struct i2c_driver *driver){   driver->driver.owner = owner;   driver->driver.bus = &i2c_bus_type;    //将i2c_driver放在i2c_bus_type链表中      res = driver_register(&driver->driver); //注册一个i2c_driver   ... ...   if (driver->attach_adapter) {  struct i2c_adapter *adapter;                     //定义一个i2c_adapter适配器  list_for_each_entry(adapter, &adapters, list)  //for循环提取出adapters链表中所有的i2c_adapter适配器,放入到adapter结构体中
  {  driver->attach_adapter(adapter); //来匹配取出来的i2c_adapter适配器  }
  }  ... ...return 0;}

在i2c_add_driver ()函数里主要执行以下几步:

①放入到i2c_bus_type链表

②取出adapters链表中所有的i2c_adapter,然后执行i2c_driver->attach_adapter()

所以i2c_adapter适配器和i2c_driver设备驱动注册框架如下所示:
在这里插入图片描述

8.写程序

具体驱动可参考内核的以下部分
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/jiffies.h>#include <linux/i2c.h>#include <linux/mutex.h>#include <linux/fs.h>#include <asm/uaccess.h>static unsigned short ignore[]      = { I2C_CLIENT_END };static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 *//* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};/*强制识别的地址ANY_I2C_BUS那一条总线0x60 设备地址 I2C_CLIENT_END 结束地址*/static unsigned short * forces[] = {force_addr, NULL};static struct i2c_client_address_data addr_data = {.normal_i2c = normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */.probe = ignore,.ignore = ignore,//.forces     = forces, /* 强制认为存在这个设备才能进入detect 检测识别*/};static struct i2c_driver at24cxx_driver;static int major;static struct class *cls;struct i2c_client *at24cxx_client;static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset){unsigned char address;unsigned char data;struct i2c_msg msg[2];int ret;/* address = buf[0] 
 * data    = buf[1]
 */if (size != 1)return -EINVAL;copy_from_user(&address, buf, 1);/*address要读的地址*//* 数据传输三要素: 源,目的,长度 *//* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr  = at24cxx_client->addr;  /* 目的写到哪 */
msg[0].buf   = &address;              /* 源 写什么*/
msg[0].len   = 1;                     /* 地址=1 byte */
msg[0].flags = 0;                     /* 表示写 *//* 然后启动读操作 */
msg[1].addr  = at24cxx_client->addr;  /* 源 从哪里读*/
msg[1].buf   = &data;                 /* 目的 读到放在那里*/
msg[1].len   = 1;                     /* 数据=1 byte */
msg[1].flags = I2C_M_RD;                     /* 表示读 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);if (ret == 2){copy_to_user(buf, &data, 1);/*数据放到buf[0]*/return 1;}elsereturn -EIO;}static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){unsigned char val[2];struct i2c_msg msg[1];int ret;/* address = buf[0] 
 * data    = buf[1]
 */if (size != 2)return -EINVAL;copy_from_user(val, buf, 2);/* 数据传输三要素: 源,目的,长度 */
msg[0].addr  = at24cxx_client->addr;  /* 目的 */
msg[0].buf   = val;                   /* 源 */
msg[0].len   = 2;                     /* 地址+数据=2 byte */
msg[0].flags = 0;                     /* 表示写 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);if (ret == 1)return 2;elsereturn -EIO;}static struct file_operations at24cxx_fops = {.owner = THIS_MODULE,.read  = at24cxx_read,.write = at24cxx_write,};static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind){printk("at24cxx_detect\n");/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr    = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver  = &at24cxx_driver;strcpy(at24cxx_client->name, "at24cxx");i2c_attach_client(at24cxx_client);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "at24cxx");class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */return 0;}static int at24cxx_attach(struct i2c_adapter *adapter){return i2c_probe(adapter, &addr_data, at24cxx_detect);}static int at24cxx_detach(struct i2c_client *client){printk("at24cxx_detach\n");class_device_destroy(cls, MKDEV(major, 0));class_destroy(cls);unregister_chrdev(major, "at24cxx");i2c_detach_client(client);kfree(i2c_get_clientdata(client));return 0;}/* 1. 分配一个i2c_driver结构体 *//* 2. 设置i2c_driver结构体 */static struct i2c_driver at24cxx_driver = {.driver = {.name = "at24cxx",},.attach_adapter = at24cxx_attach,.detach_client  = at24cxx_detach,};static int at24cxx_init(void){i2c_add_driver(&at24cxx_driver);return 0;}static void at24cxx_exit(void){i2c_del_driver(&at24cxx_driver);}module_init(at24cxx_init);module_exit(at24cxx_exit);MODULE_LICENSE("GPL");