IIC设备驱动程序

IIC设备是一种通过IIC总线直接连接的设备,由于其简单性,被广泛引用于电子系统中。在现代电子系统中,有很多的IIC设备需要进行相互之间的通信。为了提高硬件的效率和简化电路的设计,PHILIPS公司开发了IIC总线。IC总线可以用于设备间的数据通信。

一.IIC设备的总线及其协议

IIC总线是由PHILIPS公司开发的两线式串行总线,用于连接微处理器和外部IIC设备。1IC设备产生于20世纪80年代,最初专用于音频和视频设备,现在在各种电子设备中都有广泛的应用。

1.1 IIC 总线的特点

IIC总线有两条总线线路,一条是串行数据线(SDA),一条是串行时钟线(SCL)。SDA负责数据传输,SCL负责数据传输的时钟同步。IIC 设备通过这两条总线连接到处理器的IIC总线控制器上。一种典型的设备连接如下图所示。
在这里插入图片描述
IIC的主要特点:(这些特点是选择IIC设备的重要依据)
(1)每一个连接到总线的设备都可以通过唯一的设备地址单独访问;
(2)串行的8位双向数据传输,位速率在标准模式下可达到100kb/s;快速模式下可以达到400kb/s;高速模式下可以达到3.4Mb/s;
(3)总线长度最长7.6m左右;
(4)片上滤波器可以增加抗干扰能力,保证数据的完成传输;
(5)连接到一条IIC总线上的设备数量只受到最大电容400pF的限制;
(6)它是一个多主机系统,在一条总线上可以同时有多个主机存在,通过冲突检测方式和延时等待防止数据不被破坏。同一时间只能有一个主机占用总线。
1.2 IIC 总线的信号类型
IIC总线在传输数据的过程中有3种类型的信号:开始信号、结束信号、和应答信号。
开始信号(S): 当SCL为高电平时,SDA由高电平向低电平跳变,表示将要开始传输数据。
结束信号§:当SCL为高电平时,SDA由低电平向高电平跳变,表示结束传输数据。
响应信号(ACK): 从机主接收到8位数据后,在第9个周期,拉低SDA电平,表示已经收到数据。这个信号称为应答信号。
开始信号和结束信号的波形如下图所示:
在这里插入图片描述
1.3 IIC 总线的数据传输
主机向从机发送数据:
(IIC总线中发送命令的设备称为主机。对于ARM处理器来说,主机就是IIC控制器。接受命令并响应命令的设备称为从机。)
主机通过数据线SDA向从机发送数据。当总线空闲时,SDA和SCL信号都处于高电平。
(1)当主机检测到总线空闲时,主机发出开始信号S;
(2)主机发送8位数据。这8位数据的前7位表示从机地址,第8位表示数据的传输方向。这时,第8位为0,表示向从机发送数据;
(3)被选中的从机发出响应信号ACK;
(4)从机传输一系列的字节和响应位;
(5)主机接受这些数据,并发出结束信号P,完成本次数据传输。

二.IIC设备的硬件原理
s3c2440中集成了一个IIC控制器,用来管理IIC设备,实现设备的数据接收和发送功能。
IIC控制器的内部结构如下图所示:
在这里插入图片描述
IIC控制器主要是由4个寄存器来完成所有的IIC操作的。IICCON、IICSTAT、IICADD、IICDS。
IICCON(MULTI-MASTER IIC-BUS CONTROL):控制是否发出ACK信号,是否开启IIC中断等。
IICSTAT(MULTI-MASTER IIC-BUS CONTROL/STATUS)。
IICADD(MULTI-MASTER IIC-BUS ADDRESS):挂接到总线上的从机地址。该寄存器用到[7:1]表示从机地址。在串行输出使能位IICSTAT[4]为0时,才可以写入;在任何时候可以读出。
IICDS(MULTI-MASTER IIC-BUS TRANSMIT/RECEIVE DATA SHIFT):IIC控制器将要发送或者接收到的数据保存在IICDS寄存器的位[7:1]中。在串行输出使能IICSTAT[4]为1时,才可以写入;在任何时间都可以读出。

