Linux字符设备驱动框架(六):Linux IIC总线驱动框架

/************************************************************************************

*本文为个人学习记录,如有错误,欢迎指正。

*本文参考资料: 

*        https://blog.csdn.net/li_wen01/article/details/51657040

*        https://blog.csdn.net/jscese/article/details/44003393

*        https://blog.csdn.net/mcgrady_tracy/article/details/51288138

*        https://www.cnblogs.com/xiaojiang1025/p/6500540.html

*        https://blog.csdn.net/xie0812/article/details/22984527

*        https://blog.csdn.net/zqixiao_09/article/details/50916916

************************************************************************************/

1. I2C简介

(1)IIC物理总线的构成 

IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。

(2)IIC通信的特点

同步、串行、电平信号、低速率、近距离。

(3)IIC通信时序

开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;

结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据;

数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期间,数据线SDA的数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线SDA的数据才允许改变。

应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

 

(4)IIC通信操作(s5pv210)

2. 相关数据结构

2.1 数据结构

(1)i2c_client

Linux内核使用i2c_client来描述一个挂载在I2C总线上的I2C设备。

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;    //是否已经释放的完成量
};

(2)i2c_driver

Linux内核使用i2c_driver来描述一个IIC设备的驱动程序。每个i2c_client对应一个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;                                          //指向驱动支持的设备
};

(3)i2c_adapter

Linux内核使用i2c_adapter来描述一个IIC总线适配器。IIC总线适配器就是SoC内部的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;           //用于同步的完成量
};

(4)i2c_algorithm

Linux内核使用i2c_algorithm来描述IIC适配器与IIC设备的通信方法。

struct  i2c_algorithm
{
  /*传输函数指针,指向实现IIC总线通信协议的函数,用来确定适配器支持那些传输类型    */
  int  (*master_xfer)(struct  i2c_adapter *adap,  struct  i2c_msg *msg, int num);    
  /*smbus方式传输函数指针,指向实现SMBus总线通信协议的函数。SMBus和IIC之间可以通过软件方式兼容,所以这里提供了一个函数,但是一般都赋值为NULL*/ 
   int  (*smbus_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 *);                  
};

2.2 数据结构之间的联系

(1)i2c_driver和i2c_client

i2c_client对应真实的IIC物理设备,每个IIC设备都需要一个i2c_client来描述;而i2c_driver对应一套驱动方法。i2c_driver与i2c_client的关系是一对多,即一个i2c_driver上可以支持多个同等类型的i2c_client。

(2)i2c_adapter与i2c_algorithm

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

(3)i2c_adapter和i2c_client

i2c_adapter和i2c_client的关系与IIC硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。

3. Linux I2C驱动框架

Linux内核中的IIC总线不同于SoC内部的物理IIC总线 ,内核中的IIC总线是虚拟出来的,目的是管理内核中的IIC从设备及其驱动。

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

(1)IIC核心

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

(2)IIC总线驱动

IIC总线驱动是对IIC硬件体系结构中适配器端(SoC内部的IIC总线控制器)的实现。IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter,IIC适配器的通信方法数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。不同的CPU平台对应着不同的I2C总线驱动。

(3)IIC设备驱动

IIC设备驱动是对IIC硬件体系结构中设备端的实现,与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同IIC总线适配器的差异,不考虑其实现细节地与硬件设备通讯。这部分代码一般由驱动工程师完成。

3.1 IIC核心

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

IIC核心的实现代码:/kernel/drivers/i2c/i2c-core.c。

3.1.1 IIC bus初始化

Linux内核初始化阶段,调用i2c_init() 函数来初始化IIC总线。

static int __init i2c_init(void)
{
    int retval;
 
    retval = bus_register(&i2c_bus_type);      //注册IIC总线
    if (retval)
        return retval;

  ... ...

    retval = i2c_add_driver(&dummy_driver);   //添加一个空驱动,不知为何要添加这个空驱动
    if (retval)
        goto class_err;
    return 0;
 
  ... ...

    return retval;
}

i2c_init() 函数中调用bus_register()函数注册IIC总线。IIC总线定义如下:

struct bus_type i2c_bus_type = 
{
    .name     = "i2c",
    .match    = i2c_device_match,//match方法用来进行 device 和driver 的匹配,在向总线注册设备或是驱动的的时候会调用此方法
    .probe    = i2c_device_probe,//probe方法在完成设备和驱动的配对之后调用执行
    .remove   = i2c_device_remove,
    .shutdown = i2c_device_shutdown,
    .suspend  = i2c_device_suspend,
    .resume   = i2c_device_resume,
};

