【转】s3c2440 按键驱动 — 字符设备

原文网址:http://www.xuebuyuan.com/632893.html

主机:VM - redhat 9.0

开发板:FL2440,linux-2.6.12

arm-linux-gcc:3.4.1

(1)原理图上的按键模块,可以看到相应的GPIO口,以及中断号。

由图可以得知GPF0等接高电平,当按键按下,则接低电平,所以将中断响应设置为下降沿触发。

(2)驱动程序gzliu_2440_key.c,实现为一般的字符设备驱动,完整的源码如下,其中:

// 定时器的使用参考:http://blog.csdn.net/gzliu_hit/article/details/6691355

// s3c2410_gpio_cfgpin系列函数参考:http://blog.csdn.net/gzliu_hit/article/details/6689182

// request_irq()参考:http://blog.csdn.net/gzliu_hit/article/details/6688929

http://blog.csdn.net/gzliu_hit/article/details/6688816

// cdev_init()系列函数参考:http://blog.csdn.net/gzliu_hit/article/details/6688684

#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/cdev.h>
#include <asm/uaccess.h>

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

#define DEV_NAME		 "s3c2440-key"
#define DEV_MAJOR                 250              // 主设备号
#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;                    // 等待队列
    struct cdev cdev;                               // cdev结构体
}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;
    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,
};

// s3c2440-key驱动模块加载函数
static int __init s3c2440_key_init(void)
{
    int ret;
    dev_t devno = MKDEV(DEV_MAJOR, 0);

    // 申请字符设备驱动区域
    // 已知主设备号,若未知,则动态申请alloc_chrdev_region()
    ret = register_chrdev_region(devno, 1, DEV_NAME);
    if (ret < 0)
    {
        printk(DEV_NAME " register failed, line: %d\n", __LINE__);
        printk("ret: %d\n", ret);
        return ret;
    }

    // 初始化并添加cdev结构体
    cdev_init(&dev.cdev, &s3c2440_key_fops);
    dev.cdev.owner = THIS_MODULE;

    ret = cdev_add(&dev.cdev, devno, 1);
    if (ret)
    {
        printk("error %d when adding dev\n", ret);
    }

    return 0;
}

// s3c2440-key驱动模块卸载函数
static void __exit s3c2440_key_exit(void)
{
    cdev_del(&dev.cdev);        // 删除cdev结构体
    unregister_chrdev_region(MKDEV(DEV_MAJOR, 0), 1);      // 注销设备区域
}

module_init(s3c2440_key_init);
module_exit(s3c2440_key_exit);

MODULE_AUTHOR("gzliu <[email protected]>");
MODULE_DESCRIPTION("s3c2440 key driver");
MODULE_LICENSE("GPL");

(3)编译驱动模块的Makefile:

    # Makefile 2.6  

ifneq ($(KERNELRELEASE),)
obj-m:=gzliu_2440_key.o
else
PWD:=$(shell pwd)
#KDIR:=/lib/modules/$(shell uname -r)/build
KDIR:=/root/linux-2.6.12

all:
	$(MAKE) -C $(KDIR) M=$(PWD)
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif

(4)应用层测试程序key_test.c:

#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/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;
}

(5)通过串口将程序发送到开发板的文件系统中,在FL2440开发板上的测试结果:

也可以实现为混杂设备驱动,就能自动创建设备节点。

按键驱动作为混杂设备的实现:http://blog.csdn.net/gzliu_hit/article/details/6697568

时间: 2024-10-15 05:26:51

【转】s3c2440 按键驱动 — 字符设备的相关文章

[Linux驱动]字符设备驱动学习笔记(一)

一,主设备号和次设备号代表的含义?linu内核是如果根据主设备号找驱动,次设备号找设备的. 答:通常一个主设备号代表一个驱动,比如在block设备中,一个主设备号代表一个emmc设备,不同次设备号代表的是不同的分区 Linux内核允许多个驱动共享一个主设备号,但更多的设备都遵循一个驱动对一个主设备号的原则.内核维护者一个以主设备号为key的全局哈希表,而哈希表中数据部分则为与该主设备号设备对应的驱动程序(只有一个次设备)的指针或者多个次设备驱动程序组成的数组的指针(次设备共享主设备号) 二,编写

