Linux操作系统的I2C驱动

 一、Linux的I2C体系结构

  Linux I2C体系结构分为3个组成部分

(1)Linux核心

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

(2)I2C总线驱动

  I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

(3)I2C设备驱动

  I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在首CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。

  在Linux内核源代码中的drivers目录下包含一个i2c目录,而在i2c目录下又包含如下文件和文件夹:

  • i2c_core.c

  这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口

  • i2c_dev.c

  实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配为一个设备。通过适配器访问设备是的主设备号都为89,次设备号为0~255.应用程序通过“i2c-%d”(i2c-0,i2c-1,i2c-2,...i2c-10,...)文件名并使用文件操作结构open()/write()/read()/ioctl()和close()等来访问这个设备。

  i2c-dev.c并没有针对特定的设备而设计,只是提供了通用read()等接口,应用程序可以借用这些接口访问适配器上的I2C设别的存储空间或寄存器,并控制I2C设别的工作方式。

  • chips文件夹

  这个目录中包含了一些特定的I2C设备驱动。在具体的I2C设别驱动中,调用的都是I2C核心提供的API,因此,这使得具体的I2C设备驱动不依赖于CPU类型和I2C适配器的硬件特性。

  • buses文件夹

  这个文件中包含了一些I2C总线的驱动。如针对S3C2410/S3C2440等处理器的I2C控制器驱动。

  • algos文件夹

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

  此外,内核中的i2c.h这个头文件对i2c_driver/i2c_client/i2c_adapter和i2c_algotithm这4个数据结构进行了定义。理解这4个数据结构作用有利于后续的理解。

i2c_adapter结构体

struct i2c_adapter {
  struct module *owner;      /* 所属模块 */
  unsigned int id;         /* algorithm的类型,定义于i2c-id.h,以I2C_ALGO_开始 */
  unsinged int class;
  struct i2c_algorithm *algo;   /* 总线通信方法结构体指针 */
  void *algo_data;         /* algorithm 数据 */
  int (*client_register) (struct i2c_client *);  /* client注册时调用 */
  int (*client_unregister) (struct i2c_client *); /* client注销时调用 */
  u8 level;
  struct semphore bus_lock;
  struct semphore clist_lock;
  int timeout;
  int retries;            /* 重试次数 */
  struct device dev;         /* 适配器设备 */
  struct class_device class_dev;  /* 类设备 */
  int nr;
  struct list_head client;
  struct list_head list;
  char name[48];           /* 适配器名称 */
  struct completion dev_released; /* 用于同步 */
};

i2c_algoritm结构体

struct i2c_algorithm {
  int (*master_xfer) (struct i2c_adapter, struct i2c_msg *msgs, int num);  /* i2c 传输函数指针 */
  int (*subus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write,
             u8 command, int size, union i2c_smbus_data *data);
  u32 (*functionality) (struct i2c_adapter *adap);
};

i2c_driver结构体

struct i2c_driver {
  int id;
  unsigned int class;
  int (*attach_adapter) (struct i2c_adapter *);  /* 依附i2c_adapter 函数指针 */
  int (*detach_adapter) (struct i2c_adapter *);  /* 脱离i2c_adapter 函数指针 */
  int (*detach_client) (struct i2c_client *);   /* 脱离i2c_client 函数指针 */
  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 *);
  int (*command) (struct i2c_client *,unsigned int cmd, void *arg);
  struct device_driver driver;
  const struct i2c_device_id *id_table;
  int (*detect) (struct i2c_client *,int kind, struct i2c_board_info *);
  const struct i2c_client_address_data *address_data;
  struct list_head clients;
};

i2c_client结构体

struct i2c_client {
  unsigned int flags;
  unsigned short addr;  /* 低7位芯片地址 */
  char name[I2C_NAMD_SIZE];  /* 设备名称 */
  struct i2c_adapter *adapter;
  struct i2c_driver *driver;
  struct device dev;
  int irq;
  struct list_head list;
  struct completion released;
};

(1)i2c_adapter和i2c_algorithm

  i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指针。

  i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg为单位,i2c_msg结构体也非常关键。

i2c_msg结构体

struct i2c_msg {
  __u16 addr;  /* 设备地址 */
  __u16 flags;  /* 标志 */
  __u16 len;   /* 消息长度 */
  __u8 *buf;   /* 消息数据 */
};

(2)i2c_driver和i2c_client

  i2c_driver对应一套驱动方法,其主要成员函数probe()、remove()等,另外id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_dirver上可以支持多个同等类型的client。

  i2c_client信息通常在BSP的板文件中通过i2c_board_info填充,如下面代码就定义了一个i2c设备ID为“ad7142_joystick”、地址为0x2C、中断号为IRQ_PF5的i2c_client:

  在I2C总线驱动i2c_bus_type的match()函数i2c_device_match()中,会调用i2c_match_id()函数匹配板文件的ID和i2c_driver所支持的ID表。

