linux驱动之中断与输入子系统

linux驱动

第一章 linux驱动之设备与驱动
第三章 linux驱动之总线详解



一、中断的设备树

1.中断设备树结构

intc 顶层中断控制器——gpc一级子中断控制器——soc 二级子中断控制器(包含大多数外设的具体中断控制器)

intc: interrupt-controller@a01000 {
    compatible = "arm,cortex-a7-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0xa01000 0x1000>,
          <0xa02000 0x100>;
};
intc用来描述整个GIC控制器
reg:reg指定中断控制器相关寄存器的地址及大小
interrupt-controller:声明该设备树节点是一个中断控制器。
#interrupt-cells :指定它的“子”中断控制器用几个cells来描述一个中断,可理解为用几个参数来描述一个中断信息。 在这里的意思是在intc节点的子节点将用3个参数来描述中断。


gpc: gpc@20dc000 {
    compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";
    reg = <0x20dc000 0x4000>;
    interrupt-controller;
    #interrupt-cells = <3>;
    interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&intc>;
    fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640>;
};
interrupt-parent:指定该中断控制器的“父”中断控制器。除了“顶层中断控制器”其他中断控制器都要声明“父”中断控制器。
interrupts:具体的中断描述信息,在该节点的中断控制器的“父”中断控制器,规定了使用三个cells来描述子控制器的信息:
第一个参数用于指定中断类型,在GIC中中断的类型有三种(SPI共享中断、PPI私有中断、SGI软件中断), 
第二个参数用于设定中断编号,范围和第一个参数有关。PPI中断范围是[0-15],SPI中断范围是[0-987]。
第三个参数指定中断触发方式,参数是一个u32类型,其中后四位[0-3]用于设置中断触发类型。 每一位代表一个触发方式,可进行组合,系统提供了相对的宏顶义我们可以直接使用,如下所示:


soc {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";
    interrupt-parent = <&gpc>;
    ranges;
    
	gpio5: gpio@20ac000 {
	    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	    reg = <0x20ac000 0x4000>;
	    interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
	                 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
	    clocks = <&clks IMX6UL_CLK_GPIO5>;
	    gpio-controller;
	    #gpio-cells = <2>;
	    interrupt-controller;
	    #interrupt-cells = <2>;
	    gpio-ranges = <&iomuxc 0 7 10>, <&iomuxc 10 5 2>;
	};
    //busfreq子节点
        busfreq {
        ................  //表示省略
    }
    ...............     //表示省略

soc 节点即片上外设“总节点”,我们使用的外设大多包含在里面。 具体外设也可作为中断控制器,
这里声明了它们的“父”中断控制器是 <&gpc>节点

2.中断的设备树设置

中断设备在设备树一般是在soc节点下写具体的设备的中断配置,驱动开发人员可以具体的中断设备节点下写要使用的中断设备树。

button_interrupt {
    compatible = "button_interrupt";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_button>;
    button_gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;  //默认低电平,按键按下高电平
    status = "okay";
    interrupt-parent = <&gpio5>;
    interrupts = <1 IRQ_TYPE_EDGE_RISING>;     // 指定中断,触发方式为上升沿触发。
};
interrupt-parent:指定“父控制器节点 ”。我们按键所在的引脚是gpio5_1,故我们按键所在的中断控制父节点 为gpio5。
interrupts:在gpio5节点中定义使用两个cells来描述我们的按键信息,‘1’表示的是我们按键GPIO5中引脚编号, “IRQ_TYPE_EDGE_RISING”表示的是触发方式。触发方式宏定义如下

3.中断相关函数

通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号
interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号
int gpio_to_irq(unsigned int gpio)

将中断号irq和中断函数绑定。多设备共享同一个中断是,用dev确定绑定具体设备和中断函数。flag表示中断的一些标志,这些标志决定中断发生方式和中断的某些特性。
int request_irq( unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)

释放的中断
void free_irq(unsigned int irq,void *dev)

中断函数
irqreturn_t (*irq_handler_t) (int, void *)

中断使能和禁止函数
void enable_irq(unsigned int irq) 
void disable_irq(unsigned int irq)

void disable_irq_nosync(unsigned int irq)

4.中断调用的代码步骤

在prone函数 

{
'''获取中断号 ''

通过节点获得中断号
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号
gpio是具体的引脚号,可以通过of_get_named_gpio获得
int gpio_to_irq(unsigned int gpio)


'''绑定中断号和中断函数''
request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
}
在exit函数中释放中断

{
 free_irq(irq, NULL);
 }
/*
 * @Author:topeet
 * @Description: 使用gpio_to_irq函数来获取中断号
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;

/**
 * @description: 中断处理函数test_key
 * @param {int} irq :要申请的中断号
 * @param {void} *args :
 * @return {*}IRQ_HANDLED
 */
irqreturn_t test_key(int irq, void *args)
{
    printk("test_key \n");
    return IRQ_RETVAL(IRQ_HANDLED);
}
/****************************************************************************************
 * @brief beep_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,
 * @param inode : 文件索引
 * @param file  : 文件
 * @return 成功返回 0 	    	
 ****************************************************************************************/
int beep_probe(struct platform_device *pdev)
{
    int ret = 0;
    // 打印匹配成功进入probe函数
    printk("beep_probe\n");
    // of_find_node_by_path函数通过路径查找节点,/test_key是设备树下的节点路径
        test_device_node = of_find_node_by_path("/test_key");

    //of_get_named_gpio函数获取 GPIO 编号
        gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);

    //设置GPIO为输入模式
    gpio_direction_input(gpio_nu);
    //获取GPIO对应的中断号
    irq = gpio_to_irq(gpio_nu);
    printk("irq is %d \n", irq);
    /*申请中断,irq:中断号名字  
     test_key:中断处理函数
     IRQF_TRIGGER_RISING:中断标志,意为上升沿触发
     "test_key":中断的名字
     */
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
   
    return 0;
}

