26、基于Firefly-rk3399,读取按键上升下降沿中断

一、中断处理过程流程图:

中断控制器(GIC)

在这里插入图片描述

**中断原则:**a.不能嵌套

​ b.处理函数越快越好

**耗时中断的处理:**a.分为上半部和下半部

​ b.内核线程处理中断

中断上半部:在CPU处理紧急事件时候,不可以被其他中断打断,此时cpu关中断

中断下半部:在CPU处理非紧急事件时候,可以被其他中断打断

在这里插入图片描述

​ 所以有时候底半部相应会比较慢,为了解决此问题,引入内核线程(work pthread)、工作队列(work queue),在执行中断上半部时,往队列放入处理函数。

二、中断底半部机制:
1、软中断

Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中,内容如下:

struct softirq_action
{
 	void (*action)(struct softirq_action *);
};

在 kernel/softirq.c 文件中一共定义了 10 个软中断,如下所示:

static struct softirq_action softirq_vec[NR_SOFTIRQS];

NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:

enum
{
     HI_SOFTIRQ=0, /* 高优先级软中断 */
     TIMER_SOFTIRQ, /* 定时器软中断 */
     NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
     NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
     BLOCK_SOFTIRQ, 
     IRQ_POLL_SOFTIRQ, 
     TASKLET_SOFTIRQ, /* tasklet 软中断 */
     SCHED_SOFTIRQ, /* 调度软中断 */
     HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
     RCU_SOFTIRQ, /* RCU 软中断 */
     NR_SOFTIRQS
};

NR_SOFTIRQS 为 10

softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,全局数组softirq_vec 传入action ,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个 CPU 所执行的软中断服务函数确是相同的,都是数组 softirq_vec 中定义的 action 函数。要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数,open_softirq 函数原型如下:

void open_softirq(int nr, void (*action)(struct softirq_action *))

nr:要开启的软中断,在示例代码 31.1.2.3 中选择要开启的软中断。

action:软中断对应的处理函数

注册好软中断以后需要通过 raise_softirq 函数触发,raise_softirq 函数原型如下:

void raise_softirq(unsigned int nr)

nr:要触发的软中断,在示例代码 31.1.2.3 中选择要注册的软中断。

**返回值:**没有返回值。

软中断必须在编译的时候静态注册!Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面,函数内容如下:

