Chinaunix首页 | 论坛 | 博客
  • 博客访问: 64754
  • 博文数量: 25
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2013-11-20 12:20
个人简介

潜心学习

文章分类

全部博文(25)

分类: LINUX

2013-11-21 10:08:34

采用平台设备驱动机制来写,s5pc110自带按键控制器,且按键控制器内部带有硬件滤波电路.支持多个按键同时按下.
一:按键控制器原理分析
参考s5pc110开发手册KEYPAD INTERFACE一章的keypad scanning procedure小节
初始化状态下列线全为低电平,行线全为高电平,当按键按下,对应的行线和列线短接,行线变为低电平,读取行线寄存器便可确定是哪一行产生了中断,再循环的将列线输出低电平并去读取刚检测到行线,若行线为低电平,说明
对应的列线参生了中断,行列便可以确定是哪个按键按下.(为单个按键的扫描过程,多个在这不做讨论)
二:驱动分析

设备在哪里?--->平台文件:arch/arm/plat-s5p/devs.c
按键相关代码如下:

点击(此处)折叠或打开

  1. /* Keypad interface */
  2. static struct resource s3c_keypad_resource[] = {
  3.     [0] = {
  4.         .start = S3C_PA_KEYPAD,//0xE1600000
  5.         .end = S3C_PA_KEYPAD + S3C_SZ_KEYPAD - 1,//#define S5PV2XX_SZ_KEYPAD SZ_4K(#define SZ_4K 0x00001000)
  6.         .flags = IORESOURCE_MEM,//I/O内存资源
  7.     },
  8.     [1] = {
  9.         .start = IRQ_KEYPAD,//121
  10.         .end = IRQ_KEYPAD,
  11.         .flags = IORESOURCE_IRQ,//中断资源
  12.     }
  13. };

  14. struct platform_device s3c_device_keypad = {
  15.     .name = "s3c-keypad",
  16.     .id = -1,
  17.     .num_resources = ARRAY_SIZE(s3c_keypad_resource),
  18.     .resource = s3c_keypad_resource,
  19. };
资源在哪注册?--->板级文件arch/arm/mach-s5pv210/mach-smdkc110.c

点击(此处)折叠或打开

  1. static void __init smdkc110_machine_init(void)
  2. {
  3.     arm_pm_restart = smdkc110_pm_restart;

  4.     s3c_usb_set_serial();
  5.     platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));//此处将所有的设备注册到内核中
  6.     ....
  7. }
smdkc110_machine_init()函数中platform_add_devices()函数的smdkc110_devices参数便是平台设备结构体的一个数组---->arch/arm/mach-s5pv210/mach-smdkc110.c

点击(此处)折叠或打开

  1. static struct platform_device *smdkc110_devices[] __initdata = {
  2. ....
  3. &s3c_device_keypad,
  4. ......
  5. };
platform_add_devices函数的实现--->driver/input/keyboard/s3c-keypad.c

点击(此处)折叠或打开

  1. int platform_add_devices(struct platform_device **devs, int num)
  2. {
  3.     int i, ret = 0;

  4.     for (i = 0; i < num; i++) {
  5.         ret = platform_device_register(devs[i]);//注册平台设备
  6.         if (ret) {
  7.             while (--i >= 0)
  8.                 platform_device_unregister(devs[i]);
  9.             break;
  10.         }
  11.     }

  12.     return ret;
  13. }
按键驱动程序文件--->drivers/input/keyboard/s3c-keypad.c
驱动的注册:

点击(此处)折叠或打开

  1. static int __init s3c_keypad_init(void)
  2. {
  3.     int ret;

  4.     ret = platform_driver_register(&s3c_keypad_driver);//驱动的注册

  5.     if (!ret)
  6.         pr_info("S3C Keypad Driver\n");

  7.     return ret;
  8. }
s3c_keypad_driver注册到驱动链表,然后按照驱动的名字去匹配设备,匹配成功调用驱动结构体中的probe函数
s3c_keypad_driver结构体:---->driver/input/keyboard/s3c-keypad.c

