IIC设备是一种通过IIC总线连接的设备,由于其简单性,被广泛引用于电子系统中。在现代电子系统中,有很多的IIC设备需要进行相互之间通信

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

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,它是一个多主机系统,在一条总线上可以同时有多个主机存在,通过冲突检测方式和延时等待防止数据不被破坏。同一时间只能有一个主机占用总线

IIC总线在传输数据的过程中有3种类型的信号:开始信号、结束信号、和应答信号

>>开始信号(S): 当SCL为高电平时,SDA由高电平向低电平跳变,表示将要开始传输数据

>>结束信号(P):当SCL为高电平时,SDA由低电平向高电平跳变,表示结束传输数据

>>响应信号(ACK): 从机接收到8位数据后,在第9个周期,拉低SDA电平,表示已经收到数据。这个信号称为应答信号

开始信号和结束信号的波形如下图:

主机:IIC总线中发送命令的设备,对于ARM处理器来说,主机就是IIC控制器

从机:接受命令的设备

主机向从机发送数据:

主机通过数据线SDA向从机发送数据。当总线空闲时,SDA和SCL信号都处于高电平。主机向从机发送数据的过程:

1,当主机检测到总线空闲时,主机发出开始信号

2,主机发送8位数据。这8位数据的前7位表示从机地址,第8位表示数据的传输方向。这时,第8位为0,表示向从机发送数据

3,被选中的从机发出响应信号ACK

4,从机传输一系列的字节和响应位

5,主机接受这些数据,并发出结束信号P,完成本次数据传输

由上图可知,IIC控制器主要是由4个寄存器来完成所有的IIC操作的。

IICCON:控制是否发出ACK信号,是否开启IIC中断

IICSTAT:

IICADD:挂载到总线上的从机地址。该寄存器的[7:1]表示从机地址。IICADD寄存器在串行输出使能位IICSTAT[4]为0时,才可以写入;在任何时候可以读出

IICDS:保存将要发送或者接收到的数据。IICCDS在串行输出使能IICSTAT[4]为1时,才可以写入;在任何时间都可以读出

因为IIC设备种类太多,如果每一个IIC设备写一个驱动程序,那么显得内核非常大。不符合软件工程代码复用,所以对其层次话:

这里简单的将IIC设备驱动分为设备层、总线层。理解这两个层次的重点是理解4个数据结构,这4个数据结构是i2c_driver、i2c_client、i2c_algorithm、i2c_adapter。i2c_driver、i2c_client属于设备层;i2c_algorithm、i2c_adapter属于总线型。如下图:

设备层关系到实际的IIC设备,如芯片AT24C08就是一个IIC设备。总线层包括CPU中的IIC总线控制器和控制总线通信的方法。

值得注意的是:一个系统中可能有很多个总线层,也就是包含多个总线控制器;也可能有多个设备层,包含不同的IIC设备

由IIC总线规范可知,IIC总线由两条物理线路组成,这两条物理线路是SDA和SCL。只要连接到SDA和SCL总线上的设备都可以叫做IIC设备。一个IIC设备由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个。

AT24C08的自定义地址码如图:A0,A1,A2接低电平,所以自定义地址码为0;

如果在两个不同IIC总线上挂接了两块类型和地址相同的芯片,那么这两块芯片的地址相同。这显然是地址冲突,解决的办法是为总线适配器指定一个ID号,那么新的芯片地址就由总线适配器的ID和设备地址组成

除了地址之外,IIC设备还有一些重要的注意事项:

1,i2c_client数据结构是描述IIC设备的“模板”,驱动程序的设备结构中应包含该结构

2,adapter指向设备连接的总线适配器,系统可能有多个总线适配器。内核中静态指针数组adapters记录所有已经注册的总线适配器设备

3,driver是指向设备驱动程序,这个驱动程序是在系统检测到设备存在时赋值的

IIC设备驱动     i2c_driver:

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个。要不实现上面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;                    //指向驱动支持的设备

};

结构体i2c_driver和i2c_client的关系较为简单,其中i2c_driver表示一个IIC设备驱动,i2c_client表示一个IIC设备。关系如下图:

IIC总线适配器就是一个IIC总线控制器,在物理上连接若干个IIC设备。IIC总线适配器本质上是一个物理设备,其主要功能是完成IIC总线控制器相关的数据通信:

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;               //用于同步的完成量

};

