键盘驱动

  作者:赵老师,华清远见嵌进式学院讲师。

  按键字符设备的file_operatiONs结构定义为:

STatic struct file_operations button_fops =
    {
    .owner = THIS_MODULE,
    .ioctl = button_ioctl,
    .open = button_open,
    .read = button_read,
    .release = button_release,
    };

以下为open和release函数接口的实现。

/* 打开文件, 申请中断 */
    static int button_open(struct inode *inode,struct file *filp) 
    {
    int ret = nonseekable_open(inode, filp);
    if (ret < 0) 
    {
         return ret;
    }
    init_gpio(); /* 相关GPIO端口的初始化*/
    request_IRqs(); /* 申请4个中断 */
    if (ret < 0) 
    {
         return ret;
    }
    init_keybuffer(); /* 初始化按键缓冲数据结构 */
    return ret;
    }
    /* 封闭文件, 屏蔽中断 */
    static int button_release(struct inode *inode,struct file *filp)
    {
    free_irqs(); /* 屏蔽中断 */
    return 0;
    }

  在open函数接口中,进行了GPIO端口的初始化、申请硬件中断以及按键缓冲的初始化等工作。在以前的章节中提过,中断端口是比较宝贵而且数目有限的资源。因此需要留意,最好要在第一次打开设备时申请(调用request_irq函数)中断端口,而不是在驱动模块加载的时候申请。假如已加载的设备驱动占用而在一定时间段内不使用某些中断资源,则这些资源不会被其他驱动所使用,只能白白浪费掉。而在打开设备的时候(调用open函数接口)申请中断,则不同的设备驱动可以共享这些宝贵的中断资源。

  以下为中断申请和开释的部分以及中断处理函数。

/* 中断处理函数,其中irq为中断号 */
    static irqreturn_t button_irq(int irq, void *dev_id, struct pt_regs *regs)
    {
    unsigned char ucKey = 0;
    disable_irqs(); /* 屏蔽中断 */
    /* 延迟50毫秒, 屏蔽按键毛刺 */
    udelay(50000);
    ucKey = button_scan(irq); /* 扫描按键,获得进行操纵的按键的ID */
    if ((ucKey >= 1) && (ucKey <= 16))
    {
        /* 假如缓冲区已满, 则不添加 */
        if (((key_buffer.head + 1) & (MAX_KEY_COUNT - 1)) != key_buffer.tail)
        {
         spin_lock_irq(&buffer_lock);
         key_buffer.jiffy[key_buffer.tail] = get_tick_count();
           key_buffer.tail ++;
           key_buffer.tail &= (MAX_KEY_COUNT -1);

         spin_unlock_irq(&buffer_lock);
         }
    }
    init_gpio(); /* 初始化GPIO端口,主要是为了恢复中断端口配置 */
    enable_irqs(); /* 开启中断 */
    return IRQ_HANDLED;/* 2.6内核返回值一般是这个宏 */
    }
    /* 申请4个中断 */
    static int request_irqs(void)
    {
    int ret, i, j;
    for (i = 0; i < MAX_COLUMN; i++)
    {
         ret = request_irq(key_info_matrix[i][0].irq_no, 
              button_irq, SA_INTERRUPT, BUTT*_DEVICE_NAME, NULL);
         if (ret < 0)
         {
             for (j = 0; j < i; j++)
             {
             free_irq(key_info_matrix[j][0].irq_no, NULL); 
             }
             return -EFAULT;
         }
     }
     return 0;
    }
    /* 开释中断 */
    static __inline void free_irqs(void)
    {
    int i;
    for (i = 0; i < MAX_COLUMN; i++)
    {
        free_irq(key_info_matrix[i][0].irq_no, NULL);
    }
    }

  中断处理函数在每次中断产生的时候会被调用,因此它的执行时间要尽可能得短。通常中断处理函数只是简单地唤醒等待资源的任务,而复杂且耗时的工作则让这个任务往完成。中断处理函数不能向用户空间发送数据或者接收数据,不能做任何可能发生睡眠的操纵,而且不能调用schedule()函数。

  为了简单起见,而且考虑到按键操纵的时间比较长,在本实例中的中断处理函数button_irq()里,通过调用睡眠函数来消除毛刺信号。读者可以根据以上先容的对中断处理函数的要求改进该部分代码。

  按键扫描函数如下所示。首先根据中断号确定操纵按键所在行的位置,然后采用逐列扫描法终极确定操纵按键所在的位置。

