②tiny4412 Linux驱动开发之KEY驱动程序

今天写一下按键驱动,本次并没有用输入子系统,但仍然不适合新手直接学,建议先看一下其他人写的按键驱动,然后再看这个,本博文主要是为了复习一下之前的知识.

硬件平台:tiny4412(Cortex A9);

软件平台:Linux-3.5

本次用按键驱动LED灯,我用的tiny4412开发板上有4个按键,4个LED灯,所以可以灯和按键一一对应,首先先查看原理图,如下:

LED:

②tiny4412 Linux驱动开发之KEY驱动程序_第1张图片

对用的引脚分别是GPM4_0, GPM4_1, GPM4_2, GPM4_3,如下图:

②tiny4412 Linux驱动开发之KEY驱动程序_第2张图片

我们查阅datesheet:


从上图可以知道LED相关引脚的配置寄存器的地址是:0x110002E0,那么数据寄存器就是0x110002E0 + 4,接下来在找按键的,如下图:

②tiny4412 Linux驱动开发之KEY驱动程序_第3张图片

查询到对应的引脚分别是GPX3_2, GPX3_3, GPX3_4, GPX3_5:

②tiny4412 Linux驱动开发之KEY驱动程序_第4张图片

这个我们就不查datasheet了,因为三星的寄存器地址都已经封装好了,我们可以在驱动了通过宏直接调用,当然,所有按键的都是封装好了,其实上面的LED的引脚也可以不用查datasheet,不过,为了把知识都用上,就这里还是保留一部分直接操作寄存器的语句.接下来就直接上代码了,代码不是很难,关键部分是有注释的,特别地给中断增加了软件消抖,程序如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 

#define GPM4COM     0x110002E0
#define KEY_DEV_MAJOR 0

#define LED_Switch1 _IOW('L', 0x1234, int)
#define LED_Switch2 _IOW('L', 0x1235, int)
#define LED_Switch3 _IOW('L', 0x1236, int)
#define LED_Switch4 _IOW('L', 0x1237, int)

volatile unsigned long *led_conf = NULL;
volatile unsigned long *led_data = NULL;

struct key_event{
    int code;
    int value;
};

// 设备对象属性
struct exynos4412_intkey{
    dev_t devno;
    struct cdev *cdev;
    struct class *cls;
    struct device *dev;
    int irqno;
    struct key_event event;
    unsigned char have_data;
    wait_queue_head_t wq_head;
};

// 单个按键信息的对象
struct key_desc{
    char *name;
    int gpio;
    int code;
    int int_flag;      // 中断触发方式
};

struct exynos4412_intkey *key_dev;