每一个适配器对应一个驱动程序,该驱动程序描述了适配器与设备之间的通信方法:

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_client。总线层包括两个重要的数据结构,分别是i2c_adapter和i2c_algorithm。一个i2c_algorithm结构表示适配器对应的传输数据方法。3个数据结构关系:

IIC设备层次结构较为简单,但是写IIC设备驱动程序却相当复杂。

IIC设备驱动程序的步骤:

IIC子系统:

IIC子系统是作为模块加载到系统中的。

初始化函数:

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;

}

struct bus_type i2c_bus_type = {

    .name        = "i2c",

    .dev_attrs    = i2c_dev_attrs,

    .match        = i2c_device_match,

    .uevent        = i2c_device_uevent,

    .probe        = i2c_device_probe,

    .remove        = i2c_device_remove,

    .shutdown    = i2c_device_shutdown,

    .suspend    = i2c_device_suspend,

    .resume        = i2c_device_resume,

};

static struct class i2c_adapter_class = {

    .owner            = THIS_MODULE,

    .name            = "i2c-adapter",

    .dev_attrs        = i2c_adapter_attrs,

};

static struct i2c_driver dummy_driver = {

    .driver.name    = "dummy",

    .probe        = dummy_probe,

    .remove        = dummy_remove,

    .id_table    = dummy_id,

};

IIC子系统退出函数:

static void __exit i2c_exit(void)

{

    i2c_del_driver(&dummy_driver);        //注销IIC设备驱动程序,主要功能是去掉总线中的该设备驱动程序

    class_unregister(&i2c_adapter_class);              //注销适配器类

    bus_unregister(&i2c_bus_type);                       //注销I2C总线

}

适配器驱动程序是IIC设备驱动程序需要实现的主要驱动程序,这个驱动程序需要根据具体的适配器硬件来编写。

I2c_adapter结构体为描述各种IIC适配器提供了“模板",它定义了注册总线上所有设备的clients链表、指向具体IIC适配器的总线通信方法I2c_algorithm的algo指针、实现i2c总线的操作原子性的lock信号量。但i2c_adapter结构体只是所有适配器的共有属性,并不能代表所有类型的适配器

s3c2440对应的适配器为:

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;                  //适配器主体结构体

#ifdef CONFIG_CPU_FREQ

    struct notifier_block    freq_transition;

#endif

};

enum s3c24xx_i2c_state {

    STATE_IDLE,

    STATE_START,

    STATE_READ,

    STATE_WRITE,

    STATE_STOP

};

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;                                               //指向消息数据的缓冲区

};

当拿到一块新的电路板,并研究了响应的IIC适配器之后,就应该使用内核提供的框架函数向IIC子系统添加一个新的适配器

过程:

1,分配一个IIC适配器,并初始化相应的变量

2,使用i2c_add_adapter()函数向IIC子系统添加适配器结构体i2c_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);           //锁定内核锁

    /* "above" here means "above or equal to", sigh */

    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);                       // 注册适配器设备

}

关于IDR机制,请参考:http://www.cnblogs.com/lfsblack/archive/2012/09/15/2686557.html

static DEFINE_IDR(i2c_adapter_idr);

通过ID号获得适配器指针:

struct i2c_adapter* i2c_get_adapter(int id)

{

    struct i2c_adapter *adapter;                  //适配器指针

    mutex_lock(&core_lock);           //锁定内核锁

    adapter = (struct i2c_adapter *)idr_find(&i2c_adapter_idr, id);          //通过ID号,查询适配器指针

    if (adapter && !try_module_get(adapter->owner))              //适配器引用计数+1

        adapter = NULL;

    mutex_unlock(&core_lock);                            //释放内核锁

    return adapter;

}

适配器卸载函数:

主要任务:注销适配器的数据结构,删除总线上的所有设备的I2c_client数据结构和对应的i2c_driver驱动程序,并减少其代表总线上所有设备的相应驱动程序数据结构的引用计数(如果到达0,则卸载设备驱动程序):

int i2c_del_adapter(struct i2c_adapter *adap)