点击(此处)折叠或打开

  1. static struct platform_driver s3c_keypad_driver = {
  2.     .probe        = s3c_keypad_probe,
  3.     .remove        = s3c_keypad_remove,
  4.     .suspend    = s3c_keypad_suspend,//挂起(节能相关)
  5.     .resume        = s3c_keypad_resume,//唤醒(节能相关)
  6.     .driver        = {
  7.         .owner    = THIS_MODULE,
  8.         .name    = "s3c-keypad",
  9.     },
  10. };
驱动的注册和匹配过程这里不作分析,自己可以去跟源代码.下面来分析probe函数--->driver/input/keyboard/s3c-keypad.c

点击(此处)折叠或打开

  1. static int __init s3c_keypad_probe(struct platform_device *pdev)
  2. {
  3.     struct resource *res, *keypad_mem, *keypad_irq;//资源结构体
  4.     struct input_dev *input_dev;//输入设备结构体
  5.     struct s3c_keypad *s3c_keypad;//按键结构体
  6.     int ret, size;
  7.     int key, code;

  8.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取平台的I/O内存资源(按键控制器的寄存器的地址)
  9.     if (res == NULL) {
  10.         dev_err(&pdev->dev, "no memory resource specified\n");
  11.         return -ENOENT;
  12.     }

  13.     size = (res->end - res->start) + 1;//计算寄存器资源的大小,从0开始计数所以需要加1

  14.     keypad_mem = request_mem_region(res->start, size, pdev->name);//申请I/O资源,避免冲突
  15.     if (keypad_mem == NULL) {
  16.         dev_err(&pdev->dev, "failed to get memory region\n");
  17.         ret = -ENOENT;
  18.         goto err_req;
  19.     }

  20.     key_base = ioremap(res->start, size);//映射I/O资源
  21.     if (key_base == NULL) {
  22.         pr_err("Failed to remap register block\n");
  23.         ret = -ENOMEM;
  24.         goto err_map;
  25.     }

  26.     keypad_clock = clk_get(&pdev->dev, "keypad");//获取时钟,硬件消抖需要时钟
  27.     if (IS_ERR(keypad_clock)) {
  28.         dev_err(&pdev->dev, "failed to find keypad clock source\n");
  29.         ret = PTR_ERR(keypad_clock);
  30.         goto err_clk;
  31.     }

  32.     clk_enable(keypad_clock);//使能时钟

  33.     s3c_keypad = kzalloc(sizeof(struct s3c_keypad), GFP_KERNEL);//分配按键结构体
  34.     input_dev = input_allocate_device();//分配输入设备结构体(输入子系统)

  35.     if (!s3c_keypad || !input_dev) {
  36.         ret = -ENOMEM;
  37.         goto err_alloc;
  38.     }

  39.     platform_set_drvdata(pdev, s3c_keypad);//填充平台数据
  40.     s3c_keypad->dev = input_dev;//填充按键结构体的dev成员

  41.     writel(KEYIFCON_INIT, key_base+S3C_KEYIFCON);//按键初始化
  42.     writel(KEYIFFC_DIV, key_base+S3C_KEYIFFC);//硬件消抖时钟配置

  43.     /* Set GPIO Port for keypad mode and pull-up disable*/

  44. #if defined(CONFIG_KEYPAD_S3C_MSM)//该宏没有定义
  45.     s3c_setup_keypad_cfg_gpio();
  46. #else
  47.     s3c_setup_keypad_cfg_gpio(KEYPAD_ROWS, KEYPAD_COLUMNS);//gpio管脚设置
  48. #endif
  49.     writel(KEYIFCOL_CLEAR, key_base+S3C_KEYIFCOL);//清除列线

  50.     /* create and register the input driver */
  51.     set_bit(EV_KEY, input_dev->evbit);//设置按键事件
  52.     s3c_keypad->nr_rows = KEYPAD_ROWS;//按键矩阵的行数
  53.     s3c_keypad->no_cols = KEYPAD_COLUMNS;//按键矩阵的列数
  54.     s3c_keypad->total_keys = MAX_KEYPAD_NR;//按键数

  55.     for (key = 0; key < s3c_keypad->total_keys; key++) {
  56.         code = s3c_keypad->keycodes[key] = keypad_keycode[key];//按键结构体的按键编码数组的赋值
  57.         if (code <= 0)
  58.             continue;
  59.         set_bit(code & KEY_MAX, input_dev->keybit);//set_bit和keybit在下文有分析
  60.     }

  61.     input_dev->name = DEVICE_NAME;
  62.     input_dev->phys = "s3c-keypad/input0";

  63.     input_dev->id.bustype = BUS_HOST;
  64.     input_dev->id.vendor = 0x0001;
  65.     input_dev->id.product = 0x0001;
  66.     input_dev->id.version = 0x0001;

  67.     input_dev->keycode = keypad_keycode;//以上都市填充input_dev结构体

  68.     /* Scan timer init */
  69.     init_timer(&keypad_timer);//初始化一个定时器
  70.     keypad_timer.function = keypad_timer_handler;//定时器函数
  71.     keypad_timer.data = (unsigned long)s3c_keypad;//定时器私有数据,定时器函数的参数

  72.     /* For IRQ_KEYPAD */
  73.     keypad_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);//获取中断资源
  74.     if (keypad_irq == NULL) {
  75.         dev_err(&pdev->dev, "no irq resource specified\n");
  76.         ret = -ENOENT;
  77.         goto err_irq;
  78.     }
  79.     ret = request_irq(keypad_irq->start, s3c_keypad_isr, 0,
  80.         DEVICE_NAME, (void *) pdev);//申请中断
  81.     if (ret) {
  82.         pr_err("request_irq failed (IRQ_KEYPAD)\n");
  83.         ret = -EIO;
  84.         goto err_irq;
  85.     }

  86.     ret = input_register_device(input_dev);//注册input设备
  87.     if (ret) {
  88.         pr_err("Unable to register s3c-keypad input device!!!\n");
  89.         goto out;
  90.     }

  91.     keypad_timer.expires = jiffies + (HZ/10);//定时器定时时间

  92.     if (is_timer_on == false) {//添加定时器
  93.         add_timer(&keypad_timer);
  94.         is_timer_on = true;
  95.     } else {
  96.         mod_timer(&keypad_timer, keypad_timer.expires);
  97.     }

  98.     pr_info(DEVICE_NAME " Initialized\n");
  99.     return 0;
  100. /*以下是错误处理*/
  101. out:
  102.     free_irq(keypad_irq->start, input_dev);
  103.     free_irq(keypad_irq->end, input_dev);

  104. err_irq:
  105.     input_free_device(input_dev);
  106.     kfree(s3c_keypad);

  107. err_alloc:
  108.     clk_disable(keypad_clock);
  109.     clk_put(keypad_clock);

  110. err_clk:
  111.     iounmap(key_base);

  112. err_map:
  113.     release_resource(keypad_mem);
  114.     kfree(keypad_mem);

  115. err_req:
  116.     return ret;
  117. }
