i2c总线,设备,驱动之间的关系

Posted on 2014-07-04 09:44  tongchuhuozhai  阅读(7668)  评论(4编辑  收藏  举报

------ 总线上先添加好所有具体驱动,i2c.c遍历i2c_boardinfo链表,依次建立i2c_client, 并对每一个i2c_client与所有这个线上的驱动匹配,匹配上,就调用这个驱动的i2c_xxx_probe  ------


  所有设备驱动在init函数里,一般只做注册平台驱动的动作,注意不是平台设备.以i2c.c为例,这个驱动的平台probe函数里做的事情比较多.因为i2c_boardinfo早已在具体驱动注册到链表,i2c.c的平台驱动就是要把每一个i2c_boardinfo,实例化一个设备出来,并把这个设备添加到总线上来.好了, i2c总线上有了一个设备,然后总线针对这个设备,会遍历总线上的所有驱动,这些驱动也是在具体驱动的平台probe里调用i2c_add_driver添加到总线上来的.所以在设备添加到总线上之前,i2c总线上的所有驱动都已经添加好了.注意这里所说的设备是i2c设备,即由i2c_boardinfo实例出一个i2c设备.添加设备,完整说法应是在总线上添加设备,一个实体只有依附于总线,被总线所管理,才能算是一个设备.所以一般根据设备所在的总线来命名设备,如i2c设备.在代码里由i2c_client结构描述.好了,简单小总结一下.

  按发生时间顺序说:

       第1步:  每个设备的驱动在init时, 先要本设备的i2c信息, 包括地址等, 添加到i2c全局链表中. 使用i2c总线通讯的每个设备的驱动, 都要在模块初始化的一开始, 就要做这件事. 所有设备的驱动初始化都走完之后, 再走i2c总线设备的初始化。所有驱动的init函数里把自己的i2c_boardinfo添加到i2c全局链表里,这个链表会在第4步,i2c.c的probe添加i2c设备时用到.以光感供应商光宝驱动代码为例,在6795平台,路径kernel-3.0/drivers/misc/mediatec/alsps/ltr559/ltr559.c, 

static int __init ltr559_init(void)
{
   struct alsps_hw *hw = ltr559_get_cust_alsps_hw();
    i2c_register_board_info(hw->i2c_num, &i2c_ltr559, 1);
    if(platform_driver_register(&ltr559_alsps_driver))
    {
        APS_ERR("failed to register driver");
        return -ENODEV;
    }
    return 0;
}

  其他加速度,地磁,陀螺仪init都清一色和上面一样的调用.  在自己去写这个init函数或看到init函数时,要想到mt_i2c_probe用这个东西做的事情,添加设备啦,匹配驱动啦.

   第2步: 所有具体驱动在i2c总线上都添加好,这个时间发生在各具体驱动的平台probe里调用i2c_add_driver. 还以上面为例:

static struct platform_driver ltr559_alsps_driver =
{
    .probe      = ltr559_probe,
    .remove     = ltr559_remove,    
    .driver     = 
    {
        .name = "als_ps",
        .owner  = THIS_MODULE,
           .of_match_table = alsps_of_match,
    }
};
static int ltr559_probe(struct platform_device *pdev) 
{
    struct alsps_hw *hw = ltr559_get_cust_alsps_hw();
    ltr559_power(hw, 1);
    if(i2c_add_driver(&ltr559_i2c_driver))
    {
        APS_ERR("add driver error\n");
        return -1;
    } 
    return 0;
}

  写这个函数时,要知道这是在mt_i2c_probe之前,只有这样才能被mt_i2c_probe添加设备时遍历到.写一处代码,能联想到其关联地方. 

 

  第3步: i2c.c的平台probe调用.虽然同是module_init, 但makefile可以按排调用顺序.i2c.c的probe一定是在第一步中所有具体驱动都添加完才调用.以确保i2c.c的probe能够把所有具体驱动都能遍历到.在注册adapter之前,i2c总线也算一个设备, 所以有设备的驱动模块,  以mt6795为例,kernel-3.0/drivers/misc/mediatek/i2c/i2c.c,平台probe函数为:

 

static struct platform_driver mt_i2c_driver = {
  .probe   = mt_i2c_probe,
};

static S32 mt_i2c_probe(struct platform_device *pdev)
{
.......... 其关键一句为如下
 ret = i2c_add_numbered_adapter(&i2c->adap);  //这就走进了标准i2c-core.c
...........
}

     第4步:  i2c.c的probe调用i2c-core.c标准核心的i2c_adapter_register,一条i2c总线只有一个适配器.这个probe运行一次,就初始化一条i2c总线.目前liux 都采用i2c_add_numbered_addapter方式,即静态注册方式,即把adapter注册到指定的i2c总线上。

  i2c_adapter_register建立这条i2c总线的sysfs设备模型,包括的属性文件有:
  name, new_device, delete_device.  还有一个包括这条i2c总线上的所有挂载设备的集合。 
  如8850项目的所有sensor都挂到第3路i2c总线上,即i2c-2. 在/sys/devices/platform/mt-i2c.2/i2c-2下就有以各i2c设备地址命名的目录名, 如bma056 这个设备的i2c模型在/sys/devices/platform/mt-i2c.2/i2c-2/2-0018. 2-0018, 就是这个目录, 2表示所在的i2c总线的id, 0018表示这个设备的地址。  看一条总线上所有设备的地址, 就看这里就行。  代码在:kernel-3.0/drivers/i2c/i2c-core.c

static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    int    id;

    mutex_lock(&core_lock);
    id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1,
               GFP_KERNEL);
    mutex_unlock(&core_lock);
    if (id < 0)
        return id == -ENOSPC ? -EBUSY : id;

    return i2c_register_adapter(adap);
}