/* 
    ** 进进中断后, 扫描铵键码 
    ** 返回: 按键码(1-16), 0xff表示错误 
    */
    static __inline unsigned char button_scan(int irq)
    {
    unsigned char key_id = 0xff;
    unsigned char column = 0xff, row = 0xff;
    s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_INP); /* GPF0 */
       s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_INP); /* GPF2 */
       s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_INP); /* GPG3 */
       s3c2410_gpio_cfgpin(S3C2410_GPG11, S3C2410_GPG11_INP); /* GPG11 *
/
    switch (irq)
    { /* 根据irq值确定操纵按键所在行的位置*/
        case IRQ_EINT0:
        {
          column = 0;
        }
        break;
        case IRQ_EINT2:
        {
          column = 1;
        }
        break;
        case IRQ_EINT11:
        {
          column = 2;
        }
        break;
        case IRQ_EINT19:
        {
          column = 3;
        }
        break;
    } 
    if (column != 0xff)
    { /* 开始逐列扫描, 扫描第0列 */
         s3c2410_gpio_setpin(S3C2410_GPE11, 0); /* 将KSCAN0置为低电平 */
         s3c2410_gpio_setpin(S3C2410_GPG6, 1);
         s3c2410_gpio_setpin(S3C2410_GPE13, 1);
         s3c2410_gpio_setpin(S3C2410_GPG2, 1);
     if(!s3c2410_gpio_getpin(key_info_matrix[column][0].irq_gpio_port))
         { /* 观察对应的中断线的输进端口值 */
          key_id = key_info_matrix[column][0].key_id;
          return key_id;
         }
         /* 扫描第1列*/
         s3c2410_gpio_setpin(S3C2410_GPE11, 1);
         s3c2410_gpio_setpin(S3C2410_GPG6, 0); /* 将KSCAN1置为低电平 */
         s3c2410_gpio_setpin(S3C2410_GPE13, 1);
         s3c2410_gpio_setpin(S3C2410_GPG2, 1);
         if(!s3c2410_gpio_getpin(key_info_matrix[column][1].irq_gpio_port))
         {
          key_id = key_info_matrix[column][1].key_id; 
          return key_id;
         }
         /* 扫描第2列*/
         s3c2410_gpio_setpin(S3C2410_GPE11, 1);
         s3c2410_gpio_setpin(S3C2410_GPG6, 1);
         s3c2410_gpio_setpin(S3C2410_GPE13, 0); /* 将KSCAN2置为低电平 */
         s3c2410_gpio_setpin(S3C2410_GPG2, 1); 
         if(!s3c2410_gpio_getpin(key_info_matrix[column][2].irq_gpio_port))
         {
          key_id = key_info_matrix[column][2].key_id;
          return key_id;
         }
         /* 扫描第3列*/
         s3c2410_gpio_setpin(S3C2410_GPE11, 1);
         s3c2410_gpio_setpin(S3C2410_GPG6, 1);
         s3c2410_gpio_setpin(S3C2410_GPE13, 1);
         s3c2410_gpio_setpin(S3C2410_GPG2, 0); /* 将KSCAN3置为低电平 */
         if(!s3c2410_gpio_getpin(key_info_matrix[column][3].irq_gpio_port))
         {
           key_id = key_info_matrix[column][3].key_id;
           return key_id;
         }
     } 
     return key_id;
     }

  以下是read函数接口的实现。首先在按键缓冲中删除已经过期的按键操纵信息,接下来,从按键缓冲中读取一条信息(按键ID)并传递给用户层。

/* 从缓冲删除过期数据(5秒前的按键值) */
    static void remove_timeoutkey(void)
    {
    unsigned long tick;
    spin_lock_irq(&buffer_lock); /* 获得一个自旋锁 */
    while(key_buffer.head != key_buffer.tail)
    {
         tick = get_tick_count() - key_buffer.jiffy[key_buffer.head];
         if (tick < 5000) /* 5秒 */
         break;
         key_buffer.buf[key_buffer.head] = 0;
         key_buffer.jiffy[key_buffer.head] = 0;
         key_buffer.head ++;
         key_buffer.head &= (MAX_KEY_COUNT -1);
    }
    spin_unlock_irq(&buffer_lock); /* 开释自旋锁 */
    }
    /* 读键盘 */
    static ssize_t button_read(struct file *filp, 
                char *buffer, size_t count, loff_t *f_pos)
    {
     ssize_t ret = 0;
     remove_timeoutkey(); /* 删除过期的按键操纵信息 */
     spin_lock_irq(&buffer_lock);
     while((key_buffer.head != key_buffer.tail) && (((size_t)ret) < count))
     {
           put_user((char)(key_buffer.buf[key_buffer.head]), &buffer[ret]);
           key_buffer.buf[key_buffer.head] = 0;
           key_buffer.jiffy[key_buffer.head] = 0;
           key_buffer.head ++;
           key_buffer.head &= (MAX_KEY_COUNT -1);
           ret ++;
     }
     spin_unlock_irq(&buffer_lock);
     return ret;
    }

  以上先容了按键驱动程序中的主要内容。

  “本文由华清远见http://www.embedu.org/index.htm提供”



  来源:华清远见
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值