void __init softirq_init(void)
{
     int cpu;

     for_each_possible_cpu(cpu) {
         per_cpu(tasklet_vec, cpu).tail =
         &per_cpu(tasklet_vec, cpu).head;
         per_cpu(tasklet_hi_vec, cpu).tail =
         &per_cpu(tasklet_hi_vec, cpu).head;
     }

     open_softirq(TASKLET_SOFTIRQ, tasklet_action);
     open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
softirq_init 函数默认会打开 TASKLET_SOFTIRQ 和HI_SOFTIRQ
2、tasklet

tasklet 是利用软中断来实现的另外一种下半部机制

/* 用于创建tasklet变量 */
struct tasklet_struct
{
    struct tasklet_struct *next; /* 下一个 tasklet */
    unsigned long state; /* tasklet 状态 */
    atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
    void (*func)(unsigned long); /* tasklet 执行的函数 */
    unsigned long data; /* 函数 func 的参数 */
};

func 函数就是 tasklet 要执行的处理函数,用户实现具体的函数内容,相当于中断处理函数。

① tasklet_init

如果要使用 tasklet,必须先定义一个 tasklet_struct 变量,然后使用 tasklet_init 函数对其进行初始化,taskled_init 函数原型如下:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

t:要初始化的tasklet,即struct tasklet_struct定义的值

func: tasklet 的处理函数。task_function

**data:**要传递给 func 函数的参数

**返回值:**没有返回值。

也 可 以 使 用 宏 DECLARE_TASKLET 来 一 次 性 完 成 tasklet 的 定 义 和 初 始 化 ,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下:

DECLARE_TASKLET(name, func, data)

其中 name 为要定义的 tasklet 名字,其实就是 tasklet_struct 类型的变量名,func 就是 tasklet的处理函数,data 是传递给 func 函数的参数。

②tasklet_schedule

在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下

void tasklet_schedule(struct tasklet_struct *t)

函数参数和返回值含义如下:

t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。

**返回值:**没有返回值。

tasklet框架:

/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 , 由tasklet_init传入*/
void testtasklet_func(unsigned long data)
{
	 /* tasklet 具体处理内容 */
}
/* 中断处理函数 由request_irq传入 */
irqreturn_t test_handler(int irq, void *dev_id)
{
     ......
     /* 调度 tasklet 传入tasklet结构体 */
     tasklet_schedule(&testtasklet);
     ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
     ......
     /* 初始化 tasklet 传入 tasklet  tasklet函数 tasklet函数的参数*/
     tasklet_init(&testtasklet, testtasklet_func, data);
     /* 注册中断处理函数 */
     request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
     ......
}
3、工作队列

​ 工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。

Linux 内核使用 work_struct 结构体表示一个工作,内容如下(省略掉条件编译):

struct work_struct {
     atomic_long_t data; 
     struct list_head entry;
     work_func_t func; /* 工作队列处理函数 */
};

这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下(省略掉条件编译):

struct workqueue_struct {
     struct list_head pwqs; 
     struct list_head list; 
     struct mutex mutex; 
     int work_color;
     int flush_color; 
     atomic_t nr_pwqs_to_flush;
     struct wq_flusher *first_flusher;
     struct list_head flusher_queue; 
     struct list_head flusher_overflow;
     struct list_head maydays; 
     struct worker *rescuer; 
     int nr_drainers; 
     int saved_max_active;
     struct workqueue_attrs *unbound_attrs;
     struct pool_workqueue *dfl_pwq; 
     char name[WQ_NAME_LEN];
     struct rcu_head rcu;
     unsigned int flags ____cacheline_aligned;
     struct pool_workqueue __percpu *cpu_pwqs;
     struct pool_workqueue __rcu *numa_pwq_tbl[];
};

​ Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用worker 结构体表示工作者线程,worker 结构体内容如下:

struct worker {
     union {
     struct list_head entry; 
     struct hlist_node hentry;
     };
     struct work_struct *current_work; 
     work_func_t current_func; 
     struct pool_workqueue *current_pwq;
     struct list_head scheduled; 
     struct task_struct *task; 
     struct worker_pool *pool; 
     struct list_head node; 
     unsigned long last_active; 
     unsigned int flags; 
     int id; 
     int sleeping; 
     char desc[WORKER_DESC_LEN];
     struct workqueue_struct *rescue_wq;
     work_func_t last_func;
};

​ 每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。

①INIT_WORK

简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:

#define INIT_WORK(_work, _func)

_work 表示要初始化的工作,_func 是工作对应的处理函数。也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)

n 表示定义的工作(work_struct),f 表示工作对应的处理函数。

②schedule_work

和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

bool schedule_work(struct work_struct *work)

函数参数和返回值含义如下:

work:要调度的工作。

**返回值:**0 成功,其他值 失败

工作队列使用框架:

/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
 	/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
     ......
     /* 调度 work */
     schedule_work(&testwork);
     ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
     ......
     /* 初始化 work */
     INIT_WORK(&testwork, testwork_func_t);
     /* 注册中断处理函数 */
     request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
     ......
}
三、设备树中的中断
interrupt-parent = <&GPIO1>
interrupts = <5  ris>
5为硬件中断号,xlate解析设备树信息,irq_domain会将5转化为虚拟中断号传递给platform_device
之后就可以使用request_irq了

设备树中中断节点语法:

硬件框图:

在这里插入图片描述

​ 在硬件上,“中断控制器”只有 GIC 这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。很多芯片有多个 GPIO 模块,比如 GPIO1、GPIO2 等等。所以软件上的“中断控制器”就有很多个:GIC、GPIO1、GPIO2 等等。

​ GPIO1 连接到 GIC,GPIO2 连接到 GIC,所以 GPIO1 的父亲是 GIC,GPIO2 的父亲是 GIC。

1、中断控制器的三个主要属性:
compatible = {...};
/* 表明它是“中断控制器 */ 
interrupt-controller;
/* 表明引用这个中断控制器的话需要多少个 cell,
① #interrupt-cells=<1>
	别的节点要使用这个中断控制器时,只需要一个 cell 来表明使用“哪一个中断”,指明中断类型。
② #interrupt-cells=<2>
	别的节点要使用这个中断控制器时,需要一个 cell 来表明使用“哪一个中断 还需要另一个 cell 来描述中断,一般是表明触发类型”
	第 2 个 cell 的 bits[3:0] 用来表示中断触发类型(trigger type and level flags):
	1 = low-to-high edge triggered,上升沿触发
	2 = high-to-low edge triggered,下降沿触发
	4 = active high level-sensitive,高电平触发
	8 = active low level-sensitive,低电平触发 */