三.IIC设备驱动程序的层次结构
IIC设备驱动分为设备层、总线层。其中4个数据结构是i2c_driver、i2c_client、i2c_algorithm、i2c_adapter。i2c_driver、i2c_client属于设备层;i2c_algorithm、i2c_adapter属于总线型。
设备层关系到实际的IIC设备,总线层包括CPU中的IIC总线控制器和控制总线通信的方法。
一个系统中可能有很多个总线层,也就是包含多个总线控制器;也可能有多个设备层,包含不同的IIC设备。
在这里插入图片描述
3.1 设备层
IIC设备层由IIC设备和对应的设备驱动程序组成,分别用数据结构i2c_driver和i2c_client表示。
3.1.1 i2c_client

struct  i2c_client
{
  unsigned short  flags;//标志位
  unsigned short  addr;//设备的地址,低7位为芯片地址
  char name[I2C_NAME_SIZE];//设备的名称,最大为20个字节
  struct  i2c_adapter *adapter;//依附的适配器i2c_adapter,适配器指明所属的总线
  struct  i2c_driver *driver;//指向设备对应的驱动程序
  struct device  dev;//设备结构体
  int irq;//设备申请的中断号
  struct list_head  list;//连接到总线上的所有设备
  struct list_head   detected;//已经被发现的设备链表
  struct completion  released;//是否已经释放的完成量
};

设备结构体i2c_client中addr的低8位表示设备地址。设备地址由读写位、器件类型和自定义地址组成,如下图所示:
在这里插入图片描述
第7位是R/W位,0表示写,2表示读,所以I2C设备通常有两个地址,即读地址和写地址。类型器件由中间4位组成,这是由半导体公司生产的时候就已经固定的。自定义地址码由低3位组成。由用户自己设置,通常的做法如EEPROM这些器件是由外部I芯片的3个引脚所组合电平决定的(A0,A1,A2)。A0,A1,A2 就是自定义的地址码。自定义的地址码只能表示8个地址,所以同一IIC总线上同一型号的芯片最多只能挂载8个。
在这里插入图片描述
3.1.2 i2c_drive

struct  i2c_driver
{
  int id;                         //驱动标识ID
  unsigned int class;               //驱动的类型
  int (*attach_adapter)(struct i2c_adapter *);//当检测到适配器时调用的函数
  int (*detach_adapter)(struct i2c_adapter*);//卸载适配器时调用的函数
  int (*detach_client)(struct i2c_client *)   __deprecated;             //卸载设备时调用的函数
  //以下是一种新类型驱动需要的函数,这些函数支持IIC设备动态插入和拔出。上面3个和下面5个不能同时定义
  int  (*probe)(struct i2c_client *,const struct  i2c_device_id *);//新类型设备探测函数
  int (*remove)(struct i2c_client *);//新类型设备的移除函数
  void (*shutdown)(struct i2c_client *); //关闭IIC设备
  int (*suspend)(struct  i2c_client *,pm_messge_t mesg);           //挂起IIC设备
  int (*resume)(struct  i2c_client *);//恢复IIC设备
  int (*command)(struct i2c_client *client,unsigned int cmd,void *arg); //使用命令使设备完成特殊的功能。类似ioctl()函数
  struct devcie_driver  driver; //设备驱动结构体
  const struct  i2c_device_id *id_table;//设备ID表
  int (*detect)(struct i2c_client *,int  kind,struct  i2c_board_info *); //自动探测设备的回调函数
  const  struct i2c_client_address_data *address_data;                 //设备所在的地址范围
  struct  list_head    clients;//指向驱动支持的设备
};

3.2 总线层
IIC总线层由总线适配器和适配器驱动程序组成,分别用数据结构i2c_adapter和i2c_algorithm表示。
3.2.1 i2c_adapter

