mini2440_buttons驱动详解

 

//mini2440_buttons_my.c//
//后面加了_my//

//按键驱动程序//

//mini2440所用到的按键资源//
//
// 按键          对应的IO寄存器     对应的中断引脚//
// K1             GPG0                 EINT8      //
// K2             GPG3                 EINT11     //
// K3             GPG5                 EINT13     //
// K4             GPG6                 EINT14     //
// K5             GPG7                 EINT15     //
// K6             GPG11                EINT19     //
//

//要搞清楚谁是输入//
//在这里,按键控制对应的中断引脚,从而控制对应的IO寄存器//
//相当于信息从外面输入//
//我们要做的是根据对应的输入信息,来采取相应的响应动作//
//这就达到了中断响应的目的//
//其核心就是要检测//
//那么,该如何去检测呢?//
//通过什么来检测呢?//

//如何得知一个设备究竟用到哪些资源呢?//
//这是个非常重要的问题//
//我想应该看具体的电路原理图//
//只有看图,才能了解具体的电路连接情况//
//从而得知设备所需的硬件资源//
//厂商的原理图通常给的都比较详细//

//引用的头文件//

#include <linux/module.h> //模块有关的//

#include <linux/kernel.h> //内核有关的//

#include <linux/fs.h> //文件系统有关的//

#include <linux/init.h> //init//

#include <linux/delay.h> //delay//

#include <linux/poll.h> //poll//

#include <asm/irq.h> //中断//

#include <linux/interrupt.h> //linux中断//

#include <asm/uaccess.h> //uaccess//

#include <asm/arch/regs-gpio.h> //寄存器设置//

#include <asm/hardware.h> //hardware//

//定义宏//

#define BUTTON_MAJOR 221 //主设备号,本来是232,我改为221//

#define DEVICE_NAME "buttons_my" //设备名,本来是buttons,我加上了_my//

//定义按钮中断的描述结构体//
//由它把按钮中断的信息综合起来//
//各个成员表示什么意思?//

struct button_irq_desc
{
int irq; //中断号//
//中断号唯一表示一个中断//

int pin; //中断控制的寄存器//
//该寄存器的值由中断引脚设置//
//我们希望从该寄存器读出控制信息//

int pin_setting; //中断的引脚//
//该引脚的电平由按键来控制//
//从而最终我们由按键控制了寄存器的值//

int number; //编号//
char *name; //名称//
};

//指定6个按键的信息//

static struct button_irq_desc button_irqs [] =
{
{IRQ_EINT8,S3C2410_GPG0,S3C2410_GPG0_EINT8,0,"KEY1"}, //K1//
{IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG3_EINT11,1,"KEY2"}, //K2//
{IRQ_EINT13,S3C2410_GPG5,S3C2410_GPG5_EINT13,2,"KEY3"}, //K3//
{IRQ_EINT14,S3C2410_GPG6,S3C2410_GPG6_EINT14,3,"KEY4"}, //K4//
{IRQ_EINT15,S3C2410_GPG7,S3C2410_GPG7_EINT15,4,"KEY5"}, //K5//
{IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG11_EINT19,5,"KEY6"}, //K6//
}

//这样,资源就组织起来了//
//事实上,在这里我们不仅组织起了硬件资源//
//我们也把一定的软件资源也糅合进去了//
//像中断号//


//key_values数组//
//存放各个按键在发生中断情况下的值//
//volatile是什么意思呢?//
//这个数组是我们存放按键操作结果的,因此非常重要//

static volatile int key_values [] = {0,0,0,0,0,0};


//宏DECLARE_WAIT_QUEUE_HEAD(),是干什么的呢?//
//该宏应该是创建了一个等待队列//
//等待队列,是进程调度的一种重要方法//
//等待队列也很有意思,button_waitq,表示按键等待的队列//
//就是说,按键一按下,就会激活其等待队列里的进程,来做相应的处理//
//因此,按键的等待队列,或者说中断所设置的等待队列,//
//是中断处理中非常重要的资源,它大大扩展了中断处理的能力//


static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //button_waitq是什么呢?//
//应该是等待队列的名称//


//key_values数组中是否有数据的标志,0表示无数据可读,1表示有数据可读//

static volatile int ev_press = 0; //初始为0//

//中断服务程序buttons_interrupt()的申明//
//即当检测到有中断时,就会执行该中断服务程序//
//那么如何检测到有中断发生呢?//
//并且中断发生了,知道发生了什么样的中断呢?//
//中断有很多种,该中断服务程序究竟该服务于哪一个中断呢?//
//显然,要把中断号与中断服务程序联结起来,构成一个整体//
//这个工作可以在open函数里做//

