详解Linux-I2C驱动_i2c 时钟速率怎么使用adb查看-程序员宅基地

技术标签: Linux driver  Linux 驱动  I2C  

目录

一、LinuxI2C驱动--概述

1.1 写在前面

本人学生一枚,之前没有详细的接触过linux驱动,只是读过宋宝华的《Linux设备驱动开发详解》,这段时间想静下心来学习下linux i2c驱动,在网上找了很多资料,前辈们写的文章让我受益匪浅,但是一开始上手真的很痛苦,基本上大家都是从linux i2c体系结构的三大组成谈起:i2c核心,i2c总线驱动,i2c设备驱动,好抽象。所以我才想写这个文章,从一个新人的角度分享下我学习linux i2c驱动的心得,写的不对的地方欢迎大家批评指正。

因为对Linux设备模型还不是很熟悉,所以我按照如何去实现一个i2c传输来讲述,对于平台总线、设备与总线如何去匹配等暂时忽略。

当然很多东西都是我从网上搜刮而来的,也请大家原谅。我会把一些有用的博文链接放在后面,希望对大家有用。

1.2 I2C

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

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

1.3 硬件

开发板:飞凌OK210

CPU型号:Samsung S5PV210

EEPROM型号:AT24C01A

linux-i2c-1.3.png

1.4 软件

linux版本:Linux 2.6.35.7

I2C总线驱动:drivers/i2c/busses/i2c-s3c2410.c

eeprom驱动:drivers/misc/eeprom/at24.c

1.5 参考

二、LinuxI2C驱动--I2C总线

本节分析下I2C总线协议,因为我的开发板是三星s5pv210芯片,所以就以此为例。

2.1 I2C总线物理结构

linux-i2c-2.1.png

I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

2.2 I2C总线特性

  • 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系来软件设定地址
  • 多主机总线,如果两个或者更多的主机同时初始化数据传输,可以通过仲裁防止数据被破坏。
  • 串行8位双向数据传输
  • 标准模式传输速率为100kbits/s
  • 快速模式传输速率为400kbits/s
  • 7位地址模
  • 支持主机发、主机收,从机发、从机收

2.3 开始和停止条件

当SCL是高电平时,SDA线由高电平向低电平切换,表示开始;当SCL是高电平时,SDA线由低电平向高电平切换,表示停止。如下图所示。

linux-i2c-2.3.png

2.4 数据传输格式

发送到SDA线上的每个字节必须为8位,每次传输可以发送的字节数不受限制,但是每个字节后面必须跟一个响应位。

linux-i2c-2.4.1.png

linux-i2c-2.4.2.png

2.5 响应

数据传输必须带响应,响应时钟脉冲由主机产生,在SCL的第9个时钟脉冲上,前8个时钟脉冲用来传输8位即1byte的数据。

当发送端收到响应时钟脉冲的时候就会拉高SDA从而释放SDA线,而接收端通过拉低SDA先来表示收到数据,即SDA在响应期间保持低电平。

linux-i2c-2.5.png

2.6 总线仲裁

当两个主机在总线上产生竞争时就需要仲裁。

SDA线低电平的优先级高于高电平。当一个主机首先产生低电平,而紧接着另一个主机产生高电平,但是由于低电平的优先级高于高电平,所以总线成低电平,也就是发低电平的主机占有总线而发高电平的主机不占有总线。如果两个主机都是发送低电平,那么继续比较下一个时钟周期的电平来决定谁占有总线,以此类推。

linux-i2c-2.6.png

三、LinuxI2C驱动--解析EEPROM的读写

本节介绍eeprom的读写时序,参考的是AT24C01A的datasheet。

3.1 概述

AT24C01A的存储大小是1K,页大小是8个字节。

linux-i2c-3.1.png

3.2 设备地址

linux-i2c-3.2.png

7位地址,前四位是1010,后三位由芯片引脚决定,由原理图可知后三位是000,也就是设备地址为0x50,因为数据传输是8位的,最后一位决定是读还是写。

3.3 读eeprom

linux-i2c-3.3.png

读任意地址eeprom的数据,首先第一个字节得先在SDA上发出eeprom的设备地址,也就是0x50,并且8位数据的最后一位是低电平表示写设备,然后第二个字节是要读的数据在eeprom内的地址,这样以后再产生开始条件,第三个字节在SDA上发出设备地址,此时的最后一位是高电平,表示读设备,第四个字节的数据就是读eeprom的对应地址的数据。

可以看到,读eeprom需要两个开始条件,也就是2条消息,第一条消息写eeprom确定读的位置,大小为2个字节,第二条消息才是真正的读eeprom。

3.4 写eeprom

linux-i2c-3.4.png

写eeprom就相对简单,只需一个开始条件,第一个字节发出设备地址和置最低位为低电平表示写eeprom,第二个字节发出要读数据在eerpom的地址,第三个字节读到的数据就对应地址在eeprom上的数据

四、LinuxI2C驱动--从两个访问eeprom的例子开始

本小节介绍两个在linux应用层访问eeprom的方法,并给出示例代码方便大家理解。第一个方法是通过sysfs文件系统对eeprom进行访问,第二个方法是通过eeprom的设备文件进行访问。这两个方法分别对应了i2c设备驱动的两个不同的实现,在后面的小结会详细的分析。

4.1 通过sysfs文件系统访问I2C设备

eeprom的设备驱动在/sys/bus/i2c/devices/0-0050/目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。

我们可以先用cat命令来看下eeprom的内容。

[root@FORLINX210]# cat eeprom                                                                      
�����������X�����������������������������������������������

发现里面都是乱码,然后用echo命令把字符串“test”输入给eeprom文件,然后再cat出来。

就会发现字符串test已经存在eeprom里面了,我们知道sysfs文件系统断电后就没了,也无法对数据进行保存,为了验证确实把“test”字符串存储在了eeprom,可以把系统断电重启,然后cat eeprom,会发现test还是存在的,证明确实对eeprom进行了写入操作。

当然,因为eeprom已经映射为一个文件了,我们还可以通过文件I/O写应用程序对其进行简单的访问测试。比如以下程序对特定地址(0x40)写入特定数据(Hi,this is an eepromtest!),然后再把写入的数据在此地址上读出来。

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main(void){
     
  int fd, size, len, i;
  char buf[50]= {
     0};
  char *bufw="Hi,this is an eepromtest!";//要写入的数据

  len=strlen(bufw);//数据长度
  fd= open("/sys/bus/i2c/devices/0-0050/eeprom",O_RDWR);//打开文件
  if(fd< 0)
  {
     
      printf("####i2c test device open failed####/n");
      return(-1);
  }
  //写操作
  lseek(fd,0x40,SEEK_SET); //定位地址,地址是0x40
  if((size=write(fd,bufw, len))<0)//写入数据
  {
     
      printf("write error\n");
      return 1;
  }
  printf("writeok\n");
  //读操作
  lseek(fd,0x40, SEEK_SET);//准备读,首先定位地址,因为前面写入的时候更新了当前文件偏移量,所以这边需要重新定位到0x40.
  if((size=read(fd,buf,len))<0)//读数据
  {
     
      printf("readerror\n");
      return 1;
  }
  printf("readok\n");
  for(i=0; i< len; i++)
      printf("buff[%d]=%x\n",i, buf[i]);//打印数据
  close(fd);

  return 0;
}

4.2 通过devfs访问I2C设备

linux的i2c驱动会针对每个i2c适配器在/dev/目录下生成一个主设备号为89的设备文件,简单的来说,对于本例的eeprom驱动,/dev/i2c/0就是它的设备文件,因此接下来的eeprom的访问就变为了对此设备文件的访问。

我们需要用到两个结构体i2cmsg和i2crdwrioctldata。

struct i2c_msg {
      //i2c消息结构体,每个i2c消息对应一个结构体
 __u16 addr; /* 从设备地址,此处就是eeprom地址,即0x50 */
 __u16 flags;    /* 一些标志,比如i2c读等*/
 __u16 len;      /* i2c消息的长度 */
 __u8 *buf;      /* 指向i2c消息中的数据 */
 };

