此文记录Linux下GPIO中断的几种配置方式,方法主要分为两类,一种是在驱动代码中声明GPIO信息,另一种从dts文件中读取GPIO信息,后者在嵌入式软件编程中更为通用。
pin写死和dts方法中间还有一个platform device/driver匹配模型的过渡,由于篇幅有限,请读者自查。

pin写死方法

gpio配置信息写在驱动代码中,也可以在安装驱动时在命令控制台设置gpio参数。中断方面分别使用tasklet和work queue分别实现底半层。以下代码均已验证。

使用tasklet机制驱动中断底半层

tasklet本身是基于软中断实现,优先级是高于进程而低于硬件中断,所以tasklet对应的延后处理函数不能进行休眠操作(比如sleep(select未验证)),tasklet对应的延后处理函数执行原先中断处理函数中比较耗时和不紧急的内容。

#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/workqueue.h>


//模块参数,可在控制台输入参数,GPIO组号、组内偏移、方向、输出时的输出初始值,这里只是缺省值
//参数名称在/sys/module/目录
static unsigned int gpio_chip_num = 3;
module_param(gpio_chip_num, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_chip_num, "gpio chip num");
 
static unsigned int gpio_offset_num = 0;
module_param(gpio_offset_num, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_offset_num, "gpio offset num");
 
/* 
* 0 - input
* 1 - output
 */
static unsigned int gpio_dir = 1;
module_param(gpio_dir, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_dir, "gpio dir");
 
static unsigned int gpio_out_val = 1;
module_param(gpio_out_val, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_out_val, "gpio out val");
 

/*模块参数,中断触发类型
 * 0 - disable irq
 * 1 - rising edge triggered
 * 2 - falling edge triggered
 * 3 - rising and falling edge triggered
 * 4 - high level triggered
 * 8 - low level triggered
 */
static unsigned int gpio_irq_type = 2;
module_param(gpio_irq_type, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_irq_type, "gpio irq type");
 
spinlock_t lock;

/* tasklet */
void btn_tasklet_func(unsigned long data);
DECLARE_TASKLET(btn_tasklet,btn_tasklet_func, 1);
void btn_tasklet_func(unsigned long data)
{
	//做原先中断处理函数中耗时不紧急的内容
        int i;
        for(i = 0;i < 5; i++)
        {
                printk("this is %d\r\n",i);
                // ssleep(1); 无法使用调度操作
        }
}

int g_data = 0;




//中断处理函数
static irqreturn_t irq_handler(int irq, void *dev_id)
{
        printk("顶半部:%s\n", __func__);
        tasklet_schedule(&btn_tasklet);
        return IRQ_HANDLED;  
}
 
