首先要介绍一下Linux中input子系统的模型,一图胜千言,所以直接上图。
上图一目了然,我们的键盘驱动就是工作在input子系统的最低层。单纯地从驱动角度讲,我们的工作就是最终调用input_event()这个函数,把扫描到的键值传递给input core层。然后的事情就不是我们考虑的了,这样一来感觉很轻松的样子。
好吧!正式开工了!首先说明一下硬件环境,我用的是友善之臂的tiny210开发板。出厂配置的内核是没有加入矩阵键盘的。不过驱动代码是有的,所以在menuconfig里面配置矩阵键盘。说实话,这工作真的很弱智的样子。为什么呢?因为210处理器上是有一个键盘控制器一样的东西。什么扫描,消抖都是硬件在干。唯一需要改动的地方就是键盘的键值表。默认的驱动里面只用44的键值数据,但是我要用的键盘要88才行。
然后我们分析一下驱动的结构,键盘在210上是作为一个平台设备存在的。那么我们就说说这个平台设备的驱动模型。平台设备就相当于一条总线上的设备,不过这条总线是抽象的。平台设备模型开发底层设备驱动的大致流程如下:
- platform_device定义:一般都在linux-3.0.8\arch\arm\plat-samsung和linux-3.0.8\arch\arm\plat-s5pv目录下面
samsung下面可能是比较通用的平台设备,而另一个就是比较专用的了。 - 注册平台设备platform_add_devices():这是由210的初始化函数统一注册起来的。
定义平台设备驱动platform_driver:这里的驱动都分布在driver的目录下,如samsung-keypad.c。 - 注册平台设备驱动platform_driver_register():这个函数是在驱动的模块加载函数中调用的。同理,platform_driver_unregister()是在驱动卸载函数中调用的。
- 匹配设备和驱动platform_match():这个函数是被platform_driver_register()调用的这个函数会检查所有的platform_device,如果找到了匹配的设备就调用platform_driver的探测函数probe()。
- platform_driver->probe():这个函数中完成了很多任务,资源申请,初始化驱动中的各种数据结构,算是比较重要的一环。完成这个函数的之后,驱动就基本可以正常工作了。
我们就顺着这个流程来读读代码,首先是platform_device的定义。来看看linxu/arch/arm/plat-samsung/里面有什么,发现了dev-keypad.c,一看就知道是我想要的。代码如下:
#include#include #include #include #include #include static struct resource samsung_keypad_resources[] = { [0] = { .start = SAMSUNG_PA_KEYPAD, .end = SAMSUNG_PA_KEYPAD + 0x20 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_KEYPAD, .end = IRQ_KEYPAD, .flags = IORESOURCE_IRQ, }, }; struct platform_device samsung_device_keypad = { .name = "samsung-keypad", .id = -1, .num_resources = ARRAY_SIZE(samsung_keypad_resources), .resource = samsung_keypad_resources, }; void __init samsung_keypad_set_platdata(struct samsung_keypad_platdata *pd) { /******/ }
这里面定义了矩阵键盘的结构体,还有就是矩阵键盘占用的io和中断资源。设备定义有了, 谁添加它呢?不急,看看linux/arch/arm/mach-s5pv210下的mach-mini210.c。这个文件可是210的命根子呀!截取一段代码来看看。
static void __init mini210_machine_init(void) { platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices)); }
这platform_add-devices()是找到了,可这mini210_devices和samsung_device_keypad又是什么关系呢?再贴代码,还是在mach-mini210.c中。
static struct platform_device *mini210_devices[] __initdata = { #ifdef CONFIG_KEYBOARD_SAMSUNG &samsung_device_keypad, #endif };
这下就清楚了,mini210_devices是一个指针数组,指向了各种platform_device,然后平 台设备就成功注册了。下一步,继续走到platform_driver。这个驱动很好找,就是linux/driver/input目录下面的samsung-keypad.c。又要贴代码了,我尽量少贴点。。
static bool samsung_keypad_report(struct samsung_keypad *keypad, unsigned int *row_state) { struct input_dev *input_dev = keypad->input_dev; unsigned int changed; unsigned int pressed; unsigned int key_down = 0; unsigned int val; unsigned int col, row; for (col = 0; col < keypad->cols; col++) { changed = row_state[col] ^ keypad->row_state[col]; key_down |= row_state[col]; if (!changed) continue; for (row = 0; row < keypad->rows; row++) { if (!(changed & (1 << row))) continue; pressed = row_state[col] & (1 << row); dev_dbg(&keypad->input_dev->dev, "key %s, row: %d, col: %d\n", pressed ? "pressed" : "released", row, col); val = MATRIX_SCAN_CODE(row, col, keypad->row_shift); if(keypad->keycodes[val] != KEY_RESERVED) { input_event(input_dev, EV_MSC, MSC_SCAN, val); input_report_key(input_dev, keypad->keycodes[val], pressed); //上面的三行代码就是我们驱动的终极目标,键值正式进入 } } input_sync(keypad->input_dev); } memcpy(keypad->row_state, row_state, sizeof(keypad->row_state)); return key_down; } static struct platform_driver samsung_keypad_driver = { .probe = samsung_keypad_probe, .remove = __devexit_p(samsung_keypad_remove), .driver = { .name = "s3c-keypad", .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &samsung_keypad_pm_ops, #endif }, .id_table = samsung_keypad_driver_ids, }; static int __init samsung_keypad_init(void) { return platform_driver_register(&samsung_keypad_driver); } module_init(samsung_keypad_init); static void __exit samsung_keypad_exit(void) { platform_driver_unregister(&samsung_keypad_driver); } module_exit(samsung_keypad_exit);
一切都是很有规律,这个驱动在加载的时候就调用了platform_driver_register()。然后驱 动就开始在设备列表中苦苦寻找他的她——platform_device。他们怎么能找到对方呢?因为驱动知道设备的名字,就存放在那个id_table里面。所以他们一定有情人终成眷属了,内核真是个好月老,不像真实世界。。。
驱动和设备结合之后,就会调用驱动里面的probe函数,这个函数真的是非常厉害,把很多事情自己一个人干了。然后呢?走走初始化什么的,驱动基本就可以工作了。遇到中断,扫描,汇报键值,生活就是这样过了下去,有点无聊,好像。。。
工作流程就是这样了,那么说好的移植呢?说好的键值表呢?嘿嘿,马上就来!我还是贴贴probe()的代码吧!他干了不少活呢!
static int __devinit samsung_keypad_probe(struct platform_device *pdev) { const struct samsung_keypad_platdata *pdata; const struct matrix_keymap_data *keymap_data; struct samsung_keypad *keypad; struct resource *res; struct input_dev *input_dev; unsigned int row_shift; unsigned int keymap_size; int error; /**略 **/ keymap_data = pdata->keymap_data; if (!keymap_data) { dev_err(&pdev->dev, "no keymap data defined\n"); return -EINVAL; } row_shift = get_count_order(pdata->cols); keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]); keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL); input_dev = input_allocate_device(); matrix_keypad_build_keymap(keymap_data, row_shift, input_dev->keycode, input_dev->keybit); //略 }
看出来了一切都在这个keymap_data中,那么他是哪儿来的呢?其实我们并不陌生,刚刚擦肩而过。回到mach-mini210.c,它还在那里。
static uint32_t mini210_keymap[] __initdata = { /* KEY(row, col, keycode) */ KEY(0, 3, KEY_1), KEY(0, 4, KEY_2), KEY(0, 5, KEY_3), KEY(0, 6, KEY_4), KEY(0, 7, KEY_5), KEY(1, 3, KEY_A), KEY(1, 4, KEY_B), KEY(1, 5, KEY_C), KEY(1, 6, KEY_D), KEY(1, 7, KEY_E), KEY(7, 1, KEY_LEFTBRACE) }; static struct matrix_keymap_data mini210_keymap_data __initdata = { .keymap = mini210_keymap, .keymap_size = ARRAY_SIZE(mini210_keymap), }; static struct samsung_keypad_platdata mini210_keypad_data __initdata = { .keymap_data = &mini210_keymap_data, .rows = 8, .cols = 8, };
就是它,嘿嘿!看来只要改掉这个mini210_keymap[]里面的东西,就大功告成了。这简直太easy了吧!打开linux-..*/include/linux/input.h。根据需要填表就好了。所以一切就这样结束了。(后边,我想再写写input子系统后面的东西,比如input core和evdev。)
后记:这篇文章其实是本人两年多前写的,这次把它从CSDN转过来的主要原因是,我准备深入学习Android系统层的技术。对于Android系统层,我其实是投入过一些时间的,但学习的目的性不强。到现在为止,我也算是搞了半年Android了,所以认真、系统地学习Android系统层也算是名正言顺的。虽然个人志在全栈,但别人肯定会把我当做搞Android的,重点研究一下肯定没有坏处。
ps:最后一段括号里的话,果然是世界最大谎言!:-p
发表评论