Linux学习笔记(17.4)——基于gpio中断的按键驱动

  1. 使用中断
    先前采用查询方式的按键驱动占用过多内存,不适合产品的实际使用,这就要使用中断。
    Linux系统也随着芯片技术的发展对中断处理不断优化,具体的演进如下(参考韦东山老师):
    Linux中断处理的演进Linux(4.1.15内核)中断系统中的重要数据结构:
    Linux中断系统中的重要数据结构Linux对中断的处理过程,放两张图片在这里仅作为引子,不作深入探讨,实际上我还没弄明白。深入研究可以“肝”一下内核源码。
  2. 按键设备驱动文件
    button_drv.c文件中,button_drv_read函数调用wait_event_interruptible,当没有按键时将进入休眠;有按键时(上升沿、下降沿或双边沿触发),进入中断服务函数gpio_btn_isr,在此中断服务函数调用wake_up_interruptible函数唤醒等待队列,button_drv_read函数得以继续执行,返回按键数据给应用程序。
/**
 * 文件    : button_drv.c
 * 作者    : glen  
 * 描述    : button driver文件
 */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

struct gbtn_irq {
    int gpio;
    struct gpio_desc *gpiod;
    int flag;
    int irq;
    int idx;
};

struct button_drv {
    struct class *class;
    struct gbtn_irq *gbtn_irq;
    char  *name;
    char kval;
    int count;
    int major;
};

static struct button_drv *btn_drv;

/* 等待队列头的静态初始化 */
static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);

/* 实现file_operations结构体成员 read 函数 */
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    int minor = iminor(filp->f_inode);
    struct button_drv *ops = (struct button_drv *)filp->private_data;

    size = (size >= 1) ? 1 : 0;
    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    if (minor >= ops->count) {
        printk("Reading button value error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EINVAL;
    }

    wait_event_interruptible(gpio_button_wait, btn_drv->kval);

    if (copy_to_user(buf, &ops->kval, size))
        return -EFAULT;

    btn_drv->kval = 0;

    printk("Read button%d value successfully:", minor);
    return size;
}

/* 实现file_operations结构体成员 open 函数 */
int button_drv_open (struct inode *nd, struct file *filp)
{
    int ret;
    int minor = iminor(nd);
    struct button_drv *ops = btn_drv;
    
    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    if (minor >= ops->count) {
        printk("Openning button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EINVAL;
    }

    ret = gpiod_direction_input(ops->gbtn_irq[minor].gpiod);
    if (ret) 
        printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
    else 
        printk("Set the button%d pin as input successfully!\n", minor);

    filp->private_data = btn_drv;

    return 0;
}

/* 实现file_operations结构体成员 release 函数 */
int button_drv_release (struct inode *nd, struct file *filp)
{
    int minor = iminor(nd);
    struct button_drv *ops = (struct button_drv *)filp->private_data;

    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    if (minor >= ops->count) {
        printk("Closing button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EINVAL;
    }

    filp->private_data = NULL;

    return 0;
}

/* 实现file_operations结构体成员 poll 函数 */
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
    return 0;
}

/**
 * 1. 构造file_operations结构体 
 */
static struct file_operations button_drv_ops = {
    .owner   = THIS_MODULE,
    .read    = button_drv_read,
    .open    = button_drv_open,
    .release = button_drv_release,
    .poll    = button_drv_poll,
};

/* 中断服务函数 */
static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
{
    int val;
    struct gbtn_irq *ops = dev_id;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);

    printk("button%d %d %d\n", ops->idx, ops->gpio, val);
    btn_drv->kval = (ops->gpio << 4) | val;

    /* 唤醒等待队列 */
    wake_up_interruptible(&gpio_button_wait);

    return IRQ_HANDLED;
}

