一、概 述
总线设备驱动模型主要包含总线、设备、驱动三个部分。
现实总线:一个现实的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,};*/
.........//注册其他设备
};
}