(3)i2c_adapter与i2c_client

 

   虽然I2C硬件体系结构比较简单,但是I2C体系结构在Linux中的实现却相当复杂。当工程师拿到实际的电路板,面对复杂的Linux I2C子系统,应该如何下手写驱动呢?究竟有哪些是需要亲自做的,哪些是内核已经提供的呢?理解这个问题非常有意义,可以使我们面对具体问题时迅速地抓住重点。

  一方面,适配器驱动可能是Linux内核本身还不包含的;另一方面,挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的,因此,功能是要实现的主要工作如下:

  • 提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制I2C适配器从硬件上产生各种信号以及处理I2C中断等。
  • 驱动I2C适配器的algorithm,用具体适配器的xxx)xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针复制给i2c_adapter的algo指针。
  • 实现I2C设备驱动中的i2c_driver接口,用具体设备的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备的ID表复制给i2c_driver的probe/remove/suspend/resume.
  • 实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的连接,而挂机啊在总线上的设备则是千差万别的,例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write和yyy_ioctl函数等;如果是声卡,就实现ALSA驱动。

  上述工作中前两个属于I2C总线驱动,后两个属于I2C设备驱动,做完这些工作,系统会增加两个内核模块。

 

二、Linux I2C核心

  I2C核心(drivers/i2c/i2c-core.c)提供了一组不依赖于平台的接口函数。这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I2C总线驱动和设备驱动之间依赖于I2C核心作为组带,I2C和心中的主要函数如下:

(1)增加/删除i2c_adapter:

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

(2)增加/删除i2c_driver

int i2c_register_driver(struct module *owner,struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);

(3)i2c_client依附/脱离

int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);

   当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。相反地,在client被取消关联的时候,sysfs文件盒设备也被注销。

 (4)I2C传输、发送和接收

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

  i2c_transfer()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer函数分别完成一条写消息和一条读消息。

代码如下:

int i2c_master_send(struct i2c_client *client, const char *buf, int count)
{
  int ret;
  struct i2c_adapter *adap = client->adapter;
}

  i2c_transfer函数本身不具备驱动适配器物理硬件完成消息交互的能力,他只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。

 

三、Linux I2C总线驱动

1 I2C适配器驱动加载和卸载

I2C总线驱动模块的加载函数要完成两个工作:

  • 初始化I2C适配器所有的硬件资源,如申请I/O地址、中断号。
  • 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个数据结构的成员已经被xxx适配器的响应函数指针所初始化。

I2C总线驱动模块的卸载函数要完成的工作与加载函数相反。

  • 释放I2C适配器所使用的硬件资源,如释放I/O地址,中断号等。
  • 通过i2c_del_adapter删除i2c_adapter的数据结构。

I2C总线驱动的模块加载和卸载函数:

static int __init i2c_adapter_xxx_init(void)
{
  xxx_adapter_hw_init();
  i2c_add_adapter(&xxx_adapter);
}

static int __exit i2c_adapter_xxx_exit(void)
{
  xxx_adapter_hw_freee();
  i2c_del_adapter(&xxx_adapter);
}

  上述代码中xxx_adapter_hw_init和xxx_adapter_hw_free函数的实现都与具体的CPU和I2C适配器硬件相关。

2 I2C总线通信方法

  我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。functionality()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。master_xfer()函数在I2C适配器上完成传递给他的i2c_msg数组中的每个I2C消息,代码清单如下:

static

  上述代码实际上给出了一个master_xfer()函数处理I2C消息数组的流程,对于数组中的每个消息,判断消息类型,若为都消息,则赋从设备地址为(msg->addr<<1)|1,否则为msg->addr<<1,对每个消息产生一个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生一个停止位。如图所示

 

  master_xfer函数模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C适配器和CPU具体硬件直接相关,需要由驱动工程师根据芯片的数据手册来实现。

 

四、Linux I2C设备驱动

  I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充i2c_driver中的成员函数,i2c_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义为全局变量并初始化,代码如下:

static struct i2c_driver yyy_driver = {
  .driver = {
    .name = "yyy",
  },
  .probe  = yyy_probe,
  .remove  = yyy_remove,
  .id_table = yyy_id,
};