/* platform_driver结构体的 probe成员函数实现 */
int btn_hw_drv_probe (struct platform_device *pdev)
{
    int i;
    int ret;
    int count;
    struct device_node *node = pdev->dev.of_node;

    /* 从设备节点获取gpio数量 */
    count = of_gpio_count(node);
    if (!count) {
        printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }
    
    btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
    if (btn_drv == NULL) 
        return -ENOMEM;

    btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
    if (btn_drv->gbtn_irq == NULL)
        return -ENOMEM;

    for (i = 0; i < count; i++) {
        btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
        if (btn_drv->gbtn_irq[i].gpiod == NULL) {
            printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }

        btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
        btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);

        btn_drv->gbtn_irq[i].idx = i;
    }

    for (i = 0; i < count; i++) 
        /* 申请irq中断, 将中断服务程序注册到上半部 */
        ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
                          "gpio_btn", &btn_drv->gbtn_irq[i]);

    /* 注册file_operationss结构体对象 -- button_drv_ops  */
    btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
    btn_drv->class = class_create(THIS_MODULE, "gbtn");
    if (IS_ERR(btn_drv->class)) {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(btn_drv->major, "gbtn");
        return PTR_ERR(btn_drv->class);
    }

    for (i = 0; i < count; i++)
        device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);

    btn_drv->count = count;
        
    return 0;
}

/* platform_driver结构体的 remove成员函数实现 */
int btn_hw_drv_remove(struct platform_device *pdev)
{
    int i;
    struct device_node *node = pdev->dev.of_node;
    int count = of_gpio_count(node);

    for (i = 0; i < count; i++) {
        device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
        free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
    }
    class_destroy(btn_drv->class);
    unregister_chrdev(btn_drv->major, "gbtn");

    kfree(btn_drv);
    return 0;
}

/* 构造用于配置的设备属性 */
static const struct of_device_id gbtns_id[] = {
    {.compatible = "glen,gbtn"},
    { },
};

/* 构造(初始化)file_operations结构体 */
static struct platform_driver btn_hw_drv = {
    .driver = {
        .name = "gbtn",
        .of_match_table = gbtns_id,
    },
    .probe = btn_hw_drv_probe,
    .remove = btn_hw_drv_remove,
};

/* 初始化 */
static int __init button_drv_init(void)
{
    int ret;
    ret = platform_driver_register(&btn_hw_drv);
    if (ret)
        pr_err("Unable to initialize button driver\n");
    else
        pr_info("The button driver is registered.\n");

    
    return 0;
}
module_init(button_drv_init);

static void __exit button_drv_exit(void)
{
    platform_driver_unregister(&btn_hw_drv);
    printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);

/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");

probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。
3. 微调设备树文件

		pinctrl_btn0:btn0 {
			fsl,pins = <
				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18	0xF080	/* KEY0 */ 
			>;
		};

		pinctrl_btn1:btn1 {
			fsl,pins = <
                MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xF080	/* KEY1  此按键不存在 */
			>;
		};
    /* 在根节点下添加基于pinctrl的gbtns设备节点 */
    gbtns {
        compatible = "glen,gbtn";
        #address-cells = <1>;

        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_btn0 
		             &pinctrl_btn1>;

        gpio-controller;
        #gpio-cells = <2>;
        gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
                 &gpio1 3 GPIO_ACTIVE_LOW>;   /* button1 */

    };
  • 取消了gpios前缀“xxx-",相应地,在驱动程序用gpiod_get_index_optional函数获取gpio描述符时,第2个形参 ”const char *con_id“ 传递NULL即可;
  • 将pinctrl-0、gpios属性值由 “<>,<>;” 改为 “<>;",效果是一样的
  1. 应用程序(不作修改)
/*
 * 文件名   :  button_drv_test.c
 * 作者     :  glen
 * 描述     :  button_drv应用程序
 */

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    char kval;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, &kval, 1);
        if (ret <= 0) {
            printf("Read glen button value failed!\r\n");
            close(fd);
            return -1;
        } else {
            printf("The glen button value is: %d!\r\n", kval);
        }
        sleep(1);
    }

    /* 关闭文件 */
    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}
  1. 在alientek_linux_alpha开发板实测验证如下
/ # cd drv_module/
/drv_module # insmod button_drv.ko
The button driver is registered.
/drv_module # ./btn_drv_test /dev/gbtn0
Set the button0 pin as input successfully!
button0 18 1
Read button0 value successfully:The glen button value is: 33!

button0 18 0
Read button0 value successfully:The glen button value is: 32!

random: nonblocking pool is initialized
button0 18 1
Read button0 value successfully:The glen button value is: 33!

button0 18 0
Read button0 value successfully:The glen button value is: 32!

button0 18 1
Read button0 value successfully:The glen button value is: 33!

button0 18 0
Read button0 value successfully:The glen button value is: 32!
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值