IIC总线提供的match方法:match方法用来进行 i2c_driver 和 i2c_client 的匹配,在向总线注册i2c_driver或i2c_client的的时候会调用此方法。匹配的方法是拿id_table 中的每一项与 i2c_client 的name 进行匹配,如果名字相同则匹配成功。其函数定义如下:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client    *client = i2c_verify_client(dev); 
    struct i2c_driver    *driver;
 
    if (!client)
        return 0; 
 
    driver = to_i2c_driver(drv);
    /* match on an id table if there is one */
    if (driver->id_table)//如果IIC驱动的id_table 存在的话,使用i2c_match_id 进行函数进行匹配。
        return i2c_match_id(driver->id_table, client) != NULL;    
    return 0;
}

 i2c_driver 和 i2c_client匹配成功后,IIC总线提供的probe方法将被调用执行,即执行i2c_device_probe()函数。实质上,最终调用执行的是IIC设备驱动中的probe函数,即i2c_driver->probe。

static int i2c_device_probe(struct device *dev)
{
    struct i2c_client    *client = i2c_verify_client(dev);
    struct i2c_driver    *driver;
    int status;
 
    if (!client)
        return 0;
 
    driver = to_i2c_driver(dev->driver);
    if (!driver->probe || !driver->id_table)
        return -ENODEV;
    client->driver = driver;
  
  ... ...
 
    status = driver->probe(client, i2c_match_id(driver->id_table, client));//调用IIC设备驱动中的probe函数

  if (status)
        client->driver = NULL;
    return status;
}

3.1.2 IIC核心提供的接口函数

(1)增加/删除IIC总线适配器

/*增加一个IIC总线适配器*/
int i2c_add_adapter(struct i2c_adapter *adapter);
/*删除一个IIC总线适配器*/ int i2c_del_adapter(struct i2c_adapter *adap);

(2)增加/删除IIC从设备驱动

/*增加一个IIC从设备驱动*/
int i2c_add_driver(struct i2c_driver *driver);
/*删除一个IIC从设备驱动*/ void i2c_del_driver(struct i2c_driver *driver);

(3)IIC数据传输

/*
*参数:    struct i2c_adapter *adap:IIC总线适配器
*            struct i2c_msg*msgs:
*            int num:
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg*msgs, int num) ;

/*从以下代码可知,IIC的数据传输是调用i2c_adapter->i2c_algorithm->master_xfer完成*/
int i2c_transfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num)  
{  
    ... ...
    if (adap->algo->master_xfer)
    {  
         for (ret = 0, try = 0; try <=adap->retries; try++) 
        {  
             ret = adap->algo->master_xfer(adap, msgs,num);  
        }  
    }
    ... ...
} 

IIC总线上的数据传输是以字节为单位的,有读和写两种通信模式。IIC子系统为了实现这种通信方法,提供了i2c_msg结构,对于每一个START信号,都对应一个i2c_msg对象,实际操作中我们会将所有的请求封装成一个struct i2c_msg[],一次性将所有的请求通过i2c_transfer()发送给匹配到的client的从属的adapter,由adapter根据相应的algo域以及master_xfer域通过主机驱动来将这些请求发送给硬件上的设备。

struct i2c_msg 
{
    __u16 addr;    //IIC从设备地址
    __u16 flags;  //操作标志位,I2C_M_RD为读(1),写为0

#define I2C_M_TEN           0x0010    /* this is a ten bit chip address */
#define I2C_M_RD            0x0001    /* read data, from slave to master */
#define I2C_M_NOSTART       0x4000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR  0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK     0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN      0x0400    /* length will be first received byte */

    __u16 len;      //传输的数据长度,字节为单位
    __u8 *buf;      //存放read或write的数据的buffer
};

 

3.2 IIC总线驱动

IIC总线驱动是对IIC硬件体系结构中适配器端(SoC内部的IIC总线控制器)的实现。IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter,IIC适配器的通信方法数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。不同的CPU平台对应着不同的I2C总线驱动。

IIC总线驱动的核心代码:/kernel/drivers/i2c/busses/i2c-s3c2410.c(SAMSUNG 2410平台)。

i2c-s3c2410.c利用platform总线来实现IIC总线驱动,将IIC适配器的硬件信息挂载在platform device,将IIC总线适配器的驱动程序挂载在platform driver下。不同的CPU平台对应着不同的I2C总线驱动。

3.2.1 IIC总线适配器的platform device初始化

platform device的对象是IIC总线适配器,即SoC内部的IIC总线控制器。

构建IIC总线控制器的设备信息。

/*IIC总线控制器的硬件资源*/
static struct resource s3c_i2c_resource[] = 
{
    [0] = {
        .start = S3C_PA_IIC,            //IIC总线控制器寄存器开始地址
        .end   = S3C_PA_IIC + SZ_4K - 1//IIC总线控制器寄存器结束地址
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_IIC,
        .end   = IRQ_IIC,
        .flags = IORESOURCE_IRQ,
    },
};