中断处理函数分析--->
driver/input/keyboard/s3c-keypad.c

点击(此处)折叠或打开

  1. static irqreturn_t s3c_keypad_isr(int irq, void *dev_id)
  2. {
  3.     /* disable keypad interrupt and schedule for keypad timer handler */
  4.     writel(readl(key_base + S3C_KEYIFCON) & ~(INT_F_EN|INT_R_EN),
  5.             key_base + S3C_KEYIFCON);//禁止按键按下和松开的中断,按下期间不响应其他的按下松开中断

  6.     keypad_timer.expires = jiffies + (HZ/100);//更新定时器的定时时间
  7.     if (is_timer_on == false) {
  8.         add_timer(&keypad_timer);
  9.         is_timer_on = true;
  10.     } else {
  11.         mod_timer(&keypad_timer, keypad_timer.expires);//激活定时器,在probe函数的116行将is_timer_on赋值为true
  12.     }
  13.     /*Clear the keypad interrupt status*/
  14.     writel(KEYIFSTSCLR_CLEAR, key_base + S3C_KEYIFSTSCLR);//清按键松开的中断

  15.     return IRQ_HANDLED;
  16. }
总结:中断处理函数里主要做的事就是激活定时器,等定时时间到去执行定时函数
定时函数分析:--->drivers/input/keyboard/s3c-keypad.c