#interrupt-cells = <1>;
需要指定使用的是哪个中断控制器的哪一个中断
    interrupt-parent = <&GPIO1>
    interrupts = <5  ris>
/* 对于 ARM 处理的GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:第一个 cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 32~287(256 个),对于 PPI 中断来说中断号的范围为 16~31,但是该 cell 描述的中断号是从 0 开始。第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 中断的 CPU 掩码。*/
2、中断相关api
① request_irq
request_irq 函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断
原型:
    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了

使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。
void free_irq(unsigned int irq, void *dev)

irq:要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

使用 request_irq 函数申请中断的时候需要设置中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)

第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构

②enable_irq disable_irq
常用的中断使用和禁止函数
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。

③disable_irq_nosync

disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

④local_irq_enable

有时候我们需要关闭当前处理器的整个中断系统,也就是在学习 STM32 的时候常说的关闭全局中断,这个时候可以使用如下两个函数

local_irq_enable()
local_irq_disable()

local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。

local_irq_restore 用于恢复中断,将中断到 flags 状态。

四、Firefly-rk3399实际操作
1、修改设备树:

①pinctl子系统使能GPIO0_B4、GPIO4_D4

key_diy@0: {
	compatible = "rk3399, keydrv";
	key-gpios = <&gpio0 12 GPIO_ACTIVE_HIGHT>
};
key_diy@1: {
	compatible = "rk3399, keydrv";
	key-gpios = <&gpio4 29 GPIO_ACTIVE_HIGHT>
};

②在此节点基础上增加中断信息

 key_diy@0 {
     compatible = "rk3399, keydrv";
     // GPIO0_B4  高电平有效
     gpios = <&gpio0 12 GPIO_ACTIVE_HIGH
              &gpio4 29 GPIO_ACTIVE_HIGH>;
 };
注意:
     key_diy@0 {
     compatible = "rk3399, keydrv";
     // GPIO0_B4  高电平有效
     key-gpios = <&gpio0 12 GPIO_ACTIVE_HIGH
              &gpio4 29 GPIO_ACTIVE_HIGH>;
 	};
此写法调用of_gpio_count报错。
在设备树中,gpio =, gpios =, xxx-gpios =, 和 xxx-gpio = 都是用来配置 GPIO 引脚的属性,但它们的使用场景和具体含义有所不同。以下是它们的区别及用法:

1. gpio =
用途:用于描述单个 GPIO 引脚的配置。
语法:单个 GPIO 引脚的控制器、引脚编号和标志(例如 GPIO_ACTIVE_HIGH 或 GPIO_ACTIVE_LOW)。
场景:通常用于简单的单个 GPIO 控制设备,如 LED、开关等。
示例:
dts
复制代码
led {
    compatible = "gpio-leds";
    gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>;
};
解释:表示使用 gpio0 控制器的第 10 个引脚,且该引脚为高电平激活。
2. gpios =
用途:用于描述多个 GPIO 引脚的配置。
语法:可以指定多个 GPIO 引脚,每个引脚由 <&gpioX pin_number gpio_flags> 描述,多个引脚之间使用逗号或换行分隔。
场景:适用于涉及多个 GPIO 引脚的设备,如多个 LED、多个按键输入等。
示例:
dts
复制代码
leds {
    compatible = "gpio-leds";
    gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>,
            <&gpio1 5 GPIO_ACTIVE_LOW>;
};
解释:该设备使用了两个 GPIO 引脚:gpio0 控制器的第 10 引脚(高电平激活)和 gpio1 控制器的第 5 引脚(低电平激活)。
3. xxx-gpio =
用途:用于描述与特定功能相关的单个 GPIO 引脚。这里的 xxx 代表功能的名称,例如 reset, power, enable 等,通常与特定功能的硬件引脚相关。
语法:类似于 gpio =,但 xxx-gpio 用于描述特定功能的引脚,表示该引脚控制某个硬件功能。
场景:通常用于需要指定特定硬件功能的 GPIO 引脚,如重置引脚、使能引脚等。
示例:
dts
复制代码
reset {
    compatible = "gpio-reset";
    reset-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
};
解释:表示使用 gpio0 控制器的第 12 引脚作为重置引脚,且该引脚的低电平激活。
4. xxx-gpios =
用途:用于描述与特定功能相关的多个 GPIO 引脚。和 gpios = 类似,但它适用于特定功能的多个 GPIO 引脚配置。
语法:可以列出多个 GPIO 引脚,每个引脚由 <&gpioX pin_number gpio_flags> 描述,通常用于描述与某个硬件功能相关的多个引脚,如多个按键输入、多个复位引脚等。
场景:用于涉及多个 GPIO 引脚的特定功能,例如多个按键、多个复位引脚等。
示例:
dts
复制代码
gpio_keys {
    compatible = "gpio-keys";
    key1-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>;
    key2-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
};
解释:表示 gpio_keys 设备使用了两个按键,每个按键都连接到不同的 GPIO 引脚,其中 key1 使用 gpio0 控制器的第 10 引脚(高电平激活),而 key2 使用 gpio1 控制器的第 12 引脚(低电平激活)。
总结:四者的区别
属性	用途	设备类型	语法示例
gpio =	单个 GPIO 引脚配置	描述单个 GPIO 引脚的设备	gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>;
gpios =	多个 GPIO 引脚配置	描述多个 GPIO 引脚的设备	gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>, <&gpio1 5 GPIO_ACTIVE_LOW>;
xxx-gpio =	特定功能的单个 GPIO 引脚配置	描述与特定功能相关的单个 GPIO 引脚	reset-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
xxx-gpios =	特定功能的多个 GPIO 引脚配置	描述与特定功能相关的多个 GPIO 引脚	key-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>, <&gpio1 5 GPIO_ACTIVE_LOW>;
小结
gpio 和 gpios 用于描述 GPIO 引脚,区别在于是否涉及多个 GPIO 引脚。
xxx-gpio 和 xxx-gpios 用于描述与特定功能相关的 GPIO 引脚,区别在于单个 GPIO 配置与多个 GPIO 配置。
在实际应用中,gpio 和 gpios 多用于常见的 GPIO 控制,而 xxx-gpio 和 xxx-gpios 更多用于与特定硬件功能(如重置、按键等)相关的 GPIO 配置。
2、驱动编写