//参数irq---中断号//
//中断服务程序应该是与中断号一一对应的//
//对应于某个中断号的中断一发生,就会调用该中断号对应的服务程序//
//那么,检测中断的发生,就成了先决条件//
//参数dev_id ---具体是哪一个按钮//

static irqreturn_t buttons_interrupt(int irq,void *dev_id);

//mini2440_buttons_open()函数申明//
//驱动函数open调用的具体函数//
//由open函数具体实现硬件的初始化工作//
//以及软件的初始化工作//
//为我们的键盘设备的运行创造好环境//

static int mini2440_buttons_open(struct inode *inode,struct file *file);

//mini2440_buttons_close()函数的申明//
//release调用的具体函数//
//设备软件环境的拆卸//
//具体就是中断的释放工作//
//因为中断资源,也是系统宝贵的资源,所以不用的时候,要释放//

static int mini2440_buttons_close(struct inode *inode,struct file *file);

//mini2440_buttons_read()函数的申明//
//read调用的具体函数//
//由它读取键盘输入的结果//
//实质上就是读取key_values数组的值//
//它完成了键盘作为输入设备的核心功能//
//数组是否可读,要根据标志位ev_press来判断//
//如果数组可读,则读取数据到用户buffer中//
//如果数组不可读,则进程进入等待队列,等待到数组可读为止//
//等待队列机制,是中断管理中常用到的机制//
//因为有些进程经常需要等待某一事件的发生//

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
//注意__user,指的是用户空间//
//即要把键盘的输入结果读取到用户空间去//

//mini2440_buttons_poll()函数的申明//
//poll调用的具体函数//
//poll实质上是select的调用函数//
//如果有按键数据,则select会立刻返回//
//如果没有按键数据,则等待//
//实质上这是键盘等待输入的机制//

static unsigned int mini2440_buttons_poll(struct file *file,struct poll_table_struct *wait);


//file_operations结构体//
//驱动函数的设置//
//分别将前面的驱动函数设置进来//

static struct file_operations mini2440_buttons_fops =
{
.owner = THIS_MODULE,

.open = mini2440_buttons_open, //open()//

.release = mini2440_buttons_close, //release()//

.read = mini2440_buttons_read, //read()//

.poll = mini2440_buttons_poll //poll()//
};

//mini2440_buttons_init()函数的申明//
//module_init调用的具体函数//
//模块创建时的初始化函数//
//主要做的工作是注册设备和创建设备//
//而具体的硬件初始化工作,它可以不做//
//而把它留给fops里的函数来做//

static int __init mini2440_buttons_init(void);

//mini2440_buttons_exit()函数的申明//
//模块卸载时的扫尾工作//
//主要是设备的卸载工作//

static void __exit mini2440_buttons_exit(void);

//模块创建时的入口点//

module_init(mini2440_buttons_init);


//模块卸载时的入口点//

module_exit(mini2440_buttons_exit);

//驱动程序的一些信息//

MODULE_AUTHOR("http://www.arm9.net"); //驱动程序的作者//

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver"); //描述信息//

MODULE_LICENSE("GPL"); //遵循的协议//

//下面是前面申明函数的实现//

static int __init mini2440_buttons_init(void)
{
int ret; //设备注册的返回值//

//注册设备驱动程序//
//设备号,设备名,和驱动函数//

ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops);

//对注册失败的处理//

if(ret < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return ret;
}

//创建设备//
//devfs_mk_cdev()函数是内核态的设备创建函数//
//而mknod是用户态的设备创建函数//

devfs_mk_cdev(MKDEV(BUTTON_MAJOR,0),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP,DEVICE_NAME);

printk(DEVICE_NAME " initialized\n");

return 0;
}


//

static void __exit mini2440_buttons_exit(void)
{
//移除设备//

devfs_remove(DEVICE_NAME);

//注消设备驱动//

unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);
}


//

