一、概述
在设备驱动模型中,需要关心总线、设备和驱动这3个实体,
总线将设备和驱动绑定。
在系统每注册一个设备的时候,会寻找与之匹配的驱动;
同样,系统每注册一个驱动的时候,会寻找匹配的设备,而匹配由总线完成。
注册设备与驱动不分先后顺序。
对于没有总线得设备,需要一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。android把内部i2c、LCD等归纳到platform_device,便于统一管理。
二、驱动学习流程
以i2c为例 ,一般而言所说得驱动包含两部分
1、bus_driver用于将设备挂载在i2总线上,代码在i2c\bus\i2c_qup.c中,使用得总线由主芯片支持决定。
2、设备本身而言的驱动。代码在driver\misc\eeprom\microp_nutorn.c中。由外设或者从设备的芯片支持决定。
针对驱动得开发过程,针对性得学习时大致有以下流程
1、定义 、注册platform_device 包含它的子类实例
2、定义、注册 platform_driver 包含它的子类实例
注意点:device是一个类的概念,这一类得device共用一条总线。每个子类是挂载在这条总线下得不同得从设备。
同样的,driver包含总线得driver以及针对每个从设备不同的driver
2.1定义注册platform_device
1、定义platform_device 资源信息
代码在 kernel/arch/arm/mach-msm/device-8064.c
定义系统需求得resource
它描述了一个I2C总线设备的资源,设备驱动会根据flags获取相应的资源信息
A当为 IORESOURCE_MEM 表示描述的是内存类型的资源信息,此时 start end 表示起始内存得地址,同类设备可占据两个内存区域,这个一般是固定设计好得值。移植过程中尽量不变。
B当为IORESOURCE_IRQ表示描述了这个 I2C 设备的中断资源,此时 start end表示设备只用得中断号起始值。
C当为IORESOURCE_IO 表示 描述的是GPIO资源信息。此时 start end 表示 设备只用得GPIO值。可在GPIO table中对比。
static struct resource resources_qup_i2c_gsbi3[] = {
{
.name = "gsbi_qup_i2c_addr",
.start = MSM_GSBI2_PHYS,
.end = MSM_GSBI2_PHYS + 4 - 1,
.flags = IORESOURCE_MEM,
},
{
.name = "i2c_clk",
.start = 25,
.end = 25,
.flags = IORESOURCE_IO,
},
};
有resource 信息,就可以定义 platform_device
struct platform_device apq8064_device_qup_i2c_gsbi3= {
.name = "qup_i2c", //与driver注册是名称匹配
.id = MSM_GSBI3_QUP_I2C_BUS_ID,
.num_resources = ARRAY_SIZE(gsbi3_qup_i2c_resources),
.resource = gsbi3_qup_i2c_resources,
};
2、注册
前面前定义好了 platform_device 结构体后 在板级支持文件 board-xxx.c文件中进行注册 代码在:
kernel/arch/arm/mach-msm/boad-8064.c
static struct platform_device *common_not_mpq_devices[] __initdata =
{
&apq8064_device_qup_i2c_gsbi3,
};
//在初始化时会向系统中添加该类设备
static void __init apq8064_common_init(void)
{
platform_add_devices(common_not_mpq_devices );
}
3、设备client device
前面仅仅定义并注册了设备的大类,也就是总线级别得device,
现在可以针对每个从设备添加对应的client的地址等信息
代码在: kernel/arch/arm/mach-msm/boad-8064.c
定义每个slaver得地址等信息
static struct i2c_board_info __initdata enterprise_nuvoton_microp[] = {
I2C_BOARD_INFO("microp", 0x15), //microp名称要与具体driver中相同,
.irq=MSM_GPIO_TO_INT(7),
.platform_data = &nuvoton_microp_pdata,
};
4、注册
最终在 初始化函数中被加载到board中得of_node中去。
static void __init register_i2c_devices(void)
{
i2c_register_board_info();
}
2.2定义注册platform_driver
1、总线驱动程序
需要实现结构体platform_driver中得接口
I2C总线我们使用得高通平台,所以是QUP,代码在
i2c/bus/i2c_qup.c
static struct platform_driver qup_i2c_driver = {
.probe = qup_i2c_probe,
.remove = __devexit_p(qup_i2c_remove),
.driver = {
.name = "qup_i2c", // 与platform_device 中name要一样
.owner = THIS_MODULE,
.pm = &i2c_qup_dev_pm_ops,
//.of_match_table = i2c_qup_dt_match,
},
};
如果使用device tree需要额外定义
static struct of_device_id i2c_qup_dt_match[] = {
{
.compatible = "qcom,i2c-qup",
},
{}
};
//其中 "qcom,i2c-qup" 与DTS中总线得属性得要一样
在初始时中初始化函数qup_i2c_init_driver被调用,
static int __init qup_i2c_init_driver(void)
{
return platform_driver_register(&qup_i2c_driver);
}
platform_driver_register是注册驱动的函数在其中会扫描board上或者设备树中得总线信息并进行匹配,如果匹配成功 i2c_qup_probe()函数回被调用.
在使用platform_add_devices 添加来几个相同qup类得总线device,此时 i2c_qup_probe()函数就被调用几次
probe函数 是每个driver最重要配置函数。基础操作都在这里面。
在定义得资源会在这里具体得实现。详细可参考代码
2、设备驱动
第1步把bus驱动做好来,现在是设备驱动,针对每个client从设备得驱动,代码:
driver/misc/eeprom/microp_nuvoton.c
添加具体驱动信息。
static struct i2c_driver microp_nuvoton_driver = {
.driver = {
.name = TEGRA_MICROP_NAME,
.owner = THIS_MODULE,
},
.probe = microP_probe,
.remove = microP_remove,
.suspend= microp_suspend,
.resume= microp_resume,
.id_table = microP_id,
};
在初始化时会被添加到驱动list中。所有的驱动有一个总得list,负责对其进行resume和suepend进行相关得操作
static int __init microP_init(void)
{
return i2c_add_driver(µp_nuvoton_driver);
}
添加驱动时,name与 "microp"匹配。microP_probe会被调用。
在这里会针对中得资源进行申请。同时做一些针对本驱动得定时器、或者延时的work、使能关闭中断等操作。
三、注意事项
个人认为在参考移植得代码时除去probe中得相关初始化操作外着重主要以下几点
1、resume、suspend函数:对电源管理影响较大
2、定时器或者延时work等:确定延时运行得work,定时运行得work
3、中断处理函数:确认定义的中断触发条件,输入类驱动重点。
4、数据接受和发送得接口函数:大数据收发类驱动得重点。一般有指令模式和数据模式
5、其他一些类似使能、关闭某些功能得标志位得操作函数:影响自身或着其他驱动上层