1 Linux I2C设备驱动的模块加载与卸载

  I2C设备驱动的模块加载函数通用方法是在I2C设备驱动的模块加载函数进行,通过I2C核心的i2c_add_driver()函数添加i2c_driver的工作,而在模块卸载函数中需要做相反的工作;通过I2C狠心的i2c_del_driver()函数删除i2c_drivdr,I2C设备驱动的加载工作与写在功函数模板如下:

static int __init yyy_init(void)
{
  return i2c_add_adapter(&yyy_driver);
}

static void __exit yyy_exit(void)
{
  return i2c_del_adapter(&yyy_driver);
}

2 Linux I2C设备驱动的数据传输

  在I2C设备上读写数据的时序和数据通常通过i2c_msg数组组织,最后通过i2c_transfer()函数完成,代码清单所示为一个都去指定偏移offs寄存器的例子。

struct i2c_msg msg[2];
/* 第一条消息是写消息 */
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &offs;

/* 第二条消息是读消息 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = sizeof(buf);
msg[1].buf = &buf[0];

i2c_transfer(client->adapter,msg,2);

3 Linux的i2c_dev.c文件分析

  i2c-dev.c文件完全可以被看作一个I2C设备驱动。不过,它实现的一个i2c_client是虚拟的、临时的。锁着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的client链表中。i2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c_dev.c的主题是“i2c_driver成员函数 + 字符设备驱动”。

  i2c-dev.c中提供i2ccdev_read()/i2cdev_write()函数来对应用户空间要使用的read()/write()文件操作接口,这两个函数分别调用i2c_master_recv()和i2c_master_send()函数来构造一条I2C消息并印发适配器algorithm通信函数的调用。但是,很遗憾,大多数稍微复杂一点I2C设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期,这种情况下,在应用层仍然调用read()、write()文件的API来读写I2C设备,将不能正确地读写。许多工程师碰到类似的问题,往往经过相当长时间的调试都没法解决I2C设备的读写,连错误的原因也无法找到,显然是对i2cdev_read()和i2cdec_write()函数的作用有所误解。

  鉴于上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有踢啊大的使用价值。只能适用于非RepStart模式的情况。对于两条以上的消息组成的读写,在用户空间需要重新组织i2c-msg消息并调用I2C_RDWR_IOCTL命令。

 

五、S3C2410 I2C总线驱动实例

1 S3C2410 I2C控制器硬件描述

  S3C2410处理器内部集成了一个I2C控制器,通常4个寄存器就可方便的对其进行控制,这4个寄存器如下:

  • IICCON:I2C控制寄存器
  • IICSTAT:I2C状态寄存器
  • IICDS:I2C收发数据移位寄存器
  • IICADD:I2C地址寄存器

  S3C2410处理器内部集成的I2C控制器可支持主、从两种模式,我们主要使用其主模式。通过对IICCON、IICDS和IICADD寄存器的操作,可在I2C总线上产生开始位、停止位、数据和地址位,而传输的状态则通过IICSTAT寄存器获取。

2 S3C2410 I2C总线驱动总体分析

  S3C2410的I2C总线驱动drivers/i2c/busses/i2c-s3c2410.c支持S3C24xx、S3C64xx、S5PC1xx和S5P6xx处理器。它主要完成的工作有:

  • 设计对应于i2c_adapter_xxx_init()模板的S3C2410的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。

设计对应于i2c_adapter_xxx_xfer()模板的S3C2410适配器的通信方法函数。

  针对S3C24xx、S3C64xx、S5PC1xx和S5P64xx处理器、functionality()函数s3c24xx_i2c_func()只需简单地返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能。

   

3 S3C2410 I2C适配器驱动的模块加载和卸载

  I2C适配器驱动被作为一个单独的模块加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体,如下所示:

static int __init i2c_adap_s3c_init(void)
{
  int ret;
  ret = platform_driver_register(&s3c24xx_i2c_driver);

  return ret;
}

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

platform_driver结构体包含了具体适配器的probe()函数、romove()函数、resume()函数指针等信息,他需要被定义和赋值。代码如下:

static struct platform_driver s3c24xx_i2c_driver = {
  .probe = s3c24xx_i2c_probe,
  .remove = s3c24xx_i2c_remove,
  .resume = s3c24xx_i2c_resume,
  .driver = {
    .name = THIS_MODULE,
    .name = "s3c24xx-i2c",
  },
};

当通过Linux内核源码/drivers/base/platform.c文件中定义platform_driver_unregister()函数注册platform_driver结构体时,其中probe指针指向的s3c24xx_i2c_probe()函数将被调用,已初始化适配器硬件。代码如下

static int s3c24xx_i2c_probe(struct platform_device *pde)
{  
  struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *npd;
  struct resource *resource;
  int retval;
  
  npd = pdev->dev.platform_data;
  if(!npd)
  {
    dev_err(&pdev->dev,"no platform data\n");
    return -EINVAL;
  }

  i2c = kmalloc(sizeof(struct s3c24xx_i2c),GFP_KERNEL);
  memset(i2c,0,sizeof(struct s3c24xx_i2c));
  if(!i2c)
  {
    dev_err(&pdev->dev,"no memory for state\n");
    return -ENOMEM;
  }
  
  strlcpy(i2c->adap.name,"s3c24xx-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);
  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);  
  resource = platform_get_resource(pdev,IORESOURCE_MEM,0);
  i2c->ioarea = request_mem_region(resource->start,resource_size(resource),pdev->name);
  i2c->regs = ioremap(res->start,resource_size(resource));
  i2c->adap.algo_data = i2c;
  i2c->adap.dev.parent = &pdev->dev;
  ret = s3c24xx_i2c_init(i2c);
  i2c->irq = platform_get_irq(pdev,0);
  ret = request_irq(i2c->irq,s3c24xx_i2c_irq,IRQF_DISABLED,dev_name(&pdev->dev),i2c);
  ret = s3c24xx_i2c_register_cpufreq(i2c);
  i2c->adap.nr = pdata->bus_num;
  ret = i2c_add_numbered_adapter(&i2c->adap);
  platform_set_drvdata(pdev,i2c);
}

  上述代码中的主体工作使能硬件并申请I2C适配器使用的I/O地址、中断号等,在这些工作度完成无误后,通过I2C核心提供的i2c_add_adapter()函数添加这个适配器。当处理器包含多个I2C控制器时,我们通过板文件定义的platform数据中的bus_num进行区分。 

  与s3c24xx_i2c_probe()函数完成相反功能的函数是s3c24xx_i2c_remove()函数,他在适配器模块卸载函数调用platform_driver_unregister()函数是通过platform_driver的remove指针方式被调用。xxx_i2c_remove()的设计模块如下:

static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
  struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
  s3c24xx_i2c_derefister_cpufreq(i2c);
  
  i2c_del_adapter(&i2c_adap);
  free_irq(i2c->irq,i2c);
  
  clk_disable(i2c->clk);
  clk_put(i2c->clk);
  iounmap(i2c->regs);
  release_resource(i2c->ioarea);
  kfree(i2c->ioarea);
  kfree(i2c);

  return 0;
}

代码清单的s3c24xx_i2c结构体进行适配器所有信息的封装。其结构体定义如下:

struct s3c24xx_i2c {
  spinlock_t		lock;
  wait_queue_head_t	wait;
  unsigned int		suspended:1;

  struct i2c_msg    *msg;
  unsigned int		msg_num;
  unsigned int		msg_idx;
  unsigned int		msg_ptr;

  unsigned int		tx_setup;
  unsigned int		irq;

  enum s3c24xx_i2c_state	state;
  unsigned long		clkrate;

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

#ifdef CONFIG_CPU_FREQ
  struct notifier_block	freq_transition;
#endif
};

4 S3C2410 I2C总线通信方法

  由代码清单可以看出,I2C配置适配器对应的i2c_algorithm结构体实例为s3c24xx_i2c_algorithm。

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

  上述代码第一行指定了S3C2410 I2C总线通信传输函数s3c24xx_i2c_xfer(),这个函数非常关键,所有的I2C总线上对设备的访问最周应该由他来完成,代码清单所示这个重要函数以及其依赖的s3c24xx_i2c_doxfer()函数和s3c24xx_i2c_message_start()函数的源代码。

/* s3c24xx_i2c_xfer
 *
 * first port of call from the i2c bus code when an message needs
 * transferring across the i2c bus.
*/

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;
  int retry;
  int ret;
  struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;
  if (pdata->cfg_gpio)
    pdata->cfg_gpio(to_platform_device(i2c->dev));

  for (retry = 0; retry < adap->retries; retry++) 
  {     ret = s3c24xx_i2c_doxfer(i2c, msgs, num);     if (ret != -EAGAIN)       return ret;      
    dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);     udelay(100);   }   return -EREMOTEIO; }
