基于MTK6577+Android学习GPIO驱动架构
先来看设备上gpio的一些目录视图:
/sys/class/misc/mtgpio
图1
/sys/devices/platform/mt-gpio
图2
/sys/dev/char/10:47
图3
/sys 下的子目录 |
所包含的内容 |
/sys/devices |
这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构,下文会对它的内部结构作进一步分析; |
/sys/dev |
这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核 2.6.26 首次引入; |
/sys/bus |
这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分; |
/sys/class |
这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分; |
1. GPIO驱动的源代码及编译相关
Gpio驱动主要源代码:\mediatek\platform\mt6577\kernel\core\gpio.c
Gpio驱动makefile: \mediatek\platform\mt6577\kernel\core\makefile
obj-y += gpio.o
y表示编译近kenel,只能看到gpio.o,不会生成ko文件,但如果是m,表示编译成module,会生成ko文件。
Mtk平台生成.o文件路径:\kernel\out\mediatek
Mtk平台生成.ko文件路径:
\out\target\product\hsimobile77_ics2\system\lib\modules
2. Gpio驱动和设备的注册
2.1 platform总线、设备与驱动
在Linux 2.6的设备驱动模型中,关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2 C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总 线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。
注意,所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,例如,在S3C6410处理器中,把内部集成的I2 C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备
设备与驱动的两种绑定方式:在设备注册时进行绑定及在驱动注册时进行绑定。 以一个USB设备为例,有两种情形:
(1)先插上USB设备并挂到总线中,然后在安装USB驱动程序过程中从总线上遍历各个设备,看驱动程序是否与其相匹配,如果匹配就将两者邦定。这就是platform_driver_register()
(2)先安装USB驱动程序,然后当有USB设备插入时,那么就遍历总线上的各个驱动,看两者是否匹配,如果匹配就将其绑定。这就是platform_device_register()函数
2.2 GPIO设备和驱动的注册顺序
图4
为什么是先注册GPIO驱动,然后才是注册GPIO设备呢?先来看mt6577_board.c文件的相关内容,位于:\mediatek\platform\mt6577\kernel\core
static __init intboard_init(void) { … mt6577_board_init(); … return 0; } late_initcall(board_init);
再来看late_initcall和module_init的定义,位于\kernel\include\linux\init.h下
/* * A "pure" initcall has nodependencies on anything else, and purely * initializes variables that couldn't be staticallyinitialized. * * This only exists for built-in code, not formodules. */ #definepure_initcall(fn) __define_initcall("0",fn,0) #definecore_initcall(fn) __define_initcall("1",fn,1) #definecore_initcall_sync(fn) __define_initcall("1s",fn,1s) #definepostcore_initcall(fn) __define_initcall("2",fn,2) #definepostcore_initcall_sync(fn) __define_initcall("2s",fn,2s) #definearch_initcall(fn) __define_initcall("3",fn,3) #definearch_initcall_sync(fn) __define_initcall("3s",fn,3s) #define subsys_initcall(fn) __define_initcall("4",fn,4) #definesubsys_initcall_sync(fn) __define_initcall("4s",fn,4s) #definefs_initcall(fn) __define_initcall("5",fn,5) #definefs_initcall_sync(fn) __define_initcall("5s",fn,5s) #definerootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) #definedevice_initcall(fn) __define_initcall("6",fn,6) #definedevice_initcall_sync(fn) __define_initcall("6s",fn,6s) #definelate_initcall(fn) __define_initcall("7",fn,7) #definelate_initcall_sync(fn) __define_initcall("7s",fn,7s) #define__initcall(fn) device_initcall(fn) ……… /** * module_init() - driver initialization entrypoint * @x: function to be run at kernel boot timeor module insertion * * module_init() will either be called duringdo_initcalls() (if * builtin) or at module insertion time (if amodule). There can only * be one per module. */ #definemodule_init(x) __initcall(x);
所有的__init函数在区段.init.text区段中,同时还在.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等)。
这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关中所述的这些函数本身在.init.text区段中的顺序无关。在2.6内核中,initcall.init区段又分成7个子区段不同的区段,调用的顺序不一样,数字越小的优先级越高。
也就是说 late_initcall 还要在 module_init的后面。所以后面的介绍就按照调用的先后顺序来阐述。
2.3 GPIO驱动的注册
先来看相关的源代码
static structplatform_driver gpio_driver = { .probe = mt_gpio_probe, .remove = mt_gpio_remove, .shutdown = mt_gpio_shutdown, .suspend = mt_gpio_suspend, .resume = mt_gpio_resume, .driver = { .name = GPIO_DEVICE, }, }; /*---------------------------------------------------------------------------*/ static int __initmt_gpio_init(void) { int ret = 0; GPIOLOG("version: %s\n",VERSION); ret = platform_driver_register(&gpio_driver); return ret; } /*---------------------------------------------------------------------------*/ static void __exitmt_gpio_exit(void) { platform_driver_unregister(&gpio_driver); return; } /*---------------------------------------------------------------------------*/ module_init(mt_gpio_init); module_exit(mt_gpio_exit);
在linux内核中,所有标识为__init的函数(如mt_gpio_init)在连接(连接器)时都放在.init.text这个区段内,此外,所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后释放init区段(包括.init.text和.initcall.init)
(1) module_init和module_exit的含义
module_init和module_exit是内核两个特殊宏,用于告诉内核在加载和卸载模块时调用对应的函数。module_init返回整型值,若初始化成功,应返回0,如初始化失败时,应返回错误编码。module_exit不返回任何值。
module_init的定义和解析:
#definemodule_init(x) __initcall(x); #define__initcall(fn) device_initcall(fn) #definedevice_initcall(fn) __define_initcall("6",fn,6) #define__define_initcall(level,fn,id) \ static initcall_t__initcall_##fn##id __used \ __attribute__((__section__(".initcall"level ".init"))) = fn
initcall_t是一个函数指针类型,定义为typedef int (*initcall_t)(void);
static initcall_t__initcall_##fn##id的意思就是定义函数指针
static int* __initcall_fnid(void)(##表示替换连接),并将fn赋值给它。
而函数属性__attribute__((__section__()))表示将函数指针放到section所表示的代码段中。也就是是将定义的函数指针放到.initcalllevel.init段中。
下面就以module_init(mt_gpio_init)为例来说明,如下:
__define_initcall(“6”,mt_gpio_init,6)
这个宏定义的含义是:
1) 声明一个函数指针__initcal_ mt_gpio_init6 = mt_gpio_init;
2) 这个指针变量__initcal_ mt_gpio_init6需要放到名称为.initcall6.init段中,其实际就是将这个函数mt_gpio_init的首地址放置到这个section中。
module_exit宏定义如下:
#definemodule_exit(x) __exitcall(x); #define__exitcall(fn) \ static exitcall_t __exitcall_##fn__exit_call = fn #define__exit_call __used__section(.exitcall.exit) #ifndef __KERNEL__ #ifndef __section # define__section(S) __attribute__ ((__section__(#S))) #endif
(2) 注册GPIO驱动
调用platform_driver_register(&gpio_driver)来向内核注册一个platforem设备的驱动gpio_driver,先来看gpio_driver的值:
static structplatform_driver gpio_driver = { .probe = mt_gpio_probe, .remove = mt_gpio_remove, .shutdown = mt_gpio_shutdown, .suspend = mt_gpio_suspend, .resume = mt_gpio_resume, .driver = { .name = GPIO_DEVICE, }, }; #defineGPIO_DEVICE "mt-gpio"
需要注意的是:platform_driver 和 platform_device 中的 name 变量的值必须是相同的。
1) platform_driver结构体
在kernel/include/linux/platform_device.h中定义:
structplatform_driver { int (*probe)(structplatform_device *); int (*remove)(structplatform_device *); void (*shutdown)(structplatform_device *); int (*suspend)(structplatform_device *, pm_message_t state); int (*resume)(structplatform_device *); struct device_driver driver; const struct platform_device_id*id_table; };
2) platform_driver_register函数
当注册成功时会调用platform_driver结构元素probe函数指针,这里是mt_gpio_probe。platform driver中的函数都是以platform device作为参数进入。
2.4 GPIO设备的注册
\mediatek\platform\mt6577\kernel\core\mt6577_devs.c下的mt6577_board_init()调用platform_device_register(&gpio_dev)来注册设备。
(1) gpio_dev结构体
struct platform_devicegpio_dev = { .name = "mt-gpio", .id = -1, }; platform_device结构体在\kernel\include\linux\ platform_device.h定义 structplatform_device { const char * name; /* 设备名 */ int id; struct device dev; u32 num_resources; /* 设备所使用各类资源数量 */ struct resource * resource; /* 资源 */ const struct platform_device_id *id_entry; /* MFD cell pointer */ struct mfd_cell *mfd_cell; /* arch specific additions */ struct pdev_archdata archdata; };
(2) platform_device_register
常用的platform_device_register,内部调用了platform_device_add,将设备挂在了platform总线上
参考链接
module_init的加载和释放
http://blog.csdn.net/dysh1985/article/details/7597105
linux内核__define_initcall分析
http://blog.csdn.net/skyflying2012/article/details/8764486