相关函数介绍(只写本次使用的):

int of_gpio_count(const struct device_node *np);
np:可以直接通过of_find_node_by_name获取,也可以通过pdev->dev.of_node得到
np: 设备树节点的指针,指向当前设备的设备树节点。设备树节点包含了设备的所有信息,包括与 GPIO 相关的配置。
返回值
返回一个整数,表示设备树中该设备节点下定义的 GPIO 引脚的数量。
返回负值时,表示发生了错误,常见的错误返回值包括:
 -EINVAL:表示设备树节点没有 gpios 属性。
 -ENODATA:设备树节点没有找到有效的 GPIO 数据。
kzalloc  : kernel  zero  alloc
int of_get_gpio_flags(const struct device_node *np, int index, unsigned int *flags);
参数说明
np: 设备树节点指针,指向需要解析的设备树节点。该节点通常包含有 GPIO 配置的属性(例如 gpios)。
index: GPIO 引脚的索引,表示你希望获取的 GPIO 引脚在 gpios 属性数组中的位置。索引从 0 开始。
flags: 用于返回 GPIO 标志的指针。该指针指向一个 unsigned int 变量,函数会通过此变量返回与 GPIO 引脚相关的标志信息,例如引脚的方向、激活电平等。
返回值
成功:返回 0,并将 GPIO 标志存储在 flags 中。
失败:返回负数错误码,表示无法找到 GPIO 引脚或解析失败。
功能描述
of_get_gpio_flags 函数会根据传入的设备树节点 np 和 index,从设备树中解析出对应 GPIO 引脚的标志信息。设备树中的 GPIO 通常会包括以下标志信息:
GPIO 的激活电平(例如高电平激活或低电平激活)。
GPIO 的触发方式(例如边缘触发或电平触发)。
GPIO 的方向(输入或输出)
int gpio_to_irq(unsigned int gpio);
参数说明
gpio: 要转换为中断号的 GPIO 引脚的编号(不是描述符)。该引脚必须已被配置为能够生成中断(例如,通过设备树或通过 API 设置为中断触发)。
返回值
成功:返回一个中断号,表示该 GPIO 引脚关联的中断。
失败:返回一个负数错误码,表示 GPIO 引脚无法转换为中断号。常见的错误包括:
-EINVAL:表示该 GPIO 引脚没有配置为中断源,或者是一个无效的 GPIO 引脚。
-ENOTTY:表示该 GPIO 引脚无法支持中断。
功能描述
gpio_to_irq 函数将一个 GPIO 引脚的编号转换为一个有效的中断号。在 Linux 内核中,GPIO 引脚可以被配置为触发中断事件(例如,电平变化或边沿触发),gpio_to_irq 允许驱动程序通过获取中断号来绑定中断处理程序,从而在引脚的电平或状态发生变化时响应中断。
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *devname, void *dev_id);
参数说明
irq: 中断号,表示你希望请求的中断。这是一个整数,通常是由硬件或设备树提供,或者通过其他方式确定。
handler: 中断处理程序,是一个函数指针,指向该中断号发生时内核调用的函数。该函数将被内核调用来处理对应的中断事件。
flags: 中断标志,控制中断的触发方式和其他配置。例如,指定是上升沿、中断触发类型,是否允许共享中断等。常见的标志包括:
        IRQF_TRIGGER_RISING:上升沿触发
        IRQF_TRIGGER_FALLING:下降沿触发
        IRQF_SHARED:表示允许多个设备共享同一个中断号
        IRQF_DISABLED:在处理中断时禁用本地中断(通常仅用于某些内核的低层实现)