static int mini2440_buttons_open(struct inode *inode,struct file *file)
{
int i;    //循环变量,因为有6个按钮//

int err; //中断注册函数的返回值//

//对每个按钮分别处理,用for循环来做//
//具体地是要联结寄存器和相应的引脚//
//联结中断号和相应的中断服务程序//
//这一步类似于前面所说的驱动的注册//
//我们可以成功称作中断的注册//

for(i = 0;i < sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
{
//寄存器与中断引脚的联结//

   s3c2410_gpio_cfgpin(button_irqs[i].pin,button_irqs[i].pin_setting);

   //中断的注册//
//request_irq()函数//
//要注意其输入参数//
//&button_irqs[i]是该中断享有的资源//
//会被传入buttons_interrupt,进行处理//

   err = request_irq(button_irqs[i].irq,buttons_interrupt,NULL,button_irqs[i].name,(void *)&button_irqs[i]);

   //中断类型的设置//
//set_irq_type()函数//
//IRQT_BOTHEDGE的中断类型代表什么样的中断呢?//

   //有几个非常重要的问题//
//中断注册后,并设置好其中断类型之后,当有中断发生时,//
//即按下某个按钮时,系统能够自动检测到有中断发生吗?//
//检测到有中断发生,它能够自动辨别是几号中断吗?//
//知道了是几号中断,那么它能自动调用其中断服务程序吗?//
//对这几个问题的解答,够成了linux系统中断处理机制的核心//

   set_irq_type(button_irqs[i].irq,IRQT_BOTHEDGE);

   //注册失败的处理//

   if(err)
break; //跳出循环//
}

//若有一个按钮中断注册失败//
//则还需把前面注册成功的中断给拆了//

if(err)
{
i--; //回到前面一个按钮的处理//

   for(;i >=0; i--) //依此拆除//
{
//使中断不起作用//

    disable_irq(button_irqs[i].irq);

    //释放中断资源//

    free_irq(button_irqs[i].irq,(void *)&button_irqs[i]);
}

   return -EBUSY; //中断注册没成功的最终的返回值//
}

return 0; //正常返回//
}


//
//此中断服务程序,在每中断一次,就要对key_values数组设一下值//
//并对数组可读标志位ev_press设一下值//
//并唤醒在等待队列里的进程//
//这是中断处理经常要做的事情//
//在这里,等待队列button_waitq里经常等待的进程是数组的读取进程//
//就是说,读取进程在没有读到数据的时候就一直在等待,等待按键的输入//
//读取进程在等待,并不代表所有进程在等待,其它进程该干啥干啥去//

static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
//button_irq_desc结构体变量//
//对传入的资源进行处理//

struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;

//获取寄存器的值//
//这一步至关重要//
//s3c2410_gpio_getpin()函数直接获取寄存器的值//

//要注意,按一下按钮,会发生两次中断//

//即按下是一次中断,放开又是一次中断//

int up = s3c2410_gpio_getpin(button_irqs->pin);

//通过电路原理图,可以知道没按下的时候,中断引脚应该是高电平//
//从而寄存器的值应该是1//
//变量取up也是有意义的,表示默认状态是弹起的状态//
//当按下按钮的状态下,寄存器的值就应该是0//

//下面对up的值进行处理//
//即是要把数据经过一定的变换存入key_values数组中//

if(up)   //如果是弹起的状态//
//那么就要在key_values数组的相应位存入很大的一个值//
//同时又要能从值里辨别出是哪个按键//

   key_values[button_irqs->number] = (button_irqs->number + 1) + 0x80;
//比如K1键开启的状态下,key_values[0]被置为(0+1)+0x80,即为129//

else //如果按键是闭合的状态//
//那么就要在key_values数组的相应位存入一个很小的数//
//同时又要能从值中辨别出是哪个键//

   key_values[button_irqs->number] = (button_irqs->number + 1);
//比如K1键闭合,则key_values[0]被置为(0+1),即为1//

//对数组可读标志位进行设置//

ev_press = 1; //表示数组已经可读了//

//唤醒休眠的进程?//
//button_waitq队列里存放有相应的处理进程//
//如读取数组的值的进程//
//要注意wake_up_interruptible()这些函数的用法//

wake_up_interruptible(&button_waitq);

//返回//

return IRQ_RETVAL(IRQ_HANDLED); //?//
}

//