static int __init gpio_dev_test_init(void)
{
        unsigned int gpio_num;
        unsigned int irq_num;
        unsigned int irqflags = 0;

 
        //初始化自旋锁lock
        spin_lock_init(&lock);
 
        gpio_num = gpio_chip_num * 32 + gpio_offset_num;
        //注册要操作的GPIO编号
        
 
        /* 一般gpio_request封装了mem_request(),起保护作用,最后要调用mem_free之类的。主要是告诉内核这地址被占用了。当其它地方调用同一地址的gpio_request就会报告错误,该地址已被申请。在/proc/mem应该会有地址占用表描述。
        这种用法的保护作用前提是大家都遵守先申请再访问,有一个地方没遵守这个规则,这功能就失效了。好比进程互斥,必需大家在访问临界资源的时候都得先获取锁一样,其中一个没遵守约定,代码就废了。 */
        if (gpio_request(gpio_num, NULL)) {
                pr_err("[%s %d]gpio_request fail! gpio_num=%d \n",
                 __func__, __LINE__, gpio_num);
                goto out;
        }
       
        //设置方向
        if(gpio_dir){
                //设置方向为输入
                if (gpio_direction_input(gpio_num)) {
                        pr_err("[%s %d]gpio_direction_input fail!\n",
                        __func__, __LINE__);
                        goto out;
                }
                //读出GPIO输入值
                pr_info ("[%s %d]gpio%d_%d in %d\n", __func__, __LINE__,
                        gpio_num / 32, gpio_num % 32,gpio_get_value(gpio_num));
        }else{
                //设置方向为输出,并输出一个初始值
                if (gpio_direction_output(gpio_num, !!gpio_out_val)) {
                pr_err("[%s %d]gpio_direction_output fail!\n",__func__, __LINE__);
                        goto out;
                pr_info("[%s %d]gpio%d_%d out %d\n", __func__, __LINE__,gpio_num / 8, gpio_num % 8, !!gpio_out_val);
                }
        }
        
        //设置中断类型
        switch (gpio_irq_type) {
                case 1:
                        irqflags = IRQF_TRIGGER_RISING;
                        break;
                case 2:
                        irqflags = IRQF_TRIGGER_FALLING;
                        break;
                case 3:
                        irqflags = IRQF_TRIGGER_RISING |
                                IRQF_TRIGGER_FALLING;
                        break;
                case 4:
                        irqflags = IRQF_TRIGGER_HIGH;
                        break;
                case 8:
                        irqflags = IRQF_TRIGGER_LOW;
                        break;
                default:
                        pr_info("[%s %d]gpio_irq_type error!\n",
                        __func__, __LINE__);
                        goto out;
        }
 
        pr_info("[%s %d]gpio_irq_type = %d\n", __func__, __LINE__, gpio_irq_type);
 
        /* IRQF_SHARED:这个中断标志经常能遇见,这个标志意思就是多个中断处理程序之间可以共享中断线,概括起来就是没有这个标志就只能独自一个人占用,标志了,就是很多人可以占用这个中断号来 */
        irqflags |= IRQF_SHARED;
        //根据GPIO编号映射中断号
        irq_num = gpio_to_irq(gpio_num);
        printk("irq_num is %d\r\n",irq_num);
        /*  注册中断
            irq_num:由gpio_to_irq()函数获取的中断号
            irq_handler:中断顶半部触发函数
            irqflags:中断触发类型
            "gpio_dev_test":设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称。
            dev_id:最后一个参数,看到第三个参数中IRQF_SHARED时候,你会不会有这样的疑问,假如现在我要释放当前共享的指定这个中断程序时候,我如何释放?会不会把其他占用也会删除掉。
                    这就是第五个参数的意义,如果中断线是共享的,那么就必须传递能够代表当前设备的唯一信息。
 
            函数返回值:成功返回0。如果返回非0,就表示有错误发生,这个时候你可以考虑当前中断是否被占用了,所以可以加上IRQF_SHARED标志
        */
        if (request_irq(irq_num, irq_handler, irqflags,"gpio_dev_test", &gpio_irq_type)) {
                pr_info("[%s %d]request_irq error!\n", __func__, __LINE__);
                goto out;
        }

        return 0;
 
out:
gpio_free(gpio_num);
return -EIO;
}
 
 
static void __exit gpio_dev_test_exit(void)
{
        unsigned int gpio_num;
        unsigned long flags;
 
        pr_info("[%s %d]\n", __func__, __LINE__);
 
        gpio_num = gpio_chip_num * 32 + gpio_offset_num;
 
        if (gpio_irq_type){
                printk("exit \r\n");
                tasklet_kill(&btn_tasklet);
                //释放注册的中断
                spin_lock_irqsave(&lock, flags);
                free_irq(gpio_to_irq(gpio_num), &gpio_irq_type);
                spin_unlock_irqrestore(&lock, flags);
        }
        //释放注册的GPIO编号
        gpio_free(gpio_num);
}
 
module_init(gpio_dev_test_init);
module_exit(gpio_dev_test_exit);
 
MODULE_DESCRIPTION("GPIO device test Driver sample");
MODULE_LICENSE("GPL");

使用work_queue机制驱动中断底半层

工作队列诞生的本质就是解决tasklet的延后处理函数不能进行休眠操作的问题,因为在某些场合需要在延后的处理流程中进行休眠操作,对于这样的情况必须使用工作队列,

#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/workqueue.h>