devname: 设备名称,用于标识请求该中断的设备。它通常是一个字符串,表示设备的名字,例如 "gpio_button"。随便写
dev_id: 设备特定数据,它通常是设备的指针或者与该设备相关的结构体的指针。它会传递给中断处理程序,以便在处理中断时访问特定设备的状态或数据。

返回值
成功:返回 0,表示成功注册了中断处理程序。
失败:返回一个负数错误码,表示请求中断失败。常见的错误码包括:
-EBUSY:表示中断号已经被其他处理程序占用。
-EINVAL:表示提供的参数无效。
功能描述
request_irq 函数将指定的中断号与一个处理程序(IRQ handler)进行绑定。内核将会在该中断号触发时调用处理程序。在中断处理程序中,你可以根据需要处理硬件中断事件,比如读取数据、更新设备状态等。
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>

struct gpio_key {
	int gpio;
	int irq;
	enum of_gpio_flags flags;
};

struct gpio_key *gpios_key;

static irqreturn_t gpio_key_rk3399(int irq, void *dev_id)
{
	struct gpio_key *gpios_key1 = dev_id;

	printk("key %d val %d\r\n", irq, gpio_get_value(gpios_key1->gpio));

	return IRQ_HANDLED;
}

static int rk3399_key_probe(struct platform_device *pdev) {
	int count = 0;
	int i = 0;
	int err = 0;

	printk("enter interrupt\r\n");
    /* 获取gpio数 */
	count = of_gpio_count(pdev->dev.of_node);
	printk("count is %d\r\n", count);
	gpios_key = kzalloc(count * sizeof(struct gpio_key), GFP_KERNEL);
    /* 设置中断方式 */
	for(i=0; i<count; i++) {
		gpios_key[i].gpio = of_get_gpio_flags(pdev->dev.of_node, i, &(gpios_key[i].flags));
		gpios_key[i].irq = gpio_to_irq(gpios_key[i].gpio);
		err = request_irq(gpios_key[i].irq, gpio_key_rk3399, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "rk3399_key", &gpios_key[i]);
	}

	return err;
}

static int rk3399_key_remove(struct platform_device *pdev) {
	int count = 0;
	int i = 0;
	
	count = of_gpio_count(pdev->dev.of_node);
	for(i=0; i<count; i++) {
		free_irq(gpios_key[i].irq, &gpios_key[i]);
	}
	
	return 0;
}


static const struct of_device_id firefly_rk3399_key[] = {
	{ .compatible = "rk3399, keydrv" },
	{},
};

struct platform_driver key_interrupt_drv = {
	.probe = rk3399_key_probe,
	.remove = rk3399_key_remove,
	.driver = {
        /* 匹配platform_device */
		.name = "rk3399",
        /* 匹配设备树platform_device */
		.of_match_table = firefly_rk3399_key,
	},
};

/* 入口函数 申请platform设备驱动 */
static int gpio_interrupt_init(void) {
	return platform_driver_register(&key_interrupt_drv);
}

static void gpio_interrupt_exit(void) {
	platform_driver_unregister(&key_interrupt_drv);
}

module_init(gpio_interrupt_init);
module_exit(gpio_interrupt_exit);
MODULE_LICENSE("GPL");

编译内核:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

编译设备树:

make dtbs ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
cp arch/arm64/boot/dts/rockchip/rk3399-firefly-linux.dtb boot/rk3399.dtb
genext2fs -b 32768 -B $((64*1024*1024/32768)) -d boot/ -i 8192 -U boot.img

编译驱动:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值