Linux-2.6.32.2内核在mini2440上的移植(十六)---基于中断的按键驱动移植

http://blog.163.com/[email protected]/blog/static/109968875201232010516794/

【引用】Linux-2.6.32.2内核在mini2440上的移植(十六)---基于中断的按键驱动移植

2012-04-20 10:51:06|分类: mini2440开发板 |标签: |字号订阅

移植环境(红色粗字体字修改后内容,蓝色粗体字为特别注意内容)

1,主机环境:VMare下CentOS 5.5 ,1G内存。

2,集成开发环境:Elipse IDE

3,编译编译环境:arm-linux-gcc v4.4.3,arm-none-linux-gnueabi-gcc v4.5.1。

4,开发板:mini2440,2M nor flash,128M nand flash。

5,u-boot版本:u-boot-2009.08

6,linux 版本:linux-2.6.32.2

7,参考文章:

嵌入式linux应用开发完全手册,韦东山,编著。

Mini2440 之Linux 移植开发实战指南

【1】硬件原理

Mini2440 具有6 个用户测试按键,它们都是连接到CPU 的中断引脚。如图:

Linux-2.6.32.2内核在mini2440上的移植(十六)---基于中断的按键驱动移植

从图中可以看出,6 个用户按键分别对应如下CPU 资源引脚:

按键 对应的端口寄存器 对应的中断 对应的复用功能
K1 GPG0 EINT8 仅有GPIO和中断功能
K2 GPG3 EINT11 nSS1
K3 GPG5 EINT13 SPIMISO
K4 GPG6 EINT14 SPIMOSI
K5 GPG7 EINT15 SPICLK
K6 GPG11 EINT19 TCLK

为何如此安排这些按键资源呢?
首先,它们都具备中断功能,因此可以直接做一些中断相关的实验,其次 GPG3,5,6,7这一组合可以形成一个全功能的SPI 接口,我们知道,有些全键盘就是通过SPI 接口扩展实现的,比如三星的公板SMDK2440 就带有这种接口的键盘,只不过它需要添加一个SPI 接口的键盘芯片来实现。所以,我们不但在开发板上直接把这些引脚接到按键上,而且还特意增加了CON12 座以方便把这些按键引出到面板使用,或者作为扩展全功能键盘的接口。这也是mini2440 精心设计的细节之一。

【2】驱动程序分析及编写

在/linux-2.6.32.2/drivers/misc目录下创建一个新的驱动程序文件mini2440_buttons.c,内容及详细注释如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#define DEVICE_NAME "buttons"
//设备名称
/*定义中断所用的结构体*/
struct button_irq_desc {
int irq;
//按键对应的中断号
int pin; //按键所对应的GPIO 端口
int pin_setting; //按键对应的引脚描述,实际并未用到,保留
int number; //定义键值,以传递给应用层/用户态
char *name; //每个按键的名称
};

