现在的位置: 首页 > 综合 > 正文

s3c2440按键驱动 — 混杂设备

2013年08月03日 ⁄ 综合 ⁄ 共 5735字 ⁄ 字号 评论关闭

本驱动的开发环境参见:http://blog.csdn.net/gzliu_hit/article/details/6694199

(1)将按键设备实现为混杂设备驱动,会动态生成次设备号,并自动创建设备节点/dev/misc/gzliu_2440_key,完整驱动gzliu_2440_key.c如下:

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>

#include <asm/arch/regs-gpio.h>

#define DEV_NAME		 "gzliu_2440_key"
#define MAX_KEY_BUF               16      // 按键缓冲区大小
#define KEY_NUM                   4
#define GZLIU_KEY_DOWN_X          0          // 按键按下,不确定是否是干扰
#define GZLIU_KEY_DOWN            1            // 按键按下
#define GZLIU_KEY_UP              2                   // 按键抬起
#define KEY_TIMER_DELAY_1        (HZ/50)              // 按键按下延迟20ms
#define KEY_TIMER_DELAY_2        (HZ/10)              // 按键抬起之前延迟100ms

typedef unsigned char KEY_RET;

// 设备结构体
static struct key_dev
{
    unsigned int key_status[KEY_NUM];   // 4个按键的状态
    KEY_RET buf[MAX_KEY_BUF];             // 按键缓冲区
    unsigned int head, tail;                     // 按键缓冲区头,尾
    wait_queue_head_t wq;                    // 等待队列
}dev;

static struct timer_list key_timer[KEY_NUM];    // 4个按键的去抖定时器

// 按键硬件资源、键值信息结构体
struct key_info
{
    int irq;                           // 中断号
    unsigned int port;      // GPIO端口
    int port_type;                       // GPIO端口配置
    char *key;                         // 键名
};

// 中断、GPIO宏定义与s3c2410一样,来自头文件:
// include/asm-arm/arch-s3c2410/regs-gpio.h
// include/asm-arm/arch-s3c2410/irqs.h
static struct key_info key_info_tab[KEY_NUM] = 
{
    { IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, "KEY_1" }, 
    { IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, "KEY_2" },
    { IRQ_EINT3, S3C2410_GPF3, S3C2410_GPF3_EINT3, "KEY_3" },
    { IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, "KEY_4" },
};

// 中断处理程序
static irqreturn_t s3c2440_key_irq(int irq, void *dev_id)
{
    int key = (int)dev_id;
    
    // 关中断,转入查询模式
    // 每次按键只产生一次中断
    disable_irq(key_info_tab[key].irq);
    
    dev.key_status[key] = GZLIU_KEY_DOWN_X;       // 状态为按下
    key_timer[key].expires = jiffies + KEY_TIMER_DELAY_1;    // 延迟
    add_timer(&key_timer[key]);       // 启动定时器
    
    return IRQ_HANDLED;

}/* s3c2440_key_irq() */

// 申请系统中断,中断方式为下降沿触发
static int request_irqs(void)
{
    int i, ret;
    
    for (i=0; i<KEY_NUM; i++)
    {
        // 设置4个GPIO口为中断触发方式
        s3c2410_gpio_cfgpin(key_info_tab[i].port, key_info_tab[i].port_type);
        
        // 申请中断,快速中断,设置为下降沿触发
        // 将按键序号作为参数传入中断服务程序
        ret = request_irq(key_info_tab[i].irq, (void *)s3c2440_key_irq, SA_INTERRUPT | IRQT_FALLING, key_info_tab[i].key, (void *)i);
        if (ret)
        {
            return i;
        }
    }
    return 0;
    
}/* request_irqs() */

// 释放中断
static void free_irqs(void)
{
    int i;
    for (i=0; i<KEY_NUM; i++)
    {
        disable_irq(key_info_tab[i].irq);
        free_irq(key_info_tab[i].irq, (void *)i);
    }
}

// 定时器处理函数
static void s3c2440_key_timer(unsigned long data)
{
    int key = data;
    
    int status = s3c2410_gpio_getpin(key_info_tab[key].port);
    if (!status)      // 按键为按下状态
    {
        if (dev.key_status[key] == GZLIU_KEY_DOWN_X)     // 从中断进入
        {
            dev.key_status[key] = GZLIU_KEY_DOWN;
            dev.buf[dev.tail] = (KEY_RET)key;
            dev.tail = (dev.tail + 1) % MAX_KEY_BUF;
            wake_up_interruptible(&dev.wq);               // 唤醒等待队列
        }
        // 延迟更长的时间,等待按键抬起
        key_timer[key].expires = jiffies + KEY_TIMER_DELAY_2;
        add_timer(&key_timer[key]);
    }
    else    // 按键已经抬起
    {
        dev.key_status[key] = GZLIU_KEY_UP;
        enable_irq(key_info_tab[key].irq);                 // 按键抬起,使能中断
    }
    
}/* s3c2440_key_timer */