{

    struct i2c_client *client, *_n;

    int res = 0;

    mutex_lock(&core_lock);

    /* First make sure that this adapter was ever added */

    if (idr_find(&i2c_adapter_idr, adap->nr) != adap) {                          //查找要卸载的适配器

        pr_debug("i2c-core: attempting to delete unregistered "

             "adapter [%s]\n", adap->name);

        res = -EINVAL;

        goto out_unlock;

    }

    /* Tell drivers about this removal */

    res = bus_for_each_drv(&i2c_bus_type, NULL, adap,

                   i2c_do_del_adapter);

    if (res)

        goto out_unlock;

    /* detach any active clients. This must be done first, because

     * it can fail; in which case we give up. */

    list_for_each_entry_safe_reverse(client, _n, &adap->clients, list) {

        struct i2c_driver    *driver;

        driver = client->driver;

        /* new style, follow standard driver model */

        if (!driver || is_newstyle_driver(driver)) {

            i2c_unregister_device(client);

            continue;

        }

        /* legacy drivers create and remove clients themselves */

        if ((res = driver->detach_client(client))) {

            dev_err(&adap->dev, "detach_client failed for client "

                "[%s] at address 0x%02x\n", client->name,

                client->addr);

            goto out_unlock;

        }

    }

    /* clean up the sysfs representation */

    init_completion(&adap->dev_released);

    device_unregister(&adap->dev);                             设备注销

    /* wait for sysfs to drop all references */

    wait_for_completion(&adap->dev_released);

    /* free bus id */

    idr_remove(&i2c_adapter_idr, adap->nr);                      //删除IDR,ID号

    dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);

    /* Clear the device structure in case this adapter is ever going to be

       added again */

    memset(&adap->dev, 0, sizeof(adap->dev));

 out_unlock:

    mutex_unlock(&core_lock);

    return res;

}

IIC总线通信方法s3c24xx_i2c_algorithm结构体:

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {

    .master_xfer        = s3c24xx_i2c_xfer,

    .functionality        = s3c24xx_i2c_func,

};

这里只实现了IIC总线通信协议

通信方法因不同的适配器有所不同,要跟据具体的硬件来实现

协议支持函数s3c24xx_i2c_func()

该函数返回总线支持的协议,如I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL、I2C_FUNC_PROTOCOL_MANGLING协议:

static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)

{

    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;

}

传输函数s3c24xx_i2c_xfer():

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错误

}