//模块参数,可在控制台输入参数,GPIO组号、组内偏移、方向、输出时的输出初始值,这里只是缺省值
//参数名称在/sys/module/目录
//这里对应datasheet中的GPIO3_O端口
static unsigned int gpio_chip_num = 3;
module_param(gpio_chip_num, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_chip_num, "gpio chip num");
 
static unsigned int gpio_offset_num = 0;
module_param(gpio_offset_num, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_offset_num, "gpio offset num");
 
/* 
* 0 - output
* 1 - input
*外部中断只可将GPIO设置为input
 */
static unsigned int gpio_dir = 1;
module_param(gpio_dir, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_dir, "gpio dir");
 
static unsigned int gpio_out_val = 1;
module_param(gpio_out_val, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_out_val, "gpio out val");
 

/*模块参数,中断触发类型
 * 0 - disable irq
 * 1 - rising edge triggered
 * 2 - falling edge triggered
 * 3 - rising and falling edge triggered
 * 4 - high level triggered
 * 8 - low level triggered
 */
static unsigned int gpio_irq_type = 2;
module_param(gpio_irq_type, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_irq_type, "gpio irq type");
 
spinlock_t lock;

/* work queue */
struct work_struct my_wq;
void my_do_work(struct work_struct *work)
{
        int i;
        for(i = 0;i < 5; i++)
        {
                printk("this is %d\r\n",i);
                ssleep(1);
        }
}

//中断处理函数
static irqreturn_t irq_handler(int irq, void *dev_id)
{
        printk("hello,irq test\r\n");
        schedule_work(&my_wq); //work queue
        return IRQ_HANDLED;  
        //tasklet_schedule(&my_tasklet);
        
}
 
static int __init gpio_dev_test_init(void)
{
        unsigned int gpio_num;
        unsigned int irq_num;
        unsigned int irqflags = 0;
 
        //初始化自旋锁lock
        spin_lock_init(&lock);
 
        gpio_num = gpio_chip_num * 32 + gpio_offset_num;
        //注册要操作的GPIO编号
        //tasklet_init(&my_tasklet,my_do_tasklet,0);
 
        /* 一般gpio_request封装了mem_request(),起保护作用,最后要调用mem_free之类的。主要是告诉内核这地址被占用了。当其它地方调用同一地址的gpio_request就会报告错误,该地址已被申请。在/proc/mem应该会有地址占用表描述。
        这种用法的保护作用前提是大家都遵守先申请再访问,有一个地方没遵守这个规则,这功能就失效了。好比进程互斥,必需大家在访问临界资源的时候都得先获取锁一样,其中一个没遵守约定,代码就废了。 */
        if (gpio_request(gpio_num, NULL)) {
                pr_err("[%s %d]gpio_request fail! gpio_num=%d \n",
                 __func__, __LINE__, gpio_num);
                goto out;
        }
       
        //设置方向
        if(gpio_dir){
                //设置方向为输入
                if (gpio_direction_input(gpio_num)) {
                        pr_err("[%s %d]gpio_direction_input fail!\n",
                        __func__, __LINE__);
                        goto out;
                }
                //读出GPIO输入值
                pr_info ("[%s %d]gpio%d_%d in %d\n", __func__, __LINE__,
                        gpio_num / 32, gpio_num % 32,gpio_get_value(gpio_num));
        }else{
                //设置方向为输出,并输出一个初始值
                if (gpio_direction_output(gpio_num, !!gpio_out_val)) {
                pr_err("[%s %d]gpio_direction_output fail!\n",__func__, __LINE__);
                        goto out;
                pr_info("[%s %d]gpio%d_%d out %d\n", __func__, __LINE__,gpio_num / 8, gpio_num % 8, !!gpio_out_val);
                }
        }
        
        //设置中断类型
        switch (gpio_irq_type) {
                case 1:
                        irqflags = IRQF_TRIGGER_RISING;
                        break;
                case 2:
                        irqflags = IRQF_TRIGGER_FALLING;
                        break;
                case 3:
                        irqflags = IRQF_TRIGGER_RISING |
                                IRQF_TRIGGER_FALLING;
                        break;
                case 4:
                        irqflags = IRQF_TRIGGER_HIGH;
                        break;
                case 8:
                        irqflags = IRQF_TRIGGER_LOW;
                        break;
                default:
                        pr_info("[%s %d]gpio_irq_type error!\n",
                        __func__, __LINE__);
                        goto out;
        }
 
        pr_info("[%s %d]gpio_irq_type = %d\n", __func__, __LINE__, gpio_irq_type);
 
        /* IRQF_SHARED:这个中断标志经常能遇见,这个标志意思就是多个中断处理程序之间可以共享中断线,概括起来就是没有这个标志就只能独自一个人占用,标志了,就是很多人可以占用这个中断号来 */
        irqflags |= IRQF_SHARED;
        //根据GPIO编号映射中断号
        irq_num = gpio_to_irq(gpio_num);
        printk("irq_num is %d\r\n",irq_num);
        /*  注册中断
            irq_num:由gpio_to_irq()函数获取的中断号
            irq_handler:中断顶半部触发函数
            irqflags:中断触发类型
            "gpio_dev_test":设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称。
            dev_id:最后一个参数,看到第三个参数中IRQF_SHARED时候,你会不会有这样的疑问,假如现在我要释放当前共享的指定这个中断程序时候,我如何释放?会不会把其他占用也会删除掉。
                    这就是第五个参数的意义,如果中断线是共享的,那么就必须传递能够代表当前设备的唯一信息。
 
            函数返回值:成功返回0。如果返回非0,就表示有错误发生,这个时候你可以考虑当前中断是否被占用了,所以可以加上IRQF_SHARED标志
        */
        if (request_irq(irq_num, irq_handler, irqflags,"gpio_dev_test", &gpio_irq_type)) {
                pr_info("[%s %d]request_irq error!\n", __func__, __LINE__);
                goto out;
        }

        INIT_WORK(&my_wq,my_do_work); //work queue
        return 0;
 
out:
gpio_free(gpio_num);
return -EIO;
}
 
 
static void __exit gpio_dev_test_exit(void)
{
        unsigned int gpio_num;
        unsigned long flags;
 
        pr_info("[%s %d]\n", __func__, __LINE__);
 
        gpio_num = gpio_chip_num * 32 + gpio_offset_num;
 
        if (gpio_irq_type){
                printk("exit \r\n");
                //释放注册的中断
                spin_lock_irqsave(&lock, flags);
                free_irq(gpio_to_irq(gpio_num), &gpio_irq_type);
                spin_unlock_irqrestore(&lock, flags);
        }
        //释放注册的GPIO编号
        gpio_free(gpio_num);
}
 