/*结构体实体定义*/
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT8 , S3C2410_GPG(0) , S3C2410_GPG0_EINT8 , 0, "KEY0"},
{IRQ_EINT11, S3C2410_GPG(3) , S3C2410_GPG3_EINT11 , 1, "KEY1"},
{IRQ_EINT13, S3C2410_GPG(5) , S3C2410_GPG5_EINT13 , 2, "KEY2"},
{IRQ_EINT14, S3C2410_GPG(6) , S3C2410_GPG6_EINT14 , 3, "KEY3"},
{IRQ_EINT15, S3C2410_GPG(7) , S3C2410_GPG7_EINT15 , 4, "KEY4"},
{IRQ_EINT19, S3C2410_GPG(11), S3C2410_GPG11_EINT19, 5, "KEY5"},
};
/*开发板上按键的状态变量,注意这里是’0’,对应的ASCII 码为30*/
static volatile char key_values [] = {'0', '0', '0', '0', '0', '0'};
/*因为本驱动是基于中断方式的,在此创建一个等待队列,以配合中断函数使用;当有按键按下并读取到键
值时,将会唤醒此队列,并设置中断标志,以便能通过 read 函数判断和读取键值传递到用户态;当没有按
键按下时,系统并不会轮询按键状态,以节省时钟资源*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/*中断标识变量,配合上面的队列使用,中断服务程序会把它设置为1,read 函数会把它清零*/
static volatile int ev_press = 0;
/*本按键驱动的中断服务程序*/
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;
int down;
// udelay(0);
/*获取被按下的按键状态*/
down = !s3c2410_gpio_getpin(button_irqs->pin);
/*状态改变,按键被按下,从这句可以看出,当按键没有被按下的时候,寄存器的值为1(上拉),但按
键被按下的时候,寄存器对应的值为0*/
if (down != (key_values[button_irqs->number] & 1)) { // Changed
/*如果key1 被按下,则key_value[0]就变为’1’,对应的ASCII 码为31*/
key_values[button_irqs->number] = '0' + down;
ev_press = 1;
/*设置中断标志为1*/
wake_up_interruptible(&button_waitq); /*唤醒等待队列*/
}
return IRQ_RETVAL(IRQ_HANDLED);
}
/*
*在应用程序执行open(“/dev/buttons”,…)时会调用到此函数,在这里,它的作用主要是注册6 个按键的中断。
*所用的中断类型是IRQ_TYPE_EDGE_BOTH,也就是双沿触发,在上升沿和下降沿均会产生中断,这样做
是为了更加有效地判断按键状态
*/
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
int i;
int err = 0;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
/*注册中断函数*/
err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH,
button_irqs[i].name, (void *)&button_irqs[i]);
if (err)
break;
}
if (err) {
/*如果出错,释放已经注册的中断,并返回*/
i--;
for (; i >= 0; i--) {
if (button_irqs[i].irq < 0) {
continue;
}
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return -EBUSY;
}
/*注册成功,则中断队列标记为1,表示可以通过read 读取*/
ev_press = 1;
/*正常返回*/
return 0;
}
/*
*此函数对应应用程序的系统调用close(fd)函数,在此,它的主要作用是当关闭设备时释放6 个按键的中断*
处理函数
*/

static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
int i;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
/*释放中断号,并注销中断处理函数*/
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return 0;
}
/*
*对应应用程序的read(fd,…)函数,主要用来向用户空间传递键值
*/
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;
if (!ev_press) {
if (filp->f_flags & O_NONBLOCK)
/*当中断标识为0 时,并且该设备是以非阻塞方式打开时,返回*/
return -EAGAIN;
else
/*当中断标识为0 时,并且该设备是以阻塞方式打开时,进入休眠状态,等待被唤醒*/
wait_event_interruptible(button_waitq, ev_press);
}
/*把中断标识清零*/
ev_press = 0;
/*一组键值被传递到用户空间*/
err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
return err ? -EFAULT : min(sizeof(key_values), count);
}
static unsigned int s3c24xx_buttons_poll( struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
/*把调用poll 或者select 的进程挂入队列,以便被驱动程序唤醒*/
poll_wait(file, &button_waitq, wait);
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
/*设备操作集*/
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
.poll = s3c24xx_buttons_poll,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};

/*设备初始化,主要是注册设备*/
static int __init dev_init(void)
{
int ret;
/*把按键设备注册为misc 设备,其设备号是自动分配的*/
ret = misc_register(&misc);

if(ret < 0)
{
printk(DEVICE_NAME "register falid!\n");
return ret;
}
printk (DEVICE_NAME"\tinitialized\n");
return 0;
}
/*注销设备*/
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
module_init(dev_init);
//模块初始化,仅当使用insmod/podprobe 命令加载时有用,如果设备不是通过模块方式加载,此处将不会被调用
module_exit(dev_exit); //卸载模块,当该设备通过模块方式加载后,可以通过rmmod 命令卸载,将调用此函数
MODULE_LICENSE("GPL"); //版权信息
MODULE_AUTHOR("singleboy.");
//作者名字

【3】为内核添加按键设备的内核配置选项

把按键驱动加入到内核中,打开 linux-2.6.32.2/drivers/misc/Kconfig 文件,定位到16行附近,加入如下红色部分内容:

if MISC_DEVICES

config MINI2440_BUTTONS
tristate "Buttons driver for FriendlyARM Mini2440 development boards"
depends on MACH_MINI2440
default y if MACH_MINI2440
help
this is buttons driver for FriendlyARM Mini2440 development boards

config LEDS_MINI2440
tristate "LED Support for Mini2440 GPIO LEDs"
depends on MACH_MINI2440
default y if MACH_MINI2440
help
This option enables support for LEDs connected to GPIO lines
on Mini2440 boards.