真正的传输函数:

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;

    /* 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);                                                                          //睡眠1ms,使总线停止

 out:

    return ret;

}

enum s3c24xx_i2c_state {

    STATE_IDLE,                    //总线空闲状态

    STATE_START,                //总线开始状态

    STATE_READ,                  //总线写数据状态

    STATE_WRITE,                //总线读书据状态

    STATE_STOP                   //总线停止状态

};

判断总线闲忙状态s3c24xx_i2c_set_master():

在适配器发送数据以前,需要判断总线的忙闲状态。读取IICSTAT寄存器的[5]位,可以判断总线的忙闲状态。当为0时,总线空闲;当为1时总线繁忙:

static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)

{

    unsigned long iicstat;                                   //用于存储IICSTAT的状态

    int timeout = 400;                                         //尝试400次,获得总线

    while (timeout-- > 0) {

        iicstat = readl(i2c->regs + S3C2410_IICSTAT);                    //读取寄存器IICSTAT的值

        if (!(iicstat & S3C2410_IICSTAT_BUSBUSY))                                 //检查第5位是否为0

            return 0;

        msleep(1);                                                                                 //等待1ms

    }

    return -ETIMEDOUT;

}

适配器使能函数s3c24xx_i2c_enable_irq()

IIC设备是一种慢速设备,所以在读写数据的过程中,内核进程需要睡眠等待。当数据发送完后,会从总线发送一个中断信号,唤醒睡眠中的进程,所以适配器应该使能中断。中断使能由IICCON寄存器的[5]位设置,该位为0表示Tx/Rx中断禁止;该位为1表示Tx/Rx中断使能。s3c24xx_i2c_enable_irq()函数用来使中断使能。所以向IICCON寄存器的位[5]写1:

static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)

{

    unsigned long tmp;                       //寄存器缓存变量

    tmp = readl(i2c->regs + S3C2410_IICCON);             //读IICCON寄存器

    writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);        //将IICCON的第5位置1

}

启动适配器消息传输函数s3c24xx_i2c_message_start():

s3c24xx_i2c_message_start()函数写s3c2440适配器对应的寄存器,向IIC设备传递开始位和IIC设备地址。主要功能:

1,s3c2440的适配器对应的IICON和IICSTAT寄存器

2,写从设备地址,并发出开始信号S

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,

                      struct i2c_msg *msg)

{

    unsigned int addr = (msg->addr & 0x7f) << 1;                 //取从设备的低7位地址,并向前移动一位。设置设备地址,前7位表示设备地址,最后一位表示读写,0写1读

    unsigned long stat;                                                           //缓存IICSTAT寄存器

    unsigned long iiccon;                                                        //缓存IICCO寄存器

    stat = 0;                                                                             //状态初始化为0

    stat |=  S3C2410_IICSTAT_TXRXEN;                               //使能接收和发送功能,是适配器可以收发数据

    if (msg->flags & I2C_M_RD) {                                          //如果消息类型是从IIC设备到适配器读数据

        stat |= S3C2410_IICSTAT_MASTER_RX;                   //将适配器设置为主机接收器

        addr |= 1;                                                                    //将地址的最低位置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);              //读出IICCON寄存器的值

    writel(stat, i2c->regs + S3C2410_IICSTAT);                              //设置IICSTAT的值,使其为主机发送器,接收使能

    dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);          //打印调试信息

    writeb(addr, i2c->regs + S3C2410_IICDS);                                                  //写地址寄存器的值。将IIC设备地址写入IICDS寄存器中,寄存器值[7:1]表示设备地址。IICADD寄存器必须在输出使能为IICSTAT[4]为0时,才可以写入,所以上面的writel函数设置使能为输出使能为IISTAT[4]。

    /* 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);                                  //写IICCON寄存器的值

    stat |= S3C2410_IICSTAT_START;                                //设置为启动状态

    writel(stat, i2c->regs + S3C2410_IICSTAT);                      //发出S开始信号,当S信号发出后,IICDS寄存器的数据将自动发出到总线上

}

static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)    //使能ACK响应信号

{

    unsigned long tmp;                             //暂存IICCON寄存器

    tmp = readl(i2c->regs + S3C2410_IICCON);             //取出IICCON寄存器的值

    writel(tmp | S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);  写IICCON,使能ACK

}

适配器中断处理函数:s3c24xx_i2c_irq()

顺着通信函数s3c24xx_i2c_xfer()的执行流程分析,函数最终会返回,但并没有传输数据。传输数据的过程被交到了中断处理函数中。这是因为IIC设备的读写是非常慢的,需要使用中断的方法提高处理器的效率,这在操作系统的过程中非常常见。

通过s3c24xx_i2c_algorithm通信方法中函数的调用关系,数据通信的过程如下:

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()唤醒等待队列,完成数据的传输过程

当s3c2440的IIC适配器处于主机模式时,IIC操作的第一步总是向IIC总线写入设备的地址及开始信号。这步由s3c24xx_i2c_set_master()和s3c24xx_i2c_message_start()完成。而收发数据的后继操作在IIC中断处理程序s3c24xx_i2c_irq()中完成的

中断处理函数:

IIC中断的产生有3种情况:

1,当总线仲裁失败时产生中断

2,当发送/接受完一个字节的数据(包括响应位)时产生中断

3,当发出地址信息或接收到一个IIC设备地址并且吻合时产生中断

在这3种情况下都触发中断,由于当发送/接收完一个字节后会产生中断,所以可以在中断处理函数中处理数据的传输:

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,表示失败

        /* deal with arbitration loss */

        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;                                                                               //跳到退出直接返回

    }

    /* 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():

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 ,IIC设备处于停止状态,发送一个停止信号给IIC适配器。这是即使有数据产生,也不会产生中断信号

        dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);

        s3c24xx_i2c_disable_irq(i2c);                          //接收和发送数据时,将不会产生中断

        goto out_ack;

    case STATE_START:                                            //发出开始信号S

        /* 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)) {    //当没有接收到IIC设备的应答ACK信号,说明对应地址的IIC设备不存在,停止总线工作

            /* ack was not received... */

            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;                         一个写消息

        /* 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) {          //is_lastmsg()判断是否只有一条消息,如果这条消息为0字节,那么发送停止信号P。0长度信息用于设备探测probe()时检测设备

            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)) {      //没有接收到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中

            /* 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 {             //开始传输消息,将IICDS的数据发到总线上

                /* 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);             //禁止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);        //发出停止信号,并唤醒对立

            } 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_stop()

主要完成以下功能:

1,向总线发出结束P信号

2,唤醒等待在队列s3c24xx_i2c->wait中的进程,一次传输完毕

3,禁止中断 ,适配器中不产生中断信号

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

    /* stop the transfer */

    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);             //唤醒等待队列中的进程

}

几个小函数:

static inline int is_lastmsg(struct s3c24xx_i2c *i2c)                 //用来判断当前处理的消息是否为最后一个消息

{

    return i2c->msg_idx >= (i2c->msg_num - 1);

}