struct i2c_adapter
{
        struct module *owner; //模块计数
        unsigned  int id;//alogorithm的类型,定义于i2c_id.h中
        unsigned   int  class;  //允许探测的驱动类型
        const struct i2c_algorithm *algo;//指向适配器的驱动程序
        void *algo_data; //指向适配器的私有数据,根据不同的情况使用方法不同
        int (*client_register)(struct  i2c_client *); //设备client注册时调用
        int (*client_unregister(struct  i2c_client *);//设备client注销时调用
        u8 level;                                                         
        struct  mutex  bus_lock; //对总线进行操作时,将获得总线锁
        struct  mutex  clist_lock ; //链表操作的互斥锁
        int timeout;//超时
        int retries; //重试次数
       struct device dev;//指向 适配器的设备结构体
       int  nr ;                                                          
       struct  list_head      clients; //连接总线上的设备的链表
       char name[48]; //适配器名称
       struct completion     dev_released;//用于同步的完成量
};

3.2.2 i2c_algorithm

struct  i2c_algorithm
{
   int  (*master_xfer)(struct  i2c_adapter *adap,  struct  i2c_msg *msg, int num);//传输函数指针,指向实现IIC总线通信协议的函数,用来确定适配器支持那些传输类型
   int  (*smbus_xfer)(struct  i2c_adapter *adap, u16  addr, unsigned  short flags, char  read_write, u8 command, int size, union  i2c_smbus_data  *data);//smbus方式传输函数指针,指向实现SMBus总线通信协议的函数。SMBus和IIC之间可以通过软件方式兼容,所以这里提供了一个函数,但是一般都赋值为NULL
   u32  (*functionality)(struct  i2c_adapter *);//返回适配器支持的功能
};

四.IIC子系统的初始化
初始化函数i2c_init():

static int __init i2c_init(void)
{
    int retval;//返回值,成功0,错误返回负值
    retval = bus_register(&i2c_bus_type);       //注册一条IIC的BUS总线
    if (retval)
        return retval;
    retval = class_register(&i2c_adapter_class);       //注册适配器类,用于实现sys文件系统的部分功能
    if (retval)
        goto bus_err;
    retval = i2c_add_driver(&dummy_driver);               //将一个空驱动程序注册到IIC总线中
    if (retval)
        goto class_err;
    return 0;
class_err:
    class_unregister(&i2c_adapter_class);//类注销
bus_err:
    bus_unregister(&i2c_bus_type);//总线注销
    return retval;
}

退出函数i2c_exit():

static void __exit i2c_exit(void)
{
    i2c_del_driver(&dummy_driver);//注销IIC设备驱动程序,主要功能是去掉总线中的该设备驱动程序
    class_unregister(&i2c_adapter_class); //注销适配器类
    bus_unregister(&i2c_bus_type);//注销I2C总线
}

五.适配器驱动程序
5.1 s3c24xx_i2c适配器:

struct s3c24xx_i2c {
    spinlock_t        lock;//lock自旋锁
    wait_queue_head_t    wait;//等待队列头。由于IIC设备是低速设备,所以可以采取“阻塞-中断”的驱动模型,即读写i2c设备的用户程序在IIC设备操作期间进入阻塞状态,待IIC操作完成后,总线适配器将引发中断,再将相应的中断处理函数中唤醒受阻的用户进程。该队列用来放阻塞的进程
    unsigned int        suspended:1;//设备是否挂起
    struct i2c_msg        *msg;//从适配器到设备一次传输的单位,用这个结构体将数据包装起来便于操作 ,
    unsigned int        msg_num;//表示消息的个数
    unsigned int        msg_idx;//表示第几个消息。当完成一个消息后,该值增加
    unsigned int        msg_ptr;//总是指向当前交互中要传送、接受的下一个字节,在i2c_msg.buf中的偏移量位置
    unsigned int        tx_setup;//表示写IIC设备寄存器的一个时间,这里被设置为50ms
    unsigned int        irq;//适配器申请的中断号
    enum s3c24xx_i2c_state    state; //表示IIC设备目前的状态
    unsigned long        clkrate;//时钟速率
    void __iomem        *regs;//IIC设备寄存器地址
    struct clk        *clk; //对应的时钟
    struct device        *dev;//适配器对应的设备结构体
    struct resource        *ioarea;//适配器的资源
    struct i2c_adapter    adap;//适配器主体结构体

5.2 i2c_msg(IIC消息)

struct  i2c_msg
{
         __u16   addr;//IIC设备地址。 这个字段说明一个适配器在获得总线控制权后,可以与多个IIC设备进行交互。
         __u16   flags;//消息类型标志    
#define  I2C_M_TEN         0x0010//这是有10位地址芯片
#define  I2C_M_RD            0x0001//表示从 从机到主机读数据
#define  I2C_M_NOSTART       0x4000
//FUNC_PROTOCOL_MANLING协议的相关标志
#define  I2C_M_REV_DIR_ADDR   0x2000          //FUNC_PROTOCOL_MANLING协议的相关标志
#define  I2C_M_IGNORE_NAK         0x1000 //FUNC_PROTOCOL_MANLING协议的相关标志
#define  I2C_M_NO_RD_ACK           0x0800         //FUNC_PROTOCOL_MANLING协议的相关标志
#define  I2C_M_RECV_LEN            0x0400//第一次接收的字节长度
        __u16    len;//消息字节长度
        __u8       * buf;//指向消息数据的缓冲区
};

5.3 IIC适配器加载函数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)//存放分配ID号的内存
        return -ENOMEM;//内存分配失败
    mutex_lock(&core_lock);//锁定内核锁
    res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);            //分配ID号,并将ID号和指针关联
    mutex_unlock(&core_lock);//释放内核锁
    if (res < 0) {
        if (res == -EAGAIN)
            goto retry; //分配失败,重试
        return res;
    }
    adapter->nr = id;
    return i2c_register_adapter(adapter);// 注册适配器设备
}

