当前位置: 首页 > article >正文

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

1、了解WKUP唤醒引脚

1)、打开STM32MP157数据手册,搜索“WKUP”,见下表:

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

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

下图是“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参考手册,搜索“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.kler.cn/a/418753.html

相关文章:

  • 单链表---移除链表元素
  • TimeSpan和DateTime
  • registry 删除私有仓库镜像
  • 继上一篇,设置弹框次数以及自适应图片弹框,部分机型(vivo)老手机不显示的问题
  • Java对象与XML互相转换(xstream)
  • 探索文件系统,Python os库是你的瑞士军刀
  • HarmonyOS(61) 组件间状态共享的分类以及状态选择器的选取优先级
  • Android Glide批量加载Bitmap,拼接组装大Bitmap,更新单个AppCompatImageView,Kotlin(3)
  • 【element-tiptap】导出word
  • 在CentOS 7上设置Apache的mod_rewrite的方法
  • 【数据结构计数排序】计数排序
  • Java面经之JVM
  • Qt Sensors 传感器控制介绍篇
  • 【JAVA】IntelliJ IDEA 如何创建一个 Java 项目
  • Vue3+node.js实现注册
  • (免费送源码)计算机毕业设计原创定制:Apache+JSP+Ajax+Springboot+MySQL Springboot自习室在线预约系统
  • VPN连不上学校服务器
  • 大模型开发中LCEL与LLMChain响应度的对比
  • Electron PC桌面应用exe开发
  • C#中switch语句使用
  • 大模型日报 2024-12-01
  • 大模型开发和微调工具Llama-Factory-->数据处理
  • Linux设置开启启动脚本
  • Vue 3 服务端渲染(SSR)教程
  • SpringMVC |(一)SpringMVC概述
  • DevOps工程技术价值流:Jenkins驱动的持续集成与交付实践