struct i2c_rdwr_ioctl_data {
     
 struct i2c_msg __user *msgs;    /* 指向一个i2c消息 */
 __u32 nmsgs;            /* i2c消息的数量 */
};

对一个eeprom上的特定地址(0x10)写入特定数据(0x58)并在从此地址读出写入数据的示例程序如下所示。

#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int main()
{
     
    int fd,ret;
    struct i2c_rdwr_ioctl_data e2prom_data;
    fd=open("/dev/i2c/0",O_RDWR);//打开eeprom设备文件结点
    if(fd<0)
    {
     
        perror("open error");
    }

    e2prom_data.nmsgs=2; 
    e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));//分配空间
    if(!e2prom_data.msgs)
    {
     
        perror("malloc error");
        exit(1);
    }
    ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
    ioctl(fd,I2C_RETRIES,2);/*重复次数*/

    /*写eeprom*/
    e2prom_data.nmsgs=1;//由前面eeprom读写分析可知,写eeprom需要一条消息
    (e2prom_data.msgs[0]).len=2; //此消息的长度为2个字节,第一个字节是要写入数据的地址,第二个字节是要写入的数据
    (e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
    (e2prom_data.msgs[0]).flags=0; //写
    (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
    (e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址
    (e2prom_data.msgs[0]).buf[1]=0x58;//写入的数据
    ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际写入操作,后面会详细分析
    if(ret<0)
    {
     
        perror("ioctl error1");
    }
    sleep(1);

    /*读eeprom*/
    e2prom_data.nmsgs=2;//读eeprom需要两条消息
    (e2prom_data.msgs[0]).len=1; //第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为1个字节
    (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
    (e2prom_data.msgs[0]).flags=0;//先是写
    (e2prom_data.msgs[0]).buf[0]=0x10;//e2prom上需要读的数据的地址
    (e2prom_data.msgs[1]).len=1;//第二条消息才是读eeprom,
    (e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址 
    (e2prom_data.msgs[1]).flags=I2C_M_RD;//然后是读
    (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
    (e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲,读到的数据放到此缓冲区
    ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际的读操作
    if(ret<0)
    {
     
        perror("ioctl error2");
    }

    printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);
    /***打印读出的值,没错的话,就应该是前面写的0x58了***/
    close(fd);

    return 0;
}

4.3 总结

本小节介绍了两种在linux应用层访问eeprom的方法,并且给出了示例程序,通过sysfs文件系统访问eeprom操作简单,无需了解eeprom的硬件特性以及访问时序,而通过devfs访问eeprom的方法则需要了解eeprom的读写时序。

后面分析后会发现,第一种通过sysfs文件系统的二进制结点访问eeprom的方法是由eeprom的设备驱动实现的,是一种专有的方法;而第二种通过devfs访问eeprom的方法是linux i2c提供的一种通用的方法,访问设备的能力有限。

五、LinuxI2C驱动--浅谈LinuxI2C驱动架构

前面几个小结介绍了i2c总线的协议,又介绍了我们关注的eeprom的读写访问时序,还给出了两个访问eeprom的例子,我的目的是为了能更好的理解后面解析Linux下i2c驱动。

网上介绍Linux I2C驱动架构的文章非常的多,我把这些内容做了个归纳与简化,但是在搬出这些非常抽象的内容之前,我想先谈下我的理解。

如下图

linux-i2c-5.0.png

图中画了一个三星的s5pv210处理器,在处理器的里面集成了一个I2C适配器,外面有一个eeprom,通过SDA、SCL连接到cpu内集成的i2c适配器上。这样cpu就可以控制i2c适配器与外部的eeprom进行交互,也就是i2c适配器产生符合i2c协议的信号与eeprom进行通信。

所以对应到linux驱动下,控制i2c适配器有一套驱动代码,叫做i2c总线驱动,是用来产生i2c时序信号的,可以发送和接受数据;控制eeprom有一套驱动代码,叫做i2c设备驱动,这套驱动代码才是真正的对硬件eeprom控制。这也符合linux设备驱动分层的思想。而两套驱动代码之间有一个i2c核心,用来起到承上启下的作用。

以一个写eeprom为例,应用层发出写eeprom消息,i2c设备驱动接到消息,把消息封装成一个前文提到的i2c消息结构体,然后经i2c核心的调度把消息传给i2c适配器,i2c适配器就根据当前cpu的i2c总线协议把消息通过SDA和SCL发给了eeprom。

接下来开始搬运,,

5.1 I2C体系结构

linux的i2c体系结构分为三个组成部分。放张图加深理解。

linux-i2c-5.0.png

(1)i2c核心

提供了I2C总线驱动的注册、注销方法 提供了I2C设备驱动的注册、注销方法 提供了I2C通信方法(algorithm) 对应代码:drivers/i2c/i2c-core.c

(2)i2c总线驱动

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

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

I2C总线驱动由i2cadapter和i2calgorithm来描述 对应代码:drivers/i2c/busses/i2c-s3c2410.c

(3)i2c设备驱动

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

I2C设备驱动程序由i2c_driver来描述

对应代码:drivers/misc/eeprom/at24.c

5.2 I2C重要数据结构

在include/linux/i2c.h中定义四个I2C驱动中重要的数据结构:i2cadapter,i2calgorithm,i2cdriver,i2cclient.

i2c_adapter对应物理上的一个i2c适配器

struct i2c_adapter {
     
  struct module *owner;//所属模块
  unsigned int id;
  unsigned int class;       /* classes to allow probing for */
  const struct i2c_algorithm *algo; /* 总线通讯方法指针,需要其产生特定的访问周期信号 */
  void *algo_data;

  /* data fields that are valid for all devices   */
  struct rt_mutex bus_lock;

  int timeout;            /* in jiffies */
  int retries;/* 重复次数 */
  struct device dev;      /* the adapter device */

  int nr;
  char name[48];
  struct completion dev_released;

  struct list_head userspace_clients;
};

i2c_algorithm对应一套通讯方法

struct i2c_algorithm {
     
  int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
            int num);//产生i2c访问周期说需要的信号
  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 *);//返回说支持的通讯协议
};

i2c_driver对应一套驱动方法

struct i2c_driver {
     
 unsigned int class;

 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;//该驱动所支持的i2c设备的ID表
 int (*detect)(struct i2c_client *, struct i2c_board_info *);
 const unsigned short *address_list;
 struct list_head clients;
};

i2cclient对应真实的物理设备,每个i2c设备都需要一个i2cclient来描述

struct i2c_client {
     
 unsigned short flags;       /* div., see below      */
 unsigned short addr;        /* chip address - NOTE: 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;
};

(1)i2cadapter与i2calgorithm 一个I2C适配器需要i2calgorithm中提供的通信函数来控制适配器上产生特定的访问周期。i2calgorithm中的关键函数masterxfer()用于产生I2C访问周期需要的信号,以i2cmsg为单位。

struct i2c_msg {
     
 __u16 addr; /* slave address            */
 __u16 flags;
 __u16 len;      /* msg length               */
 __u8 *buf;      /* pointer to msg data          */
};

(2)i2cadapter与i2cclient i2cdriver与i2cclient是一对多的关系,一个i2cdriver上可以支持多个同等类型的i2cclient。

(3)i2cadapter与i2cclient i2cadapter与i2cclient的关系与I2C硬件体系中适配器和从设备的关系一致,i2cclient依附在i2cadapter上。

六、LinuxI2C驱动--I2C设备驱动

本节主要分析eeprom的所属的i2c设备驱动,此驱动主要实现了能够通过sysfs文件系统访问eeprom。

6.1 eeprom板级设备资源

因为原开发板的eeprom驱动还没调试好,板级资源还没写好,所以需要自己加进去。 修改arch/arm/mach-s5pv210/mach-smdkc110.c文件。

static struct at24_platform_data at24c01 = {
     
 .byte_len = SZ_8K / 8,/*eeprom大小*/
 .page_size = 8,/*页大小*/
};

/* I2C0 */
static struct i2c_board_info i2c_devs0[] __initdata = {
     
 {
     
     I2C_BOARD_INFO("24c01",0x50),//0x50是eeprom的设备地址
     .platform_data=&at24c01,
 },
}

这样以后后面调用smdkc110machineinit就会把资源注册进去。

 static void __init smdkc110_machine_init(void)
 {
     
 ….
 i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
 ….
 }

6.2 AT24C01A EEPROM 的I2C设备驱动

6.2.1 at24_driver

前面讲i2c驱动架构的时候,说到I2C设备驱动主要由i2c_driver来描述。

在drivers/misc/eeprom/at24.c中可以看到eeprom驱动对i2c_driver结构的实例化。

 static struct i2c_driver at24_driver = {
     
 .driver = {
     
     .name = "at24",
     .owner = THIS_MODULE,
 },
 .probe = at24_probe,
 .remove = __devexit_p(at24_remove),
 .id_table = at24_ids,
 };

其中probe和remove会在模块初始化和卸载的时候被调用。

 static int __init at24_init(void)//模块初始化
 {
     
    io_limit = rounddown_pow_of_two(io_limit);//io_limit是写eeprom时允许一次写入的最大字节,默认128Byte,是驱动模块参数。
    return i2c_add_driver(&at24_driver);//添加i2c_driver,在i2c核心中实现,会调用at24_probe.
 }
 module_init(at24_init);

 static void __exit at24_exit(void)//模块卸载
 {
     
    i2c_del_driver(&at24_driver);//删除i2c_driver,会调用at24_remove
 }
 module_exit(at24_exit);
6.2.2 at24_probe() / at24_remove()
static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
     
struct at24_platform_data chip;
bool writable;
int use_smbus = 0;
struct at24_data *at24;
int err;
unsigned i, num_addresses;
kernel_ulong_t magic;

//获取板级设备信息
if (client->dev.platform_data) {
     
    chip = *(struct at24_platform_data *)client->dev.platform_data;
} else {
     
    if (!id->driver_data) {
     
        err = -ENODEV;
        goto err_out;
    }
    magic = id->driver_data;
    chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
    magic >>= AT24_SIZE_BYTELEN;
    chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
    /*
     * This is slow, but we can't know all eeproms, so we better
     * play safe. Specifying custom eeprom-types via platform_data
     * is recommended anyhow.
     */
    chip.page_size = 1;

    chip.setup = NULL;
    chip.context = NULL;
}

//检查参数,必须为2的幂
if (!is_power_of_2(chip.byte_len))
    dev_warn(&client->dev,
        "byte_len looks suspicious (no power of 2)!\n");
if (!is_power_of_2(chip.page_size))
    dev_warn(&client->dev,
        "page_size looks suspicious (no power of 2)!\n");

/* Use I2C operations unless we're stuck with SMBus extensions. */
//检查是否支持I2C协议,如果不支持则检查是否支持SMBUS
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
     
    if (chip.flags & AT24_FLAG_ADDR16) {
     
        err = -EPFNOSUPPORT;
        goto err_out;
    }
    if (i2c_check_functionality(client->adapter,
            I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
     
        use_smbus = I2C_SMBUS_I2C_BLOCK_DATA;
    } else if (i2c_check_functionality(client->adapter,
            I2C_FUNC_SMBUS_READ_WORD_DATA)) {
     
        use_smbus = I2C_SMBUS_WORD_DATA;
    } else if (i2c_check_functionality(client->adapter,
            I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
     
        use_smbus = I2C_SMBUS_BYTE_DATA;
    } else {
     
        err = -EPFNOSUPPORT;
        goto err_out;
    }
}

if (chip.flags & AT24_FLAG_TAKE8ADDR)//检查时候使用8个地址
    num_addresses = 8;
else
    num_addresses = DIV_ROUND_UP(chip.byte_len,//AT24C01使用一个地址
        (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);

at24 = kzalloc(sizeof(struct at24_data) +
    num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);//为at24_data分配内存,同时根据地址个数分配i2c_client
if (!at24) {
     
    err = -ENOMEM;
    goto err_out;
}

mutex_init(&at24->lock);
//初始化at24_data,也就是填充此结构体
at24->use_smbus = use_smbus;
at24->chip = chip;
at24->num_addresses = num_addresses;

/*
 * Export the EEPROM bytes through sysfs, since that's convenient.
 * By default, only root should see the data (maybe passwords etc)
 */
//以二进制结点的形式呈现eeprom的数据
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)) {
     

        unsigned write_max = chip.page_size;

        at24->macc.write = at24_macc_write;

        at24->bin.write = at24_bin_write;//绑定写函数
        at24->bin.attr.mode |= S_IWUSR;//文件拥有者可写

        if (write_max > io_limit)//一次最多写io_limit个字节
            write_max = io_limit;
        if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
            write_max = I2C_SMBUS_BLOCK_MAX;
        at24->write_max = write_max;

        /* buffer (data + address at the beginning) */
        at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL);//分配缓冲区,多余两个字节用于保存寄存器地址
        if (!at24->writebuf) {
     
            err = -ENOMEM;
            goto err_struct;
        }
    } else {
     
        dev_warn(&client->dev,
            "cannot write due to controller restrictions.");
    }
}

at24->client[0] = client;

/* use dummy devices for multiple-address chips */
for (i = 1; i < num_addresses; i++) {
     
    at24->client[i] = i2c_new_dummy(client->adapter,
                client->addr + i);
    if (!at24->client[i]) {
     
        dev_err(&client->dev, "address 0x%02x unavailable\n",
                client->addr + i);
        err = -EADDRINUSE;
        goto err_clients;
    }
}

//向sysfs文件系统注册二进制结点
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
if (err)
    goto err_clients;

//保存驱动数据
i2c_set_clientdata(client, at24);

dev_info(&client->dev, "%zu byte %s EEPROM %s\n",
    at24->bin.size, client->name,
    writable ? "(writable)" : "(read-only)");
if (use_smbus == I2C_SMBUS_WORD_DATA ||
    use_smbus == I2C_SMBUS_BYTE_DATA) {
     
    dev_notice(&client->dev, "Falling back to %s reads, "
           "performance will suffer\n", use_smbus ==
           I2C_SMBUS_WORD_DATA ? "word" : "byte");
}
dev_dbg(&client->dev,
    "page_size %d, num_addresses %d, write_max %d, use_smbus %d\n",
    chip.page_size, num_addresses,
    at24->write_max, use_smbus);

/* export data to kernel code */
if (chip.setup)
    chip.setup(&at24->macc, chip.context);

return 0;
err_clients:
for (i = 1; i < num_addresses; i++)
    if (at24->client[i])
        i2c_unregister_device(at24->client[i]);

kfree(at24->writebuf);
err_struct:
kfree(at24);
err_out:
dev_dbg(&client->dev, "probe error %d\n", err);
return err;
}