5.4 IDR机制
IDR机制在Linux内核中指的就是整数ID管理机制。从实质上来讲,这就是一种将一个整数ID号和一个指针关联在一起的机制。
5.4 1 IDR机制原理
IDR机制适用在那些需要把某个整数和特定指针关联在一起的地方。 例如,在IIC总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送该设备的地址。当适配器要访问总线上的IIC设备时,首先要知道它们的ID号,同时要在内核中建立一个用于描述该设备的结构体和驱动程序。将该设备的ID号和它的设备结构体联系起来的最简单的方法是通过数组进行索引,但如果ID号的范围很大(比如132 位的ID号),则用数组索引会占据大量的内存空间,这显然不可能:第二种方法是用链表,但如果总线中实际存在的设备较多,则链表的查询效率会很低。这种情况下,就可以采用IDR机制,该机制内部采用红黑树(radix, 类似于二分数)实现,可以很方便地将整数和指针关联起来,并且具有很高的搜索效率。IDR机制的主要代码在/include/inwx/idr.h实现。
5.5 适配器卸载函数i2c_del_adapter()
注销适配器的数据结构,删除总线上的所有设备的I2c_client数据结构和对应的i2c_driver驱动程序,并减少其代表总线上所有设备的相应驱动程序数据结构的引用计数(如果到达0,则卸载设备驱动程序)。
int i2c_del_adapter(struct i2c_adapter *adap);
5.6 总线通信方法
协议支持函数s3c24xx_i2c_func():

static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}

传输函数s3c24xx_i2c_xfer(实现IIC通信协议,将i2c_msg消息传给IIC设备):

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;//从适配器的私有数据中获得适配器s3c24xx_i2c结构体
    int retry;//传输错误重发次数
    int ret;//返回值
    for (retry = 0; retry < adap->retries; retry++) {
       ret = s3c24xx_i2c_doxfer(i2c, msgs, num);                                       //传输到IIC设备的具体函数
        if (ret != -EAGAIN)
            return ret;
        dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);//重试信息
        udelay(100);//延时100us
    }
    return -EREMOTEIO;// I/O错误
}

适配器的传输函数s3cxx_i2c_doxfer():

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);                     //当调用该函数启动 数据发送后,当前进程进入睡眠状态,等待中断到来,所以通过wait_event_timeout()函数将自己挂起到s3c24xx_i2c.wait等待队列上,直到等待的条件"i2c->msg_num == 0"为真,或者5s超时后才能唤醒。注意一次i2c操作可能要涉及多个字节,只有第一个字节发送是在当前进程的文件系统操作执行流中进行的,该字节操作的完成及后继字节的写入都由中断处理程序来完成。在此期间当前进程挂起在s3c24xx_i2c.wait等待队列上
    spin_unlock_irq(&i2c->lock);
    timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
    ret = i2c->msg_idx;
    if (timeout == 0)//在规定的时间内,没有成功的写入数据
        dev_dbg(i2c->dev, "timeout\n");
    else if (ret != num)//未写完规定的消息个数,则失败
        dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
    msleep(1);//睡眠1ms,使总线停止
 out:
    return ret;
}