/*IIC总线控制器的设备信息*/
struct platform_device s3c_device_i2c0 = 
{
    .name          = "s3c2410-i2c"//名称,与platform driver匹配时使用
    .id            = 1,
    .num_resources = ARRAY_SIZE(s3c_i2c_resource),
    .resource      = s3c_i2c_resource,
};

/*IIC总线控制器的默认平台数据*/
static struct s3c2410_platform_i2c default_i2c_data0 __initdata = 
{
    .flags        = 0,
    .slave_addr   = 0x10//从设备的设备地址
    .frequency    = 400*1000//IIC的时钟频率
    .sda_delay    = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON,
};

/*设置IIC总线控制器的平台数据*/
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
    struct s3c2410_platform_i2c *npd;

    if (!pd)
        pd = &default_i2c_data0;

    npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);
    if (!npd)
        printk(KERN_ERR "%s: no memory for platform data\n", __func__);
    else if (!npd->cfg_gpio)
        npd->cfg_gpio = s3c_i2c0_cfg_gpio;

    s3c_device_i2c0.dev.platform_data = npd;
}

向内核注册IIC总线控制器的platform device。

/*开发板的所有设备信息*/
static struct platform_device *smdkc110_devices[] __initdata = 
{
    ... ...
    &s3c_device_i2c0,
    ... ...
}

static void __init smdkc110_machine_init(void)
{
    ... ...
    platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));//向内核注册开发板的所有设备信息
    s3c_i2c0_set_platdata(NULL);//设置IIC总线控制器的平台数据,NULL表示使用默认的平台数据
    ... ...
}

3.2.2  IIC总线适配器的platform driver初始化

 platform driver的对象是IIC总线适配器的驱动程序。分析/kernel/driver/i2c/busees/i2c_s3c2410.c。

注册IIC总线适配器的驱动程序。

从如下代码可知,IIC总线适配器的驱动注册函数i2c_adap_s3c_init()被subsys_initcall修饰,则i2c_adap_s3c_init()函数将在内核初始化阶段被调度执行。

/*platform driver支持的设备表*/
static struct platform_device_id s3c24xx_driver_ids[] = 
{
    {
        .name        = "s3c2410-i2c",
        .driver_data    = TYPE_S3C2410,
    }, 
  {
        .name        = "s3c2440-i2c",
        .driver_data    = TYPE_S3C2440,
    }, 
  { },
};

/*使用MODULE_DEVICE_TABLE 宏声明,s3c24xx_driver_ids 是platform类型的一个设备表*/
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);

/*IIC总线适配器的驱动信息*/
static struct platform_driver s3c24xx_i2c_driver = {
    .probe     = s3c24xx_i2c_probe,
    .remove    = s3c24xx_i2c_remove,
    .id_table  = s3c24xx_driver_ids, //与IIC总线适配器的platform device匹配
    .driver    = 
  {
        .owner = THIS_MODULE,
        .name  = "s3c-i2c",
        .pm    = S3C24XX_DEV_PM_OPS,
    },
};

/*注册IIC总线适配器的驱动*/
static int __init i2c_adap_s3c_init(void)
{
    return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

在IIC总线适配器的platform device与platform driver匹配成功之后,platform driver中的probe函数将被调度运行,即s3c24xx_i2c_probe()函数。

s3c24xx_i2c_probe()函数的主要工作是对IIC总线适配器进行硬件初始化,并向内核注册一个i2c_adapter。

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    struct s3c24xx_i2c *i2c;
    struct s3c2410_platform_i2c *pdata;
    struct resource *res;
    int ret;
 
    pdata = pdev->dev.platform_data;//获取IIC总线适配器的平台数据
 
    i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
    
  /*填充i2c变量*/
    strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
    i2c->adap.owner   = THIS_MODULE;
    i2c->adap.algo    = &s3c24xx_i2c_algorithm;//初始化IIC总线适配器的控制算法
    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);//初始化一个等待队列
 
    /* find the clock and enable it */
   /*初始化IIC总线适配器的时钟*/
    i2c->dev = &pdev->dev;
    i2c->clk = clk_get(&pdev->dev, "i2c");
    dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
    clk_enable(i2c->clk);
 
    /* map the registers */
    //获取IIC适配器的内存资源信息
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
   //申请内存资源
    i2c->ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
   //虚拟地址映射
    i2c->regs = ioremap(res->start, resource_size(res));
 
    /* setup info block for the i2c core */
 
    i2c->adap.algo_data = i2c;
    i2c->adap.dev.parent = &pdev->dev;
 
    /* initialise the i2c controller */
    //初始化IIC总线适配器,主要针对相关寄存器进行操作
    ret = s3c24xx_i2c_init(i2c);
 
    /* find the IRQ for this unit (note, this relies on the init call to
     * ensure no current IRQs pending
     */
    //获取IIC总线适配器的中断资源信息
    i2c->irq = ret = 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);
 
    /* Note, previous versions of the driver used i2c_add_adapter()
     * to add the bus at any number. We now pass the bus number via
     * the platform data, so if unset it will now default to always
     * being bus 0.
     */
 
    i2c->adap.nr = pdata->bus_num;
   
   //向内核注册一个i2c adapter
    ret = i2c_add_numbered_adapter(&i2c->adap);
 
    ... ...
    return 0;
 
 ... ...
}
static int s3c24xx_i2c_probe(struct platform_device *pdev)

 