at24probe()函数主要的工作是在sys目录在创建bin结点文件,也就是前面通过sysfs文件系统访问i2c设备中提到的/sys/bus/i2c/devices/0-0050/eeprom文件,用户可以用此文件来操作eeprom,提供读/写操作方法,在probe里面读写操作已经与二进制结点绑定,读操作函数是at24binread(),写操作函数是at24bin_write()。

其中有个重要的结构体:

struct at24_data {
     
 struct at24_platform_data chip;
 struct memory_accessor macc;
 int use_smbus;

 /*
  * Lock protects against activities from other Linux tasks,
  * but not from changes by other I2C masters.
  */
 struct mutex lock;
 struct bin_attribute bin;//二进制结点

 u8 *writebuf;//写缓冲区
 unsigned write_max;
 unsigned num_addresses;

 /* 
  * Some chips tie up multiple I2C addresses; dummy devices reserve
  * them for us, and we'll use them with SMBus calls.
  */
 struct i2c_client *client[];
};

at24_data是此驱动的一些私有数据的封装,包括二进制结点,以及写缓冲区。

static int __devexit at24_remove(struct i2c_client *client)
{
     
struct at24_data *at24;
int i;

at24 = i2c_get_clientdata(client);
sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);

for (i = 1; i < at24->num_addresses; i++)
    i2c_unregister_device(at24->client[i]);

