Linux驱动开发第3步_INPUT子系统框架下的外部中断

server/2024/12/2 18:11:45/

1、了解WKUP唤醒引脚

1)、打开STM32MP157.html" title=STM32MP157>STM32MP157数据手册,搜索“WKUP”,见下表:

由表可知,PA0,PA2,PC13,PI8,PI11和PC1均具有唤醒功能。

2)、打开STM32MP157.html" title=STM32MP157>STM32MP157参考手册,了解GIC中断向量表,搜索“Table 117”,找到MPU_WAKEUP_PIN。

下图是“STM32MP157.html" title=STM32MP157>STM32MP157 interrupt mapping for Cortex®-A7 GIC”的一部分。

3)、打开“linux-5.4.31/arch/arm/boot/dts/stm32mp151.dtsi”

pwr_irq: pwr@50001020 {

compatible = "st,stm32mp1-pwr";

reg = <0x50001020 0x100>;

interrupts = <GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>;

/*从参考手册中,查看Table 117,指定pwr_irq的GIC中断向量号码为149*/

interrupt-controller;

#interrupt-cells = <3>;

wakeup-gpios = <&gpioa 0 GPIO_ACTIVE_HIGH>,/*PA0高电平唤醒*/

 <&gpioa 2 GPIO_ACTIVE_HIGH>,/*PA2高电平唤醒*/

 <&gpioc 13 GPIO_ACTIVE_HIGH>,/*PC13高电平唤醒*/

 <&gpioi 8 GPIO_ACTIVE_HIGH>,/*PI8高电平唤醒*/

 <&gpioi 11 GPIO_ACTIVE_HIGH>,/*PI11高电平唤醒*/

 <&gpioc 1 GPIO_ACTIVE_HIGH>;/*PC1高电平唤醒*/

/*查询数据手册,PA0,PA2,PC13,PI8,PI11和PC1均具有唤醒功能*/

};

2、了解EXTI事件

1)、打开STM32MP157.html" title=STM32MP157>STM32MP157参考手册,搜索“Table 118”,找到EXTI[3],即为外部中断3。

2)、打开“linux-5.4.31/arch/arm/boot/dts/stm32mp151.dtsi”

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>;

};

};

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;

#gpio-cells = <2>;

interrupt-controller;/*当前节点为中断控制器*/

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0x0 0x400>;

clocks = <&rcc GPIOA>;

st,bank-name = "GPIOA";

status = "disabled";

};

/*下面省略*/

gpiog: gpio@50008000 {

gpio-controller;

#gpio-cells = <2>;

interrupt-controller;/*当前节点为中断控制器*/

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0x6000 0x400>;

clocks = <&rcc GPIOG>;

st,bank-name = "GPIOG";

status = "disabled";

};

/*下面省略*/

};

这个stm32mp151.dtsi一般无需修改。

3)、打开“/linux-5.4.31/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi”,添加如下:

key_pins_a: key_pins-0 {/*"key_pins_a既是标号也是节点*/

pins1 {

pinmux = <STM32_PINMUX('G', 3, GPIO)>,

/*设置PG3复用为GPIO功能*/

/*KEY0连接到PG3*/

<STM32_PINMUX('H', 7, GPIO)>;

/*设置PH7复用为GPIO功能*/

/*KEY1连接到PH7*/

bias-pull-up; /*设置引脚内部上拉*/

slew-rate = <0>;/*设置引脚的速度为0档,0最慢,3 最高*/

};

pins2 {

pinmux = <STM32_PINMUX('A', 0, GPIO)>;

/*设置PA0复用为GPIO功能*/

/*WK_UP连接到PA0*/

bias-pull-down; /*内部下拉*/

slew-rate = <0>;/*设置引脚的速度为0档,0最慢,3 最高*/

};

};

4)、打开“/linux-5.4.31/arch/arm/boot/dts/stm32mp157d-atk.dts”,添加如下:

key {

compatible = "zgq,key";/*设置属性compatible的值为"zgq,led"*/

status = "okay";/*设置属性status的值为"okay"*/

pinctrl-names = "default";

pinctrl-0 = <&key_pins_a>;/*指定key的父节点key_pins_a*/

key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;

/*“&gpiog”表示key-gpio引脚所使用的IO属于GPIOG组*/

/*“3’表示GPIOG组的第3号IO,即PG3引脚*/

/*“GPIO_ACTIVE_LOW”表示低电平有效,“GPIO_PULL_UP”表示上拉*/

interrupt-parent = <&gpiog>;

/*指定父中断器为&gpiog*/

/*通过interrupt-parent属性指定key所有子节点的中断父节点为gpiog*/

interrupts = <3 IRQ_TYPE_EDGE_BOTH>;

/*查看参考手册“Table 118”,EXTI[3]的事件输入号码为3*/

/*IRQ_TYPE_EDGE_BOTH为边沿触发*/

/*可以用interrupts-extended = <&gpioa 3 IRQ_TYPE_EDGE_BOTH>;替换上面两句*/

};

至此,我们就可以将PG3引脚作为外部中断引脚。

3、驱动程序如下:

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/of.h>

#include <linux/platform_device.h>

#include <linux/of_gpio.h>

#include <linux/input.h>

#include <linux/timer.h>

#include <linux/of_irq.h>

#include <linux/interrupt.h>

#define KEYINPUT_NAME "keyinput"   /* 设备名字*/

/* key设备结构体 */

struct key_dev{

struct input_dev *idev;  /* 按键对应的input_dev指针 */

struct timer_list timer; /* 定时器结构参数 */

int gpio_key;     /* 按键对应的GPIO编号 */

int irq_key;     /* 按键对应的中断号 */

};

static struct key_dev key;  /* 按键设备 */

//函数功能:按键中断服务函数

//irq: 触发该中断事件对应的中断号

//arg : arg参数可以在申请中断的时候进行配置

static irqreturn_t key_interrupt_fun(int irq,void *dev_id)

{

if(key.irq_key != irq) return IRQ_NONE;

disable_irq_nosync(irq); /*禁止按键中断*/

mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));

//设置定时器在15ms后产生中断,用于按键消抖

//如果定时器还没有激活,则该函数会激活定时器;

//返回值为0表示调用mod_timer()函数前,该定时器未被激活;

//返回值为1表示调用mod_timer()函数前,该定时器已被激活

//timer=&key.timer:要修改超时时间(定时值)的定时器;

//expires=jiffies + msecs_to_jiffies(timerperiod):修改后的超时时间;

    return IRQ_HANDLED;

}

//函数功能:Linux内核定时器中断服务函数

//用于按键消抖,读取按键值,并上报相应的事件。

static void key_timer_function(struct timer_list *arg)

{

int val;

val = gpio_get_value(key.gpio_key);

    //根据key.gpio_key读取按键值

input_report_key(key.idev, KEY_0, !val);

/向Linux系统通知KEY_0这个按键被按下,1表示按下

//由于key0被配置为边沿触发中断,且低电平表示按下,因此val取反

input_sync(key.idev);//同步事件

enable_irq(key.irq_key);

   //irq=key.irq_key表示要使能的中断号,这里是使能按键中断;

}

//函数功能:按键初始化函数

//nd为device_node型结构指针,指向设备

//返回值为0表示成功,负数表示失败

static int key_gpio_init(struct device_node *nd)