static int s3c2440_key_open(struct inode *inode, struct file *filp)
{
    int i, ret;
    
    dev.head = dev.tail = 0;
    for (i=0; i<KEY_NUM; i++)
    {
        // 初始化按键状态为抬起
        dev.key_status[i] = GZLIU_KEY_UP;
        
        // 初始化定时器        
        init_timer(&key_timer[i]);

        key_timer[i].data = i;         // 把按键序号作为参数传入定时器处理函数
        key_timer[i].function = s3c2440_key_timer;   // 定时器相应函数
    }
    init_waitqueue_head(&(dev.wq));    // 初始化等待队列
    
    // 申请中断
    ret = request_irqs();
    if (ret > 0)                 // 如果申请失败,释放已经申请的中断
    {
    	printk("request_irqs() failed, line: %d\n", __LINE__);
        for (i=ret; i>=0; i--)
        {
            disable_irq(key_info_tab[i].irq);
            free_irq(key_info_tab[i].irq, (void *)i);
        }
        return -EBUSY;
    }
    return 0;
    
}/* s3c2440_key_open() */

static ssize_t s3c2440_key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    unsigned int size;

    if (dev.head == dev.tail)         // 没有按键被按下
    {
        if (filp->f_flags & O_NONBLOCK)      // 如果应用程序采用非阻塞方式,则返回错误
        {
            return -EAGAIN;
        }
        else    // 进入阻塞模式,使应用程序休眠
        {
            wait_event_interruptible(dev.wq, dev.head != dev.tail);
        }
    }
    size = (dev.tail - dev.head) % MAX_KEY_BUF;
    // 数据分为两个部分,在buf的两端
    if (dev.head > dev.tail)
    {
        ret = copy_to_user(buf, (dev.buf+dev.head), MAX_KEY_BUF - dev.head);
        if (ret)
        {
            return ret;
        }
    
        ret = copy_to_user(buf+MAX_KEY_BUF - dev.head, dev.buf, dev.tail);
        if (ret)
        {
            return ret;
        }
        dev.head = dev.tail;
    }
    else
    {
        ret = copy_to_user(buf, (dev.buf+dev.head), size);
        dev.head = dev.tail;
        if (ret)
        {
            return ret;
        }
    }
    
    return size; 
}/* s3c2440_key_read() */

static int s3c2440_key_release(struct inode *inode, struct file *filp)
    { 
        int i; 
        for (i=0; i<KEY_NUM; i++) 
        { 
            del_timer(&key_timer[i]); 
        } 
        free_irqs(); 
        return 0;
    }
    static struct file_operations s3c2440_key_fops = {
        .owner =THIS_MODULE,
        .open =s3c2440_key_open,
        .read =s3c2440_key_read,
        .release =s3c2440_key_release,
    };
    static struct miscdevice misc = { 
        .minor = MISC_DYNAMIC_MINOR, 
        .name = DEV_NAME, 
        .fops = &s3c2440_key_fops, 
    };
    // s3c2440-key驱动模块加载函数
    static int __init s3c2440_key_init(void)
    { 
        int ret; 
        // 注册混杂设备 
        // 会自动创建设备节点,/dev/misc/<DEV_NAME>,主设备号10,次设备号自动获取  
        ret = misc_register(&misc);    
        if (ret < 0)    
        {        
            printk(DEV_NAME " register failed, line: %d\n", __LINE__);        
            printk("ret: %d\n", ret);        return ret;    
        } 
            return 0;
    }
    
    // s3c2440-key驱动模块卸载函数
    static void __exit s3c2440_key_exit(void)
    { 
        misc_deregister(&misc);
    }
    
    module_init(s3c2440_key_init);
    module_exit(s3c2440_key_exit);
    MODULE_AUTHOR("gzliu <gzliu@qq.com>");
    MODULE_DESCRIPTION("s3c2440 key driver");
    MODULE_LICENSE("GPL");


(2)对应的测试应用程序:

#include <stdio.h>
#include <fcntl.h>

#define MAX_KEY_BUF           16      // 按键缓冲区大小

int main()
{
	unsigned char buf[MAX_KEY_BUF];
	int fd_key;
	int ret, i;
	
	fd_key = open("/dev/misc/gzliu_2440_key", O_RDONLY);
	if (fd_key == -1)
	{
		printf("open(fd_key) failed\n");
		return -1;
	}
	
	while (1)
	{
		// 阻塞模式, 没有按键按下时, 进程会被阻塞
		ret = read(fd_key, buf, MAX_KEY_BUF);
		for (i=0; i<ret; i++)
		{
			printf("Key %d is down\n", buf[i]);
		}
	}
	
	return 0;
}

(3)使用insmod gzliu_2440_key.ko加载驱动后,查看/dev/misc目录,发现自动生成了名为gzliu_2440_key的设备文件,主设备号10,次设备号63,之后直接运行测试程序key_test即可。而当执行rmmod gzliu_2440_key.ko将驱动卸载时,又会自动删除设备文件/dev/misc/gzliu_2440_key。






抱歉!评论已关闭.