Linux 内核--总线设备驱动模型(字符/块/网络设备 && platform设备)

时间:2021-05-18 17:53:52

一、概   述

       总线设备驱动模型主要包含总线、设备、驱动三个部分。

       现实总线:一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言(例如USB、I2C等典型的设备),这自然不是问题。

       虚拟总线(platform总线):但是在嵌入式系统里面,对于一些设备(内部的设备)可能没有现成的总线,如SoC 系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。

        注意1:platform总线设备驱动模型与之前的三类驱动(字符、块设备、网络设备)没有必然的联系。设备只是搭载到了platform总线上,仅此而已。platform总线相比与常规的总线模型其优势主要是platform总线是由内核实现的,而不用自己定义总线类型,总线设备来加载总线。platform总线是内核已经实现好的。

         注意2:所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,例如,在 S3C6410处理器中,把内部集成的I2C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。


二、实例分析(TP:platform 总线设备驱动  +++  I2C总线设备驱动)

////////////////////////////////////////////////////////////////////////////////////////////
                        /*linux 驱动模型----基础知识*/
////////////////////////////////////////////////////////////////////////////////////////////
///在内核中是怎么按照driver mode(驱动模型)来实现整个系统的设备和驱动注册的???
1. 注册总线bus类型:
       在系统初始化阶段,会首先向内核注册各种常用的总线类型,比如pci, usb, spi, i2c, platform等等,当然你也可以自己发明一种总线类型注册上去。这部分代码一般放在./arch/arm/mach-xxx/board-xxx.c中。
       有两个重要的链表挂在bus上,一个是设备device链表,一个是驱动driver链表。
       它包含的最关键的函数:match()


2. 注册设备:在此之后,会将系统的设备列表,基本上整个系统的device都在这里了,一一地注册进内核,就是调用xxx_device_regisger注册的过程。
           (xxx_device_regisger:将自己加到设备device链表,然后使用总线bus匹配对应的driver )


3. 注册驱动:然后是对于各个device设备driver的注册:xxx_drvier_register()。
            (xxx_drvier_register:将自己加到设备device链表,然后使用总线bus匹配对应的driver )


4. 设备和驱动的匹配:
       大部分device和driver的匹配方式就是看名字是否相同,这部分属于总线分内的事情。match的工作是由总线(bus)来完成,匹配工作发生在xxx_device_register()或xxx_drvier_register()
       设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;
        ===>>如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;
        ===>>如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致其probe暂不调用,而要等到设备注册成功并与自己匹配绑定后才会调用。
           (这一点很重要!!!!!)  (这一点很重要!!!!!)  (这一点很重要!!!!!)  (这一点很重要!!!!!)  (这一点很重要!!!!!) (这一点很重要!!!)


////////////////////////////////////////////////////////////////////////////////////////////
                        /*TouchPanel 驱动启动顺序*/
////////////////////////////////////////////////////////////////////////////////////////////
!!注意:即使module_init()或者是late_initcall()等模块进行了初始化,并不意味着probe一定会立刻被调用,只有等到匹配成功(设备已注册)才会被调用!!!

////一、系统初始化阶段:注册总线bus类型,如i2c, platform,pci, usb, spi总线等等.


////二、module_init();---内核模块先启动(同类型模块如module_init的初始化顺序取决于makefile中的编译顺序)
1. mtk_tpd.c  (platform总线   设备与驱动)
module_init(tpd_device_init);
static int __init tpd_device_init(void) {
    printk("MediaTek touch panel driver init\n");
    if (FACTORY_BOOT == get_boot_mode()|| RECOVERY_BOOT == get_boot_mode())
        {
            if(!tpv)return 0;
        }
    platform_driver_register(&tpd_driver);
        
}

1)==>>platform_driver_register(&tpd_driver);//注册 platform 驱动"mtk-tpd"
static struct platform_driver tpd_driver = {
    .remove     = tpd_remove,
    .shutdown   = NULL,
    .probe      = tpd_probe,///
    #ifndef CONFIG_HAS_EARLYSUSPEND
    .suspend    = NULL,
    .resume     = NULL,
    #endif
    .driver     = {
        .name = TPD_DEVICE,//#define TPD_DEVICE            "mtk-tpd"
    },
};

2)==>>若match匹配成功就调用platform_driver tpd_driver 的probe 函数:tpd_probe
    (前提是:device已经被注册好了,若device未注册好,就不会调用probe函数,而要等到后面的时候,通过设备注册成功(device注册),在调用probe函数)