static int mini2440_buttons_close(struct inode *inode,struct file *file)
{
int i; //循环变量,要操作好几个按键//

//for循环,对各个按键依此释放中断//

for(i = 0;i < sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
{
//使中断失效//

   disable_irq(button_irqs[i].irq);

   //释放资源//

   free_irq(button_irqs[i].irq,(void *)&button_irqs[i]);
}

//返回//

return 0;
}


//

//要注意,该read函数,只读取一次中断的值,而不是连续地读入//
//要做到连续地读入,则需要做一个循环,不断地调用该read函数,但那不是驱动程序里该做的事情//

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
unsigned long err;   //copy_to_user()函数的返回值//

//如果key_values 数组里没有值,则会此进程会休眠//
//一直到中断来临之后,中断服务程序会唤醒此休眠进程从而继续读取值//
//key_values数组里有没有值,是靠ev_press标志位来判断的//
//有值,就是1,无值,就是0//

//进程等待队列的机制,是进程调度的一种方法//

if(!ev_press) //标志位为0,即无数据时//
{
if(filp->f_flags & O_NONBLOCK) //??//
return -EAGAIN;
else //进程休眠,放进button_waitq等待队列//
//这里,把ev_press标志位设成了休眠进程的标志位了?//
//这是为了便于利用poll_wait函数//
//也就是利于select函数//
wait_event_interruptible(button_waitq,ev_press);
//在中断处理函数中,此进程会被唤醒//
//唤醒前,ev_press 已被置1了//
//唤醒后的执行点从这里开始//
}

//下面就是标志位为1,即有数据可读的的处理情况//

//那就开始往用户空间读数据呗//

err = copy_to_user(buff,(const void *)key_values,min(sizeof(key_values),count));
//copy_to_user()函数的使用//

//对key_values数组清零//

memset((void *)key_values,0,sizeof(key_values));

//对标志位置0//
//表示读取过了//

ev_press = 0;

//对err的处理//

if(err) //读取错误//
return -EFAULT;
else //读取正确//
//则返回读取到的字节数//
return min(sizeof(key_values),count);
}

//

static unsigned int mini2440_buttons_poll(struct file *file,struct poll_table_struct *wait)
{
unsigned int mask = 0; // //

//poll_wait()函数//
//会监测进程队列button_waitq里的进程//
//例如,如果mini2440_button_read所在的进程的标志位ev_press置为1了//
//那么就不会再等待了//
//这实质上就是select函数的运行机制//

poll_wait(file,&button_waitq,wait);

if(ev_press)
mask |= POLLIN | POLLRDNORM; //??//

return mask;
}

下面是测试代码:

//按键测试程序//

#include <stdio.h> //标准输入输出头文件//

#include <stdlib.h> //标准库//

#include <unistd.h> //一些宏的定义在这里//

#include <sys/ioctl.h> //设备的控制//

#include <sys/types.h> //定义了一些类型//

#include <sys/stat.h> //状态//

#include <fcntl.h> //文件控制//

#include <sys/select.h> //选择?//

#include <sys/time.h> //时间方面的函数//

#include <errno.h> //有关错误方面的宏//

//主函数入口//

int main(void)
{
int i; //键盘输出时用到的循环变量//

int buttons_fd; //buttons设备号//

int key_value[4]; //四个按键的取值//

//打开键盘设备文件//

buttons_fd = open("/dev/buttons",0); //以0方式打开//

//打开出错处理//

if(buttons_fd < 0) //打开出错就会返回一个负值//
{
perror("open device buttons"); //perror函数?//

   exit(1); //返回1//
}

//for无限循环,等待用户输入//
//这是很典型的程序执行方式//

for(;;)
{
fd_set rds; //fd_set是types.h中定义的类型,实质上是int型//
//rds用来存储设备号//

   int ret; //for循环内定义的局部变量ret//

   FD_ZERO(&rds); //rds初始化//
//FD_ZERO是哪里定义的呢?//

   FD_SET(buttons_fd,&rds); //将buttons设备号赋给rds//
//FD_SET是哪里定义的呢?//

   //使用系统调用select检查是否能够从/dev/buttons设备读取数据//
//select函数是干什么的呢?//

   ret = select(buttons_fd + 1,&rds,NULL,NULL,NULL);
//返回值ret//
//返回值的具体意义是什么呢?//

   //对ret的处理//

   if(ret < 0) //当ret小于0//
{
perror("select");
exit(1);
   

   if(ret == 0) //当ret等于0//
{
printf("Timeout.\n");
}
else //能够读到数据//
if(FD_ISSET(buttons_fd,&rds)) //??//
{
//读取键盘驱动发出的数据//
//key_value和键盘驱动中定义一致//

     int ret = read(buttons_fd,key_value,sizeof(key_value)); //注意此处的ret和前面的ret有何不同//
//注意键盘设备读取的特点//

     //对ret的处理//
if(ret != sizeof(key_value)) //没有接收够//
{
if(errno != EAGAIN)   //???//
perror("read buttons\n");
continue;
}
else //正确接收,则打印到标准终端//
{
for(i = 0;i < 4;i++) //最开始定义的循环变量i//
printf("K%d %s, key value = 0xx\n",i,(key_value[i] & 0x80) ? "released" : key_value[i] ? "pressed down" : "",key_value[i]);
//这一连串的输出,要注意格式//
}
}
}

//关闭设备//

close(buttons_fd);

return 0; //主函数返回//

END!!

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值