// 定义所有按键的信息集合, GPX3_2, 3_3, 3_4, 3_5.
struct key_desc all_keys[] = {
    [0] = {
        .name = "key1_button",
        .gpio = EXYNOS4_GPX3(2),
        .code = KEY_UP,
        .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [1] = {
        .name = "key2_button",
        .gpio = EXYNOS4_GPX3(3),
        .code = KEY_DOWN,
        .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [2] = {
        .name = "key3_button",
        .gpio = EXYNOS4_GPX3(4),
        .code = KEY_LEFT,
        .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [3] = {
        .name = "key4_button",
        .gpio = EXYNOS4_GPX3(5),
        .code = KEY_RIGHT,
        .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
};

int key_open(struct inode *inode, struct file *filp)
{
    printk("---------%s---------\n", __func__);
    // 演示inode的作用--->获取节点的主次设备号,就可以知道当前操作的是哪个设备
    unsigned int major = imajor(inode);
    unsigned int minor = iminor(inode);
    printk("major : %d, minor: %d\n", major, minor);

    // 也可以通过filp获取到inode
    printk("filp...major: %d, minor: %d\n",
            imajor(filp->f_path.dentry->d_inode),
            iminor(filp->f_path.dentry->d_inode));

    // 数据的共享
    static int a = 155;
    filp->private_data = &a;
    /**************************以上为增值功能********************************/
    
    // 先对LED的端口进行清0操作
    *led_conf &= ~(0xffff);
    // 将4个IO口16位都设置为output输出状态
    *led_conf |= (0x1111);
    
    // 初始化数据
    memset(&key_dev->event, 0, sizeof(key_dev->event));
    key_dev->have_data = 0;
    
    return 0;
}

ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
    int ret = -1;

    // 判断当前是什么模式
    if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
        return -EAGAIN;

    // 没有数据的时候进行休眠
    wait_event_interruptible(key_dev->wq_head, key_dev->have_data);

    ret = copy_to_user(buf, &key_dev->event, count);
    if(ret > 0){
        printk("copy to user failed!\n");
        return -EFAULT;
    }

    // 清空数据,以备下次数据来临
    memset(&key_dev->event, 0, sizeof(key_dev->event));
    key_dev->have_data = 0;

    return count;
}

ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
    int val = -1,
	ret = -1;
    
    ret = copy_from_user(&val, buf, count);
    if(ret != 0){
	printk("copy from user failed!\n");
	return -EINVAL;
    }

    switch(val)
    {
        case 0:
            printk(KERN_EMERG"led1_on\n");
            *led_data &= ~0x1;
            break;
        
        case 1:
            printk(KERN_EMERG"led2_on\n");
            *led_data &= ~(0x1 << 1);
            break;

        case 2:
            printk(KERN_EMERG"led3_on\n");
            *led_data &= ~(0x1 << 2);
            break;

        case 3:
            printk(KERN_EMERG"led4_on\n");
            *led_data &= ~0x8;
            break;

        case 4:
            printk(KERN_EMERG"ledall_on\n");
            *led_data &= ~0xf;
            break;

        case 5:
            printk(KERN_EMERG"ledall_off\n");
            *led_data |= 0xf;
            break;

        default:
            break;
    }

    return ret;
}

long key_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = -1;
    switch(cmd)
    {
        case LED_Switch1:
            if(arg){
                gpio_request(EXYNOS4X12_GPM4(0), "KEY1_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(0), 0);
                gpio_free(EXYNOS4X12_GPM4(0));
            } else {
                gpio_request(EXYNOS4X12_GPM4(0), "KEY1_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(0), 1);
                gpio_free(EXYNOS4X12_GPM4(0));
            }
	    break;

        case LED_Switch2:
            if(arg){
                gpio_request(EXYNOS4X12_GPM4(1), "KEY2_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(1), 0);
                gpio_free(EXYNOS4X12_GPM4(1));
            } else {
                gpio_request(EXYNOS4X12_GPM4(1), "KEY2_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(1), 1);
                gpio_free(EXYNOS4X12_GPM4(1));
            }
            break;

	case LED_Switch3:
            if(arg){
                gpio_request(EXYNOS4X12_GPM4(2), "KEY3_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(2), 0);
                gpio_free(EXYNOS4X12_GPM4(2));
            } else {
                gpio_request(EXYNOS4X12_GPM4(2), "KEY3_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(2), 1);
                gpio_free(EXYNOS4X12_GPM4(2));
            }
            break;

        case LED_Switch4:
            if(arg){
                gpio_request(EXYNOS4X12_GPM4(3), "KEY4_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(3), 0);
                gpio_free(EXYNOS4X12_GPM4(3));
            } else {
                gpio_request(EXYNOS4X12_GPM4(3), "KEY4_LED");
                gpio_direction_output(EXYNOS4X12_GPM4(3), 1);
                gpio_free(EXYNOS4X12_GPM4(3));
            }
            break;

        default:
            *led_data |= 0xf;
            break;
    }

    return 0;
}

int key_close(struct inode *inode, struct file *filp)
{
    printk("---------%s---------\n", __func__);
    int *p = (int *)filp->private_data;
    printk("close data: %d\n", *p);
    
    *led_data |= 0xf;

    return 0;
}

const struct file_operations key_fops = {
    // .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .write = key_write,
    .unlocked_ioctl = key_ioctl,
    .release = key_close,
};

irqreturn_t key_irq_handle(int irqno, void *devid)
{
    // 区分不同的中断
    struct key_desc *p = (struct key_desc *)devid;
   
    udelay(20); // delay 20us stabilization in key pressed

    if(gpio_get_value(p->gpio)){    // 弹起
        printk(" %s UP!\n", p->name);
        key_dev->event.code = p->code;
        key_dev->event.value = 0;
    } else {    // 按下
        printk(" %s Down!\n", p->name);
        key_dev->event.code = p->code;
        key_dev->event.value = 1;
    }

    // 唤醒队列
    wake_up_interruptible(&key_dev->wq_head);
    key_dev->have_data = 1;     // 设置是有数据

    return IRQ_HANDLED;
}

static void __exit intkey_exit(void)
{
    int i;
    printk("---------%s---------\n", __func__);

    for(i = 0; i < ARRAY_SIZE(all_keys); i++)
        free_irq(gpio_to_irq(all_keys[i].gpio), &all_keys[i]);
    device_destroy(key_dev->cls, key_dev->devno);
    cdev_del(key_dev->cdev);
    class_destroy(key_dev->cls);
    unregister_chrdev_region(key_dev->devno, 1);
    kfree(key_dev);
}

static int __init intkey_init(void)
{
    int ret = -1;
    printk("---------%s---------\n", __func__);
    // 1,实例化设备对象
    key_dev = kzalloc(sizeof(struct exynos4412_intkey), GFP_KERNEL);
    if(NULL == key_dev){
        printk("kzalloc failed\n");
        return -ENOMEM;
    }

    // 2,申请设备号
    if(KEY_DEV_MAJOR > 0){
        // 静态申请
        key_dev->devno = MKDEV(KEY_DEV_MAJOR, 0);
        ret = register_chrdev_region(key_dev->devno, 1, "key_drv");
    } else {
        // 动态申请
        ret = alloc_chrdev_region(&key_dev->devno, 16, 1, "key_drv");
    }

    if(ret < 0){
        printk("register failed!\n");
        ret = -EINVAL;
        goto err1;
    }

    // 3,cdev的操作
    // 3.1 分配一个cdev
    key_dev->cdev = cdev_alloc();

    // 3.2 初始化cdev
    cdev_init(key_dev->cdev, &key_fops);

    // 3.3 注册一个cdev
    cdev_add(key_dev->cdev, key_dev->devno, 1);

    // 4,创建设备类
    key_dev->cls = class_create(THIS_MODULE, "key_cls");
    if(IS_ERR(key_dev->cls)){
        printk("class create failed!\n");
        ret = PTR_ERR(key_dev);
        goto err2;
    }

    // 5,创建设备节点
    key_dev->dev = device_create(key_dev->cls, NULL, key_dev->devno, NULL, "key%d", 1);
    if(IS_ERR(key_dev->dev)){
        printk("device create failed!\n");
        ret = PTR_ERR(key_dev->dev);
        goto err3;
    }

    // 6,硬件初始化
    int i = 0;
    for(i = 0; i < ARRAY_SIZE(all_keys); i++){
        ret =  request_irq(gpio_to_irq(all_keys[i].gpio), key_irq_handle, all_keys[i].int_flag,
                           all_keys[i].name, &all_keys[i]);
        if(ret != 0){
            printk("request irq failed!\n");
            goto err4;
        }
    }

    led_conf = (volatile unsigned long *)ioremap(GPM4COM, 8);
    led_data = led_conf + 1;

    // 7,初始化一个队列头
    init_waitqueue_head(&key_dev->wq_head);

    return 0;

err4:
    device_destroy(key_dev->cls, key_dev->devno);
err3:
    cdev_del(key_dev->cdev);
    class_destroy(key_dev->cls);
err2:
    unregister_chrdev_region(key_dev->devno, 1);
err1:
    kfree(key_dev);

    return ret;
}

module_init(intkey_init);
module_exit(intkey_exit);

MODULE_LICENSE("GPL");
下面再写个测试程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LED_Switch1 _IOW('L', 0x1234, int)
#define LED_Switch2 _IOW('L', 0x1235, int)
#define LED_Switch3 _IOW('L', 0x1236, int)
#define LED_Switch4 _IOW('L', 0x1237, int)

struct key_event{
    int code;
    int value;
};


int main(void)
{
    int ret = -1,
	fd = -1,
	val = -1;

    struct key_event event;

    fd = open("/dev/key1", O_RDWR);
    if(fd < 0){
	perror("open");
	exit(1);
    }

    while(1)
    {
	ret = read(fd, &event, sizeof(struct key_event));
	if(ret < 0){
	    perror("read");
	    exit(1);
	}
	
	if(event.code == KEY_UP){
	    if(event.value){
		printf(" key1 DOWN!\n");
		ioctl(fd, LED_Switch1, 1);
	    }
	    else {
		printf(" key1 UP!\n");
		ioctl(fd, LED_Switch1, 0);
	    }
	}

	if(event.code == KEY_DOWN){
	    if(event.value){
		printf(" key2 DOWN!\n");
		val = 1;
		write(fd, &val, 4);
	    }
	    else {
		printf(" key2 UP!\n");
		ioctl(fd, LED_Switch2, 0);
	    }
	}
	
	if(event.code == KEY_LEFT){
	    if(event.value){
		printf(" key3 DOWN!\n");
		ioctl(fd, LED_Switch3, 1);
	    }
	    else {
		printf(" key3 UP!\n");
		ioctl(fd, LED_Switch3, 0);
	    }
	}
	
	if(event.code == KEY_RIGHT){
	    if(event.value){
		printf(" key4 DOWN!\n");
		ioctl(fd, LED_Switch4, 1);
	    }
	    else {
		printf(" key4 UP!\n");
		ioctl(fd, LED_Switch4, 0);
	    }
	}

	usleep(3000);
    }

    if(close(fd) < 0){
	perror("close");
	exit(1);
    }
}
好了,还有Makefile:
#Linux源代码的路径
KERNEL_DIR = /home/george/1702/exynos/linux-3.5

#获取当前路径
CUR_DIR = $(shell pwd)

MYAPP = key_test
MODULE = intkey

all:
	make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
	arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c
clean:
	make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
	$(RM) $(MYAPP)
install:
	cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702

#需要编译的目标程序
obj-m = $(MODULE).o

其中测试程序是key_test.c,驱动程序是intkey.c.

编译验证一下,按一个键就可以亮一个LED.

你可能感兴趣的:(Linux)