int beep_remove(struct platform_device *pdev)
{
    printk("beep_remove\n");
    return 0;
}
const struct platform_device_id beep_idtable = {
    .name = "keys",
};
const struct of_device_id of_match_table_test[] = {
    {.compatible = "test1234"},
    {},
};
struct platform_driver beep_driver = {
    //3. 在beep_driver结构体中完成了beep_probe和beep_remove
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",
        .of_match_table = of_match_table_test},
    //4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
    .id_table = &beep_idtable};

/**
 * @description: 模块初始化函数
 * @param {*}
 * @return {*}
 */
static int beep_driver_init(void)
{
    //1.我们看驱动文件要从init函数开始看
    int ret = 0;
    //2.在init函数里面注册了platform_driver
    ret = platform_driver_register(&beep_driver);
   
    printk("platform_driver_register ok \n");
    return 0;
}

/**
 * @description: 模块卸载函数
 * @param {*}
 * @return {*}
 */
static void beep_driver_exit(void)
{
    free_irq(irq, NULL);//释放中断
    platform_driver_unregister(&beep_driver);
    printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);

MODULE_LICENSE("GPL");

二、输入子系统

1.输入设备结构体和事件结构体

输入设备结构体input_dev保存输入设备的一些重要信息。我们通过input_allocate_device申请一个设备结构体后,设置相关参数,然后就可以将调用input_report_key(test_dev, KEY_1, value),将设备、某个键和值联系起来,并在系统中生成一个input_event结构体,每次用户敲打key_1时,系统就会根据input_event结构体完成相关操作。

struct input_dev {
    const char *name;  //提供给用户的输入设备的名称
    const char *phys;  //提供给编程者的设备节点的名称
    const char *uniq;   //指定唯一的ID号
    struct input_id id; //输入设备标识ID

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];  //指定设备支持的事件类型
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //记录支持的键值
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //记录支持的相对坐标位图
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //记录支持的绝对坐标位图

    /*----------以下结构体成员省略----------------*/
};


struct input_event 
{ 
struct timeval time; 
__u16 type;
__u16 code;
__s32 value;
  };




2.输入系统的代码流程

先申请一个输入设备,在设置结构体的一些参数,然后再注册到系统中,最后在将事件上报的系统中。
test_dev = input_allocate_device();
// 设置 input_dev 名字
test_dev->name = "test_key";
// 设置事件和事件值
// 设置产生按键事件
__set_bit(EV_KEY, test_dev->evbit);
//设置产生哪些按键值,表示这个设备要支持key_1
__set_bit(KEY_1, test_dev->keybit);
//向 Linux内核注册 input_dev
ret = input_register_device(test_dev);
将value和KEY_1绑定。事件上报后,系统就会建立一个input_event结构体。
input_report_key(test_dev, KEY_1, value); //上报事件 
input_sync(inputdev); /* 同步事件 告诉系统上报完成*/


/*
 * @Author:topeet
 * @Description:使用输入子系统设计按键驱动
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
//添加输入子系统的头文件
#include <linux/input.h>

static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0);
struct device_node *test_device_node;
struct property *test_node_property;
//定义一个输入设备test_dev
struct input_dev *test_dev; //定义一个输入设备test_dev
int irq;
int gpio_nu;
/**
 * @description:超时处理函数 
 * @param {*}
 * @return {*}
 */