enum s3c24xx_i2c_state {
STATE_IDLE, //总线空闲状态
STATE_START, //总线开始状态
STATE_READ, //总线写数据状态
STATE_WRITE, //总线读书据状态
STATE_STOP //总线停止状态
};

判断总线闲忙状态s3c24xx_i2c_set_master();
适配器使能函数s3c24xx_i2c_enable_irq();
启动适配器消息传输函数s3c24xx_i2c_message_start();

5.7 适配器的中断处理函数s3c24xx_i2c_irq()
数据通信的过程:
(1)传输数据时,调用s3c24xx_i2c_algorithm结构体中的数据传输函数s3c24xx_i2c_xfer();
(2)s3c24xx_i2c_xfer()中会调用s3c24xx_i2c_doxfer()进行数据的传输;
(3)s3c24xx_i2c_doxfer()中向总线 发送IIC设备地址和开始信号S后,便会调用wati_event_timeout()函数进入等待状态;
(4)将数据准备好发送时,将产生中断,并调用实现注册的中断处理函数s3c24xx_i2c_irq();
(5)s3c24xx_i2c_irq()调用下一个字节传输函数i2s_s3c_irq_nextbyte()来传输数据;
(6)当数据传输完成后,会调用 s3c24xx_i2c_stop().;
(7)最后调用wake_up()唤醒等待队列,完成数据的传输过程。
5.7.1 中断处理函数s3c24xx_i2c_irq()

static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
    struct s3c24xx_i2c *i2c = dev_id;                               
    unsigned long status;//IICSTAT状态缓存
    unsigned long tmp;//缓存寄存器
    status = readl(i2c->regs + S3C2410_IICSTAT);                     //读取IICSTAT的值
    if (status & S3C2410_IICSTAT_ARBITR) {               //因仲裁失败引发的中断,IICSTAT[3]为0,表示仲裁成功,为1,表示失败
            dev_err(i2c->dev, "deal with arbitration loss\n");
    }
    if (i2c->state == STATE_IDLE) {//当总线为空闲状态时,由于非读写引起的中断,将会执行下面的分支清除中断信号,继续传输数据。IICCON[4]为1表示发生中断,总线上的数据传输停止。要使继续传输数据,需要写入0清除
        dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");
        tmp = readl(i2c->regs + S3C2410_IICCON);                       //读IICCON寄存器
        tmp &= ~S3C2410_IICCON_IRQPEND; //将 IICCON的位[4]清零,表示清除中断
        writel(tmp, i2c->regs +  S3C2410_IICCON);//写IICCON寄存器
        goto out;//跳到退出直接返回
    }
    i2s_s3c_irq_nextbyte(i2c, status);//传输或者接收下一个字节
 out:
    return IRQ_HANDLED;
}