kfree(at24->writebuf);
kfree(at24);
return 0;
}

at24remove()基本就是at24probe()的反操作。

6.2.3 at24_bin_read()
static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
     struct bin_attribute *attr,
     char *buf, loff_t off, size_t count)
{
     
 struct at24_data *at24;

 //通过kobj获得device,再获取driver_data
 at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
 return at24_read(at24, buf, off, count);//调用at24_read()
}

at24binread()通过devgetdrvdata()获取at24data结构体数据。然后调用at24read()。

static ssize_t at24_read(struct at24_data *at24,
     char *buf, loff_t off, size_t count)
{
     
 ssize_t retval = 0;

 if (unlikely(!count))
     return count;

 /*
  * Read data from chip, protecting against concurrent updates
  * from this host, but not from other I2C masters.
  */
 mutex_lock(&at24->lock);//访问设备前加锁

 while (count) {
     
     ssize_t status;

     status = at24_eeprom_read(at24, buf, off, count);
     if (status <= 0) {
     
         if (retval == 0)
             retval = status;
         break;
     }
     buf += status;
     off += status;
     count -= status;
     retval += status;
 }

 mutex_unlock(&at24->lock);//访问结束后解锁

 return retval;
}

at24read()传入的参数,at24是驱动私有数据结构体at24data,buf是读eeprom后读到的数据存储的缓冲区,off是数据的偏移地址,count是要读数据的大小。at24read()主要调用at24eeprom_read()去读,但是此函数读eeprom能读到的数据个数有限制,不一定一次就把count个数据都读到,所以用while来读,并且读到status个数据后更新count,表示还剩多少个数据没读到,同时也要更新数据偏移off,和读入缓冲buf。

static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
    unsigned offset, size_t count)
{
     
struct i2c_msg msg[2];
u8 msgbuf[2];
struct i2c_client *client;
unsigned long timeout, read_time;
int status, i;

memset(msg, 0, sizeof(msg));

/*
 * REVISIT some multi-address chips don't rollover page reads to
 * the next slave address, so we may need to truncate the count.
 * Those chips might need another quirk flag.
 *
 * If the real hardware used four adjacent 24c02 chips and that
 * were misconfigured as one 24c08, that would be a similar effect:
 * one "eeprom" file not four, but larger reads would fail when
 * they crossed certain pages.
 */

/*
 * Slave address and byte offset derive from the offset. Always
 * set the byte address; on a multi-master board, another master
 * may have changed the chip's "current" address pointer.
 */
client = at24_translate_offset(at24, &offset);//获得client

if (count > io_limit)
    count = io_limit;

switch (at24->use_smbus) {
     //如果使用SMBUS
case I2C_SMBUS_I2C_BLOCK_DATA:
    /* Smaller eeproms can work given some SMBus extension calls */
    if (count > I2C_SMBUS_BLOCK_MAX)
        count = I2C_SMBUS_BLOCK_MAX;
    break;
case I2C_SMBUS_WORD_DATA:
    count = 2;
    break;
case I2C_SMBUS_BYTE_DATA:
    count = 1;
    break;
default://使用I2C协议
    /*
     * When we have a better choice than SMBus calls, use a
     * combined I2C message. Write address; then read up to
     * io_limit data bytes. Note that read page rollover helps us
     * here (unlike writes). msgbuf is u8 and will cast to our
     * needs.
     */
    i = 0;
    if (at24->chip.flags & AT24_FLAG_ADDR16)
        msgbuf[i++] = offset >> 8;
    msgbuf[i++] = offset;

    //由前小节读eeprom的时序可知,需要2条消息,第一条消息是写eeprom
    msg[0].addr = client->addr;//设备地址,即0x50
    msg[0].buf = msgbuf;
    msg[0].len = i;

    //第二条消息才是读eeprom,读到的数据存储在buf中。
    msg[1].addr = client->addr;//设备地址
    msg[1].flags = I2C_M_RD;//读
    msg[1].buf = buf;//读缓冲区
    msg[1].len = count;//要读数据的长度
}

/*
 * Reads fail if the previous write didn't complete yet. We may
 * loop a few times until this one succeeds, waiting at least
 * long enough for one entire page write to work.
 */
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
     
    read_time = jiffies;
    switch (at24->use_smbus) {
     
    case I2C_SMBUS_I2C_BLOCK_DATA:
        status = i2c_smbus_read_i2c_block_data(client, offset,
                count, buf);
        break;
    case I2C_SMBUS_WORD_DATA:
        status = i2c_smbus_read_word_data(client, offset);
        if (status >= 0) {
     
            buf[0] = status & 0xff;
            buf[1] = status >> 8;
            status = count;
        }
        break;
    case I2C_SMBUS_BYTE_DATA:
        status = i2c_smbus_read_byte_data(client, offset);
        if (status >= 0) {
     
            buf[0] = status;
            status = count;
        }
        break;
    default://使用I2C协议去读
        status = i2c_transfer(client->adapter, msg, 2);//实际的数据传输,
        if (status == 2)
            status = count;
    }
    dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
            count, offset, status, jiffies);

    if (status == count)//已经全部读取,则返回
        return count;

    /* REVISIT: at HZ=100, this is sloooow */
    msleep(1);
} while (time_before(read_time, timeout));

return -ETIMEDOUT;
}

at24eepromread()根据读eeprom所需要的时序,填充两个i2c消息结构体,第一个i2c消息结构体是写eeprom,告诉eeprom要读的数据是哪个,第二个i2c消息才是真正的读eeprom。最后把这两个i2c消息结构体传给i2ctransfer()进行实际的消息传输。i2ctransfer()是i2c核心的函数,用于i2c设备与i2c适配器直接的消息传递,后面会分析。这里我们看到了i2c设备驱动通过i2c核心向i2c总线驱动传递消息的主要途径,i2c总线驱动接收到i2c消息后就会控制i2c适配器根据传入的i2c消息,通过SDA和SCL与eeprom进行交互。

6.2.4 at24_bin_write()
 static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
     struct bin_attribute *attr,
     char *buf, loff_t off, size_t count)
 {
     
 struct at24_data *at24;

 at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
 return at24_write(at24, buf, off, count);
 }

at24binwrite()与at24binread()一样操作,获得at24data后调用at24write().

static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
           size_t count)
{
     
 ssize_t retval = 0;

 if (unlikely(!count))
     return count;

 /*
  * Write data to chip, protecting against concurrent updates
  * from this host, but not from other I2C masters.
  */
 mutex_lock(&at24->lock);

 while (count) {
     
     ssize_t status;

     status = at24_eeprom_write(at24, buf, off, count);
     if (status <= 0) {
     
         if (retval == 0)
             retval = status;
         break;
     }
     buf += status;
     off += status;
     count -= status;
     retval += status;
 }

 mutex_unlock(&at24->lock);

 return retval;
}

at24write()的操作也是类似的,通过调用at24eeprom_write()来实现。

static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
    unsigned offset, size_t count)
{
     
struct i2c_client *client;
struct i2c_msg msg;
ssize_t status;
unsigned long timeout, write_time;
unsigned next_page;

/* Get corresponding I2C address and adjust offset */
client = at24_translate_offset(at24, &offset);//获得对应的client

/* write_max is at most a page */
//检查写入字数
if (count > at24->write_max)
    count = at24->write_max;

/* Never roll over backwards, to the start of this page */
//写入不会越过页边界(下一页)
next_page = roundup(offset + 1, at24->chip.page_size);
if (offset + count > next_page)
    count = next_page - offset;

/* If we'll use I2C calls for I/O, set up the message */
if (!at24->use_smbus) {
     //使用i2c协议,则填充i2c消息结构体
    int i = 0;

    //由前小节分析,写eeprom只需一条i2c消息
    msg.addr = client->addr;//设备地址
    msg.flags = 0;//写eeprom

    /* msg.buf is u8 and casts will mask the values */
    msg.buf = at24->writebuf;//写缓冲区
    if (at24->chip.flags & AT24_FLAG_ADDR16)
        msg.buf[i++] = offset >> 8;

    msg.buf[i++] = offset;
    memcpy(&msg.buf[i], buf, count);//复制需要发送的数据
    msg.len = i + count;//发送传读为要发送的数据长度,加上地址长度
}

/*
 * Writes fail if the previous one didn't complete yet. We may
 * loop a few times until this one succeeds, waiting at least
 * long enough for one entire page write to work.
 */
timeout = jiffies + msecs_to_jiffies(write_timeout);//超时时间,为驱动模块参数,默认25ms
do {
     
    write_time = jiffies;
    if (at24->use_smbus) {
     
        status = i2c_smbus_write_i2c_block_data(client,
                offset, count, buf);
        if (status == 0)
            status = count;
    } else {
     //i2c传输
        status = i2c_transfer(client->adapter, &msg, 1);//实际传输
        if (status == 1)
            status = count;
    }
    dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
            count, offset, status, jiffies);

    if (status == count)//已经全部写入,返回
        return count;

    /* REVISIT: at HZ=100, this is sloooow */
    msleep(1);
} while (time_before(write_time, timeout));

