开发板原理图
设备树修改
按键驱动代码
#include "asm-generic/gpio.h"
#include <linux/wait.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/sched.h>
#define KEY_DOWN 0
#define KEY_UP 1
#define KEY_NAME "key_dev"
#define KEY_NUM 1
struct mykey_dev{
dev_t dev;
int major;
int minor;
int gpio;
struct class *class;
struct cdev cdev;
struct device *device;
struct device_node *device_node;
unsigned int irq;
wait_queue_head_t rq_head;
struct timer_list timer;
unsigned char flags;
unsigned char keyval;
};
static struct mykey_dev key_dev;
static void timer_function(unsigned long key){
if(!gpio_get_value(key_dev.gpio)){
if(key_dev.keyval == KEY_DOWN)
key_dev.flags = 1;
}else if(gpio_get_value(key_dev.gpio)){
if(key_dev.keyval == KEY_UP)
key_dev.flags = 1;
}
wake_up(&key_dev.rq_head);
}
static irqreturn_t interrupt_handel(int irq, void *dev_instance){
key_dev.flags = 0;
if (!gpio_get_value(key_dev.gpio)){
key_dev.keyval = KEY_DOWN;
}else{
key_dev.keyval = KEY_UP;
}
mod_timer(&key_dev.timer, jiffies+HZ/50);
// printk("IRQ interrupt handler for %d\n", irq);
return IRQ_HANDLED;
}
int mykey_open(struct inode *node, struct file *filp){
filp->private_data = &key_dev;
return 0;
}
ssize_t mykey_read(struct file *filp, char __user *buff, size_t size, loff_t *offset){
unsigned char val;
struct mykey_dev *dev = (struct mykey_dev *)filp->private_data;
if(!dev->flags){
if(filp->f_flags & O_NONBLOCK){
return -EINVAL;
}else{
wait_event(dev->rq_head, dev->flags);
}
}
dev->flags = 0;
val = dev->keyval;
if(copy_to_user(buff, &val, size))
return -EFAULT;
return size;
}
ssize_t mykey_write(struct file *filp, const char __user *buff, size_t size, loff_t *offset){
return 0;
}
int mykey_release(struct inode *node, struct file *filp){
return 0;
}
static const struct file_operations mykey_fops = {
.open = mykey_open,
.read = mykey_read,
.write = mykey_write,
.release = mykey_release,
};
static int __init mykey_init(void)
{
int ret;
key_dev.major = 0;
key_dev.flags = 0;
key_dev.keyval = KEY_UP;
if(key_dev.major){
key_dev.dev = MKDEV(key_dev.major,0);
ret = register_chrdev_region(key_dev.dev, KEY_NUM, KEY_NAME);
if (ret)
return -EINVAL;
}else{
ret = alloc_chrdev_region(&key_dev.dev, 0, KEY_NUM, KEY_NAME);
if (ret)
return -EINVAL;
}
cdev_init(&key_dev.cdev, &mykey_fops);
ret = cdev_add(&key_dev.cdev, key_dev.dev, KEY_NUM);
if (ret)
goto unregister_chrdev;
key_dev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(key_dev.class)) {
printk(KERN_ERR "class_create() failed for key_dev\n");
ret = PTR_ERR(key_dev.class);
goto unregister_cdev;
}
key_dev.device = device_create(key_dev.class, NULL, key_dev.dev, NULL, KEY_NAME);
if (IS_ERR(key_dev.device)) {
ret = PTR_ERR(key_dev.device);
goto destroy_class;
}
key_dev.device_node = of_find_node_by_path("/key0");
if (!key_dev.device_node) {
pr_crit("could not find key node\n");
goto destroy_dev;
}
key_dev.gpio = of_get_named_gpio(key_dev.device_node, "key-gpios", 0);
if (!gpio_is_valid(key_dev.gpio)) {
dev_err(key_dev.device, "no sensor key pin available\n");
goto destroy_dev;
}
printk("gpio_id:%d\n",key_dev.gpio);
ret = gpio_request(key_dev.gpio, "key");
if(ret){
printk("failed to request gpio %d\n", key_dev.gpio);
goto destroy_dev;
}
gpio_direction_input(key_dev.gpio);
init_waitqueue_head(&key_dev.rq_head);
key_dev.irq = irq_of_parse_and_map(key_dev.device_node, 0);
if (!key_dev.irq) {
pr_warn("Key: unable to parse irq\n");
goto gpio1_free;
}
init_timer(&key_dev.timer);
key_dev.timer.expires = ~0;
key_dev.timer.function = timer_function;
add_timer(&key_dev.timer);
ret = request_irq(key_dev.irq, interrupt_handel, IRQ_TYPE_EDGE_BOTH, "key_irq", &key_dev);
if(ret){
printk("Key request irq failed\n");
goto gpio1_free;
}
return 0;
gpio1_free:
gpio_free(key_dev.gpio);
destroy_dev:
device_destroy(key_dev.class, key_dev.dev);
destroy_class:
class_destroy(key_dev.class);
unregister_cdev:
cdev_del(&key_dev.cdev);
unregister_chrdev:
unregister_chrdev_region(key_dev.dev, KEY_NUM);
return -EINVAL;
}
/* This function is called on driver exit */
static void __exit mykey_exit(void)
{
del_timer(&key_dev.timer);
free_irq(key_dev.irq, &key_dev);
gpio_free(key_dev.gpio);
device_destroy(key_dev.class, key_dev.dev);
class_destroy(key_dev.class);
cdev_del(&key_dev.cdev);
unregister_chrdev_region(key_dev.dev, KEY_NUM);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
应用层测试代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define KEY_PATH "/dev/key_dev"
#define KEY_DOWN 0
int main(int argc, char *argv[])
{
int ret,fd;
unsigned char val = -1;
fd = open(KEY_PATH, O_RDONLY);
if(fd < 0){
perror("open key_dev file");
return EXIT_FAILURE;
}
while(1){
ret = read(fd, &val, 1);
if(ret < 0){
perror("read key_dev file");
goto READ_ERR;
}
if(val == KEY_DOWN){
printf("key_down\n");
}else{
printf("key_up\n");
}
}
READ_ERR:
close(fd);
return 0;
}
Makefile参考
ARCH = arch
CROSS_COMPILE = arm-linux-gnueabihf
CC = ${CROSS_COMPILE}-gcc
KERNELDIR := /home/linux_0
CURRENT_PATH := $(shell pwd)
obj-m := mykey.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
$(CC) -o key_test key_test.c
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
@rm key_test -f
开发板验证
拷贝到开发板进行验证,加载驱动,执行key_test后,按下按键打印key_down;松开按键打印key_up。