static inline int is_msgend(struct s3c24xx_i2c *i2c)         //判断当前消息是否已经传输完所有字节

{

    return i2c->msg_ptr >= i2c->msg->len;

}

static inline int is_msglast(struct s3c24xx_i2c *i2c)             //判断当前是否正在处理当前消息的最后一个字节

{

    return i2c->msg_ptr == i2c->msg->len-1;

}

static inline void s3c24xx_i2c_disable_ack(struct s3c24xx_i2c *i2c)             //禁止适配器发出应答信号

{

    unsigned long tmp;

    tmp = readl(i2c->regs + S3C2410_IICCON);                  //   IICCON[7]为0,表示不发出ACK信号

    writel(tmp & ~S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);

}

IIC设备层驱动程序:

IIC设备驱动被作为一个单独的模块加入进内核,在模块的加载和卸载函数中需要注册和注销一个平台驱动结构体platform_driver。

static int __init i2c_adap_s3c_init(void)

{

    int ret;                                                                 //返回值

    ret = platform_driver_register(&s3c2410_i2c_driver);    //注册驱动程序 。该函数将平台驱动添加到虚拟的总线上,以便与设备进行关联。platform_driver_register()函数中会调用s3c2410_i2c_driver中定义的s3c24xx_i2c_probe()函数进行设备探测,从而将驱动和设备都加入总线中           

    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",

    },

};

探测函数: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);                      //初始化等待队列头部

    /* find the clock and enable it */      //以下代码找到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);                              //启动时钟

    /* map the registers */

    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);                                                                                 //输出映射基地址,调试时用

    /* setup info block for the i2c core */

    i2c->adap.algo_data = i2c;                                                                                            //将私有数据指向适配器结构体

    i2c->adap.dev.parent = &pdev->dev;                                                                             //组织设备模型

    /* initialise the i2c controller */

    ret = s3c24xx_i2c_init(i2c);                                                //初始化IIC控制器

    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;

    }

    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;                          //适配器的总线编号

    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;

}

与s3c24xx_i2c_probe()函数相反功能的函数是移除函数:s3c24xx_i2c_remove()。它在模块卸载函数调用platform_driver_unregister()函数时通过platform_driver的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;

}

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

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)

{

    unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;  //设置IICCON[5]为1,表示发送和接收数据时,会引发中断。设置[7]为1,表示需要发出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引脚

        pdata->cfg_gpio(to_platform_device(i2c->dev));

    /* write slave address */

    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

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

    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;

    }

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

    dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);                          //打印频率信息

    dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);                          //打印IICCON寄存器

    /* check for s3c2440 i2c controller  */

    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;

}

设置控制器数据发送频率函数s3c24xx_i2c_clockrate()

在控制器初始化函数s3c24xx_i2c_init(),调用s3c24xx_i2c_clockrate()函数设置数据发送频率。此发送频率由IICCON寄存器控制。发送频率可以由一个公式得到:

发送频率 =  IICCLK  /  (IICCON[3:0] + 1)

IICCLK = PCLK / 16   (当IICCON[6] == 0)

活IICCLK = PCLK / 512  (当IICCON[6] == 1)

PCLK是由clk_get_rate()函数获得适配器的时钟频率。

第一个参数是适配器指针,第二个参数是返回的发送频率:

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;        /* clkin now in KHz */             //将单位转化为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;

    }

    /* ok, we may have to search for something suitable... */

    start = (pdata->max_freq == 0) ? pdata->bus_freq : pdata->max_freq;

    end = pdata->min_freq;

    start /= 1000;

    end /= 1000;

    /* search loop... */

    for (; start > end; start--) {

        freq = s3c24xx_i2c_calcdivisor(clkin, start, &div1, &divs);

        if (freq_acceptable(freq, start))

            goto found;

    }

    /* cannot find frequency spec */

    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);                           得到最终的分频系数,这个系数将写入寄存器

}

有一种落差是,你配不上自己的野心,也辜负了所受的苦难