5.7.2 字节传输函数i2s_s3c_irq_nextbyte()

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:  //发出停止信号P 
        dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
        s3c24xx_i2c_disable_irq(i2c);//接收和发送数据时,将不会产生中断
        goto out_ack;
    case STATE_START:                                            //发出开始信号S
        if (iicstat & S3C2410_IICSTAT_LASTBIT &&
            !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {//当没有接收到IIC设备的应答ACK信号,说明对应地址的IIC设备不存在,停止总线工作
            dev_dbg(i2c->dev, "ack was not received\n");
            s3c24xx_i2c_stop(i2c, -ENXIO);                   //停止总线工作,发出P信号
            goto out_ack;
        }
        if (i2c->msg->flags & I2C_M_RD)//一个读信息
            i2c->state = STATE_READ;
        else
            i2c->state = STATE_WRITE; //一个写消息
 //is_lastmsg()判断是否只有一条消息,如果这条消息为0字节,那么发送停止信号P。0长度信息用于设备探测probe()时检测设备
        if (is_lastmsg(i2c) && i2c->msg->len == 0) {         
            s3c24xx_i2c_stop(i2c, 0);
            goto out_ack;
        }

        if (i2c->state == STATE_READ)
            goto prepare_read;                             //直接跳到读命令去
    case STATE_WRITE:
        if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {//没有接收到IIC设备的ACK信号,表示出错,停止总线传输
            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);//将一个字节的数据写到IICDS中
            ndelay(i2c->tx_setup);//等待数据发送到总线
        } else if (!is_lastmsg(i2c)) {//如果不是最后一个消息,则移向下一个消息
        dev_dbg(i2c->dev, "WRITE: Next Message\n");
            i2c->msg_ptr = 0;
            i2c->msg_idx++;
            i2c->msg++;
            if (i2c->msg->flags & I2C_M_NOSTART)
            {//不处理这种新类型的消息,直接停止
                if (i2c->msg->flags & I2C_M_RD) 
                {
                     s3c24xx_i2c_stop(i2c, -EINVAL);
                }
                goto retry_write;
            } else {//开始传输消息,将IICDS的数据发到总线上
                s3c24xx_i2c_message_start(i2c, i2c->msg);          
                i2c->state = STATE_START;               //置开始状态
            }
        } else {
            s3c24xx_i2c_stop(i2c, 0);              //所有消息传递结束,停止总线
        }
        break;

    case STATE_READ:                                  //读数据
         byte = readb(i2c->regs + S3C2410_IICDS);//从数据寄存器读出数据
        i2c->msg->buf[i2c->msg_ptr++] = byte;                       //放到缓冲区
 prepare_read:
        if (is_msglast(i2c)) {                                 //一个消息的最后一个字节
            if (is_lastmsg(i2c))                               //最后一个消息
                s3c24xx_i2c_disable_ack(i2c);             //禁止ACK信号
        } else if (is_msgend(i2c)) {                               //读完一个消息
              if (is_lastmsg(i2c)) {                               //最后一个消息
                     dev_dbg(i2c->dev, "READ: Send Stop\n");
                s3c24xx_i2c_stop(i2c, 0);        //发出停止信号,并唤醒对立
            } else {//传输下一个消息
                         dev_dbg(i2c->dev, "READ: Next Transfer\n");
                i2c->msg_ptr = 0;
                i2c->msg_idx++;                             //移到下一个消息索引
                i2c->msg++;                                   //移到下一个消息
            }
             break;
    }

 out_ack: //清除中断,不然重复执行该中断函数
    tmp = readl(i2c->regs + S3C2410_IICCON);
    tmp &= ~S3C2410_IICCON_IRQPEND;
    writel(tmp, i2c->regs + S3C2410_IICCON);
 out:
    return ret;
}

5.7.3 适配器传输停止函数s3c24xx_i2c_stop()

static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret)
{
    unsigned long iicstat = readl(i2c->regs + S3C2410_IICSTAT); //读IICSTAT寄存器
    dev_dbg(i2c->dev, "STOP\n");
    iicstat &= ~S3C2410_IICSTAT_START;                                        //写IICSTAT[5]为0,则放出P信号
    writel(iicstat, i2c->regs + S3C2410_IICSTAT);
    i2c->state = STATE_STOP; //设置适配器为停止状态
    s3c24xx_i2c_master_complete(i2c, ret);//唤醒传输等待队列中的进程
    s3c24xx_i2c_disable_irq(i2c);//禁止中断
}

static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)
{
    dev_dbg(i2c->dev, "master_complete %d\n", ret);
    i2c->msg_ptr = 0;
    i2c->msg = NULL;
    i2c->msg_idx++;
    i2c->msg_num = 0;//表示适配器中已经没有待传输的消息
    if (ret)
        i2c->msg_idx = ret;
    wake_up(&i2c->wait);//唤醒等待队列中的进程
}

5.7.4 中断处理函数的辅助函数
is_lastmag()函数:判断当前处理的消息是否为最后一个消息,如果是返回1,否则返回0。
is_msgend()函数:判断当前消息是否已经传输完所有字节。
s3c24xx_i2c_disable_ack()函数:禁止适配器发出应答ACK信号。

六. IIC设备层驱动程序
6.1 平台驱动的加载和卸载,以及s3c2410_i2c_driver

static int __init i2c_adap_s3c_init(void)
{
    int ret;//返回值
    ret = platform_driver_register(&s3c2410_i2c_driver);    //注册驱动程序    
    if (ret == 0) {
        ret = platform_driver_register(&s3c2440_i2c_driver);               //再次注册
        if (ret)
            platform_driver_unregister(&s3c2410_i2c_driver);               //注销驱动程序
    }
    return ret;
}