3)==>>static int tpd_probe(struct platform_device *pdev) //注册的device,可以给driver传参数
{

//注册misc设备
 if (misc_register(&tpd_misc_device))
    {
    printk("mtk_tpd: tpd_misc_device register failed\n");
    }


/* allocate input device *///注册input设备
 if((tpd=(struct tpd_device*)kmalloc(sizeof(struct tpd_device), GFP_KERNEL))==NULL) return -ENOMEM;
    memset(tpd, 0, sizeof(struct tpd_device));
if((tpd->dev=input_allocate_device())==NULL) { kfree(tpd); return -ENOMEM; }


//设置input设备支持的类型
    set_bit(EV_ABS, tpd->dev->evbit);
    set_bit(EV_KEY, tpd->dev->evbit);
    set_bit(ABS_X, tpd->dev->absbit);
    set_bit(ABS_Y, tpd->dev->absbit);
    set_bit(ABS_PRESSURE, tpd->dev->absbit);
    set_bit(BTN_TOUCH, tpd->dev->keybit);
    set_bit(INPUT_PROP_DIRECT, tpd->dev->propbit);



///这里是在遍历mtk的tpd_driver_list里面的所有的tp ic驱动,并调用初始化函数tpd_local_init()
//每一个module touch IC 驱动(如ft5206)都会添加到这个静态数组里面
for(i = 1; i < TP_DRV_MAX_COUNT; i++)
    {
            /* add tpd driver into list */
        if(tpd_driver_list[i].tpd_device_name != NULL)
        {
            tpd_driver_list[i].tpd_local_init();//////
            //msleep(1);
            if(tpd_load_status ==1) {
                TPD_DMESG("[mtk-tpd]tpd_probe, tpd_driver_name=%s\n", tpd_driver_list[i].tpd_device_name);
                g_tpd_drv = &tpd_driver_list[i];
                break;
            }
        }    
  }




///初始化虚拟设备
 if(g_tpd_drv->tpd_have_button)
    {
        tpd_button_init();
    }

}


4)===>>static int tpd_local_init(void)@ft5206_driver.c
{

......................
////注册i2c驱动   ///注册成功后会调用tp ic 驱动的probe()函数,如ft5206的probe()
if(i2c_add_driver(&tpd_i2c_driver)!=0)
       {
          TPD_DMESG("ft5206 unable to add i2c driver.\n");
          return -1;
    }


///配置虚拟按键
#ifdef TPD_HAVE_BUTTON     
    tpd_button_setting(TPD_KEY_COUNT, tpd_keys_local, tpd_keys_dim_local);// initialize tpd button data
#endif   

......................

}



2. ft5206_driver.c  (I2C总线  设备与驱动)
module_init(tpd_driver_init);

1)===>>>static int __init tpd_driver_init(void) {

i2c_register_board_info(0, &ft5206_i2c_tpd, 1);//注册一个I2C Touch设备
/* static struct i2c_board_info __initdata ft5206_i2c_tpd={ I2C_BOARD_INFO("ft5206", (0x70>>1))};*/

tpd_driver_add(&tpd_device_driver);//添加一个驱动,其实就是上面说的静态数组tpd_driver_lis里面。

}
static struct tpd_driver_t tpd_device_driver = {
         .tpd_device_name = "FT5206",
         .tpd_local_init = tpd_local_init,
         .suspend = tpd_suspend,
         .resume = tpd_resume,
#ifdef TPD_HAVE_BUTTON
         .tpd_have_button = 1,
#else
         .tpd_have_button = 0,
#endif        
 };



2)===>>>若match匹配成功就调用struct i2c_driver tpd_i2c_driver 的probe 函数:tpd_probe:
static struct i2c_driver tpd_i2c_driver = {
  .driver = {
     .name = "ft5206",
  },
  .probe = tpd_probe,   ////////
  .remove = __devexit_p(tpd_remove),
  .id_table = ft5206_tpd_id,
  .detect = tpd_detect,

 };


static int __devinit tpd_probe(struct i2c_client *client, const struct i2c_device_id *id)
 {    
    if(!tpv)
    {
        printk("gtp: sancao\n");
        return;
    }
        
        ////上电、申请中断、设置GPIO等、Update FW、IC提供的控制 touch 的interface等。
        mt_set_gpio_mode(GPIO_CTP_RST_PIN, 0);
    mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT);
    mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ZERO);  
    msleep(1);


        ////tp 固件处理
#ifdef CONFIG_SUPPORT_FTS_CTP_UPG
        printk("[TSP] Step 0:init \n");
    msleep(100);
    fts_ctpm_fw_upgrade_with_i_file();
        printk("[TSP] Step 8:init stop\n");
#endif    

        ////tp 中断处理线程
    thread = kthread_run(touch_event_handler, 0, TPD_DEVICE);

}





////三、late_initcall(board_init);---内核模块后启动
1. late_initcall(board_init);@board.c
static __init int board_init(void)
{
  mt_board_init();

}


2. mt_board_init();@Mt_dev.c
__init int mt_board_init(void)
{

 .........

 resource_fb[0].end   = FB_START + FB_SIZE - 1;////==>>>最终调用@disp_get_lcm_name_boot@@disp_drv.c(读取lk中的变量,并相应的设置kernel中需要用到的变量)



 retval = HW_TP_Init();///==>>retval = platform_device_register(&mtk_tpd_dev);///注册platform设备"mtk-tpd"
                      /* static struct platform_device mtk_tpd_dev = {
                             .name = "mtk-tpd",
                             .id   = -1,};*/


  .........//注册其他设备
};

}