【DSP开发】【Linux开发】IIC设备驱动程序的更多相关文章

  1. 【DSP开发】【Linux开发】Linux下PCI设备驱动程序开发

    PCI是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为计算机系统中应用最为广泛,并且最为通用的总线标准.Linux的内核能较好地支持PCI总线,本文以Inte ...

  2. [中英对照]User-Space Device Drivers in Linux: A First Look | 初识Linux用户态设备驱动程序

    如对Linux用户态驱动程序开发有兴趣,请阅读本文,否则请飘过. User-Space Device Drivers in Linux: A First Look | 初识Linux用户态设备驱动程序 ...

  3. IIC设备驱动程序

    IIC设备是一种通过IIC总线连接的设备,由于其简单性,被广泛引用于电子系统中.在现代电子系统中,有很多的IIC设备需要进行相互之间通信 IIC总线是由PHILIPS公司开发的两线式串行总线,用于连接 ...

  4. Linux下PCI设备驱动程序开发 --- PCI驱动程序实现(三)

    三.PCI驱动程序实现 1. 关键数据结构 PCI设备上有三种地址空间:PCI的I/O空间.PCI的存储空间和PCI的配置空间.CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给 ...

  5. Linux 简单字符设备驱动程序 (自顶向下)

    第零章:扯扯淡 特此总结一下写的一个简单字符设备驱动程序的过程,我要强调一下“自顶向下”这个介绍方法,因为我觉得这样更容易让没有接触过设备驱动程序的童鞋更容易理解,“自顶向下”最初从<计算机网络 ...

  6. Linux中块设备驱动程序分析

    基于<Linux设备驱动程序>书中的sbull程序以对Linux块设备驱动总结分析. 開始之前先来了解这个块设备中的核心数据结构: struct sbull_dev {         i ...

  7. 【Linux 驱动】设备驱动程序再理解

    学习设备驱动编程也有一段时间了,也写过了几个驱动程序,因此有对设备驱动程序有了一些新的理解和认识,总结一下.学习设备驱动编程也有一段时间了,也写过了几个驱动程序.因此有对设备驱动程序有了一些新的理解和 ...

  8. RN开发-Linux开发环境搭建(Ubuntu 12.04)

    1.首先安装JDK 2.安装Android开发环境 3.安装node.js 3.1 官网下载 : node-v6.9.1-linux-x64 3.2 添加环境变量 sudo vi /etc/profi ...

  9. 【Linux开发】【DSP开发】Linux设备驱动之——PCI 总线

    PCI总线概述  随着通用处理器和嵌入式技术的迅猛发展,越来越多的电子设备需要由处理器控制.目前大多数CPU和外部设备都会提供PCI总线的接口,PCI总线已成为计算机系统中一种应用广泛.通用的总线标准 ...

随机推荐

  1. Python之抓取网页元素

    import urllib.request from bs4 import BeautifulSoup url = "http://www.wal-martchina.com/walmart ...

  2. 自制centos6开机界面

    1.先准备好一张640x480大小的图片并上传至主机(可在画图工具中调整图片大小) 注意如没有rz命令,可以先安装: yum install lrzsz 2.制作背景图 制作需要用到convert命令 ...

  3. CentOS 安装oracle client

    下载Oracle Client 1.通过下载地址下载 下载地址:https://www.oracle.com/database/technologies/instant-client/linux-x8 ...

  4. JavaScript复制内容到剪贴板

    移动端 需要复制内容到剪贴板时, clipborad.js 不支持ios微信版浏览器,可配合使用 execCommand 使其兼容, 完成一键复制淘口令的功能. 注意使用clipborad.js时,i ...

  5. K8S中DaemonSet

    DaemonSet DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本.当有 Node 加入集群时,也会为他们新增一个 Pod .当有 Node 从集群移除时,这些 Pod ...

  6. css基础(css书写 背景设置 标签分类 css特性)

      css书写位置   行内式写法 <p style="color:red;" font-size:12px;></p> 外联式写法 <link re ...

  7. C++入门经典-友元

    1:在讲述类的内容时说明了隐藏数据成员的好处,但是有时类会允许一些特殊的函数直接读写其私有数据成员. 使用friend关键字可以使特定的函数或者别的类的所有成员函数对私有数据成员进行读写.这既可以保持 ...

  8. <context:component-scan>标签报错解决方案

  9. Ubuntu16.04安装nginx(并启用SSL)

    一.安装环境介绍 需要预先安装gcc,通常ubuntu默认自带,所以默认已经有这个环境了,后续步骤默认是使用root账户进行的 二.下载及安装nginx相关组件 1.进入任意目录,我选用的是通常选用的 ...

  10. Ubuntu16.04配置vncserver后 导致重复进入登陆界面,无法进入桌面的问题

      1.在配置vncserver的时候,可能导致该用户不能正常登录桌面. 2.问题现象:正确输入密码,系统无法进入桌面,闪回到登录界面. 3.在登录界面按ctrl+Alt+F1,进入虚拟控制台(输入r ...