return -ETIMEDOUT;
}

与at24eepromread()类似,at24eepromwrite()因为写eeprom需要1条i2c消息,最后实际的传输也是通过i2c_transfer()实现。

6.3 总结

由上面简单的分析可知,通过sysfs文件系统访问eeprom,对/sys/bus/i2c/devices/0-0050/eeprom的读写是通过at24binread()/at24binwrite() ==> at24eepromread()/at24eepromwrite() ==>i2c_transfer()来实现的。

七、LinuxI2C驱动--I2C总线驱动

前面分析了i2c设备驱动如何实现通过sysfs文件系统访问eeprom,对于读写eeprom,最后都是调用了i2c_transfer(),此函数的实现在i2c核心中。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
     
 unsigned long orig_jiffies;
 int ret, try;

 /* REVISIT the fault reporting model here is weak:
  *
  *  - When we get an error after receiving N bytes from a slave,
  *    there is no way to report "N".
  *
  *  - When we get a NAK after transmitting N bytes to a slave,
  *    there is no way to report "N" ... or to let the master
  *    continue executing the rest of this combined message, if
  *    that's the appropriate response.
  *
  *  - When for example "num" is two and we successfully complete
  *    the first message but get an error part way through the
  *    second, it's unclear whether that should be reported as
  *    one (discarding status on the second message) or errno
  *    (discarding status on the first one).
  */

 if (adap->algo->master_xfer) {
     
 #ifdef DEBUG
     for (ret = 0; ret < num; ret++) {
     
         dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
             "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
             ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
             (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
     }
 #endif      

     if (in_atomic() || irqs_disabled()) {
     
         ret = rt_mutex_trylock(&adap->bus_lock); 
         if (!ret)
             /* I2C activity is ongoing. */
             return -EAGAIN;
     } else {
     
         rt_mutex_lock(&adap->bus_lock);
     }   

     /* Retry automatically on arbitration loss */
     orig_jiffies = jiffies;
     for (ret = 0, try = 0; try <= adap->retries; try++) {
     
         ret = adap->algo->master_xfer(adap, msgs, num);//i2c总线驱动的入口
         if (ret != -EAGAIN)
             break;
         if (time_after(jiffies, orig_jiffies + adap->timeout))
             break;
     }
     rt_mutex_unlock(&adap->bus_lock);

     return ret;
 } else {
     
     dev_dbg(&adap->dev, "I2C level transfers not supported\n");
     return -EOPNOTSUPP;
 }
}

可以看到,语句ret = adap->algo->masterxfer(adap, msgs, num)就是i2c总线驱动的入口,此语句是寻找i2cadapter对应的i2calgorithm后,使用masterxfer()驱动硬件流程来进行实际的传输。

那么i2cadapter是在哪里绑定了i2calgorithm呢?master_xfer()又是如何来启动i2c传输的呢?在i2c总线驱动中我们就可以找到答案。

7.1 三星S5PV210 i2c适配器的硬件描述

s5pv210处理器内部集成了一个i2c控制器,通过4个主要的寄存器就可以对其进行控制。 在arch/arm/plat-samsung/include/plat/regs-iic.h中列出了这几个寄存器。

#define S3C2410_IICREG(x) (x)

#define S3C2410_IICCON    S3C2410_IICREG(0x00)//i2c控制寄存器
#define S3C2410_IICSTAT   S3C2410_IICREG(0x04)//i2c状态寄存器
#define S3C2410_IICADD    S3C2410_IICREG(0x08)//i2c地址寄存器
#define S3C2410_IICDS     S3C2410_IICREG(0x0C)//i2c收发数据移位寄存器

i2c寄存器支持收发两种模式,我们主要使用主模式,通过对IICCON、IICDS和IICADD寄存器的操作,可以在i2c总线上产生开始位,停止位,数据和地址,而传输的状态则是通过IICSTAT寄存器获取。

在三星的i2c总线说明文档中给出了i2c总线进行传输的整个流程。

linux-i2c-7.1.png

以通过i2c总线写eeprom为例,具体的流程如下:

  1. 设置GPIO的相关引脚为IIC输出;
  2. 设置IIC(打开ACK,打开IIC中断,设置CLK等);
  3. 设备地址赋给IICDS ,并设置IICSTAT,启动IIC发送设备地址出去;从而找到相应的设备即IIC总线上的设备。
  4. 第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断;
  5. 在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断;
  6. 中断处理函数把第三个Byte(真正的数据)发送到设备中。
  7. 发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕。
  8. IIC Stop信号,关IIC中断,置位各寄存器。

在下面的小节中将结合代码来分析i2c总线对上面流程的具体实现。

7.2 i2c总线驱动的加载/卸载

i2c总线驱动被作为一个单独的模块加载,下面首先分析它的加载/卸载函数。

static int __init i2c_adap_s3c_init(void)
{
     
 return platform_driver_register(&s3c24xx_i2c_driver);//注册为平台驱动
}
subsys_initcall(i2c_adap_s3c_init);

static void __exit i2c_adap_s3c_exit(void)
{
     
 platform_driver_unregister(&s3c24xx_i2c_driver);
}
module_exit(i2c_adap_s3c_exit);

三星s5pv210的i2c总线驱动是作为平台驱动来实现的,其中传入的结构体s3c24xxi2cdriver就是platform_driver。

static struct platform_driver s3c24xx_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,
 },
};

7.3 i2c总线驱动的probe

i2c总线驱动的probe函数会在一个合适的设备被发现的时候由总线驱动调用。

/* s3c24xx_i2c_probe
*
* called by the bus driver when a suitable device is found
*/
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
     
struct s3c24xx_i2c *i2c;//封装i2c适配器的信息
struct s3c2410_platform_i2c *pdata;//i2c平台数据
struct resource *res;//平台资源
int ret;

pdata = pdev->dev.platform_data;//找到平台数据
if (!pdata) {
     
    dev_err(&pdev->dev, "no platform data\n");
    return -EINVAL;
}

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);//为i2c适配器私有数据结构体分配内存空间,并且初始化为0
if (!i2c) {
     
    dev_err(&pdev->dev, "no memory for state\n");
    return -ENOMEM;
}

//填充i2c适配器私有数据结构体
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);//i2c适配器私有数据的锁进行初始化
init_waitqueue_head(&i2c->wait);//初始化等待队列

/* find the clock and enable it */
//找到时钟,并且使能
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");//找到时钟

if (IS_ERR(i2c->clk)) {
     
    dev_err(&pdev->dev, "cannot get clock\n");
    ret = -ENOENT;
    goto err_noclk;
}

dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);

clk_enable(i2c->clk);//使能

/* map the registers */
//映射寄存器

//获取平台设备资源,对于IORESOURSE_MEM类型的资源,start,end表示platform_device占据的内存的开始地址和结束地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
     
    dev_err(&pdev->dev, "cannot find IO resource\n");
    ret = -ENOENT;
    goto err_clk;
}

//申请io内存资源
i2c->ioarea = request_mem_region(res->start, resource_size(res),
                 pdev->name);