module_init(gpio_dev_test_init);
module_exit(gpio_dev_test_exit);
 
MODULE_DESCRIPTION("GPIO device test Driver sample");
MODULE_LICENSE("GPL");

dts方法

dts方法不从驱动中获取gpio信息,而是从记录板级信息的dts文件中读取。

示例am335X.dts中的按键gpio配置

gpio_keys: volume_keys@0 {
		compatible = "gpio-keys";
		#address-cells = <1>;
		#size-cells = <0>;
		autorepeat;

		switch@9 {
			label = "volume-up";
			linux,code = <115>;
			gpios = <&gpio3 0 GPIO_ACTIVE_LOW>;
			gpio-key,wakeup;
		};

		switch@10 {
			label = "volume-down";
			linux,code = <114>;
			gpios = <&gpio0 29 GPIO_ACTIVE_LOW>;
			gpio-key,wakeup;
		};
	};

示例按键驱动,实现了多个dts配置的按键同时共享中断,中断的底板使用work_queue机制

#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
 
#define DTS_DRIVER_NAME "gpio-keys"

struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
}*gpio_keys;

spinlock_t lock;
 
/* work queue */
struct work_struct my_wq;
void my_do_work(struct work_struct *work)
{
        int i;
        for(i = 0;i < 5; i++)
        {
                printk("this is %d\r\n",i);
                ssleep(1);
        }
}
 
/*中断处理函数*/
static irqreturn_t irq_handler(int irq, void *dev_id)
{
        printk("hello,irq test\r\n");

        schedule_work(&my_wq); //work queue
        return IRQ_HANDLED;  
}