点击(此处)折叠或打开

  1. static void keypad_timer_handler(unsigned long data)
  2. {
  3.     u32 press_mask;
  4.     u32 release_mask;
  5.     u32 restart_timer = 0;
  6.     int i, col;
  7.     struct s3c_keypad *pdata = (struct s3c_keypad *)data;//取出私有数据,在probe函数的
  8.     struct input_dev *dev = pdata->dev;

  9.     keypad_scan();//扫描按键,下面分析
  10.     for (col = 0; col < KEYPAD_COLUMNS; col++) {//列线
  11.         press_mask = ((keymask[col] ^ prevmask[col]) & keymask[col]);//prevmask[]为按键按下的前一次的状态(代码中假设按下相应的位为1)^操作为不同为1,所以前面的括号的作用是判断是是否有按键按下或松开,前面一个括号的值再与上keymask的值便可以确定是否有按键按下
  12.         release_mask = ((keymask[col] ^ prevmask[col]) & prevmask[col]);//同理,确认是否有按键松开
  13.         i = col * KEYPAD_ROWS;//这里是获得按键码在按键码数组中的小标(每列占用KEYPAD_ROWS个按键)

  14.         while (press_mask) {//有按键按下
  15.             if (press_mask & 1) {//跟i++一起某一列中的哪一行
  16.                 input_report_key(dev, pdata->keycodes[i], 1);
  17.                 pr_debug("key Pressed : key %d map %d\n",
  18.                         i, pdata->keycodes[i]);
  19.                 printk("key pressed :key %d map %d\n",i,pdata->keycodes[i]);
  20.             }
  21.             press_mask >>= 1;
  22.             i++;//行++
  23.         }

  24.         i = col * KEYPAD_ROWS;

  25.         while (release_mask) {//有按键松开,同理按下的操作
  26.             if (release_mask & 1) {
  27.                 input_report_key(dev, pdata->keycodes[i], 0);
  28.                 pr_debug("key Released : %d map %d\n",
  29.                         i, pdata->keycodes[i]);
  30.                 printk("key pressed :key %d map %d\n",i,pdata->keycodes[i]);
  31.             }
  32.             release_mask >>= 1;
  33.             i++;
  34.         }
  35.         prevmask[col] = keymask[col];//保存上一次的按键状态

  36.         restart_timer |= keymask[col];//keymask不为0说明有按键按下或松开,restart_timer的作用就是标志是否有按键状态变化
  37.     }

  38.     if (restart_timer) {
  39.         mod_timer(&keypad_timer, jiffies + HZ/10);//有按键按下或松开,重新设定定时时间并激活定时器,开始下一次计时
  40.     } else {
  41.         writel(KEYIFCON_INIT, key_base + S3C_KEYIFCON);//中断和消抖使能
  42.         is_timer_on = false;//没有按键,关闭定时器
  43.     }
  44. }
keypad_scan()分析

点击(此处)折叠或打开

  1. static int keypad_scan(void)
  2. {
  3.     u32 col, cval, rval;

  4.     pr_debug("keypad_scan() is called\n");

  5.     pr_debug("row val = %x", readl(key_base + S3C_KEYIFROW));

  6.     for (col = 0; col < KEYPAD_COLUMNS; col++) {//循环的将列线输出低电平(并设置为正常输出状态)
  7.         /* clear that column number and make that normal output */
  8.         cval = KEYCOL_DMASK & ~((1 << col) | (1 << (col + 8)));//KEYCOL_DMASK=0xff
  9.         writel(cval, key_base + S3C_KEYIFCOL);//列线输出低电平

  10.         
阅读(1229) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~