if (i2c->ioarea == NULL) {
     
    dev_err(&pdev->dev, "cannot request IO\n");
    ret = -ENXIO;
    goto err_clk;
}

//映射io
//I/O端口空间映射到内存的虚拟地址
i2c->regs = ioremap(res->start, resource_size(res));

if (i2c->regs == NULL) {
     
    dev_err(&pdev->dev, "cannot map IO\n");
    ret = -ENXIO;
    goto err_ioarea;
}

dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
    i2c->regs, i2c->ioarea, res);

/* setup info block for the i2c core */
//设置i2c核心所需数据

i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;

/* initialise the i2c controller */
//i2c适配器私有数据结构提填充完了,就初始化i2c控制器

ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
    goto err_iomap;

/* find the IRQ for this unit (note, this relies on the init call to
 * ensure no current IRQs pending
 */

//找到要申请的中断号
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
     
    dev_err(&pdev->dev, "cannot find IRQ\n");
    goto err_iomap;
}

//申请中断,指定了中断处理函数s3c24xx_i2c_irq
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
          dev_name(&pdev->dev), i2c);

if (ret != 0) {
     
    dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
    goto err_iomap;
}

//动态变频,忽略
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
     
    dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
    goto err_irq;
}

/* Note, previous versions of the driver used i2c_add_adapter()
 * to add the bus at any number. We now pass the bus number via
 * the platform data, so if unset it will now default to always
 * being bus 0.
 */

i2c->adap.nr = pdata->bus_num;

//添加i2c适配器(cpu内部集成)
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
     
    dev_err(&pdev->dev, "failed to add bus to i2c core\n");
    goto err_cpufreq;
}

platform_set_drvdata(pdev, i2c);

clk_disable(i2c->clk);

dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return 0;
err_cpufreq:
    s3c24xx_i2c_deregister_cpufreq(i2c);
err_irq:
    free_irq(i2c->irq, i2c);
err_iomap:
    iounmap(i2c->regs);
err_ioarea:
    release_resource(i2c->ioarea);
    kfree(i2c->ioarea);
err_clk:
    clk_disable(i2c->clk);
    clk_put(i2c->clk);
err_noclk:
    kfree(i2c);
return ret;
}

可以看到,i2c24xxi2cprobe()的主要工作有:使能硬件,申请i2c适配器使用的io地址、中断号,然后向i2c核心添加了这个适配器。

s3c24xx_i2c是i2c适配器的私有数据结构体,封装了适配器的所有信息。

struct s3c24xx_i2c {
     
spinlock_t      lock;//用于防止并发访问的锁
wait_queue_head_t   wait;//等待队列
unsigned int        suspended:1;

struct i2c_msg      *msg;//i2c消息
unsigned int        msg_num;//i2c消息的数量
unsigned int        msg_idx;//当前消息中的一个指针
unsigned int        msg_ptr;//消息索引

unsigned int        tx_setup;//等待数据发送到总线上的一个建立时间
unsigned int        irq;//中断

enum s3c24xx_i2c_state  state;//i2c状态
unsigned long       clkrate;

void __iomem        *regs;
struct clk      *clk;
struct device       *dev;
struct resource     *ioarea;
struct i2c_adapter  adap;//i2c_adapter

#ifdef CONFIG_CPU_FREQ
struct notifier_block   freq_transition;
#endif
};

初始化i2c控制器函数s3c24xxi2cinit()如下

 static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
 {
     
 unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;//中断使能,ACK使能
 struct s3c2410_platform_i2c *pdata;
 unsigned int freq;

 /* get the plafrom data */

 pdata = i2c->dev->platform_data;//获取平台数据

 /* inititalise the gpio */

 if (pdata->cfg_gpio)//初始化gpio ,流程(1)
     pdata->cfg_gpio(to_platform_device(i2c->dev));

 /* write slave address */

 writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);//写从设备地址

 dev_dbg(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);

 writel(iicon, i2c->regs + S3C2410_IICCON);//写控制寄存器,也就是使能中断和使能ACK,流程(2)

 /* we need to work out the divisors for the clock... */

 if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
     //计算时钟分频
     writel(0, i2c->regs + S3C2410_IICCON);
     dev_err(i2c->dev, "cannot meet bus frequency required\n");
     return -EINVAL;
 }

 /* todo - check that the i2c lines aren't being dragged anywhere */

 dev_dbg(i2c->dev, "bus frequency set to %d KHz\n", freq);
 dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);

 dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
 writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);

 return 0;
 }

s3c24xxi2cinit()中完成了前面所说的通过i2c总线写eeprom流程的(1)(2)两步。


在浅谈LinuxI2C驱动架构这一小节中提到了,i2c总线驱动是对I2C硬件体系结构中适配器端的实现,主要是实现了两个结构i2cadapter和i2calgorithm,从而控制i2c适配器产生通讯信号。

在i2c24xxi2cprobe()中就填充了i2cadapter,并且通过i2c->adap.algo = &s3c24xxi2calgorithm给i2cadapter绑定了i2c_algorithm。

其中s3c24xxi2calgorithm为

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
      
    .master_xfer = s3c24xx_i2c_xfer, 
    .functionality = s3c24xx_i2c_func, 
}; 

其中s3c24xxi2cxfer()用来启动i2c传输,s3c24xxi2cfunc()返回所支持的通讯协议。

所以说,i2c设备通过i2ctransfer()进行实际传输,在i2c核心中我们已经看到,i2ctransfer实际是调用了i2cadapter对应的masterxfer(),此处,在i2c总线驱动中,把masterxfer()指定为了s3c24xxi2cxfer(),所以说此时,传输任务交给了s3c24xxi2c_xfer()。

通过后面分析我们会看到,s3c24xxi2cxfer()只是启动了i2c传输,把i2c传输这个任务进行推进并且完成还需要靠我们在probe中注册的中断来完成,对应的中断处理函数是s3c24xxi2cirq(),后面都会详细分析。

7.4 启动i2c传输

接下来就是分析负责启动i2c传输任务的s3c24xxi2cxfer()。

 static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
         struct i2c_msg *msgs, int num)
 {
     
 struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;//获得i2c适配器私有数据结构
 int retry;
 int ret;

 clk_enable(i2c->clk);//使能时钟

 for (retry = 0; retry < adap->retries; retry++) {
     //传输不成功,则重试,retries为重试次数。

     ret = s3c24xx_i2c_doxfer(i2c, msgs, num);//启动一次i2c传输

     if (ret != -EAGAIN)
         goto out;

     dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

     udelay(100);
 }
 ret = -EREMOTEIO;
 out:
    clk_disable(i2c->clk);

 return ret;
 }

可以看到s3c24xxi2cxfer()是调用了s3c24xxi2cdoxfer()来启动传输的。

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
               struct i2c_msg *msgs, int num)
{
     
 unsigned long timeout;
 int ret;

 if (i2c->suspended)
     return -EIO;

 ret = s3c24xx_i2c_set_master(i2c);//检查i2c总线状态,总线不忙返回0
 if (ret != 0) {
     
     dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
     ret = -EAGAIN;
     goto out;
 }

 spin_lock_irq(&i2c->lock);

 //把消息写入i2c适配器的私有数据结构体中
 i2c->msg     = msgs;//i2c消息
 i2c->msg_num = num;//消息数量
 i2c->msg_ptr = 0;//消息指针,指向当前消息未发送部分的开始
 i2c->msg_idx = 0;//消息索引
 i2c->state   = STATE_START;//将状态改为STATE_START

 s3c24xx_i2c_enable_irq(i2c);//使能中断
 s3c24xx_i2c_message_start(i2c, msgs);//发送第一个byte,获得ACK后触发中断。
 spin_unlock_irq(&i2c->lock);

 timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);//等待消息传输完成,否则超时