初始化函数为什么两次调用platform_driver_register()函数,这是因为第一个返回0,表示驱动注册成功,但并不表示探测函数s3c24xx_i2c_probe()探测IIC设备成功,有可能第一次注册时因为硬件被占用而探测函数失败,所以为了保证探测的成功率,又一次注册并探测了一次设备。同样卸载也要两次。

static void __exit i2c_adap_s3c_exit(void)
{
    platform_driver_unregister(&s3c2410_i2c_driver);            //注销平台驱动
    platform_driver_unregister(&s3c2440_i2c_driver);
}

static struct platform_driver s3c2440_i2c_driver = {
    .probe        = s3c24xx_i2c_probe,
    .remove        = s3c24xx_i2c_remove,
    .suspend_late    = s3c24xx_i2c_suspend_late,
    .resume        = s3c24xx_i2c_resume,
    .driver        = {
        .owner    = THIS_MODULE,
        .name    = "s3c2440-i2c",
    },
};

6.2 探测函数s3c24xx_i2c_probe()
在该函数中将初始化适配器、IIC等硬件设备。主要完成如下功能:
(1)申请一个适配器结构体I2c,并对其赋初值
(2)获得I2c时钟资源
(3)将适配器的寄存器资源映射到虚拟内存中
(4)申请中断处理函数
(5)初始化IIC控制器
(6)添加适配器I2c到内核

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    struct s3c24xx_i2c *i2c;//适配器指针
    struct s3c2410_platform_i2c *pdata;//IIC平台设备相关的数据
    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);//动态分配一个适配器数据结构,并对其动态赋值
    if (!i2c) {//内存不足,失败
        dev_err(&pdev->dev, "no memory for state\n");
        return -ENOMEM;
    }
    strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));//给适配器起名为s3c2410-i2c
    i2c->adap.owner   = THIS_MODULE;//模块指针
    i2c->adap.algo    = &s3c24xx_i2c_algorithm;                            //给适配器的一个通信方法
    i2c->adap.retries = 2;//2次总线仲裁尝试
    i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;//定义适配器类
    i2c->tx_setup     = 50; //数据从适配器传输到总线的时间为50ns
    spin_lock_init(&i2c->lock); //初始化自旋锁
    init_waitqueue_head(&i2c->wait);//初始化等待队列头部
 //以下代码找到i2c的时钟,并且调用clk_enable()函数启动它
    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);//启动时钟
    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;
    }
    i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,pdev->name);//申请一块I/O内存,对应适配器的几个寄存器
    if (i2c->ioarea == NULL) {                                                                                                         //  I/O内存获取失败则退出
        dev_err(&pdev->dev, "cannot request IO\n");
        ret = -ENXIO;
        goto err_clk;
    }
    i2c->regs = ioremap(res->start, (res->end-res->start)+1);//将设备内存映射到虚拟地址空间,这样可以使用函数访问
    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);//输出映射基地址,调试时用
    i2c->adap.algo_data = i2c;//将私有数据指向适配器结构体
    i2c->adap.dev.parent = &pdev->dev;                                                                             //组织设备模型
    ret = s3c24xx_i2c_init(i2c);//初始化IIC控制器
    if (ret != 0)//初始化失败
        goto err_iomap;
    i2c->irq = ret = platform_get_irq(pdev, 0);                                    //获得平台设备的第一个中断号
    if (ret <= 0) {
        dev_err(&pdev->dev, "cannot find IRQ\n");
        goto err_iomap;
    }
    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;
    }
      i2c->adap.nr = pdata->bus_num;                          //适配器的总线编号
   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);               //设置平台设备的私有数据为i2c适配器
    dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
    return 0;//成功返回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);  //释放i2c适配器结构体资源
    return ret;
}

6.3 移除函数s3c24xx_i2c_remove()

static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
    struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);//得到适配器结构体指针
    s3c24xx_i2c_deregister_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);//释放I/O资源
    kfree(i2c->ioarea);//释放资源所占用的内存
    kfree(i2c); //释放适配器的内存
    return 0;
}

