Linux下矩阵键盘驱动分析与移植

= 1347

本文出自:【InTheWorld的博客】

首先要介绍一下Linux中input子系统的模型,一图胜千言,所以直接上图。

input_subsys

上图一目了然,我们的键盘驱动就是工作在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 <linux/platform_device.h>
#include <mach/irqs.h>
#include <mach/map.h>
#include <plat/cpu.h>
#include <plat/devs.h>
#include <plat/keypad.h>

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

发表评论