static void timer_function(unsigned long data)
{

	int value;
	value = !gpio_get_value(gpio_nu);
	input_report_key(test_dev, KEY_1, value); //上报事件
	input_sync(test_dev);
}

//中断处理函数
irqreturn_t test_key(int irq, void *args)
{
	printk("test_key\n");
	test_timer.expires = jiffies + msecs_to_jiffies(20);
	//定时器注册到内核里面
	add_timer(&test_timer);
	return IRQ_RETVAL(IRQ_HANDLED);
}
/**
 * @brief beep_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,
 * @param inode : 文件索引
 * @param file  : 文件
 * @return 成功返回 0           
*/
int beep_probe(struct platform_device *pdev)
{
	int ret = 0;
	printk("beep_probe\n");
	//of_find_node_by_path函数通过路径查找节点,/test_key是设备树下的节点路径
		test_device_node = of_find_node_by_path("/test_key");
	if (test_device_node == NULL)
	{
		printk("of_find_node_by_path is error \n");
		return -1;
	}
	//of_get_named_gpio函数获取 GPIO 编号
		gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
	if (gpio_nu < 0)
	{
		printk("of_get_namd_gpio is error \n");
		return -1;
	}
	// 设置GPIO为输入模式
	gpio_direction_input(gpio_nu);
	//irq = gpio_to_irq(gpio_nu);
	// 获取中断号
	irq = irq_of_parse_and_map(test_device_node, 0);
	printk("irq is %d \n", irq);
 	/*申请中断,irq:中断号名字  
     test_key:中断处理函数
     IRQF_TRIGGER_RISING:中断标志,意为上升沿触发
     "test_key":中断的名字
     */
	ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "test_key", NULL);
	if (ret < 0)
	{
		printk("request_irq is error \n");
		return -1;
	}
	//申请一个 input_dev输入设备
	test_dev = input_allocate_device();
	// 设置 input_dev 名字
	test_dev->name = "test_key";
	// 设置事件和事件值
	// 设置产生按键事件
	__set_bit(EV_KEY, test_dev->evbit);
	//设置产生哪些按键值,表示这个设备要支持key_1
	__set_bit(KEY_1, test_dev->keybit);
	//向 Linux内核注册 input_dev
	ret = input_register_device(test_dev);
	if (ret < 0)
	{
		printk("input_register_device is error \n");
		goto error_input_register;
	}
	return 0;
error_input_register:
	input_unregister_device(test_dev);
}

int beep_remove(struct platform_device *pdev)
{
	printk("beep_remove\n");
	return 0;
}
const struct platform_device_id beep_idtable = {
	.name = "keys",
};
const struct of_device_id of_match_table_test[] = {
	{.compatible = "test1234"},
	{},
};
struct platform_driver beep_driver = {
	//3. 在beep_driver结构体中完成了beep_probe和beep_remove
	.probe = beep_probe,
	.remove = beep_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "beep_test",
		.of_match_table = of_match_table_test
	},
	//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
	.id_table = &beep_idtable 
};

static int beep_driver_init(void)
{ 
	// 1.我们看驱动文件要从init函数开始看
	int ret = 0;
	//2. 在init函数里面注册了platform_driver
	ret = platform_driver_register(&beep_driver); 
	if (ret < 0)
	{
		printk("platform_driver_register error \n");
	}
	printk("platform_driver_register ok \n");

	return 0;
}

static void beep_driver_exit(void)
{
	del_timer(&test_timer);
	free_irq(irq, NULL);
	input_unregister_device(test_dev);
	platform_driver_unregister(&beep_driver);
	printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);

MODULE_LICENSE("GPL");

3.一些细节补充

1.input子系统本质上也是字符设备,当系统开机时,就会执行input.c,在input.c中会建立一个input的class,同时申请设备号,所以所有的输入子系统的主设备号为都是13,并完成字符设备的注册。

2.输入子系统分为了Drivers(驱动层)、Input Core(输入子系统核心层)、 handlers(事件处理层)三部分。
(1) Drivers主要实现对硬件设备的读写访问,设置中断,并将硬件产生的事件转为Input Core定义的规范提交给Handlers;
(2) Input Core起到承上启下的作用,为Drivers提供了规范及接口,并通知Handlers对事件进行处理;提供了输入子系统的驱动设置的相关函数。
(3) Handlers并不涉及硬件方面的具体操作,是一个纯软件层,包含了不同的解决方案,如按键、键盘、鼠标、游戏手柄等。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值