栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

【嵌入式Linux】嵌入式Linux驱动开发基础知识之Linux中断系统简介及按键中断设备树驱动编写

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【嵌入式Linux】嵌入式Linux驱动开发基础知识之Linux中断系统简介及按键中断设备树驱动编写

文章目录
  • 前言
  • 1、异常与中断的概念及处理流程
    • 1.1、异常与中断的概念
    • 1.2、中断的处理流程
    • 1.3、异常向量表
  • 2、Linux系统对中断的处理
    • 2.1、进程、线程、中断的核心:栈
      • 2.1.1、ARM处理器是如何工作的?
      • 2.1.2、中断时如何保存现场?
      • 2.1.3、进程和线程
    • 2.2、Linux系统对中断的演进
      • 2.2.1、硬件中断和软件中断
      • 2.2.2、中断处理原则:不能嵌套
      • 2.2.3、中断处理原则:越快越好
      • 2.2.4、中断的上半部分和下半部分
      • 2.2.5、下半部要做的事情耗时不是太长:tasklet
      • 2.2.6、下半部要做的事情太多并且很复杂:工作队列
      • 2.2.7、新技术:threaded irq
    • 2.3、Linux中断系统中重要的数据结构
      • 2.3.1、struct irq_desc数组
      • 2.3.2、irqaction结构体
      • 2.3.3、 irq_data结构体
      • 2.3.4、irq_domain结构体
      • 2.3.4、irq_chip结构体
    • 2.4、设备树中指定中断 代码中获得中断
      • 2.4.1、设备树里中断节点的语法
        • 2.4.1.1、设备中的中断控制器
        • 2.4.1.2、设备树中使用中断
      • 2.4.2、设备树里中断节点的示例
      • 2.4.3、在驱动中获得中断
        • 2.4.3.1、 对于platform_device
        • 2.4.3.2、 对于I2C设备、SPI设备
        • 2.4.3.3、 上述两种情况都不符合
        • 2.4.3.3、 对于GPIO
    • 2.5、编写按键中断驱动程序、修改设备树
      • 2.5.1、设备树
      • 2.5.2、按键中断驱动程序

前言

韦东山嵌入式Linux驱动开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容
视频教程地址:https://www.bilibili.com/video/BV14f4y1Q7ti

1、异常与中断的概念及处理流程 1.1、异常与中断的概念

异常:当异常发生时,CPU会根据异常向量表跳转,比如reset发生时,CPU会跳转到软件指定好的代码段去执行reset程序
中断:中断是异常的子集,是一种特殊的异常,其包含一个中断控制器来判断是哪种中断,并且对其进行排序再通知CPU

在嵌入式系统的异常、中断处理模型是这样的:

▲嵌入式系统的异常、中断处理模型

CPU在运行的过程中,也会被各种“异常”打断。这些“异常”有:
  ① 指令未定义
  ② 指令、数据访问有问题
  ③ SWI(软中断)
  ④ 快中断
  ⑤ 中断

中断也属于一种“异常”,导致中断发生的情况有很多,比如:
  ① 按键
  ② 定时器
  ③ ADC转换完成
  ④ UART发送完数据、收到数据
  ⑤ 等等
这些众多的“中断源”,汇集到“中断控制器”,由“中断控制器”选择优先级最高的中断并通知CPU。

1.2、中断的处理流程

arm对异常(中断)处理过程:
  ① 初始化:
    a. 设置中断源,让它可以产生中断
    b. 设置中断控制器(可以屏蔽某个中断,优先级)
    c. 设置CPU总开关(使能中断)
  ② 执行其他程序:正常程序
  ③ 产生中断:比如按下按键—>中断控制器—>CPU
  ④ CPU 每执行完一条指令都会检查有无中断/异常产生
  ⑤ CPU发现有中断/异常产生,开始处理。
    对于不同的异常,跳去不同的地址执行程序。
    这地址上,只是一条跳转指令,跳去执行某个函数(地址),这个就是异常向量。
③④⑤都是硬件做的。
  ⑥ 这些函数做什么事情?
    软件做的:
      a. 保存现场(各种寄存器)
      b. 处理异常(中断):
      分辨中断源,再调用不同的处理函数
      c. 恢复现场

1.3、异常向量表

u-boot或是Linux内核,都有类似如下的代码:

_start: b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**
	ldr	pc, _fiq

这就是异常向量表,每一条指令对应一种异常。
发生复位时,CPU就去 执行第1条指令:b reset。
发生中断时,CPU就去执行“ldr pc, _irq”这条指令。
这些指令存放的位置是固定的,比如对于ARM9芯片中断向量的地址是0x18。
当发生中断时,CPU就强制跳去执行0x18处的代码。

在向量表里,一般都是放置一条跳转指令,发生该异常时,CPU就会执行向量表中的跳转指令,去调用更复杂的函数。
当然,向量表的位置并不总是从0地址开始,很多芯片可以设置某个vector base寄存器,指定向量表在其他位置,比如设置vector base为0x80000000,指定为DDR的某个地址。但是表中的各个异常向量的偏移地址,是固定的:复位向量偏移地址是0,中断是0x18。

2、Linux系统对中断的处理 2.1、进程、线程、中断的核心:栈

要想知道中断是如何在ARM架构的CPU上实现的就必须理解ARM架构CPU是如何完成工作的

2.1.1、ARM处理器是如何工作的?

ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:
  ① 对内存只有读、写指令
  ② 对于数据的运算是在CPU内部实现
  ③ 使用RISC指令的CPU复杂度小一点,易于设计
比如计算a=a+b这样的算式,需要经过下面4个步骤才可以实现:

▲a=a+b

这一操作会涉及到一系列寄存器

▲a=a+b涉及到的寄存器

CPU运行时,先去取得指令,再执行指令:
  ① 把内存a的值读入CPU寄存器R0
  ② 把内存b的值读入CPU寄存器R1
  ③ 把R0、R1累加,存入R0
  ④ 把R0的值写入内存a

2.1.2、中断时如何保存现场?

什么是现场:程序执行时寄存器中的值
如何保存现场:将寄存器中的值存入栈内存中
如何恢复现场:将栈内存中的值存入寄存器中
保存现场的场景并不局限于中断,下图可以概括程序A、B的切换过程,其他情况是类似的:

▲程序A、B的切换过程

a. 函数调用:
  在函数A里调用函数B,实际就是中断函数A的执行。
  那么需要把函数A调用B之前瞬间的CPU寄存器的值,保存到栈里;
  再去执行函数B;
  函数B返回之后,就从栈中恢复函数A对应的CPU寄存器值,继续执行。
b. 中断处理
  进程A正在执行,这时候发生了中断。
  CPU强制跳到中断异常向量地址去执行,
  这时就需要保存进程A被中断瞬间的CPU寄存器值,
  可以保存在进程A的内核态栈,也可以保存在进程A的内核结构体中。
  中断处理完毕,要继续运行进程A之前,恢复这些值。

c. 进程切换
  在所谓的多任务操作系统中,我们以为多个程序是同时运行的。
  如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序依次执行一小段时间,进程A的时间用完了,就切换到进程B。
怎么切换?
  切换过程是发生在内核态里的,跟中断的处理类似。
  进程A的被切换瞬间的CPU寄存器值保存在某个地方;
  恢复进程B之前保存的CPU寄存器值,这样就可以运行进程B了。

  所以,在中断处理的过程中,伴存着进程的保存现场、恢复现场。
进程的调度也是使用栈来保存、恢复现场:

▲进程调度保护、恢复现场操作
2.1.3、进程和线程

在Linux中:资源分配的单位是进程,调度的单位是线程,一个进程中可以有多个线程,进程间通信效率较低但是拥有独立的资源,线程间通信效率高因为一个进程中的所有线程共享打开的文件句柄、全局变量等等
线程之间是互相独立的,“同时运行”,也就是说:每一个线程,都有自己的栈。如下图示:

▲线程的栈
2.2、Linux系统对中断的演进

Linux系统中有硬件中断,也有软件中断。
对硬件中断的处理有2个原则:不能嵌套,越快越好。

2.2.1、硬件中断和软件中断

Linux系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为“硬件中断”(hard irq)。每个硬件中断都有对应的处理函数,比如按键中断、网卡中断的处理函数肯定不一样。
为方便理解,可以先认为对硬件中断的处理是用数组来实现的,数组里存放的是函数指针:

▲硬件中断数组--简化图

当发生A中断时,对应的irq_function_A函数被调用。硬件导致该函数被调用。
相对的Linux提供软件中断:

▲软件中断(soft irq)数组--简化图

软件中断何时产生?
由软件决定,对于X号软件中断,只需要把它的flag设置为1就表示发生了该中断。
软件中断何时处理?
在Linux系统中处理完硬件中断后就会去处理已经发生的软件中断,其中“硬件中断”包含每10ms发生一次的定时器中断
有哪些软件中断?


enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	IRQ_POLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ, 
	RCU_SOFTIRQ,    

	NR_SOFTIRQS
};

如何使用软中断?
软中断的触发

void raise_softirq(unsigned int nr);

软中断服务函数的设置

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

后面讲到的中断下半部tasklet就是使用软件中断实现的。

2.2.2、中断处理原则:不能嵌套

中断发生时需要保存现场,这就需要栈内存,如果中断可以嵌套的会发生什么?
对,会栈溢出,无论多大的栈也会有用尽的一刻

2.2.3、中断处理原则:越快越好

在单芯片系统中,假设中断处理很慢,那应用程序在这段时间内就无法执行:系统显得很迟顿。
在SMP(Symmetric Multi-Processing)对称多处理结构系统中,假设中断处理很慢,那么正在处理这个中断的CPU上的其他线程也无法执行。
在中断的处理过程中,该CPU是不能进行进程调度的,所以中断的处理要越快越好,尽早让其他中断能被处理──进程调度靠定时器中断来实现。

如何使用中断呢?

//为中断irq注册中断处理函数handler
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)

但是并不是所有的中断服务都可以快速处理的,那么怎么办?
这时候就可以将中断分为上下两部分,上半部使用硬件中断服务函数,下半部分使用tasklet软件中断或者其他办法

2.2.4、中断的上半部分和下半部分

中断的上下两部分对应于紧急和不紧急的事情,当然这个紧急需要开发者去衡量

▲中断的上半部分和下半部分

中断下半部的实现有很多种方法,讲2种主要的:tasklet(小任务)、work queue(工作队列)。

2.2.5、下半部要做的事情耗时不是太长:tasklet

如果下半部要做的事情耗时不是太长,那么可以使用tasklet来处理

▲软件中断中的tasklet

▲中断的上下半部分逻辑流程图

▲中断上半部和下半部处理代码示意图--软硬件中断

硬件中断A处理过程中,又再次发生了中断A
从流程图分析可以发现这么做可以起到中断上半部分一对多的效果,即在硬件中断发生多次时,上半部分会多次执行但只会执行一次中断下半部分
硬件中断A处理过程中,又再次发生了中断B
这时会先处理中断A的上半部然后处理中断B的上半部,最后处理中断A和中断B的下半部,多个中断的下半部,是汇集在一起处理的

总结
  a. 中断的处理可以分为上半部,下半部
  b. 中断上半部,用来处理紧急的事,它是在关中断的状态下执行的
  c. 中断下半部,用来处理耗时的、不那么紧急的事,它是在开中断的状态下执行的
  d. 中断下半部执行时,有可能会被多次打断,有可能会再次发生同一个中断
  e. 中断上半部执行完后,触发中断下半部的处理
  f. 中断上半部、下半部的执行过程中,不能休眠:中断休眠的话,以后谁来调度进程啊?

2.2.6、下半部要做的事情太多并且很复杂:工作队列

  在中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。但是毕竟整个中断的处理还没走完,这期间APP是无法执行的,假设下半部要执行1、2分钟,在这1、2分钟里APP都是无法响应的,这谁受得了?
  所以,如果中断要做的事情实在太耗时,那就不能用软件中断来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和APP都一样竞争执行,APP有机会执行,系统不会卡顿。

▲kworker线程

kwoker线程时内核系统创建的,开发者通过向工作队列(work queue)中塞入工作(work)来使用这个内核线程

那我们怎么使用work、work queue呢?
a. 创建work:

static DECLARE_WORK(mem_error_work, mem_error_handler);

b.将work塞入工作队列

schedule_work(&mem_error_work);


static inline bool schedule_work(struct work_struct *work)
{
	return queue_work(system_wq, work);
}**

c. 谁来执行work中的函数?
不用我们管,schedule_work函数不仅仅是把work放入队列,还会把kworker线程唤醒。此线程抢到时间运行时,它就会从队列中取出work,执行里面的函数。
d. 谁把work提交给work queue?
在中断场景中,可以在中断上半部调用schedule_work函数。