s3c24xxi2cdoxfer()首先调用s3c24xxi2csetmaster()来检查总线状态,s3c24xxi2csetmaster()的实现如下

 static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)
 {
     
 unsigned long iicstat;
 int timeout = 400;

 while (timeout-- > 0) {
     
     iicstat = readl(i2c->regs + S3C2410_IICSTAT);//读i2c状态寄存器

     if (!(iicstat & S3C2410_IICSTAT_BUSBUSY))//总线不忙,则返回0;否则直到超时
         return 0;

     msleep(1);
 }

 writel(iicstat & ~S3C2410_IICSTAT_TXRXEN, i2c->regs + S3C2410_IICSTAT);
 if (!(readl(i2c->regs + S3C2410_IICSTAT) & S3C2410_IICSTAT_BUSBUSY))
     return 0;

 return -ETIMEDOUT;
 }

在获知总线不忙后,把要消息写入i2c适配器私有数据结构,并且把状态改为STATESTART。 然后使能中断,通过s3c24xxi2cmessagestart()发送第一个byte,这样在获取ACK后就会触发中断来推进i2c的传输。

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
                   struct i2c_msg *msg)
{
     
 unsigned int addr = (msg->addr & 0x7f) << 1;//从设备地址,7位地址,最低位用来表示读或者写,1为读,0为写。
 unsigned long stat;
 unsigned long iiccon;

 stat = 0;
 stat |=  S3C2410_IICSTAT_TXRXEN;//使能RxTx

 if (msg->flags & I2C_M_RD) {
     //从i2c消息判断,如果是读
     stat |= S3C2410_IICSTAT_MASTER_RX;//把状态设为主模式读
     addr |= 1;//别且设置第一byte最低位为1,表示读
 } else//否则是写
     stat |= S3C2410_IICSTAT_MASTER_TX;//把状态设为主模式写

 if (msg->flags & I2C_M_REV_DIR_ADDR)//如果是读写反转
     addr ^= 1;//读写交换

 /* todo - check for wether ack wanted or not */
 s3c24xx_i2c_enable_ack(i2c);//使能ACK

 iiccon = readl(i2c->regs + S3C2410_IICCON);
 writel(stat, i2c->regs + S3C2410_IICSTAT);//根据前面的设置来配置控制寄存器,流程(3)

 dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
 writeb(addr, i2c->regs + S3C2410_IICDS);//把第一个byte写入i2c收发数据移位寄存器,流程(3)
 /* delay here to ensure the data byte has gotten onto the bus
  * before the transaction is started */

 ndelay(i2c->tx_setup);

 dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
 writel(iiccon, i2c->regs + S3C2410_IICCON);

 stat |= S3C2410_IICSTAT_START;
 writel(stat, i2c->regs + S3C2410_IICSTAT);//修改状态,流程(3)
}

s3c24xxi2cmessage_start()在i2c总线上发送了一个开始信号,即完成了通过i2c总线写eeprom中的流程(3)的工作,设备地址赋给IICDS ,并设置IICSTAT,启动IIC发送设备地址出去,当从设备收到此数据并且回复ACK后,i2c适配器收到ACK后就会触发中断来推进i2c的传输。

7.5 通过中断来推进i2c的传输

发送完第一个byte,收到ACK信号后就会进入中断,并且以后只要收到ACK信号就都会进入中断。中断在probe中已经注册,它的实现 如下

static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
     
 struct s3c24xx_i2c *i2c = dev_id;
 unsigned long status;
 unsigned long tmp;

 status = readl(i2c->regs + S3C2410_IICSTAT);//获得i2c状态寄存器的值

 if (status & S3C2410_IICSTAT_ARBITR) {
     //需要仲裁
     /* deal with arbitration loss */
     dev_err(i2c->dev, "deal with arbitration loss\n");
 }

 if (i2c->state == STATE_IDLE) {
     //空闲状态
     dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");

     tmp = readl(i2c->regs + S3C2410_IICCON);
     tmp &= ~S3C2410_IICCON_IRQPEND;
     writel(tmp, i2c->regs +  S3C2410_IICCON);
     goto out;
 }

 /* pretty much this leaves us with the fact that we've
  * transmitted or received whatever byte we last sent */

 i2c_s3c_irq_nextbyte(i2c, status);//推进传输,传输下一个byte
 out:
 return IRQ_HANDLED;
}  

i2c总线驱动的中断处理函数s3c24xxi2cirq()是调用i2cs3cirq_nextbyte()来推进i2c的传输的。

static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat
{
     
unsigned long tmp;
unsigned char byte;
int ret = 0;

switch (i2c->state) {
     //根据i2c的状态选择

case STATE_IDLE://空闲
    dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
    goto out;
    break;

case STATE_STOP://停止
    dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
    s3c24xx_i2c_disable_irq(i2c);//禁止中断
    goto out_ack;

case STATE_START://开始
    /* last thing we did was send a start condition on the
     * bus, or started a new i2c message
     */
    //切换为开始状态之前,刚发送了第一个byte,也就是设备地址

    //首先检查下state时候与硬件寄存器的状态一致
    if (iicstat & S3C2410_IICSTAT_LASTBIT &&
        !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
     
        /* ack was not received... */

        dev_dbg(i2c->dev, "ack was not received\n");
        s3c24xx_i2c_stop(i2c, -ENXIO);//停止i2c传输
        goto out_ack;
    }

    if (i2c->msg->flags & I2C_M_RD)//如果当前i2c消息的标志为i2c读
        i2c->state = STATE_READ;//则修改状态为i2c读
    else
        i2c->state = STATE_WRITE;//否则修改为i2c写

    /* terminate the transfer if there is nothing to do
     * as this is used by the i2c probe to find devices. */

    if (is_lastmsg(i2c) && i2c->msg->len == 0) {
     //如果是最后一条消息则停止i2c传输。
        s3c24xx_i2c_stop(i2c, 0);
        goto out_ack;
    }

    if (i2c->state == STATE_READ)//如果i2c状态为读,就跳到读,否则,,就会跳到写,,,因为没有break
        goto prepare_read;

    /* fall through to the write state, as we will need to
     * send a byte as well */

case STATE_WRITE://第一次开始写i2c
    /* we are writing data to the device... check for the
     * end of the message, and if so, work out what to do
     */

    if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
     
        if (iicstat & S3C2410_IICSTAT_LASTBIT) {
     
            dev_dbg(i2c->dev, "WRITE: No Ack\n");

            s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
            goto out_ack;
        }
    }
retry_write://继续写

    if (!is_msgend(i2c)) {
     //不是一条消息的最后1Byte
        byte = i2c->msg->buf[i2c->msg_ptr++];//取出此消息的下一个byte
        writeb(byte, i2c->regs + S3C2410_IICDS);//写入收发数据移位寄存器。

        /* delay after writing the byte to allow the
         * data setup time on the bus, as writing the
         * data to the register causes the first bit
         * to appear on SDA, and SCL will change as
         * soon as the interrupt is acknowledged */

        ndelay(i2c->tx_setup);//延迟,等待数据发送

    } else if (!is_lastmsg(i2c)) {
     //是一条消息的最后一个byte,不是最后一条消息
        /* we need to go to the next i2c message */

        dev_dbg(i2c->dev, "WRITE: Next Message\n");

        i2c->msg_ptr = 0;//当前消息未发数据开始指针复位
        i2c->msg_idx++;//消息索引++
        i2c->msg++;//下一条消息

        /* check to see if we need to do another message */
        if (i2c->msg->flags & I2C_M_NOSTART) {
     //在发送下个消息之前,检查是否需要一个新的开始信号,如果不需要

            if (i2c->msg->flags & I2C_M_RD) {
     //如果是读
                /* cannot do this, the controller
                 * forces us to send a new START
                 * when we change direction */

                s3c24xx_i2c_stop(i2c, -EINVAL);//错误,返回
            }

            goto retry_write;//继续写
        } else {
     //如果需要一个新的开始信号
            /* send the new start */
            s3c24xx_i2c_message_start(i2c, i2c->msg);//发送一个新的开始信号
            i2c->state = STATE_START;//并且修改状态
        }

    } else {
     //是一条消息的最后
        /* send stop */

        s3c24xx_i2c_stop(i2c, 0);//停止发送
    }
    break;

case STATE_READ://开始读
    /* we have a byte of data in the data register, do
     * something with it, and then work out wether we are
     * going to do any more read/write
     */

    byte = readb(i2c->regs + S3C2410_IICDS);//先获取读到的消息,后面再决定时候有用
    i2c->msg->buf[i2c->msg_ptr++] = byte;//把消息存入读缓冲

prepare_read://如果第一个byte是读,则跳到此处。
    if (is_msglast(i2c)) {
     //是当前消息的最后一byte,也就是当前消息只剩1Byte的空余
        /* last byte of buffer */

        if (is_lastmsg(i2c))//如果也是最后一条消息
            s3c24xx_i2c_disable_ack(i2c);//那么就禁止ACK

    } else if (is_msgend(i2c)) {
     //否则如果是当前消息已经用完读缓冲
        /* ok, we've read the entire buffer, see if there
         * is anything else we need to do */

        if (is_lastmsg(i2c)) {
     //如果是最后一条消息了
            /* last message, send stop and complete */
            dev_dbg(i2c->dev, "READ: Send Stop\n");

            s3c24xx_i2c_stop(i2c, 0);//停止i2c传输
        } else {
     //否则进入下一条i2c传输
            /* go to the next transfer */
            dev_dbg(i2c->dev, "READ: Next Transfer\n");

            i2c->msg_ptr = 0;
            i2c->msg_idx++;
            i2c->msg++;//下一条i2c消息
        }
    }

    break;
}