/* 1. 从platform_device获得dts参数
 * 2. 从dts参数中获取GPIO号
 * 2. gpio_request
 * 3. gpio_direction
 * 4. gpio=>irq
 * 5. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct device_node *child_node = NULL;
	int count;
	int i = 0;
	enum of_gpio_flags flag;
	// const char *str = NULL;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_get_child_count(node);

	if (!count||count<0)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		goto out1;
	}

	gpio_keys = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);

	for_each_child_of_node(node,child_node)
	{
		gpio_keys[i].gpio = of_get_gpio_flags(child_node,0,&flag);
		if (gpio_keys[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			goto out1;
		}
		/* of_property_read_u32(child_node, "linux,code", &err);
		printk("get linux,code %d\r\n",err);

		of_property_read_string(child_node,"label",&str);
		printk("str is %s\r\n",str); */
		i++;
	}
	
	for(i = 0 ; i < count ; i++)
	{
		if(gpio_request(gpio_keys[i].gpio, NULL)) {
            printk("[%s %d]gpio_request fail! gpio_num=%d \n",
                 __func__, __LINE__, gpio_keys[i].gpio);
				 goto out2;
        }
		if(gpio_direction_input(gpio_keys[i].gpio))
		{
			printk("[%s %d]gpio_direction_input fail!\n",
                 __func__, __LINE__);
				 goto out2;
		}
		gpio_keys[i].irq  = gpio_to_irq(gpio_keys[i].gpio);
		printk("irq_num is %d\r\n",gpio_keys[i].irq);	
		if(gpio_keys[i].irq < 0)
		{
			printk("[%s %d]irq error, is %d!\n",
                 __func__, __LINE__, gpio_keys[i].irq);
				 goto out2;
		}
		//gpio_keys[i].flag = flag & OF_GPIO_ACTIVE_LOW;

		if (request_irq(gpio_keys[i].irq, irq_handler, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED), "gpio_dev_dts_test", &gpio_keys[i])) {
					printk("[%s %d]request_irq error!\n", __func__, __LINE__);
					goto out2;
        }
	}
	INIT_WORK(&my_wq,my_do_work); //work queue

    return 0;

out2:
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
gpio_free(gpio_keys[1].gpio);
gpio_free(gpio_keys[0].gpio);

out1:
return -EIO;
}

static int gpio_key_remove(struct platform_device *pdev)
{
	//int err;
	struct device_node *node = pdev->dev.of_node;
	unsigned long flags;
	int count;
	int i;

	count = of_get_child_count(node);
	for (i = 0; i < count; i++)
	{
		spin_lock_irqsave(&lock, flags);
		free_irq(gpio_keys[i].irq, &gpio_keys[i]);
		spin_unlock_irqrestore(&lock, flags);
		gpio_free(gpio_keys[i].gpio);
	}
	kfree(gpio_keys);
    return 0;
}


static const struct of_device_id my_keys[] = {
    { .compatible = DTS_DRIVER_NAME },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "gpio_dev_dts_test", //set name by yourself
        .of_match_table = my_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;   
    spin_lock_init(&lock);	//初始化自旋锁lock
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_driver_register(&gpio_keys_driver); 
	return err;
}
module_init(gpio_key_init);
/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数,卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
    platform_driver_unregister(&gpio_keys_driver);
	printk("[%s %d] exit\n", __func__, __LINE__);
}
module_exit(gpio_key_exit);


MODULE_DESCRIPTION("GPIO device dts test Driver sample");\
MODULE_AUTHOR("zeki <zeki_zhao@163.com>");
MODULE_LICENSE("GPL");

使用dts后的驱动结构如下

Android的GPIO中断 linux gpio中断驱动编写_linux

步骤如下

  • gpio_key_init中注册platform_driver驱动。
  • platform_driver中的.of_match_table成员尝试匹配dts文件中对应的"gpio-keys"节点。
  • 在上一步匹配完成后,调用platform_driver中的.probe成员获取"gpio-keys"节点信息,创建设备。
  • 通过for_each_child_of_node宏定义遍历"gpio-keys"节点下的子节点,获取gpio号等信息。
  • 获取gpio号后和写死gpio申请中断方法一样