总结:
  a. 很耗时的中断处理,应该放到线程里去
  b. 可以使用work、work queue
  c. 在中断上半部调用schedule_work函数,触发work的处理
  d. 既然是在线程中运行,那对应的函数可以休眠。

2.2.7、新技术:threaded irq
request_threaded_irq(unsigned int irq, irq_handler_t handler,
		     irq_handler_t thread_fn,
		     unsigned long flags, const char *name, void *dev);

可以只提供thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。
以前用work来线程化地处理中断,一个worker线程只能由一个CPU执行,多个中断的work都由同一个worker线程来处理,在单CPU系统中也只能忍着了。但是在SMP系统中,明明有那么多CPU空着,你偏偏让多个中断挤在这个CPU上?
新技术threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个CPU上执行,这提高了效率。

2.3、Linux中断系统中重要的数据结构

▲中断功能涉及到的数据结构
  1. struct irq_desc:该结构体映射一个中断,在上图中它可能是GIC 接收的A号中断也可能是GPIO 接收的B号中断
  2. irqaction结构体:该结构体是irq_desc的成员之一包含了一个中断的设备信息和函数指针(name、dev_id等,handler、thread_fn、thread),在中断来源于GPIOB后会调用的其中函数指针
  3. irq_data结构体:该结构体是一个中转站,里面包含irq_domain和irq_chip的结构体指针,还有软件中断号irq,硬件中断号hwirq等4. Linux中断系统相关数据
  4. irq_domain结构体:该结构体内部包含了一些函数,有些可以解析设备树中断并将数据传入内核,有些可以完成软硬件中断转换操作
  5. irq_chip结构体:该结构体内被包含了一些函数用于管理中断的使能情况
2.3.1、struct irq_desc数组

▲中断功能实现模型

▲struct irq_desc

  外部设备1、外部设备n共享一个GPIO中断B,多个GPIO中断汇聚到GIC(通用中断控制器)的A号中断,GIC再去中断CPU。那么软件处理时就是反过来,先读取GIC获得中断号A,再细分出GPIO中断B,最后判断是哪一个外部芯片发生了中断。
所以,中断的处理函数来源有三:
① GIC的处理函数:
  假设irq_desc[A].handle_irq是XXX_gpio_irq_handler(XXX指厂家),这个函数需要读取芯片的GPIO控制器,细分发生的是哪一个GPIO中断(假设是B),再去调用irq_desc[B]. handle_irq。
  注意:irq_desc[A].handle_irq细分出中断后B,调用对应的irq_desc[B].handle_irq。
  显然中断A是CPU感受到的顶层的中断,GIC中断CPU时,CPU读取GIC状态得到中断A。

② 模块的中断处理函数:
  比如对于GPIO模块向GIC发出的中断B,它的处理函数是irq_desc[B].handle_irq。
BSP开发人员会设置对应的处理函数,一般是handle_level_irq或handle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。
  注意:导致GPIO中断B发生的原因很多,可能是外部设备1,可能是外部设备n,可能只是某一个设备,也可能是多个设备。所以irq_desc[B].handle_irq会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。

③ 外部设备提供的处理函数:
  这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”:它知道如何判断设备是否发生了中断,如何处理中断。
  对于共享中断,比如GPIO中断B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。
  一旦程序确定发生了GPIO中断B,那么就会从链表里把那些函数取出来,一一执行。
  这个链表就是action链表。

2.3.2、irqaction结构体

▲irqaction结构体

  当调用request_irq、request_threaded_irq注册中断处理函数时,内核就会构造一个irqaction结构体。在里面保存name、dev_id等,最重要的是handler、thread_fn、thread。
  handler是中断处理的上半部函数,用来处理紧急的事情。
  thread_fn对应一个内核线程thread,当handler执行完毕,Linux内核会唤醒对应的内核线程。在内核线程里,会调用thread_fn函数。
  可以提供handler而不提供thread_fn,就退化为一般的request_irq函数。
  可以不提供handler只提供thread_fn,完全由内核线程来处理中断。
  也可以既提供handler也提供thread_fn,这就是中断上半部、下半部。

  里面还有一个名为sedondary的irqaction结构体,它的作用以后再分析。
  在reqeust_irq时可以传入dev_id,为何需要dev_id?作用有2:
  ① 中断处理函数执行时,可以使用dev_id
  ② 卸载中断时要传入dev_id,这样才能在action链表中根据dev_id找到对应项