【4】对应的驱动目标文件加入内核

打开linux-2.6.32.2/drivers/misc/Makefile 文件,添加如下红色部分内容:

obj-$(CONFIG_EP93XX_PWM)+= ep93xx_pwm.o
obj-$(CONFIG_C2PORT)+= c2port/
obj-$(CONFIG_MINI2440_BUTTONS) += mini2440_buttons.o
obj-$(CONFIG_LEDS_MINI2440) += mini2440_leds.o
obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o
obj-y+= eeprom/
obj-y+= cb710/

【5】确认内核配置

接上面的步骤,在内核源代码目录下执行:make menuconfig 重新配置内核,依次选择进入如下子菜单项:

Device Drivers --->
[*] Misc devices --->

<*> Buttons driver for FriendlyARM Mini2440 development boards //选项默认是选中的,若没有选中,则按空格键选中它。

退出并保存内核配置。

然后退出保存所选配置, 在命令行执行: make uImage , 将会生成arch/arm/boot/uImage,然后将其复制到/nfsboot目录下后启动开发板。可以在看到串口终端中启动信息:

... ...

brd: module loaded
buttons initialized!
S3C24XX NAND Driver, (c) 2004 Simtec Electronics

... ...

说明leds设备加载成功。

【6】buttons测试

为了测试该驱动程序,我们还需要编写一个简单的测试程序,在友善官方提供的光盘中已经提供了该测试程序的源代码,它位于\linux 示例代码\examples\buttons目录中,文件名为:buttons_test.c。将其复制到主机/root/linux-test/codetest目录下,下面是其中的代码:

#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 buttons_fd;
char buttons[6] = {'0', '0', '0', '0', '0', '0'}; //定义按键值变量,对于驱动函数中的key_values 数组

buttons_fd = open("/dev/buttons", 0); /*打开按键设备/dev/buttons*/
if (buttons_fd < 0) {
perror("open device buttons"); /*打开失败则退出*/
exit(1);
}

for (;;) { /*永读按键并打印键值和状态*/
char current_buttons[6];
int count_of_changed_key;
int i;

/*使用read 函数读取一组按键值(6 个)*/

if (read(buttons_fd, current_buttons, sizeof current_buttons) != sizeof current_buttons) {
perror("read buttons:");
exit(1);
}

/*逐个分析读取到的按键值*/

for (i = 0, count_of_changed_key = 0; i < sizeof buttons / sizeof buttons[0]; i++) {
if (buttons[i] != current_buttons[i]) {
buttons[i] = current_buttons[i];

/*打印按键值,并标明按键按下/抬起的状态*/
printf("%skey %d is %s", count_of_changed_key? ", ": "", i+1, buttons[i] == '0' ? "up" : "down");
count_of_changed_key++;
}
}
if (count_of_changed_key) {
printf("\n");
}
}

close(buttons_fd); /*关闭按键设备文件*/
return 0;
}

在终端中进入到codetest目录,然后执行:
[[email protected] codetest]# ls
adc_test adc_test.c~ backlight_test.c i2c led.c tstest.c
adc_test.c backlight_test buttons_test.c led tstest

[[email protected] codetest]# arm-linux-gcc -o buttons_test buttons_test.c
[[email protected] codetest]# cp buttons_test /nfsboot/nfs
[[email protected] codetest]#

将生成可执行目标文件buttons_test,复制到与开发板共享的nfsboot/nfs中,在开发板的命令行终端执行:

[[email protected] /]#ls -l /dev/buttons
crw-rw---- 1 root root 10, 63 Jan 1 00:00 /dev/buttons
[[email protected] /]#ls -l /dev/leds
crw-rw---- 1 root root 10, 62 Jan 1 00:00 /dev/leds
[[email protected] /]#cd /mnt/nfs
[[email protected] nfs]#ls
adc_test buttons_test test1.wav
backlight_test i2c tstest
bigworld.wav led yesterday.mp3
[[email protected] nfs]#./buttons_test
key 6 is down
key 6 is up
key 4 is down
key 4 is up
key 5 is down
key 5 is up
key 3 is down
key 3 is up
key 2 is down
key 2 is up
key 1 is down
key 1 is up

可以看到已经产生的动作。

接下来,将进行pwm驱动的移植。