/* s3c24xx_i2c_doxfer
 *
 * this starts an i2c transfer
*/

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);
  if (ret != 0) 
  {     dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);     ret = -EAGAIN;     goto out;   }   spin_lock_irq(&i2c->lock);   i2c->msg = msgs;   i2c->msg_num = num;   i2c->msg_ptr = 0;   i2c->msg_idx = 0;   i2c->state = STATE_START;   s3c24xx_i2c_enable_irq(i2c);   s3c24xx_i2c_message_start(i2c, msgs);   spin_unlock_irq(&i2c->lock);   timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);   ret = i2c->msg_idx;   /* having these next two as dev_err() makes life very    * noisy when doing an i2cdetect */   if (timeout == 0)     dev_dbg(i2c->dev, "timeout\n");   else if (ret != num)     dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);   /* ensure the stop has been through the bus */   msleep(1); out:   return ret; }
/* s3c24xx_i2c_message_start
 *
 * put the start of a message onto the bus
*/
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg)
{
  unsigned int addr = (msg->addr & 0x7f) << 1;
  unsigned long stat;
  unsigned long iiccon;

  stat = 0;
  stat |=  S3C2410_IICSTAT_TXRXEN;

  if (msg->flags & I2C_M_RD) 
  {     stat |= S3C2410_IICSTAT_MASTER_RX;     addr |= 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);   iiccon = readl(i2c->regs + S3C2410_IICCON);   writel(stat, i2c->regs + S3C2410_IICSTAT);   dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);   writeb(addr, i2c->regs + S3C2410_IICDS);   /* 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); } 

  s3c24xx_i2c_xfer()函数调用s3c24xx_i2c_doxfer函数传输I2C消息。s3c24xx_i2c_doxfer()首先将s3c2410的I2C适配器设置为I2C主设备,气候初始化s3c24xx_i2c结构体,使能I2C中断,并调用s3c24xx_i2c_message_start()函数启动I2C消息的传输。s3c24xx_i2c_message_start()函数写s3c2410适配器对应的控制寄存器,向I2C从设备传递开始位和从设备地址。

  上述代码只是启动了I2C消息数组的传输周期,并没有完整实现图给出的algorithm master_xfer流程。这个流程的完整实现需要借助I2C适配器上的中断来步步推进。下述代码所示为S3C2410 I2C适配器中断处理函数以及其依赖的i2c_s3c_irq_nextbyte()函数的源代码。

/* s3c24xx_i2c_irq
 *
 * top level IRQ servicing routine
 */

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);

  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 */   i2s_s3c_irq_nextbyte(i2c, status); out:   return IRQ_HANDLED; }