LCD背光驱动(字符设备)在FL2440的开发(2.6.33.7内核)

一.开发环境 主   机:fedora 14 (2.6.33.7)    开发板:FL2440(nandflash:K9F1G08 128m)    编译器:arm-linux-gcc 4.3.2 二.原理分析 硬件原理图分析.由原理图得知LCD的背光是由2440的GPG4口控制的,只要往GPG4口写入高电平,(1)则背光打开,写入低电平(0)则背光关闭. 三.实现步骤     1. 编写背光驱动.文件名为fl2440_backlight.c /* *======================

[Linux驱动]字符设备驱动学习笔记(二)———实例

一,注册字符设备 [cpp] view plaincopy #define GLOBALMEM_MAJOR 256 #define GLOBALMEM_SIZE 0X1000 //4k static int char_major=GLOBALMEM_MAJOR;//主设备号 struct chartest_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE]; }; struct chartest_dev glob_char_dev;

[Linux驱动]字符设备驱动学习笔记(三)———高级

一,ioctl使用实例 ioctl使用实例 驱动程序.h文件  memdev.h [cpp] view plaincopy /* 定义幻数 */ #define MEMDEV_IOC_MAGIC  'k' /* 定义命令 */ #define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1) #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int) #define MEMDEV_IOCSETDATA _I

初入android驱动之字符设备(三)

回想当初在大学玩51单片机的时候,实验室的老师第一个任务,就是设计一个基于51单片机的LED流水灯设计,并实现几种样式.第二个任务,就是设计一个基于51单片机的按键控制LED流水灯样式的设计.需要自己设计硬件图.画protel电路图,并设计出PCB,实现keil和proteus的联调,然后焊接电路板,实现其功能.那时候什么都不懂,秉这一股冲劲,各种百度.看书,那时候郭天祥的51单片机视频超火,所以那时候基本以他的书和视频学得,牛人,膜拜. 所以,这主要讲关于按键最简单的字符驱动,通过设置连接该引

字符设备驱动、平台设备驱动、设备驱动模型、sysfs的关系

Linux驱动开发的童鞋们来膜拜吧:-)  学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术.对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了.要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码.网络上有关这些技术的文章不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关联的分析.对于开发者而言,能够熟悉某一点并分享出来已很难得,但对于专注传授技术和经验给

[kernel]字符设备驱动、平台设备驱动、设备驱动模型、sysfs几者之间的比较和关联

转自:http://www.2cto.com/kf/201510/444943.html Linux驱动开发经验总结,绝对干货! 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术.对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了.要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码.网络上有关这些技术的文章不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关

字符设备驱动(程序设计)—①

via:http://blog.sina.com.cn/s/blog_7ec8fc2c010157lc.html 1.驱动程序设计 1)驱动分类 驱动这里分为 字符设备驱动.网络接口驱动.块设备驱动!这三类,其中前两者是重点. ①.字符设备 字符设备是一种 按自己来访问 的设备,字符驱动则负责驱动字符设备,这样的驱动通常是先 open.close.read和write 系统调用! ②.块设备 在大部分 Unix 系统中,块设备不能按照字节处理数据,只能一次传送一个或则会多个长度是 512 字节(

linux驱动学习(1)——字符设备驱动开发

(一)驱动程序介绍 (a)Linux驱动程序学习 知识结构: 1. Linux驱动程序设计模式(40%) 2. 内核相关知识(30%) 3. 硬件相关知识(30%) (b)驱动分类: ①字符设备: 字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用. ②块设备: 在大部分的 Unix 系统, 块设备不能按字节处理数据,只能一次传送一个或多个长度是512字节( 或一个更大的 2 次幂的数 )的整块数据,而Lin