static int i2c_register_adapter(struct i2c_adapter *adap)
{

    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
    res = device_register(&adap->dev); // adapter也作为一个设备注册,这里得到的sys路径为:

                         //  /sys/devices/platform/mt-i2c.2/i2c-2


if (adap->nr < __i2c_first_dynamic_bus_num){ printk("lct1: %s call i2c_scan_staic_board_info for i2c new device. \n", __func__); i2c_scan_static_board_info(adap); } } static void i2c_scan_static_board_info(struct i2c_adapter *adapter) { struct i2c_devinfo *devinfo; down_read(&__i2c_board_lock); list_for_each_entry(devinfo, &__i2c_board_list, list) { if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter, // 将i2c_boardinfo传入,构造一个i2c_client, 将此i2c设备与所有此i2c线上的驱动匹配 &devinfo->board_info)) dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr); } up_read(&__i2c_board_lock); } struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) {
  device_register(); // 这个注册函数调用的,正面不好追硕,看下函数调用栈


[ 4.082399].(5)[1:swapper/0][<ffffffc00057e068>] ltr559_i2c_probe+0x410/0x8ac
[ 4.083295].(5)[1:swapper/0][<ffffffc00074e8f0>] i2c_device_probe+0xac/0x108
[ 4.084201].(5)[1:swapper/0][<ffffffc000386538>] really_probe+0x80/0x2e8
[ 4.085040].(5)[1:swapper/0][<ffffffc00038682c>] __device_attach+0x5c/0x6c
[ 4.085900].(5)[1:swapper/0][<ffffffc000384f14>] bus_for_each_drv+0x50/0x94
[ 4.086770].(5)[1:swapper/0][<ffffffc000386414>] device_attach+0xa0/0xc4
[ 4.087607].(5)[1:swapper/0][<ffffffc000385190>] bus_probe_device+0x90/0xb8
[ 4.088476].(5)[1:swapper/0][<ffffffc00038318c>] device_add+0x4b0/0x564
[ 4.089301].(5)[1:swapper/0][<ffffffc000383258>] device_register+0x18/0x28
[ 4.090158].(5)[1:swapper/0][<ffffffc00074ebb8>] i2c_new_device+0x154/0x184
以上从
device_register到i2c_device_probe经历的调用,是一个标准过程,中间必有bus_for_each_drv, 可见不管什么设备注册,要要在此设备注册时,遍历该总线上的所有驱动,那些mipi总线,spi总线,uart总线,无一例外.

static int i2c_device_probe(struct device *dev)
{

........

status = driver->probe(client, i2c_match_id(driver->id_table, client)); // 调到具体驱动的i2c_xxx_probe

......

}

  由此可见,i2c.c的probe最终要把i2c全局链表里的i2c_boardinfo都建立一个i2c设备,即i2c_client,并且要很隆重的对每个设备,和在这个线上的所有具体驱动,一一匹配,只有一个才能匹配上,匹配上了,就调用这个具体驱动的probe对此设备做初始化了.

  这个就是具体驱动大名鼎鼎的i2c_xxx_probe的,是干活最多的.上电,对reset脚高低高置位,读芯片id,id读对了,就说明i2c通了,这是具体芯片驱动调试的第一步.做芯片驱动兼容的,也在这一步,这一步不对,就不用继续下面的了,这些下面的做有建立sys设备模型,初始化work,timer等,注册中断.设置misc的文件函数集,注册misc设备(这样可文件读写,ioctl就可以用了),再有就是注册输入子系统.yamaha的芯片自检节点就在输入子系统的class下. 好像就这些吧.写这个函数时,要意识到i2c.c在添加设备,给这设备做驱动匹配时,全靠设备的名字与驱动的名字是否相同来匹配. 

 

-----------------------------------    接口函数中的 i2c  地址不同 -----------------------------------

  一般i2c通讯的接口函数, 参数依次为, 从设备地址, 寄存器地址, 数据指针, 长度。 在feature phone上, 一次i2c通讯为8位数据, 开始部分的地址占8位,其中7位是地址, 最后一位是读/写标志位。 mtk feature phone的i2采用软件模拟方式, 按着原理写通讯函数, 先要把数据线电位拉低.   在智能机上, 采用linux内核, i2c的地址不包括读写标志位。 每个设备驱动都有自己实现的i2c通讯函数。mtk公开的i2c读写接口为:

在kernel-3.0/drivers/misc/mediatek/i2c/i2c.c中定义:

int mtk_i2c_master_recv(const struct i2c_client *client,
    char *buf, int count, u32 ext_flag,u32 timing)
{
    struct i2c_adapter *adap = client->adapter;
    struct mt_i2c_msg msg;
    int ret;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.timing = timing;
    msg.flags |= I2C_M_RD;
    msg.len = count;
    msg.buf = buf;
    msg.ext_flag = ext_flag;
    ret = mtk_i2c_transfer(adap, &msg, 1);  // transfer函数由mtk平台驱动工程师要实现这个函数  

    /*
     * If everything went ok (i.e. 1 msg received), return #bytes received,
     * else error code.
     */
    return (ret == 1) ? count : ret;
}
int mtk_i2c_master_send(const struct i2c_client *client,
    const char *buf, int count, u32 ext_flag,u32 timing)
{
    int ret;
    struct i2c_adapter *adap = client->adapter;
    struct mt_i2c_msg msg;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.len = count;
    msg.timing = timing;
    msg.buf = (char *)buf;
    msg.ext_flag = ext_flag;
    ret = mtk_i2c_transfer(adap, &msg, 1);

    /*
     * If everything went ok (i.e. 1 msg transmitted), return #bytes
     * transmitted, else error code.
     */
    return (ret == 1) ? count : ret;
}

  光感ltr559.c对此调用为:

static int ltr559_i2c_read_reg(u8 regnum)
{
  u8 buffer[1],reg_value[1];
    int res = 0;
    mutex_lock(&read_lock);
    
    buffer[0]= regnum;
    res = i2c_master_send(ltr559_obj->client, buffer, 0x1);//传入i2c_client, 发送的数据,发送的字节个数
    if(res <= 0)    {
        mutex_unlock(&read_lock);
      APS_ERR("read reg send res = %d\n",res);
        return res;
    }
    res = i2c_master_recv(ltr559_obj->client, reg_value, 0x1);
    if(res <= 0)
    {
        mutex_unlock(&read_lock);
        APS_ERR("read reg recv res = %d\n",res);
        return res;
    }
    mutex_unlock(&read_lock);
    return reg_value[0];
}

 

 

---------------------------  i2c 基本原理 及 i2c gpio 配置 -------------------------------------
  i2c 是多主机方式, 同一条总线上,可以有多个主线,硬件上, 每一条i2c线,有一个i2c适配器。 6592基带主芯片都有i2c的实现,如果需要使用,可以把相应的gpio口配置成i2c模式, 这样就是硬件i2c方式。 能接到i2c通讯的器件, 也都有i2c接口, 这在芯片的规格书上都有说明的,都会有支持i2c串行接口的说明,i2c地址也是固定的。 在规格书明确写出的。 有些有可选地址, 某个pin角被拉高, 是一个地址, 被拉低是另一个地址。有i2c接口的器件内部都有一个三极管开关电路, pin脚接三极管的集电极, 当基极通电流时, 开关被打开, 外接引脚被拉低。

  总线上的各种状态: 

总线空闲状态: 数据线和时钟线均拉高,
当数据线有下降沿时, 即是i2c通讯的启动信号
当数据线有上升沿时, 即是i2c通讯的结束信号
谁发起i2c通讯,就由谁结束i2c通讯。 所以i2c的启动信号与下降信号都由主机产生。
所有时钟线控制整个i2c总线的状态, 时钟线如果为高电平, 表示i2c可以是启动或结束, 如果为低电平, 表示不能启动或结束, 只能是传输数据状态。

  i2c 先配好i2c的数据线与时钟线. mtk的feature phone和智能机, 都用dws来配制gpio, i2c可用gpio来模拟,

  dws对i2c gpio 的配置, 数据线和时钟线都要配置成内部上拉, 方向配置成既可输入,又可输出。 还需要配名字, 驱动代码中有表示i2c pin角的名字变量,这样dws生成代码这个变量就会被赋值成这个pin角。 如果没配, 这个变量就取默认值0xff.  如果没配制 i2c gpio, 那么数据就会读异常,有时也能读出来,但异常, 如一个字节要用两个字节的数组存放。

   i2c要在dws配置gpio口, 如果gpio配制成i2c模式,就是硬件i2c, 如果没有配置成i2c模式,就是软件i2c, 即软件模拟i2c,就在用代码写i2c的时钟,而硬件i2c的时钟是由晶振的倍频产生的,硬件i2c比软件模拟i2c更稳定。

   判断i2c是硬件方式, 还是软件模拟方式, 就是看dws配置表上, gpio的模式。 无论是硬件还是软件, 都需要配置gpio配置表, 这样代表i2c的pin脚变量会得到赋值。 这个变量在i2c通讯函数会被用到。 mt6260 feature phone平台, 用的是软件模拟i2c. 供应商驱动有自己的i2c通讯函数的实现, 一般都能找到硬件i2c通讯的实现,和软件i2c通讯的实现。 有软件去设置时钟线,由于目前的mtk智能机平台, 都用硬件i2c方式,  所以简单看下mt6592 平台上的硬件i2c方式的实现:

   调试初期要做的是, 看i2c通没通, 一般先读一下寄存器的id,  i2c读函数返回小于0, 表示i2c通讯有错。

 

 ----------------  i2c总线作为一种平台设备被添加到平台总线  ---------------------

   主芯片内部有i2c的实现, 相应的gpio可以配置成i2c硬件方式,  这个适配器也在主芯片内部. i2c总线作为一种平台设备,在板级初始化时,被添加到平台总线上.

  mt6592有三条i2c总线(mt6795有四条),i2c总线是作为一种平台设备被注册的。 在板级初始化时,注册了三条i2c总线设备。在/sys/devices/platform/ 会有三个i2c目录,平台设备的类型为platform_device,有两个重要的成员,name, id. 如果平台设备名相同,可以用id区分,如三条i2c总线作为平台设备,名字相同,id不同。

   现在已经注册了三个i2c总线平台设备。有三个name相同, id不同的平台设备。那么在注册平台驱动时,相应的probe,就会被调用三次。 即mt6592平台的对i2c总线的驱动模块i2c.c 的平台驱动的probe就被调用三次。

代码位置:kernel-3.0/drivers/misc/mach/mt6795/mt_devs.c

static struct platform_device mt_device_i2c[] = {
    {
        .name           = "mt-i2c",
        .id             = 0,
        .num_resources  = ARRAY_SIZE(mt_resource_i2c0),
        .resource       = mt_resource_i2c0,
    },
    {
        .name           = "mt-i2c",
        .id             = 1,
        .num_resources  = ARRAY_SIZE(mt_resource_i2c1),
        .resource       = mt_resource_i2c1,
    },
    {
        .name           = "mt-i2c",
        .id             = 2,
        .num_resources  = ARRAY_SIZE(mt_resource_i2c2),
        .resource       = mt_resource_i2c2,
    },
    {
        .name           = "mt-i2c",
        .id             = 3,
        .num_resources  = ARRAY_SIZE(mt_resource_i2c3),
        .resource       = mt_resource_i2c3,
    },
};
__init int mt_board_init(void)
{
.......... // 平台设备很多,都在这里注册. 
    retval = platform_device_register(&gpio_dev);
    for (i = 0; i < ARRAY_SIZE(mt_device_i2c); i++){
        retval = platform_device_register(&mt_device_i2c[i]);
        printk("[%s]: mt_device_i2c[%d] finished probe, retval=%d\n", __func__, i, retval);
        if (retval != 0){
            return retval;
        }
    }
.............
}
late_initcall(mt_board_init);

  platform_device_register 会调用device_add,这样就会走一个流程,即总线上新加一个设备,总线就会把这个总线上的所有驱动与这个设备匹配,作为i2c总线这种平台设备,与其匹配的是i2c.c中的驱动相匹配,最终调用mt_i2c_probe这个平台函数.这个就是i2c总线的一部分,做出把所有设备添加,并把所有驱动与每个设备匹配,这种惊天动地的事,都是这个函数做出来的.在做这些事之前,mt_i2c_probe做如下事情:

    1. 申请内存资源,  中断资源, i2c总线作为一个设备, 是应该有对应的地址, 所以分配点内存资源给它.

    2. i2c中断响应函数做的事: 清除中断掩码,  保存中断状态, 清除中断状态.

    3. i2c有很多寄存器, 如控制寄存器, 从地址寄存器, 往这些寄存器写数据, 以完成相应的功能, 都需要i2c设备驱动来完成.  i2c硬件初始化就要对软件复位寄存器写1. 

  4. 设备i2c适配器的数据来源,  适配器是一个设备, 该设备的数据来源就是i2c总线.

   5. 添加i2c适配器,   这一步有两个事情, 一个是把adapter添加到基树中, 一个注册i2c适配器.   注册i2c适配器.  这个注册函数会把所有设备驱动初始化时所添加到i2c设备信息链表里的所有节点都遍历一遍,  对每个节点, 都调用i2c_new_device, 建立一个i2c设备, 随之建立一个i2c设备模型。每个挂到i2c总线上的设备都有自己的i2c设备模型。  这是注册i2c adapter非常重要的一步,  经过这一步, 挂在i2c总线上的设备才算是i2c设备, 即可以进行i2c通讯的设备。

static S32 mt_i2c_probe(struct platform_device *pdev)
{
 res   = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
    return -ENOMEM;
  }

  if (NULL == (i2c = kzalloc(sizeof(mt_i2c), GFP_KERNEL)))
    return -ENOMEM;
#ifdef CONFIG_OF
  i2c->adap.dev.of_node  = pdev->dev.of_node;
#endif
  i2c->adap.nr      = i2c->id;
  i2c->adap.owner     = THIS_MODULE;
  i2c->adap.algo      = &mt_i2c_algorithm;
  i2c->adap.algo_data   = NULL;
  i2c->adap.timeout   = 2 * HZ; /*2s*/
  i2c->adap.retries   = 1; /*DO NOT TRY*/
  /*need GFP_DMA32 flag to confirm DMA alloc PA is 32bit range*/
  i2c->dma_buf.vaddr =
        dma_alloc_coherent(&pdev->dev, MAX_DMA_TRANS_NUM,
          &i2c->dma_buf.paddr, GFP_KERNEL|GFP_DMA32);
    memset(i2c->dma_buf.vaddr, 0, MAX_DMA_TRANS_NUM);
  snprintf(i2c->adap.name, sizeof(i2c->adap.name), I2C_DRV_NAME);
  init_waitqueue_head(&i2c->wait);

  ret = request_irq(irq, mt_i2c_irq, IRQF_TRIGGER_LOW, I2C_DRV_NAME, i2c);
  mt_i2c_init_hw(i2c);
  i2c_set_adapdata(&i2c->adap, i2c);
  printk("lct1: %s, register adapter. \n", __func__);
  ret = i2c_add_numbered_adapter(&i2c->adap);
}

 

 ------- 先把设备添加好,再添加驱动的例子  ------

  i2c总线这种都是在把所有驱动加载好,再添加设备的.但也有出现反过来的.比如下面的例子,就是把设备添加好,然后再添加驱动.如果一个设备不需要挂在实际真实的总线上,就要把它挂载到平台总线,平台总线没有实际的总线.

像红外控制,就是gpio口,发送编码后的pwm波.所以就直接挂到平台总线上.代码如下:

代码位置: kernel-3.0/drivers/misc/mediatek/consumerir/consumerir.c

static struct platform_driver mt_consumerir_driver =
{
    .driver = 
    {
        .name = "consumerir",
    },
    .probe = consumerir_probe,
};

static struct platform_device mt_consumerir_dev =
{
    .name = "consumerir",
    .id = -1,
};

static int __init consumerir_init(void)
{
    int ret = 0;
    if(platform_device_register(&mt_consumerir_dev) != 0)
    {
        return -ENODEV;
    }
    
    ret = platform_driver_register(&mt_consumerir_driver);
    if (ret) 
    {
        return -ENODEV;
    }
    return 0;
}

  由此明显看出,设备中的name与驱动中name必须一致.这样总线在为这个设备遍历各个驱动时,才能和这个驱动匹配上.如两个玉佩,到时见面,就互相认识了.之前在i2c总线,驱动,设备时说过,设备在所有驱动添加完再添加.device_regisgter会调用bus_for_each_drv来遍历所有驱动.但在driver_register也会调用bus_for_each_div遍历所有设备. 所以遍历是双向的.在这个例子,用了第二种.

---------------------------------  先在总线上添加设备,之后在总线上注册驱动  -------------------------

  先说平台设备的添加,与i2c设备的添加。平台设备是平台总线设备的简称,i2c设备是i2c总线设备的简称。这里的设备以其所在的总线命名。平台设备类型为platform_device, i2c设备类型为i2c_client, 两个类型都有device这个成员。

  这里所说的设备并不实际意义的设备,只是站在某一个角度对设备的描述。

  其实对实际的设备说,就是给它下寄存器,它就能工作了,放在总线上,就是为了利用总线,设备向总芯片发命令,设备输出数据传给主芯片,所以调试初期,先看总线通没通,一般是i2c总线。实际的设备也是挂在总线上,从代码上,要有一种统一的管理,这种管理架构就要与实际尽量吻合,所以软件架构中出现的平台设备,i2c设备都是以总线的角度对设备的描述。

  所谓描述就是建立一个模型吧,通过对这个模型的操作来达到对实际设备的操作, 如解决一个物理问题时,要有各种变量,模型反映出各变量之间的联系,利用模型中的信息求出未知变量。

  在驱动注册到总线之前,必须保证总线上的所有设备都添加完。 所以在总线上添加设备的动作都是在驱动注册之前完成。

   在板级初始化时,在平台总线上注册平台设备,即在平台总线上添加设备。 这个阶段会把所有设备都添加到平台总线上。 这个阶段完成后,在/sys/devices/platform/ 就会很多目录,每个目录描述一个平台设备。

  在具体模块驱动init时,先在i2c总线上添加一个设备,这个设备就是i2c设备,实现是通过一个函数向i2c信息链表中添加一个节点, 在稍后的i2c总线驱动模块的平台驱动的probe, 会调用i2c_register_adapter, 把i2c信息链表中的每个节点都建立对应的i2c设备,即i2c_client对象。

 

----------------------------   在总线上注册驱动时,遍历总线上的所有设备 ------------------------

  注册驱动的机制是,这个驱动会遍历平台总线上的所有设备,有些书本会写, 探测总线上的所有设备,给人感觉好像硬件探测一样,其实这里完全是软件上的一种遍历,就是驱动的名字如果与设备的名字如果相同,那么驱动就和该设备匹配上。 这时具体驱动模块的平台驱动probe被调用。

  这个平台驱动的probe一般只会在i2c总线上注册一个驱动(如果是camera,应该是在mipi总线上注册一个驱动),并没有做其他具体的事情。 具体事情由i2c驱动的probe完成,包括建立一个driver的设备模型, 对设备下寄存器,注册中断,注册输入子系统,打开sensor供电。

  举个例子,gsensor, 板级初始化时, 注册平台设备, 代码:
 static struct platform_device sensor_gsensor = {
     .name           = "gsensor",
     .id             = -1,
  };
注册平台设备
  platform_device_register(&sensor_gsensor);

  bosch的加速度驱动文件bma056.c中,init函数中, 在平台总线上注册一个平台驱动,
static struct platform_driver gsensor_platform_driver = {
.probe = platfrom_probe,
};
platform_driver_register(&gsensor_platform_driver);
  平台驱动的probe会在i2c总线上注册一个i2c驱动, 即
  i2c_add_driver(&i2c_driver);

  既然在平台总线上注册驱动之前,平台总线上就已经添加好了设备。 那么在i2c总线上注册驱动之前,i2c总线上是否也已经添加好的设备。
  这个问题的答案在, 在添加i2c_board_info时, 设备的名字, 与设备的地址传给i2c的一个链表,i2c初始化时,对链表中的每一个建立一个i2c设备。 这个设备会传给i2c驱动的probe。

  看用了哪个驱动,可以到设备模型/sys/bus/i2c/drivers下有没有这个驱动的名字。

  如果平台设备的名字与平台驱动的名字不一样,就驱动就不会probe.  之前在做bosch与st兼容时,就遇到这个问题。

 

------------------------------ i2c 设备参数 -----------------------

------------------
设备的参数数据, 以加速度驱动为例. 
通过i2c总线从设备读过来的数据都是原始数据, 这些数据在驱动要做一些转换,转换的参数如灵敏度,坐标系映射, 校准参数。 这些参数对这个设备是必须的,这些设备参数就要跟设备相关联,即拿到了设备,这个设备就带着这个设备的参数。有专门的函数设置设备参数,即dev_set_drvdata(). 对于i2c总线上的设备, 即注册到i2c总线上,这个设备就是一个i2c设备,即i2c_client. 有专门的函数设置设备的参数数据, 即i2c_set_clientdata(client, data). 这个函数封装了dev_set_drvdata(). 

每个sensor驱动都有设备参数结构体类型,因为是i2c设备,所以类型的名字都是...i2c_data;  在从i2c总线读取sensor数据时,一般都要i2c_get_clientdata(client)先获取设备参数。

 

  在哪创建设备节点,要看传给创建设备节点的kobject是谁,可以是driver, 也可以是device.  

6752的sensor驱动换了传入参数,而我还在原来的地方找设备节点,就当然找不到了。

 

--------------------------  i2c_probe 函数栈 --------------------

驱动中有空指针错误, 串口log就有oops。 如果在i2c_probe有空指针, 就有如下函数栈: 

[ 4.082399].(5)[1:swapper/0][<ffffffc00057e068>] ltr559_i2c_probe+0x410/0x8ac
[ 4.083295].(5)[1:swapper/0][<ffffffc00074e8f0>] i2c_device_probe+0xac/0x108
[ 4.084201].(5)[1:swapper/0][<ffffffc000386538>] really_probe+0x80/0x2e8
[ 4.085040].(5)[1:swapper/0][<ffffffc00038682c>] __device_attach+0x5c/0x6c
[ 4.085900].(5)[1:swapper/0][<ffffffc000384f14>] bus_for_each_drv+0x50/0x94
[ 4.086770].(5)[1:swapper/0][<ffffffc000386414>] device_attach+0xa0/0xc4
[ 4.087607].(5)[1:swapper/0][<ffffffc000385190>] bus_probe_device+0x90/0xb8
[ 4.088476].(5)[1:swapper/0][<ffffffc00038318c>] device_add+0x4b0/0x564
[ 4.089301].(5)[1:swapper/0][<ffffffc000383258>] device_register+0x18/0x28
[ 4.090158].(5)[1:swapper/0][<ffffffc00074ebb8>] i2c_new_device+0x154/0x184
[ 4.091027].(5)[1:swapper/0][<ffffffc00074ef78>] i2c_register_adapter+0x1fc/0x30c
[ 4.091961].(5)[1:swapper/0][<ffffffc00074f0e0>] __i2c_add_numbered_adapter+0x58/0x88
[ 4.092939].(5)[1:swapper/0][<ffffffc00074f264>] i2c_add_numbered_adapter+0x14/0x2c
[ 4.093920].(5)[1:swapper/0][<ffffffc000647304>] mt_i2c_probe+0x348/0x45c
[ 4.094771].(5)[1:swapper/0][<ffffffc000387b94>] platform_drv_probe+0x18/0x24
[ 4.095663].(5)[1:swapper/0][<ffffffc000386624>] really_probe+0x16c/0x2e8
[ 4.096512].(5)[1:swapper/0][<ffffffc0003868e4>] __driver_attach+0xa8/0xb0
[ 4.097370].(5)[1:swapper/0][<ffffffc000384a30>] bus_for_each_dev+0x54/0x98
[ 4.098240].(5)[1:swapper/0][<ffffffc000386454>] driver_attach+0x1c/0x28
[ 4.099078].(5)[1:swapper/0][<ffffffc00038547c>] bus_add_driver+0x1bc/0x258
[ 4.099948].(5)[1:swapper/0][<ffffffc0003870d0>] driver_register+0x68/0x15c
[ 4.100821].(5)[1:swapper/0][<ffffffc0003887b8>] platform_driver_register+0x58/0x64
[ 4.101782].(5)[1:swapper/0][<ffffffc000e11c84>] mt_i2c_init+0x6c/0x78
[ 4.102601].(5)[1:swapper/0][<ffffffc000de583c>] do_one_initcall+0xa0/0x1b4
[ 4.103473].(5)[1:swapper/0][<ffffffc000de5a8c>] kernel_init_freeable+0x13c/0x1e0
[ 4.104433].(5)[1:swapper/0][<ffffffc0009c19c4>] kernel_init+0x14/0x14c
[ 4.105257]-(5)[1:swapper/0]Internal error: Oops: 96000045 [#1] PREEMPT SMP

 

 

------------------  i2c_xxx_probe在i2c总线init调用的平台probe里被遍历 ------------------

mt的i2c总线驱动也做为一种普通的驱动, 放在/kernel-3.0/dirver/misc/medietek/i2c/mt6795下, 文件名为i2c.c
这个驱动的模块初始化也一般具体驱动的初始化, 都是调用平台注册函数, platform_register. 不同是, 一般具体驱动平台probe函数里, 只调用i2c_add_driver. 即设置好本驱
动的i2c的回调函数, 而i2c.c里的平台probe则会完成此i2c总线上各个驱动的i2c_xxX_probe的调用, 如光感的ltr559_i2c_probe, 地磁的yas_i2c_probe, 加速度的
bmi_acc_i2c_probe都会在第2条i2c总线的平台probe调用. 其调用栈为:
[ 4.082399].(5)[1:swapper/0][<ffffffc00057e068>] ltr559_i2c_probe+0x410/0x8ac
[ 4.083295].(5)[1:swapper/0][<ffffffc00074e8f0>] i2c_device_probe+0xac/0x108
[ 4.084201].(5)[1:swapper/0][<ffffffc000386538>] really_probe+0x80/0x2e8
[ 4.085040].(5)[1:swapper/0][<ffffffc00038682c>] __device_attach+0x5c/0x6c
[ 4.085900].(5)[1:swapper/0][<ffffffc000384f14>] bus_for_each_drv+0x50/0x94
[ 4.086770].(5)[1:swapper/0][<ffffffc000386414>] device_attach+0xa0/0xc4
[ 4.087607].(5)[1:swapper/0][<ffffffc000385190>] bus_probe_device+0x90/0xb8
[ 4.088476].(5)[1:swapper/0][<ffffffc00038318c>] device_add+0x4b0/0x564
[ 4.089301].(5)[1:swapper/0][<ffffffc000383258>] device_register+0x18/0x28
[ 4.090158].(5)[1:swapper/0][<ffffffc00074ebb8>] i2c_new_device+0x154/0x184
[ 4.091027].(5)[1:swapper/0][<ffffffc00074ef78>] i2c_register_adapter+0x1fc/0x30c
[ 4.091961].(5)[1:swapper/0][<ffffffc00074f0e0>] __i2c_add_numbered_adapter+0x58/0x88
[ 4.092939].(5)[1:swapper/0][<ffffffc00074f264>] i2c_add_numbered_adapter+0x14/0x2c
[ 4.093920].(5)[1:swapper/0][<ffffffc000647304>] mt_i2c_probe+0x348/0x45c
这个在ltr559_i2c_probe埋个空指针错误, 开机kernel_log就会有这个调用栈.
各驱动的probe调用顺序, 应该是同时的, 因为只有具体驱动的probe在调i2c_add_driver把本驱动的i2c_probe等回调设置好后, i2c.c的平台probe才能把他遍历到.

从上面的函数栈来看, i2c注册适配器时, 会把挂在这条i2c总线上的每个设备都建立一个i2c设备对象, 即i2c_new_device, 就是把这个设备变成一个i2c设备. 具体驱动的init时,
的i2c_register_board_info向i2c链表里添加的这个设备的i2c信息(包括i2c第几条线, i2c地址)就会传给这个i2c_new_device函数. 这个变化需要做两件事, 一是要准备一个i2c
client. 一个是要调用到这个设备的i2c_xxx_probe函数, 并且把准备好的i2c client传给这个函数. 从软件的角度, 完成这些才算是把这个设备纳入到这个总线的管理下. 所以
软件的初始化, 就好像建立一个模型, 里面有各种变量的初值, 以及回调函数的有效设置.

mtklod内含mobile log, modem log. mobilelog又包括kernel log, main log, 这些都有开关设置的, 有一个开关控制开机时自启的log, 这个log在分析重启时是必须的.