/* i2s_s3c_irq_nextbyte
 *
 * process an interrupt and work out what to do
 */

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

  switch (i2c->state) {
  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
     */

    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);       goto out_ack;     }     if (i2c->msg->flags & I2C_M_RD)       i2c->state = STATE_READ;     else       i2c->state = STATE_WRITE;     /* 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)
    {       s3c24xx_i2c_stop(i2c, 0);       goto out_ack;     }     if (i2c->state == STATE_READ)       goto prepare_read;   /* fall through to the write state, as we will need to    * send a byte as well */   case STATE_WRITE:     /* 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))
    {       byte = i2c->msg->buf[i2c->msg_ptr++];       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))
    {       /* 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:     if (is_msglast(i2c))
    {       /* last byte of buffer */       if (is_lastmsg(i2c))         s3c24xx_i2c_disable_ack(i2c);     }
    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);       }
      else
      {         /* go to the next transfer */         dev_dbg(i2c->dev, "READ: Next Transfer\n");         i2c->msg_ptr = 0;         i2c->msg_idx++;         i2c->msg++;       }     }     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; }

  中断处理函数s3c24xx_i2c_irq()主要通过调用i2c_s3c_irq_nextbyte()函数进行传输工作的进一步推进。i2s_s3c)irq_nexbyte函数通过switch(i2c->state)的不同状态进行处理,在每种状态下,先检查i2c_state的状态和硬件寄存器应该处于的状态是否一致,如果不一致,则证明有误,直接返回。当I2C处于读状态STATE_READ或写状态STATE_WRITE时,通过is_lastmsg()函数判断是否传输的是最后一条I2C消息。如果是,则产生停止位,否则通过i2c_msg_idx++、i2c->msg++推进到下一条消息。

 

六、总结

  Linux I2C驱动体系结构有相当的复杂度,它主要由3部分组成,即I2C核心,I2C总线驱动和I2C设备驱动。I2C核心是I2C总线驱动和I2C设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了I2C中设备与适配器的沟通。I2C总线驱动填充了i2c_adapter和i2c_algorithm结构体,I2C设备驱动填充i2c_driver结构体并实现其本身所对应的设备类型的驱动。

  另外,系统中的i2c-dev.c文件定义的主设备号为89的设备可以方便的给应用程序提供读写I2C设备寄存器的能力,是的工程师大多数时候并不需要为具体的I2C设备驱动定义文件操作接口。  

 

  

  

 

posted @ 2016-08-31 16:00  pingfandfy  阅读(6470)  评论(0编辑  收藏  举报