6.4 控制器初始化函数s3c24xx_i2c_init()

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
    //设置IICCON[5]为1,表示发送和接收数据时,会引发中断。设置[7]为1,表示需要发出ACK信号
    unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;  
    struct s3c2410_platform_i2c *pdata;//平台设备数据指针
    unsigned int freq;//控制器工作的频率
    pdata = i2c->dev->platform_data;//得到平台设备的数据
    if (pdata->cfg_gpio)//初始化gpio引脚
        pdata->cfg_gpio(to_platform_device(i2c->dev));
    writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);//向IICADD写入IIC设备地址,IICADD的位[7:1]表示IIC设备地址
    dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);//打印地址信息
    writel(iicon, i2c->regs + S3C2410_IICCON);                                       //初始化IICCON寄存器,只允许ACK信号和中断使能,其他为0
    if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) { //设置时钟源和时钟频率
        writel(0, i2c->regs + S3C2410_IICCON);                                        //失败,则设置为0
        dev_err(i2c->dev, "cannot meet bus frequency required\n");
        return -EINVAL;
    }
    dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq); //打印频率信息
    dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);//打印IICCON寄存器
    if (s3c24xx_i2c_is2440(i2c)) {                                                                 //如果处理器是s3c2440,则设置IICLC寄存器为SDA延时时间
        dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
        writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
    }
    return 0;
}

6.5 设置控制器数据发送频率函数s3c24xx_i2c_clockrate()
发送频率 = IICCLK / (IICCON[3:0] + 1)
IICCLK = PCLK / 16 (当IICCON[6] == 0)或
IICCLK = PCLK / 512 (当IICCON[6] == 1)

static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
{
    struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;//得到平台设备数据
    unsigned long clkin = clk_get_rate(i2c->clk);                                                  //获得PCLK时钟频率
    unsigned int divs, div1;
    u32 iiccon;//缓存IICCON
    int freq;//计算的频率
    int start, end;//开始和结束频率,用于寻找一个合适的频率
    i2c->clkrate = clkin;                       
    clkin /= 1000;//将单位转化为KH
    dev_dbg(i2c->dev, "pdata %p, freq %lu %lu..%lu\n",
         pdata, pdata->bus_freq, pdata->min_freq, pdata->max_freq);//打印总线,最大、最小频率
    if (pdata->bus_freq != 0) {
        freq = s3c24xx_i2c_calcdivisor(clkin, pdata->bus_freq/1000,&div1, &divs);
        if (freq_acceptable(freq, pdata->bus_freq/1000))
            goto found;
    }
    start = (pdata->max_freq == 0) ? pdata->bus_freq : pdata->max_freq;
    end = pdata->min_freq;
    start /= 1000;
    end /= 1000;
    for (; start > end; start--) {
        freq = s3c24xx_i2c_calcdivisor(clkin, start, &div1, &divs);
        if (freq_acceptable(freq, start))
            goto found;
    }
    return -EINVAL;//不能找到一个合适的分配方式,返回错误
 found: //找到一个合适的发送频率,则写IICCON寄存器中与时钟相关的位
    *got = freq;//got为从参数返回的频率值
    iiccon = readl(i2c->regs + S3C2410_IICCON);//读出IICCON的值
    iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);//将IICCON的[6]和[3:0]清零,以避免以前分频系数的影响
    iiccon |= (divs-1);//设置位[3:0]的分频系数,divs的值 < 16
    if (div1 == 512)//如果IICCLK为PCLK / 512 ,那么设置位[6]为1
        iiccon |= S3C2410_IICCON_TXDIV_512;
    writel(iiccon, i2c->regs + S3C2410_IICCON);//重新写IICCON寄存器的值
    return 0;
}
 

static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted, unsigned int *div1, unsigned int *divs)//用来计算分频系数
{
    unsigned int calc_divs = clkin / wanted;//clkin表示输入频率,wanted表示想要分频的系数
    unsigned int calc_div1;
    if (calc_divs > (16*16)) //如果分频系数大于256,那么就设置为512,为了2的冪次数
        calc_div1 = 512;
    else
        calc_div1 = 16;
    calc_divs += calc_div1-1;//按前面公式计算分频系数
    calc_divs /= calc_div1;
    if (calc_divs == 0) //如果分频系数不合法,调整合法
        calc_divs = 1;
    if (calc_divs > 17)
        calc_divs = 17;
    *divs = calc_divs;//计算两个分频数
    *div1 = calc_div1;
    return clkin / (calc_divs * calc_div1); //得到最终的分频系数,这个系数将写入寄存器
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值