{

int ret;

    unsigned long irq_flags;

key.gpio_key = of_get_named_gpio(nd, "key-gpio", 0);

  //在key节点中,key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;

  //np指定的“设备节点”

  //propname="key-gpio",给定要读取的属性名字

  //Index=0,给定的GPIO索引为0

  //返回值:正值,获取到的GPIO编号;负值,失败。

if(!gpio_is_valid(key.gpio_key)) {

printk("key:Failed to get key-gpio\n");

return -EINVAL;

}

/* 申请使用GPIO */

ret = gpio_request(key.gpio_key, "KEY0");

  //gpio=key.gpio_key,指定要申请的“gpio编号”

  //Label="KEY0",给这个gpio引脚设置个名字为"KEY0"

  //返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;

    if (ret) {

        printk(KERN_ERR "key: Failed to request key-gpio\n");

        return ret;

}

gpio_direction_input(key.gpio_key);
    //设置“某个GPIO为输入口

//gpio= key.gpio_key,指定的“gpio编号”

    //返回值:0,表示设置成功;负值,表示设置失败。

key.irq_key = irq_of_parse_and_map(nd, 0);

  //获取GPIO对应的中断号

  //dev= nd:为设备节点;

  //Index=0:索引号,intemrupts属性可能包含多条中断信息,通过index指定要获取的信息;

  //返回值:中断号;

if(!key.irq_key){ return -EINVAL; }

irq_flags = irq_get_trigger_type(key.irq_key);

    //根据中断号key.irq_key获取“设备树中指定的中断触发类型”

if (IRQF_TRIGGER_NONE == irq_flags)

irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;

ret = request_irq(key.irq_key, key_interrupt_fun, irq_flags, "Key0_IRQ", NULL);

//用来申请“按键中断”;

  //irq=key.irq_key为中断号;

  //handler=key_interrupt_fun:中断处理函数,当中断发生以后就会执行此中断处理函数;

  //fags=irq_flags:中断标志;可以在文件“include/linux/interrupt.h”里面查看所有的中断标志;

  //name="Key0_IRQ":中断名字,设置以后可以在“/proc/interrupts”文件中看到对应的中断名字;

  //dev=NULL:如果将flags设置为IRQF_SHARED的话,dev用来区分不同的中断。

  //一般情况下将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。

  //返回值:0表示申请“按键中断”成功,如果返回“-EBUSY”的话表示该中断已经被申请过了, 其他负值,表示申请“按键中断”失败。

if (ret)

    {

        gpio_free(key.gpio_key);//释放“gpio编号”

        return ret;

    }

return 0;

}

//函数功能:platform驱动的probe函数

//当驱动与设备匹配成功以后此函数会被执行

//pdev为platform设备指针

//返回值为0,表示成功;其他负值,失败

static int Zhang_key_probe(struct platform_device *pdev)

{

int ret;

ret = key_gpio_init(pdev->dev.of_node);//按键初始化函数

if(ret < 0)return ret;

timer_setup(&key.timer, key_timer_function, 0);

    //初始化定时器“timer_list结构变量key.timer

    //key_timer_function为定时器的中断处理函数;

//flags=0为标志位;

   key.idev = input_allocate_device();

   //申请input_dev结构变量为key.idev

key.idev->name = KEYINPUT_NAME;//设置“设备名字”

#if 0

__set_bit(EV_KEY, key.idev->evbit);

 //设置产生“按键事件”

__set_bit(EV_REP, key.idev->evbit);

/* 重复事件,比如按下去不放开,就会一直输出信息 */

/* 初始化input_dev,设置产生哪些按键 */

__set_bit(KEY_0, key.idev->keybit);

#endif

#if 0

key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);

//BIT_MASK(EV_KEY)表示将第EV_KEY位置1

//BIT_MASK(EV_REP)表示将第EV_REP位置1

key.idev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);

//BIT_WORD(KEY_0)表示将“KEY_0”转换为字的偏移量

//BIT_MASK(KEY_0)表示将第KEY_0)位置1

#endif

#if 1

key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);

//BIT_MASK(EV_KEY)表示将第EV_KEY位置1

//BIT_MASK(EV_REP)表示将第EV_REP位置1

input_set_capability(key.idev, EV_KEY, KEY_0);

//当type=EV_KEY, code=KEY_0dev->keybit中的第code位置1

#endif

ret = input_register_device(key.idev);

//向Linux内核注册key.idev

// key.idev就是要注册的input_dev结构变量

//返回值:0,input_dev注册成功;负值,input_dev注册失败

if (ret) {

printk("register input device failed!\r\n");

goto free_gpio;

}

return 0;

free_gpio:

free_irq(key.irq_key,NULL);

//key.irq_key:要释放的中断号。

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

//返回值:无。

gpio_free(key.gpio_key); //释放“gpio编号”

del_timer_sync(&key.timer);

//key.timer指向要被删除的定时器

//等待其他处理器使用完定时器后再删除该定时器,del_timer_sync()不能用在

//中断服务程序中;

return -EIO;

}

//函数功能:platform驱动的remove函数,当platform驱动模块卸载时此函数会被执行

//dev为platform设备指针

//返回值为0,成功;其他负值,失败

static int Zhang_key_remove(struct platform_device *pdev)

{

free_irq(key.irq_key,NULL);

//key.irq_key:要释放的中断号。

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

//返回值:无。

gpio_free(key.gpio_key); //释放“gpio编号”

del_timer_sync(&key.timer);

//key.timer指向要被删除的定时器

//等待其他处理器使用完定时器后再删除该定时器,del_timer_sync()不能用在

//中断服务程序中;

input_unregister_device(key.idev);

//释放input_dev型结构key.idev

return 0;

}

