S3C2440独立键盘Linux设备驱动

batoom 2011-03-03

驱动程序:

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

相关推荐