zoukankan      html  css  js  c++  java
  • 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
    ", __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
    ", 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)

  • 相关阅读:
    【全过程详解】如何恢复联想隐藏的内存(分区教程)
    【全过程详解】如何安装最纯净、稳定、无更新的win10系统(下载+U盘制作+安装+win10激活+驱动更新)
    第一次博客
    Spring boot 直接访问templates中html文件
    Spring boot + mybatis + orcale
    Spring boot+ maven + thymeleaf + HTML 实现简单的web项目
    HTML 中点击<a>标签,页面跳转执行过程
    orcale 使用创建日期排序然后分页每次取排序后的固定条数
    JSON语法规则
    Windows idea 搜狗输入法输入中文只显示英文
  • 原文地址:https://www.cnblogs.com/linfeng-learning/p/9523046.html
Copyright © 2011-2022 走看看