所以在共享中断中必须提供dev_id,非共享中断可以不提供。

2.3.3、 irq_data结构体

▲ irq_data结构体

  它就是个中转站,里面有irq_chip指针 irq_domain指针,都是指向别的结构体。
  比较有意思的是irq、hwirq,irq是软件中断号,hwirq是硬件中断号。比如上面我们举的例子,在GPIO中断B是软件中断号,可以找到irq_desc[B]这个数组项;GPIO里的第x号中断,这就是hwirq。
  谁来建立irq、hwirq之间的联系呢?由irq_domain来建立。irq_domain会把本地的hwirq映射为全局的irq,什么意思?比如GPIO控制器里有第1号中断,UART模块里也有第1号中断,这两个“第1号中断”是不一样的,它们属于不同的“域”──irq_domain。

2.3.4、irq_domain结构体

▲irq_domain结构体

  当我们后面从设备树讲起,如何在设备树中指定中断,设备树的中断如何被转换为irq时,irq_domain将会起到极大的作为。
  这里基于入门的解度简单讲讲,在设备树中你会看到这样的属性:

interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;

  它表示要使用gpio1里的第5号中断,hwirq就是5。
  但是我们在驱动中会使用request_irq(irq, handler)这样的函数来注册中断,irq是什么?它是软件中断号,它应该从“gpio1的第5号中断”转换得来。
  谁把hwirq转换为irq?由gpio1的相关数据结构,就是gpio1对应的irq_domain结构体。
  irq_domain结构体中有一个irq_domain_ops结构体,里面有各种操作函数,主要是:
    ① xlate
      用来解析设备树的中断属性,提取出hwirq、type等信息。
    ② map
      把hwirq转换为irq。

2.3.4、irq_chip结构体

▲irq_chip结构体
* @irq_startup:	start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown:	shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable:		enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable:	disable the interrupt
* @irq_ack:		start of a new interrupt
* @irq_mask:		mask an interrupt source
* @irq_mask_ack:	ack and mask an interrupt source
* @irq_unmask:		unmask an interrupt source
* @irq_eoi:		end of interrupt

  我们在request_irq后,并不需要手工去使能中断,原因就是系统调用对应的irq_chip里的函数帮我们使能了中断。
  我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用irq_chip中的相关函数。
  但是对于外部设备相关的清中断操作,还是需要我们自己做的。
  就像上面图里的“外部设备1“、“外部设备n”,外设备千变万化,内核里可没有对应的清除中断操作。

2.4、设备树中指定中断 代码中获得中断 2.4.1、设备树里中断节点的语法 2.4.1.1、设备中的中断控制器

▲中断硬件框架图

  上面介绍数据结构时也提到GIC模块 和 GPIO模块都有对应的 irq_desc,其实硬件上的中断控制器只有GIC一个,但是因为芯片拥有多个GPIO模块,所以软件上的“中断控制器”就有很多个GIC、GPIO1、GPIO2 等
  这也就意味这,如果要用到一个连接到GPIOA的设备中断就会有需要使用到两个(GPIOA、GIC)中断控制器,具体到开发者所需要做到的工作:开发者需要指定中断控制器的级联关系
  假设GPIO1有32个中断源,但是它把其中的16个汇聚起来向GIC发出一个中断,把另外16个汇聚起来向GIC发出另一个中断。这就意味着GPIO1会用到GIC的两个中断,会涉及GIC里的2个hwirq。

vic: intc@10140000 {
	compatible = "arm,versatile-vic";
	interrupt-controller;		//表明这个节点对应中断控制器
	#interrupt-cells = <1>;	//表明引用这个中断控制器需要多少个cell,即用多少个cell表明一个中断
	reg = <0x10140000 0x1000>;
};

一般如果 #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-parent以及用了interrupt-parent的哪一个interrupt

2.4.1.2、设备树中使用中断
i2c@7000c000 {
	gpioext: gpio-adnp@41 {
		compatible = "ad,gpio-adnp";

		interrupt-parent = <&gpio>;	//表明使用gpio控制器中的中断
		interrupts = <160 1>;					//表明使用160号中断,上升沿触发

		gpio-controller;
		#gpio-cells = <1>;

		interrupt-controller;
		#interrupt-cells = <2>;
	};
......
};