/* acknowlegde the IRQ and get back on with the work */
out_ack:
    tmp = readl(i2c->regs + S3C2410_IICCON);
    tmp &= ~S3C2410_IICCON_IRQPEND;//清中断标志位
    writel(tmp, i2c->regs + S3C2410_IICCON);
out:
return ret;
}

i2cs3cirq_nextbyte()推进了i2c的传输,以写eeprom为例,第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断,在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断,中断处理函数把第三个Byte(真正的数据)发送到设备中,发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕,就发送IIC Stop信号,关IIC中断,置位各寄存器。这样就把通过i2c总线写eeprom的整个流程都实现了。

7.6 总结

i2c总线驱动控制i2c适配器产生通信信号,通过master_xfer()启动一个i2c传输,然后通过中断推进i2c传输。


原文地址: http://hello2mao.github.io/2015/12/02/Linux_I2C_driver.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/omnispace/article/details/50739645

智能推荐

常用的伪类和伪元素_常用伪元素有哪一些-程序员宅基地

文章浏览阅读1.4k次,点赞4次,收藏13次。常用的伪类:一个 CSS 伪类是以一个冒号( : )作为前缀,被添加到一个选择器末尾的关键字,可以让指定的元素在特定的状态呈现指定的样式。例如 :hover,当用户悬停在指定元素时,可以在这个状态给指定元素添加相应的样式,是在 DOM 树无法描述的状态下才能给元素添加样式。 :link 未访问的节点,通常用于 a 标签 :visited 已访问的节点,通常用于 a 标签 :hover 鼠标悬浮的节点 :active 当前选中的节点 :first-chil_常用伪元素有哪一些

数据挖掘面试 150 道题(附答案)_将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务-程序员宅基地

文章浏览阅读10w+次,点赞50次,收藏483次。单选题1. 某超市研究销售纪录数据后发现,买啤酒的人很大概率也会购买尿布,这种属于数据挖掘的哪类问题?(A)A. 关联规则发现B. 聚类C. 分类D. 自然语言处理2. 以下两种描述分别对应哪两种对分类算法的评价标准? (A)(a) 警察抓小偷,描述警察抓的人中有多少个是小偷的标准。(b) 描述有多少比例的小偷给警察抓了的标准。A. Precision, ..._将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务

【easyui】datagrid根据条件隐藏行_easyui datagrid 判断行隐藏-程序员宅基地

文章浏览阅读9.8k次,点赞4次,收藏3次。rowStyler: function (index, row) { if (row.isEnd == '1') {//条件 return 'display:none'; }else{_easyui datagrid 判断行隐藏

iis php5.6 mysql_Windows Server 2019 IIS10.0+PHP5.6+MySQL5.6环境搭建教程-程序员宅基地

文章浏览阅读423次。PHP版本:php5.6MySQL版本:MySQL5.6软件下载地址:php-5.6.31-nts-Win32-VC11-x64.zipvcredist2012_x64.exeMysql:https://dev.mysql.com/downloads/windows/installer/5.6.html 点击下载按钮,然后点击开始下载,不需要登录就可下载。安装IIS10.0打开左下角的开始菜单找到..._iis搭建mysql5.6

追溯计算机的本源,读电路与系统简史-程序员宅基地

文章浏览阅读3.9k次,点赞2次,收藏19次。元旦的假期和第一个周末的读书时光,比地铁阅读时光幸福了很多。无论是计算机的软硬件,还是人工智能系统,乃至IoT,都会涉及到这一古老而崭新的学科——电路与系统。读这本书,如同在追溯的时光里漫步。这里有一种强大的、迅速的、方便的原动力,它可以有各种用处,所有一切都由它造出来。它给我光,它给我热,它是我船上机械的灵魂。这原动力就是电。——儒勒凡尔纳人们对电的感知可以追溯的2000多年前..._电路与系统简史

加载webView 内存泄露 导致内存暴涨的几种解决方案_webview加载前端页面内存狂飙几百兆-程序员宅基地

文章浏览阅读3.1k次。加载webView导致内存泄露的原因是:Html中的js代码会引起内存泄露1 UIWebView *webView = [[UIWebViewalloc] initWithFrame:CGRectMake(0,64, 320,400)]; [webView loadRequest:[NSURLRequestrequestWithURL:url]];_webview加载前端页面内存狂飙几百兆

随便推点

常用字典_66,555,4444.333333,222222-程序员宅基地

文章浏览阅读7.9k次。huangyu888!@#$zhoujiakui223223jtserver1981@223wztelecom2008easygetyzdx123echina0228xiaxue123-$$4rf7uj3ed8ik!!changeme$$ebochai517qifengjc09001.1qa2ws3edwzcgame12wanzhonggamewanzhonggame..._66,555,4444.333333,222222

一文读懂服务器带外管理-程序员宅基地

文章浏览阅读1.9k次,点赞43次,收藏38次。服务器带外管理(Out-of-Band Management)是指在服务器正常运行时,通过专门的管理通道对服务器进行监控、配置和控制,而无需依赖服务器的主操作系统

Unity NGUI-程序员宅基地

文章浏览阅读344次。Unity 3.10.1 示例

【vue 引入cdn加载失败 解决办法】_联想r9000pcdn引入vue为什么超时-程序员宅基地

文章浏览阅读1.3k次。在项目index.html中放上生产环境下自动加载src下可以把文件放到自己服务器,本地加载 <% if(htmlWebpackPlugin.options.isProd){ %> <script src='file/20220519/vue.min.js' type='text/javascript'></script> <script src='file/20220519/vue-router.min.js' typ_联想r9000pcdn引入vue为什么超时

如何在云服务器上安装kali系统_华为的云服务器可以安装kaili-程序员宅基地

文章浏览阅读3.1k次。在华为云上进行安装一些其他的操作系统:以kali为例 准备阶段: 一台云服务器 除了一块云系统盘外的另一块云硬盘(如果没有可以临时进行购买一个) 操作: 00 购买成功的云硬盘在服务器控制台就是可以直接进行挂载的; 01 使用命令进行下载kali镜像或者进行上传镜像(都可以) wget https://mirrors.163.com/kali-images/kali-2021.3/kali-linux-2021..._华为的云服务器可以安装kaili

Android Dalvik虚拟机初识_每个dalvik虚拟机实例都是一个独立的进程空间-程序员宅基地

文章浏览阅读1k次。首先,让我们来思考下面几个问题:什么是Dalvik虚拟机?Dalvik VM与JVM有什么区别?Dalvik VM有什么新的特点?Dalvik VM的架构是怎么样的?首先,我得承认第一个问题问得很傻:什么是Dalvik虚拟机?没有人给出过一个明确的定义,但是,我们似乎可以从人们对Java虚拟机的描述中得到些信息。Java虚拟机(JVM)是一个虚构_每个dalvik虚拟机实例都是一个独立的进程空间