S3C2440独立键盘Linux设备驱动
驱动程序:
/****************************************************************************************
*Name: keyboard.c
*Author: Ma Dongpeng<[email protected]>
*Time: 2011-02-17 14:08:10
*Version: 1.0.0
*Description: keyborad driver for linux2.6.25
*****************************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "keyboard" // 加载模式后,执行”cat /proc/devices”命令看到的设备名称
#define KEYBOARD_MAJOR 252 // 主设备号
#define KEY_NUM 4 //按键数目
#define KEYSTATUS_DOWNX 0
#define KEYSTATUS_UP 1
#define KEY_TIMER_DELAY 15 //以ms为单位,表示延时15ms
#define KEY_TIMER_DELAY1 100
#define KEY_TIMER_DELAY2 20
static int keyboard_major=KEYBOARD_MAJOR;
struct key_irq_desc
{
int irq; //中断号
int pin; //中断引脚
int pin_setting; //设置gpio功能
int number;
char *name;
};
// 用来指定按键所用的外部中断引脚及中断触发方式, 名字
static struct key_irq_desc key_irqs [] =
{
{IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"}, /* K1 */
{IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"}, /* K2 */
{IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"}, /* K3 */
{IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"}, /* K4 */
};
struct key_dev
{
struct cdev cdev;
char key_values[KEY_NUM]; //记录键值为1表示对应的按键被按下
unsigned int key_status[KEY_NUM]; //记录按键状态
wait_queue_head_t key_waitq; //等待队列
int key_values_flag; //记录是否有任何一个按键被按下
};
struct key_dev *key_devp;
static struct timer_list key_timer[KEY_NUM]; //按键的定时器
static char __initdata info[] = "******************keyborad Driver*****************\n";
static struct class *key_class;
/******************************************************************************************
*键盘中断处理程序
*******************************************************************************************/
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id;
disable_irq(key_irqs->irq); //关中断进入查询模式
key_devp->key_status[key_irqs->number]=KEYSTATUS_DOWNX;
key_timer[key_irqs->number].expires=jiffies+KEY_TIMER_DELAY;
add_timer(&key_timer[key_irqs->number]); //启动定时器
return IRQ_RETVAL(IRQ_HANDLED);
}
/******************************************************************************************
*处理键盘事件,在key_timer_handler中被调用,记录键值唤醒等待队列
*******************************************************************************************/
static void key_event(int num)
{
//printk(KERN_ALERT "num=%d\n",num);
key_devp->key_values[num]=1;
key_devp->key_values_flag=1;
wake_up_interruptible(&key_devp->key_waitq); //唤醒等待队列
}
/******************************************************************************************
*定时器中断处理程序
*******************************************************************************************/
static void key_timer_handler(unsigned long data)
{
struct key_irq_desc *key_irqs = (struct key_irq_desc *)data;
if(s3c2410_gpio_getpin(key_irqs->pin)==KEYSTATUS_DOWNX) //键盘仍然属于按下状态
{
if(key_devp->key_status[key_irqs->number]==KEYSTATUS_DOWNX)
{
key_devp->key_status[key_irqs->number]=KEYSTATUS_UP;
key_timer[key_irqs->number].expires=jiffies+KEY_TIMER_DELAY1;//准备进入连按模式,延时较长
key_event(key_irqs->number); //记录键值唤醒等待队列
add_timer(&key_timer[key_irqs->number]); //启动定时器
}
else
{
key_timer[key_irqs->number].expires=jiffies+KEY_TIMER_DELAY2;//连按模式,延时较短
key_event(key_irqs->number); //记录键值唤醒等待队列
add_timer(&key_timer[key_irqs->number]); //启动定时器
}
}
else //键盘已经抬起
{
key_devp->key_status[key_irqs->number]=KEYSTATUS_UP;
enable_irq(key_irqs->irq);
}
}
/*******************************************************************************************
*应用程序对设备文件/dev/keyboard执行open(...)时,
* 就会调用key_open函数
********************************************************************************************/
static int key_open(struct inode *inode, struct file *file)
{
int i;
int err;
for (i = 0; i < KEY_NUM; i++)
{
set_irq_type(key_irqs[i].irq,IRQF_TRIGGER_LOW);
//set_external_irq(key_irqs[i].irq,EXT_LOWLEVEL,GPIO_PULLUP_DIS);
//将对应的引脚设置成中断功能
s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting);
// 申请中断,注册中断处理函数
err = request_irq(key_irqs[i].irq, key_interrupt, NULL,
key_irqs[i].name, (void *)&key_irqs[i]); //将&key_irqs[i]作为参数传入中断处理程序
if (err)
break;
}
if (err)
{
// 释放已经注册的中断
i--;
for (; i >= 0; i--)
{
disable_irq(key_irqs[i].irq);
free_irq(key_irqs[i].irq, (void *)&key_irqs[i]);
}
return -EBUSY;
}
return 0;
}
/********************************************************************************************
* 应用程序对设备文件/dev/keyboard执行close(...)时,
* 就会调用key_close函数
*********************************************************************************************/
static int key_close(struct inode *inode, struct file *file)
{
int i;
for (i = 0; i < KEY_NUM; i++)
{
// 释放已经注册的中断
disable_irq(key_irqs[i].irq);
free_irq(key_irqs[i].irq, (void *)&key_irqs[i]);
}
return 0;
}
/********************************************************************************************
*应用程序对设备文件/dev/keyboard执行read(...)时,
* 就会调用key_read函数
*********************************************************************************************/
static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;
retry:if(key_devp->key_values_flag==1)
{
/* 将按键状态复制给用户,并清0 */
//printk(KERN_ALERT "key value:%d %d %d %d\n",key_devp->key_values[0],key_devp->key_values[1],key_devp->key_values[2],key_devp->key_values[3]);
err = copy_to_user(buff, (const void *)key_devp->key_values, min(KEY_NUM, count));
memset((void *)key_devp->key_values, 0, min(KEY_NUM, count));
key_devp->key_values_flag=0;
}
else
{
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
else
{
/* 如果key_values_flag等于0,休眠 */
wait_event_interruptible(key_devp->key_waitq, key_devp->key_values_flag);
goto retry;
}
}
return err ? -EFAULT : min(KEY_NUM, count);
}
/********************************************************************************************
* 轮询函数判断是否能非阻塞的读取或写入
* 当用户程序调用select函数时,本函数被调用
* 如果有按键数据,则select函数会立刻返回
* 如果没有按键数据,本函数使用poll_wait等待
********************************************************************************************/
static unsigned int key_poll( struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &(key_devp->key_waitq), wait); // 此处将当前进程加入到等待队列中,但并不阻塞
if (key_devp->key_values_flag)
mask |= POLLIN | POLLRDNORM;
return mask;
}
/*********************************************************************************************
*这个结构是字符设备驱动程序的核心
* 当应用程序操作设备文件时所调用的open、read、write等函数,
* 最终会调用这个结构中的对应函数
*********************************************************************************************/
static struct file_operations key_fops =
{
.owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
.open = key_open,
.release = key_close,
.read = key_read,
.poll = key_poll,
};
/*********************************************************************************************
*注册设备,创建设备节点
**********************************************************************************************/
static int key_setup_cdev(struct key_dev *dev,int index)
{
int err;
int devno = MKDEV(KEYBOARD_MAJOR,index);
cdev_init(&dev->cdev,&key_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &key_fops;
err = cdev_add(&dev->cdev,devno,1); //向系统注册设备
if(err) printk(KERN_ALERT "Error %d adding key %d",err,index);
//注册一个类,使mdev可以在"/dev/"目录下面建立设备节点
key_class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(key_class))
{
printk("Err: failed in led class. \n");
return -1;
}
//创建一个设备节点,节点名为DEVICE_NAME
class_device_create(key_class, NULL, MKDEV(keyboard_major, 0), NULL, DEVICE_NAME);
return 0;
}
/*********************************************************************************************
* 执行“insmod key.ko”命令时就会调用这个函数
*********************************************************************************************/
static int __init key_init(void)
{
int ret,i;
printk(info);
/* 注册字符设备驱动程序
* 参数为主设备号、设备名字、file_operations结构;
* 这样,主设备号就和具体的file_operations结构联系起来了,
* 操作主设备为BUTTON_MAJOR的设备文件时,就会调用key_fops中的相关成员函数
* keyboard_major可以设为0,表示由内核自动分配主设备号
*/
dev_t devno = MKDEV(keyboard_major,0);
if(keyboard_major) ret=register_chrdev_region(devno,1,DEVICE_NAME); //注册主设备号
else //申请主设备号
{
ret = alloc_chrdev_region(devno,0,1,DEVICE_NAME);
keyboard_major = MAJOR(devno);
}
if(ret<0) return ret;
key_devp = kmalloc(sizeof(struct key_dev),GFP_KERNEL); // 动态申请设备结构体的内存
if(!key_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
memset(key_devp,0,sizeof(struct key_dev));
key_setup_cdev(key_devp,0);
for(i=0;i<KEY_NUM;i++)
key_devp->key_status[i]=KEYSTATUS_UP; //初始化键盘状态
for(i=0;i<KEY_NUM;i++)
key_devp->key_values[i]=0; //初始化键值
key_devp->key_values_flag=0; //表示四个按键都没有被按下
init_waitqueue_head(&key_devp->key_waitq);
for(i=0;i<KEY_NUM;i++)
setup_timer(&key_timer[i],key_timer_handler,(void *)&key_irqs[i]); //将&key_irqs[i]作为参数传入定时器中断处理程序
return 0;
fail_malloc: unregister_chrdev_region(devno,1);
return ret;
}
/***********************************************************************************************
* 执行”rmmod key.ko”命令时就会调用这个函数
************************************************************************************************/
static void __exit key_exit(void)
{
/* 卸载驱动程序 */
printk(KERN_ALERT "******************unregister keyboard driver*************************\n");
cdev_del(&key_devp->cdev); //注消cdev
kfree(key_devp);
class_device_destroy(key_class, MKDEV(keyboard_major, 0)); //删掉类
class_destroy(key_class); //注销类结构体
unregister_chrdev_region(MKDEV(keyboard_major,0),1); // 释放设备号
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(key_init);
module_exit(key_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("Ma Dongpeng<[email protected]>"); // 驱动程序的作者
MODULE_DESCRIPTION("keyborad Driver"); // 一些描述信息
MODULE_LICENSE("GPL"); // 遵循的协议
makefile文件:
CC = arm-linux-gcc
HOSTCC = gcc
#######################################################################
KERNELDIR = /opt/S3C2440/linux-2.6.25.9
#######################################################################
obj-m := keybord.o
#module-objs := keybord.o
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o
应用程序:
/*************************************************************************
NAME:test_keyboard.c
AUTHOR:Ma Dongpeng<[email protected]>
TIME:2011-02-19 17:03:57
VERSION:1.0.0
**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
char read_data[4]={0,0,0,0};
int fd,i;
printf("****************open keyboard***************\n");
fd = open("/dev/keyboard",O_RDONLY);
if (fd < 0) {
perror("erro open device keyboard");
exit(1);
}
while(1)
{
read(fd,read_data,4);
printf("\nread data:");
for(i=0;i<4;i++) printf("%d ",read_data[i]);
printf("\n");
}
printf("*****************close keyboard***************\n");
close(fd);
return 0;
}