新写法:既指定interrupt-parent,也指定interrupts

interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;
2.4.2、设备树里中断节点的示例

▲imx6ull.dtsi、100ask_imx6ull-14x14.dts

▲IMX6ULL的中断体系

GPC INTC的英文是:General Power Controller, Interrupt Controller。它提供中断屏蔽、中断状态查询功能,实际上这些功能在GIC里也实现了,个人觉得有点多余。除此之外,它还提供唤醒功能,这才是保留它的原因。

2.4.3、在驱动中获得中断

之前我们提到过,设备树中的节点有些能被转换为内核里的platform_device,有些不能,回顾如下:
  A. 根节点下含有compatile属性的子节点,会转换为platform_device
  B. 含有特定compatile属性的节点的子节点,会转换为platform_device
  如果一个节点的compatile属性,它的值是这4者之一:“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”,
  那么它的子结点(需含compatile属性)也可以转换为platform_device。
  C. 总线I2C、SPI节点下的子节点:不转换为platform_device
某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device。

2.4.3.1、 对于platform_device

既然可以被转换为platform_device,那么在驱动中就可以通过函数来获取中断号等资源

struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num);
2.4.3.2、 对于I2C设备、SPI设备

对于I2C设备节点,I2C总线驱动在处理设备树里的I2C子节点时,也会处理其中的中断信息。一个I2C设备会被转换为一个i2c_client结构体,中断号会保存在i2c_client的irq成员里

▲对于I2C设备

对于SPI设备节点,SPI总线驱动在处理设备树里的SPI子节点时,也会处理其中的中断信息。一个SPI设备会被转换为一个spi_device结构体,中断号会保存在spi_device的irq成员里

▲对于SPI设备
2.4.3.3、 上述两种情况都不符合

如果你的设备节点既不能转换为platform_device,它也不是I2C设备,不是SPI设备,那么在驱动程序中可以自行调用of_irq_get函数去解析内核生成的device_node,得到中断号。

2.4.3.3、 对于GPIO

假设设备树中有这些节点:

gpio-keys {
		compatible = "gpio-keys";
		pinctrl-names = "default";

		user {
				label = "User Button";
				gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
				gpio-key,wakeup;
				linux,code = ;
		};
};

可以使用下面的函数获得引脚和flag

button->gpio = of_get_gpio_flags(pp, 0, &flags);
bdata->gpiod = gpio_to_desc(button->gpio);

再去使用gpiod_to_irq获得中断号:

irq = gpiod_to_irq(bdata->gpiod);
2.5、编写按键中断驱动程序、修改设备树

▲从原理图中可以看出PG2 PG3 都是下降沿触发
2.5.1、设备树
gpio_keys_100ask {
	compatible = "100ask,gpio_key";
	gpios = <&gpiog 3 IRQ_TYPE_EDGE_RISING
			 				&gpiog 2 IRQ_TYPE_EDGE_RISING>;
};
2.5.2、按键中断驱动程序
//从设备树获得GPIO
count = of_gpio_count(node);
for (i = 0; i < count; i++)
    gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
//从GPIO获得中断号
gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);
//申请中断
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr,  
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);
//中断函数
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
	

	printk("key %d %dn", gpio_key->gpio, val);
	
	return IRQ_HANDLED;
}

gpio_key_drv.c

#include 

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


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

static struct gpio_key *gpio_keys_100ask;

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
	

	printk("key %d %dn", gpio_key->gpio, val);
	
	return IRQ_HANDLED;
}


static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;
	enum of_gpio_flags flag;
	unsigned flags = GPIOF_IN;
		
	printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node);
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio availablen", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{
		
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags failn", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;

		if (flag & OF_GPIO_ACTIVE_LOW)
			flags |= GPIOF_ACTIVE_LOW;

		err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);

		
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);
	}

	for (i = 0; i < count; i++)
	{
		
		err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);
	}
        
    return 0;
    
}

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

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
	}
	kfree(gpio_keys_100ask);
    return 0;
}


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};


static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_keys,
    },
};


static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}


static void __exit gpio_key_exit(void)
{
	printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}




module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/837509.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号