3.3 IIC从设备驱动

在我们实际开发中,I2C 总线驱动一般芯片原厂会提供,我们开发一般是设计设备驱动。

IIC从设备驱动是对IIC硬件体系结构中设备端的实现,与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同IIC总线适配器的差异,不考虑其实现细节地与硬件设备通讯。这部分代码一般由驱动工程师完成。

IIC从设备驱动挂载在IIC总线下,IIC总线管理着IIC从设备的设备信息(i2c_client)与设备驱动(i2c_driver)。因此,IIC从设备驱动的编写分为两个部分:注册IIC从设备信息、编写IIC从设备驱动程序。

Linux内核提供了struct i2c_board_info与i2c_register_board_info()函数,方便驱动工程师构建IIC从设备的设备信息 。

(1)struct i2c_board_info

 Linux内核封装了一个i2c_board_info结构体变量,描述一个IIC从设备的基本信息。内核使用i2c_board_info来构建i2c_client。

struct i2c_board_info 
{
    char            type[I2C_NAME_SIZE];//设备名称,对应i2c_client.name
    unsigned short  flags;              //标志位,对应i2c_client.flags
    unsigned short  addr;               //从设备地址,对应i2c_client.addr
    void           *platform_data;      //平台数据,对应i2c_client.dev.platform_data
    struct dev_archdata  *archdata;     //对应i2c_client.dev.archdata
#ifdef CONFIG_OF
    struct device_node   *of_node;
#endif
    int   irq;//中断号,对应i2c_client.irq
};

(2)i2c_register_board_info()函数

 Linux内核维护了一个__i2c_board_list链表,用以管理内核中的所有IIC从设备信息。i2c_register_board_info()函数的工作是向__i2c_board_list链表添加一条IIC从设备信息。

/*
*  参数:  int busnum:IIC总线编号,表示IIC从设备所挂载的IIC适配器编号
*       struct i2c_board_info const *info:IIC从设备信息
         unsigned len:i2c_board_info数组的大小,即IIC从设备的个数
*/
int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
    int status;
     ... ... 
     for (status = 0; len; len--, info++) 
   {
        struct i2c_devinfo    *devinfo;
 
        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        ... ...
        devinfo->busnum = busnum;
        devinfo->board_info = *info;
        list_add_tail(&devinfo->list, &__i2c_board_list);//向链表添加一条IIC从设备信息
     }
     ... ...
     return status;
}

 在i2c_adapter注册的时候,会利用__i2c_board_list链表中的IIC从设备信息(i2c_board_info)来构建i2c_client。具体调用关系如下:

static int s3c24xx_i2c_probe()【IIC总线驱动的probe函数】

  --i2c_add_numbered_adapter()【注册i2c_adapter】

    --i2c_register_adapter()【实质注册i2c_adapter】

      --i2c_scan_static_board_info()【扫描__i2c_board_list链表】

        --i2c_new_device()【构建i2c_client,并向内核添加IIC从设备】

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client    *client;
    int            status;

   //申请client内存空间 
  client = kzalloc(sizeof *client, GFP_KERNEL);
    ... ...
  
  /*****************填充i2c_client*******************/
    client->adapter = adap;                         //与i2c_adapter关联
    client->dev.platform_data = info->platform_data;//填充平台数据
    client->flags = info->flags;                    //填充标志位
    client->addr = info->addr;                      //填充从设备地址
    client->irq = info->irq;                        //填充中断号
    strlcpy(client->name, info->type, sizeof(client->name));//填充从设备名称

    ... ...
  /*****************初始化client->dev*****************/
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
  ... ...
    status = device_register(&client->dev);//创建IIC从设备
    
    return client;

  ... ...
}

IIC从设备的驱动程序的实例,详见驱动程序实例(六):mpu6050(IIC + cdev)

posted @ 2018-08-25 22:00  LinFeng-Learning  阅读(3335)  评论(0编辑  收藏  举报