static const struct of_device_id key_of_match[] = {

{.compatible = "zgq,key"},/*这是驱动中的compatible属性*/

{/*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/}

};

static struct platform_driver Zhang_key_driver = {

.driver = {

.name = "stm32mp1-key",

.of_match_table = key_of_match,

},

.probe = Zhang_key_probe,

    /*platform的probe函数为Zhang_key_probe()*/

.remove = Zhang_key_remove,

    /*platform的probe函数为Zhang_key_remove()*/

};

module_platform_driver(Zhang_key_driver);

//Zhang_key_driver为platform_driver结构

//用来向linux内核注册platform驱动

MODULE_AUTHOR("zgq");       //添加作者名字

MODULE_LICENSE("GPL");      //LICENSE采用“GPL协议”

MODULE_DESCRIPTION("This is Key_Driver_Test_Module!");//模块介绍

MODULE_INFO(intree, "Y"); 

//去除显示“loading out-of-tree module taints kernel.”

MODULE_ALIAS("input:key-gpio");


http://www.ppmy.cn/server/146796.html

相关文章

Linux——自定义简单shell

shell 自定义shell目标普通命令和内建命令&#xff08;补充&#xff09; shell实现实现原理实现代码 自定义shell 目标 能处理普通命令能处理内建命令要能帮助我们理解内建命令/本地变量/环境变量这些概念理解shell的运行 普通命令和内建命令&#xff08;补充&#xff09; …

将面具贴到人脸上的过程

使用OpenCV进行人脸面具贴合和变形以适应人脸的3D透视角度&#xff0c;通常需要以下步骤&#xff1a; 人脸检测&#xff1a;首先需要检测图像中的人脸位置。特征点检测&#xff1a;在检测到的人脸区域中&#xff0c;找到关键特征点&#xff0c;如眼睛、鼻子、嘴巴等。透视变换…

git push使用

推送指定分支 将当前分支推送远程 git push origin HEAD:<branch-name> 这里的 HEAD 是一个特殊的指针&#xff0c;它指向当前分支的最新提交。这条命令会将当前分支的更改推送到远程的 master 分支。 示例 git push origin HEAD:main 当前分支是test&#xff0c;远…

二维码有哪些网络安全风险隐患?

中国网民规模达10亿人&#xff0c;每个人几乎在智能手机用户在其移动设备上使用过二维码。随着人们越来越习惯使用二维码&#xff0c;黑客开始使用它们来寻找另一种窃取凭证和访问敏感信息的方法。 国际知名网络安全专家、东方联盟创始人郭盛华透露&#xff1a;“黑客针对企业的…

OODA循环在网络安全运营平台建设中的应用

OODA循环最早用于信息战领域&#xff0c;在空对空武装冲突敌对双方互相较量时&#xff0c;看谁能更快更好地完成“观察—调整—决策—行动”的循环程序。 双方都从观察开始&#xff0c;观察自己、观察环境和敌人。基于观察&#xff0c;获取相关的外部信息&#xff0c;根据感知…

Vue入门级教程二:组件化开发

在Vue 入门级教程一中&#xff0c;我们已经对 Vue.js 的基础概念、数据绑定和指令有了初步的了解。本教程将在此基础上&#xff0c;进一步深入探讨 Vue 的组件化开发、事件处理以及计算属性等重要特性&#xff0c;帮助你更全面地掌握 Vue 框架&#xff0c;构建出功能更为丰富的…

【C语言】结构体嵌套

结构体嵌套是指在一个结构体中定义另一个结构体作为其成员。这种方式可以实现更复杂的数据结构设计&#xff0c;便于对数据进行分层管理和组织&#xff0c;广泛应用于实际开发中&#xff0c;例如操作系统内核、嵌入式系统、网络协议解析等。下面是对结构体嵌套的详细介绍&#…

nginx 代理 web service 提供 soap + xml 服务

nginx 代理 web service 提供 soap xml 服务 最关键的配置: # Nginx默认反向后的端口为80&#xff0c;因此存在被代理后的端口为80的问题&#xff0c;这就导致访问出错。主要原因在Nginx的配置文件的host配置时没有设置响应的端口。Host配置只有host&#xff0c;没有对应的p…