1、中断号
中断号又称中断线,每个中断都有一个中断号,通过中断号即可区分不同的中断。
2、Linux中断API函数
需要包含头文件“#include <linux/interrupt.h>”
1)、在使用某个中断功能的时候,需要执行“申请中断”
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq:要申请中断的中断号;
handler:中断处理函数,当中断发生以后就会执行此中断处理函数;
fags:中断标志;可以在文件“include/linux/interrupt.h”里面查看所有的中断标志;
name:中断名字,设置以后可以在“/proc/interrupts”文件中看到对应的中断名字;
dev:如果将flags设置为IRQF_SHARED的话,dev用来区分不同的中断。
一般情况下将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。
返回值:0表示中断申请成功,如果返回“-EBUSY”的话表示中断已经被申请过了, 其他负值,表示中断申请失败。
注意:
在“中断服务程序”中,不能使用request_irq()函数;
在“禁止睡眠的代码段”中,不能使用request_irq()函数;
执行request_irq()函数可能会导致睡眠;
request_irq()函数会使能“中断”,不需要我们手动去使能中断。
2)、常用的中断标志
#define IRQF_SHARED 0x00000080
/*
多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq()函数的dev参数就是唯区分他们的标志;
*/
#define IRQF_TRIGGER_NONE 0x00000000 //无触发
#define IRQF_ONESHOT 0x00002000 //单次中断,中断执行一次就结束
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发
#define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发
#define IRQF_TRIGGER_LOW 0x00000008 //低电平触发
3)、在不需要使用某个中断功能的时候,需要执行“释放中断”
void free_irq(unsigned int irq, void *dev)
irq:要释放的中断号。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值:无。
4)、中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
“int”:第1个参数中断号,和request_irq()函数的irq参数保持一致。
“void *”:第2个参数是指向void型的指针,和request_irq()函数的dev参数保持一致。至于用于区分“共享中断”的不同设备,dev也可以是指向设备数据结构。
返回值:为irqreturn_t类型;
enum irqreturn {
IRQ_NONE = (0 << 0), /*中断没有被这个设备处理*/
IRQ_HANDLED = (1 << 0), /*中断被这个设备处理*/
IRQ_WAKE_THREAD = (1 << 1), /*处理程序请求唤醒处理程序线程*/
};
typedef enum irqreturn irqreturn_t;//将irqreturn起个别名叫irqreturn_t
typedef irqreturn_t (*irq_handler_t)(int, void *);
#define IRQ_RETVAL(x) ((x) ? IRQ_HANDLED : IRQ_NONE)
一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED);
5)、中断使能
void enable_irq(unsigned int irq)
irq:要使能的中断号;
需要包含头文件“interrupt.h”
6)、中断禁止
void disable_irq(unsigned int irq)
irq:要禁止的中断号;
注意:
要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。
void disable_irq_nosync(unsigned int irq)
irq:要禁止的中断号;
注意:
disable_irq_nosync()调用以后,会立即返回,不会等待当前中断处理程序执行完毕。
7)、使能总中断和关闭总中断
local_irq_enable() //打开全局中断,需要包含头文件“interrupt.h”
local_irq_disable() //关闭全局中断,需要包含头文件“interrupt.h”
local_irq_save(flags) //用于禁止中断,并且将中断状态保存在flags中;
local_irq_restore(flags) //用于恢复中断,将中断到flags状态
3、上半部和下半部
上半部:上半部就是“中断处理函数”,那些“处理过程较快,占用时间较短的中断程序”由上半部完成。
下半部:那些“处理过程比较耗时的中断服务程序”,放到“下半部”去执行。
4、根据实际情况将中断服务程序的代码放到上半部或下半部,通常依据如下:
1)、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。2)、如果要处理的内容对时间敏感,可以放到上半部。3)、如果要处理的内容与硬件有关,可以放到上半部
4)、如果要处理的内容比较耗时的中断服务程序”,则放到“下半部”去执行。
Linux内核提供了多种下半部机制:软中断,tasklet,工作队列。2.5版本的以前Linux内核是使用“bottom half”机制(BH机制)来实现“下半部”,了解一下。
Linux内核将中断程序分为“上半部和下半部”的目的就是实现中断处理函数的快进快出。
5、下半部机制
1)、软中断
软中断是一种下半部机制,要求推后的工作不能睡眠,需要包含头文件“#include <linux/interrupt.h>”
注意:
软中断必须在编译的时候静态注册,使用softirq_init()初始化软中断。
①、softirq_action结构体如下:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
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*/
};
const char * const softirq_to_name[NR_SOFTIRQS];
②、注册软中断处理函数
void open_softirq(int nr, void (*action)(struct softirq_action *))
nr:要开启的软中断。
action:软中断对应的处理函数
注意:需要包含头文件“interrupt.h”
③、触发软中断
void raise_softirq(unsigned int nr)
nr:要触发的软中断。
注意:需要包含头文件“interrupt.h”
④、软中断初始化函数
void softirq_init(void)
注意:需要包含头文件“interrupt.h”
2)、tasklet
tasklet是一种下半部机制,要求推后的工作不能睡眠。在介于软中断和tasklet之间,建议大家使用tasklet,需要包含头文件“#include <linux/interrupt.h>”
①、tasklet_struct结构体如下:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
②、初始化tasklet
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
t:要初始化的tasklet;
func:tasklet的处理函数;
data:要传递给func函数的参数;
注意:需要包含头文件“interrupt.h”
DECLARE_TASKLET(name, func, data)
name:为要定义的tasklet名字,其实就是tasklet_struct类型的变量名;
func:就是tasklet的处理函数;
data:是传递给fnc函数的参数;、
注意:需要包含头文件“interrupt.h”
③、tasklet调度函数:
void tasklet_schedule(struct tasklet_struct *t)
t:要调度的tasklet,也就是DECLARE_TASKLET(name, func, data)里面的name;
注意:需要包含头文件“interrupt.h”
注意:
在上半部,也就是“中断处理函数”中调用tasklet_schedule()函数就能使 tasklet在合适的时间运行;
④、举例:
struct tasklet_struct testtasklet; /* 定义taselet */
/* tasklet处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度tasklet */
tasklet_schedule(&testtasklet);
/*
在上半部,也就是“中断处理函数”中调用tasklet_schedule()函数使 tasklet在合适的时间运行;
*/
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
tasklet_init(&testtasklet, testtasklet_func, data);
/* 初始化tasklet*/
t=&testtasklet,要初始化的tasklet;
func=testtasklet_func,tasklet的处理函数testtasklet_func();
data:要传递给func函数的参数;
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
irq=xxx_irq:要申请中断的中断号;
handler=test_handler:中断处理函数test_handler(),当中断发生以后就会执行此中断处理函数;
fags=0:中断标志;
name="xxx":中断名字,设置以后可以在“/proc/interrupts”文件中看到对应的中断名字;
dev=&xxx_dev:将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。
返回值:0表示中断申请成功,其他负值,表示中断申请失败,如果返回-EBUSY的话表示中断已经被申请过了。
/* 注册中断处理函数 */
......
}
3)、工作队列
工作队列是一种下半部机制,它工作在进程的上下文处,将要推后的工作交给一个内核线程去执行,因此,允许工作队列进入睡眠或被重新调度。
work_struct结构体表示一个“工作”,需要包含头文件“workqueue.h”,如下;
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
workqueue_struct结构体表示“工作队列”,需要包含头文件“workqueue.h”,如下:
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[];
};
worker结构体表示“工作者线程”,需要包含头文件“workqueue_internal.h” ,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;};
Linux内核使用“工作者线程(worker thread)”来处理工作队列中的各个工作,每个“工作者线程(worker)”都有一个“工作队列(workqueue_struct)”,它会处理属于自己工作队列中的所有“工作(work_struct)”。
在实际驱动开发中,我们只需要定义“工作(work_struct)”即可,关于工作队列和工作者线程我们基本不用去管。
创建“工作(work_struct)”很简单,直接定义一个work_struct结构体变量即可,然后使用INIT_WORK宏来初始化“工作(work_struct)”,也可以使用 DECLARE_WORK宏来一次性完成“工作(work_struct)”的创建和初始化。
INIT_WORK 宏定义,需要包含头文件“workqueue.h”,如下:
#define INIT_WORK(_work, _func)
_work:表示要初始化的工作;
_func:是工作对应的处理函数;
DECLARE_WORK宏定义,需要包含头文件“workqueue.h”,如下:
#define DECLARE_WORK(n, f)
n:表示定义的“工作(work_struct)”
f:表示工作对应的处理函数
工作调度函数,需要包含头文件“workqueue.h”:
bool schedule_work(struct work_struct *work)
work:要调度的工作;返回值:0 成功,其他值 失败;
注意:
在“上半部”,也就是“中断处理函数”中调用schedule_work()函数就能使 “工作队列(workqueue_struct)”在合适的时间运行;
struct work_struct testwork; /* 定义工作(work) */
/* work处理函数 */void testwork_func_t(struct work_struct *work){
/* work具体处理内容 */}
/* 中断处理函数 */irqreturn_t test_handler(int irq, void *dev_id){
.....
schedule_work(&testwork);
/* 调度work */
//work=&testwork:要调度的工作; //返回值:0 成功,其他值 失败;
......}
/* 驱动入口函数 */static int __init xxxx_init(void){
......
INIT_WORK(&testwork, testwork_func_t);
/* 初始化work */
//_work=&testwork:表示要初始化的工作;
//_func= testwork_func_t:是工作对应的处理函数;
request_irq(xxx_irq,test_handler,0,"xxx",&xxx_dev);
/* 注册中断处理函数 */
//irq= xxx_irq:要申请中断的中断号;
//Handler= test_handler:中断处理函数,当中断发生以后就会执行此中断处理函数;
//fags=0:中断标志;
//name="xxx":中断名字;
//dev=&xxx_dev:将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。
//返回值:0表示中断申请成功,其他负值,表示中断申请失败,如果返回-EBUSY的话表示中断已经被申请过了。
......
}
6、GIC中断控制器
GIC是ARM公司给Cortex-A/R内核提供的一个中断控制器,类似 Cortex-M 内核中的NVIC;
7、GIC中断源分类:
1)、Shared Peripheral Interrupt,简写SPI,称为“共享中断”,即所有Core共享的中断。比如:GPIO中断、串口中断等,这些中断所有的Core都可以处理,不限定特定 Core;
2)、Private Peripheral Interrupt,简写PPI,称为“私有中断”。 GIC支持多核,每个核有自己专用的中断,且由指定的核心处理,这些中断就叫做私有中断;
3)、Software-generated Interrupt,简写SGI,称为“软件中断”,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会使用SGI中断来完成多核之间的通信;
8、中断ID
每个中断源都有一个唯一的中断ID,每个CPU最多支持1020个中断ID,中断ID号为ID0~ID1019。其中IQ0~ID15分配给“软件中断SGI”,IQ16~ID31分配给“私有中断PPI”,ID32~ID1019分配给“共享中断SPI”。
9、外部中断和事件控制器EXTI
Extended interrupt and event controller简写EXTI,它是ST公司设计的,用来辅助GIC管理STM32MP1的相应中断。
1)、EXTI特性:
支持76个输入事件;
两个CPU内核都支持;
所有事件输入均可让CPU唤醒;
2)、EXTI异步输入事件:
①、可配置事件,其特性如下:
可选择的有效触发边沿;
中断挂起状态寄存器位;
单独的中断和事件生成屏蔽;
支持软件触发;
②、直接事件,其特性如下:
固定上升沿有效触发;
EXTI中无中断挂起状态寄存器位(中断挂起状态由生成事件的外设提供);
单独的中断和事件生成屏蔽;
不支持软件触发;
3)、STM32MP1的中断处理方式:
外设直接产生中断到“GIC中断控制器”,然后由“GIC中断控制器”通知“CPU内核”;GPIO或外设产生中断到“外部中断和事件控制器EXTI”,然后将信号提交给“GIC中断控制器”,再由“GIC中断控制器”通知“CPU内核”;GPIO或外设产生中断到“外部中断和事件控制器EXTI”,然后直接将中断信号提交给“CPU内核”;
4)、GPIO中断
GPIO中断是我们最常用的功能。STM32MP1的所有GPIO都有中断功能,每一组GPIO最多有16个IO,比如:PA0~PA15,因此、每组GPIO就有16个中断,这16个GPIO事件输入对应EXTI0~15,其中 PA0、PB0 等都对应 EXTI0;
5)、设备树绑定信息参考文档
①、“GIC中断控制器”的设备树绑定信息参考文档:
Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml,
②、EXTI控制器的设备树绑定信息参考文档:
Documentation/devicetree/bindings/interrupt-controller/st,stm32-exti.txt
10、GIC控制器节点
Table 9. Register boundary addresses
在“stm32mp151.dtsi”文件中,“intc节点”就是“ GIC控制器节点”;
intc: interrupt-controller@a0021000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
/*使用GIC中断控制器需要用3个cells来描述一个中断*/
interrupt-controller;/*这是一个中断控制器*/
reg = <0xa0021000 0x1000>,
<0xa0022000 0x2000>;
/*表示address=0xa0021000,length=0x1000,占4096个字节*/
/*GICD的起始地址为0xa0021000,结束地址为0xA0021FFF,合计4KB*/
/*表示address=0xa0022000,length=0x2000,占8192个字节*/
/*GICC的起始地址为0xa0022000,结束地址为0xA0023FFF,合计8KB*/
};
#interrupt-cells = <3>;
/*使用GIC中断控制器需要用3个cells来描述一个中断*/
第1个cells:中断类型,0表示“共享中断SPI”,1表示“私有中断PPI”。第2个cells:中断号;
对于“共享中断SPI”来说中断号的范围为32~287(256 个);
对于“私有中断PPI”来说中断号的范围为 16~31,但是该cell描述的中断号是从 0开始。第3个cells:标志,bit[3:0]表示中断触发类型;
bit[3:0]=1表示上升沿触发;
bit[3:0]=2表示下降沿触发;
bit[3:0]=4表示高电平触发;
bit[3:0]=8表示低电平触发;
bit[15:8]为“私有中断PPI”的CPU掩码;
11、SPI6节点
SPI6中断号和中断ID,见下表:
第1列的“Num”就是SPI6的中断号:86
第2列“ID”为118,ID = Num + 32
SPI6地址范围,见下表:
Table 117. STM32MP157 interrupt mapping for Cortex®-A7 GIC
SPI6的起始地址为0x5C001000,结束地址为0x5C0013FF,合计1KB
打开stm32mp151.dtsi,找到SPI6节点内容,如下:
spi6: spi@5c001000 {
#address-cells = <1>;
/*定义子节点的reg和ranges的addres长度为32个位*/
#size-cells = <0>;
/*表示子节点的reg和ranges的length占0个位,即没有length*/
compatible = "st,stm32h7-spi";
reg = <0x5c001000 0x400>;
/*表示address=0x5c001000,length=0x400,占1024个字节*/
/*SPI6的起始地址为0x5C001000,结束地址为0x5C0013FF,合计1KB*/
interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
/*GIC_SPI表示共享中断SPI,86为中断号*/
/*中断触发类型IRQ_TYPE_LEVEL_HIGH*/
clocks = <&scmi0_clk CK_SCMI0_SPI6>;
resets = <&scmi0_reset RST_SCMI0_SPI6>;
dmas = <&mdma1 34 0x0 0x40008 0x0 0x0 0x0>,
<&mdma1 35 0x0 0x40002 0x0 0x0 0x0>;
dma-names = "rx", "tx";
power-domains = <&pd_core>;
status = "disabled";
};
12、EXTI控制器节点
exti: interrupt-controller@5000d000 {
compatible = "st,stm32mp1-exti", "syscon";
interrupt-controller;
#interrupt-cells = <2>;
/*使用EXTI中断控制器需要用2个cells来描述一个中断*/
/*第1个cells:中断号;第2个cells:中断标志位*/
reg = <0x5000d000 0x400>;
/*表示address=0x5000d000,length=0x400,占1024个字节*/
/*EXTI的起始地址为0x5000d000,结束地址为0x5000D3FF,合计1KB*/
hwlocks = <&hsem 1 1>;
/* exti_pwr is an extra interrupt controller used for
* EXTI 55 to 60. It's mapped on pwr interrupt
* controller.
*/
exti_pwr: exti-pwr {
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&pwr_irq>;
/*指定exti_pwr所有子节点的中断父节点为pwr_irq*/
st,irq-number = <6>;
};
};
#interrupt-cells = <2>;
/*使用EXTI中断控制器需要用2个cells来描述一个中断*/
第1个cells:中断号;第2个cells:中断标志位,bit[3:0]表示中断触发类型;
13、GPIOA~GPIOK寄存器地址
Table 9. Register boundary addresses (continued)
pinctrl: pin-controller@50002000 {
#address-cells = <1>;
/*定义子节点的reg和ranges的addres长度为32个位*/
#size-cells = <1>;
/*定义子节点的reg和ranges的length长度为32个位*/
compatible = "st,stm32mp157-pinctrl";
ranges = <0 0x50002000 0xa400>;
/*子节点寄存器起始地址为0*/
/*父节点寄存器起始地址为0x50002000*/
/*寄存器最大偏移地址为0xa400*/
interrupt-parent = <&exti>;
/*指定pinctrl所有子节点的中断父节点为exti*/
/*这样GPIO的中断就和EXTI联系起来*/
st,syscfg = <&exti 0x60 0xff>;
hwlocks = <&hsem 0 1>;
pins-are-numbered;
gpioa: gpio@50002000 {
gpio-controller;
/*指定gpioa节点是一个GPIO控制器*/
#gpio-cells = <2>;
/*定义描述使用一个gpio口需要提供2个指定的参数*/
interrupt-controller;
/* 指定gpioa节点为中断控制器, 其父节点为pinctrl,其中断为exti*/
#interrupt-cells = <2>;
/*interrupts属性第1个cell为某个IO在所处组的编号*/
/*第2个cell表示中断触发方式*/
reg = <0x0 0x400>;
/*表示address=0,length=0x400,占1024个字节*/
/*GPIOA的起始地址为(0x50002000+0),结束地址为(0x50002000+0+0x400-1) */
/*GPIOA的起始地址为0x50002000,结束地址为0x500023FF */
clocks = <&rcc GPIOA>;
st,bank-name = "GPIOA";
status = "disabled";
};
gpiob: gpio@50003000 {
gpio-controller;
/*指定gpiob节点是一个GPIO控制器*/
#gpio-cells = <2>;
/*定义描述使用一个gpio口需要提供2个指定的参数*/
interrupt-controller;
/* 指定gpiob节点为中断控制器, 其父节点为pinctrl,其中断为exti*/
#interrupt-cells = <2>;
/*interrupts属性第1个cell为某个IO在所处组的编号*/
/*第2个cell表示中断触发方式*/
reg = <0x1000 0x400>;
/*表示address=0x1000,length=0x400,占1024个字节*/
/*GPIOB的起始地址为(0x50002000+0x1000)*/
/*GPIOB的结束地址为(0x50002000+0x1000+0x400-1) */
/*GPIOB的起始地址为0x50003000,结束地址为0x500033FF */
clocks = <&rcc GPIOB>;
st,bank-name = "GPIOB";
status = "disabled";
};
gpioc: gpio@50004000 {
gpio-controller;
/*指定gpioc节点是一个GPIO控制器*/
#gpio-cells = <2>;
/*定义描述使用一个gpio口需要提供2个指定的参数*/
interrupt-controller;
/* 指定gpioc节点为中断控制器, 其父节点为pinctrl,其中断为exti*/
#interrupt-cells = <2>;
/*interrupts属性第1个cell为某个IO在所处组的编号*/
/*第2个cell表示中断触发方式*/
reg = <0x2000 0x400>;
/*表示address=0x2000,length=0x400,占1024个字节*/
/*GPIOC的起始地址为(0x50002000+0x2000)*/
/*GPIOC的结束地址为(0x50002000+0x2000+0x400-1) */
/*GPIOC的起始地址为0x50004000,结束地址为0x500043FF */
clocks = <&rcc GPIOC>;
st,bank-name = "GPIOC";
status = "disabled";
};
gpiod: gpio@50005000 {
gpio-controller;
/*指定gpiod节点是一个GPIO控制器*/
#gpio-cells = <2>;
/*定义描述使用一个gpio口需要提供2个指定的参数*/
interrupt-controller;
/* 指定gpiod节点为中断控制器, 其父节点为pinctrl,其中断为exti*/
#interrupt-cells = <2>;
/*interrupts属性第1个cell为某个IO在所处组的编号*/
/*第2个cell表示中断触发方式*/
reg = <0x3000 0x400>;
/*表示address=0x3000,length=0x400,占1024个字节*/
/*GPIOD的起始地址为(0x50002000+0x3000)*/
/*GPIOD的结束地址为(0x50002000+0x3000+0x400-1) */
/*GPIOD的起始地址为0x50005000,结束地址为0x500053FF */
clocks = <&rcc GPIOD>;
st,bank-name = "GPIOD";
status = "disabled";
};
gpioe: gpio@50006000 {
gpio-controller;
/*指定gpioe节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
/* 指定gpioe节点为中断控制器, 其父节点为pinctrl,其中断为exti*/
#interrupt-cells = <2>;
reg = <0x4000 0x400>;
clocks = <&rcc GPIOE>;
st,bank-name = "GPIOE";
status = "disabled";
};
gpiof: gpio@50007000 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x5000 0x400>;
clocks = <&rcc GPIOF>;
st,bank-name = "GPIOF";
status = "disabled";
};
gpiog: gpio@50008000 {
gpio-controller;
/*指定gpiogg节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x6000 0x400>;
clocks = <&rcc GPIOG>;
st,bank-name = "GPIOG";
status = "disabled";
};
gpioh: gpio@50009000 {
gpio-controller;
/*指定gpioh节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x7000 0x400>;
clocks = <&rcc GPIOH>;
st,bank-name = "GPIOH";
status = "disabled";
};
gpioi: gpio@5000a000 {
gpio-controller;
/*指定gpioi节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x8000 0x400>;
clocks = <&rcc GPIOI>;
st,bank-name = "GPIOI";
status = "disabled";
};
gpioj: gpio@5000b000 {
gpio-controller;
/*指定gpioj节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x9000 0x400>;
clocks = <&rcc GPIOJ>;
st,bank-name = "GPIOJ";
status = "disabled";
};
gpiok: gpio@5000c000 {
gpio-controller;
/*指定gpiok节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0xa000 0x400>;
clocks = <&rcc GPIOK>;
st,bank-name = "GPIOK";
status = "disabled";
};
};
14、GPIOZ寄存器:
Table 9. Register boundary addresses (continued)
pinctrl_z: pin-controller-z@54004000 {
#address-cells = <1>;
/*定义子节点的reg和ranges的addres长度为32个位*/
#size-cells = <1>;
/*定义子节点的reg和ranges的length长度为32个位*/
compatible = "st,stm32mp157-z-pinctrl";
ranges = <0 0x54004000 0x400>;
/*指定子节点寄存器起始地址为0*/
/*父节点寄存器起始地址为0x50004000*/
/*地址长度为0x400*/
pins-are-numbered;
interrupt-parent = <&exti>;
st,syscfg = <&exti 0x60 0xff>;
hwlocks = <&hsem 0 1>;
gpioz: gpio@54004000 {
gpio-controller;
/*指定gpioz节点是一个GPIO控制器*/
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
/*interrupts属性第1个cell为某个IO在所处组的编号*/
/*第2个cell表示中断触发方式*/
reg = <0 0x400>;
/*表示address=0,length=0x400,占1024个字节*/
/*GPIOZ的起始地址为(0x50004000+0)*/
/*GPIOZ的结束地址为(0x50004000+0+0x400-1) */
/*GPIOZ的起始地址为0x50004000,结束地址为0x500043FF */
clocks = <&scmi0_clk CK_SCMI0_GPIOZ>;
st,bank-name = "GPIOZ";
st,bank-ioport = <11>;
status = "disabled";
};
};
timer {
compatible = "arm,armv7-timer";
interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
interrupt-parent = <&intc>;
always-on;
}
15、简单总结一下与中断有关的设备树属性信息:
①、#interrupt-cells,指定中断源的信息cells个数;
②、imnterrupt-controller,表示当前节点为中断控制器;
③、interrupts,指定中断号,触发方式等;
④、imnterrupt-parent,指定父中断,也就是中断控制器;
⑤、interrupts-extended,指定中断控制器、中断号、中断类型和触发方式;
举例:
interrupt-parent = <&gpiog>;/*指定父中断器为&gpiog*/
interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
/*指定中断号为3,中断类型和触发方式为边沿触发*/
使用interrupts-extended可以代替上面的两句:
interrupts-extended = <&gpiog 3 IRQ_TYPE_EDGE_FALLING>;
/*指定中断控制器&gpiog、中断号为3、中断类型和触发方式下降沿触发*/
16、触发类型,位于文件“irq.h”中:
IRQ_TYPE_NONE - default, unspecified type
IRQ_TYPE_EDGE_RISING - rising edge triggered
IRQ_TYPE_EDGE_FALLING - falling edge triggered
IRQ_TYPE_EDGE_BOTH - rising and falling edge triggered
IRQ_TYPE_LEVEL_HIGH - high level triggered
IRQ_TYPE_LEVEL_LOW - low level triggered
IRQ_LEVEL - Interrupt is level type
17、打开stm32mpl57f-ev1-a7-examples.dts文件,里面有如下所示代码
test_keys {
compatible = "gpio-keys";
#address-cells = <1>;
/*定义子节点的reg和ranges的addres长度为32个位*/
#size-cells = <0>;
/*表示子节点的reg和ranges的length占0个位,即没有length*/
autorepeat;
status = "okay";
/* gpio needs vdd core in retention for wakeup */
power-domains = <&pd_core_ret>;
button@1 {
label = "PA13";
linux,code = <BTN_1>;
interrupts-extended = <&gpioa 13 IRQ_TYPE_EDGE_FALLING>;
/*指定中断控制器&gpioa、中断号为13、中断类型和触发方式IRQ_TYPE_EDGE_FALLING*/
status = "okay";
wakeup-source;
};
};
18、相关函数:
1)、获取中断号,需要包含文件“of_irq.h”
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
dev:为设备节点,Linux内核使用device_node结构体来描述一个节点。
index:索引号,interrupts属性可能包含多条中断信息,通过index指定要获取的信息;
返回值:中断号;
获取GPIO中断号,需要包含文件“gpio.h”
int gpio_to_irq(unsigned int gpio)
gpio:要获取的GPIO编号;返回值:GPIO对应的中断号