Linux按键中断驱动

简介

中断这块是个重点,结合了很多之前已经讨论过的知识点,个人觉得是一个大的应用,这里把逻辑和代码都捋一下,留个记录。

驱动框架

框架部分还是Linux底层驱动中的字符设备框架,然后在此基础上添加中断部分,在中断中为了消抖,使用了定时器。同时,为了保护数据,使用了原子操作。下面就是具体实现。代码部分参考了原子的教程,特此注明一下。

字符设备驱动

字符设备驱动框架就是一入一出,非常的简单:

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);

传入的参数就是个函数,框架内这样:

static int __init imx6uirq_init(void);
static void __exit imx6uirq_exit(void);

注意分别有关键字**__init以及__exit**修饰。之后开始写这入口和出口函数。

驱动入口

驱动入口就是

static int __init imx6uirq_init(void);

的实现。

框架中,驱动入口需要做5个事情,分别是

  1. 注册字符设备驱动;
  2. 初始化cdev
  3. 添加cdev
  4. 创建类
  5. 创建设备

当然,在框架中,我们需要一个结构体来描述字符设备的属性,所以框架实际还需要包含字符设备使用的结构体:

sturct imx6uirq_dev{
    dev_t devid;        //设备号
    int  major;         //主设备号,devid的高12位
    int minor;          //次设备号,devid的低20位
    struct cdev cdev;   //字符设备结构体
    struct class* class; //类结构体
    struct device* device; //设备结构体
    struct device_node* nd; //节点结构体
};

入口函数要做的事情,就是初始化这个结构体里的每个成员变量,让设备可以使用。之后就是一步一步实现。

注册字符设备驱动

直接上代码:

    if(imx6uirq.major){
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }
    if (ret < 0){
        goto fail_devid;
    }

这里处理了一下给定设备号和未给定设备号的情况。其实主设备号和次设备号可以不用处理,之前说过主设备号是devid的高12位,次设备号是低20位。一旦通过alloc_chrdev_region注册了设备号,majorminor都被隐形的确定了。这里这么写是为了分析。实际工作中不需要。

初始化cdev

这个实际就是初始化字符设备结构体成员cdev的过程。注意cdev本身也是个结构体。代码很简单,有接口函数,调用一下就可以了。

    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
添加cdev

初始化完成后,需要把cdev添加到系统中,也是使用API

    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if(ret < 0){
        goto fail_cdevadd;
    }
添加类

所谓的类,实际是内核中的一个概念,它所扮演的角色是设备的大管家,位置在内核中。class可以包含多个具体设备,因此就是设备类。

    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)){
        ret =  PTR_ERR(imx6uirq.class);
        goto fail_class;
    }

需要访问设备的时候,必须指定类

添加设备

device用于表示一个具体的设备结构体,通常就是个硬件设备。需要创建一个具体的设备时,必须先创建好对应的类。

    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)){
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

至此,一个简单的字符设备框架的入口就完成了。

字符设备出口

出口,就是实现

static void __exit imx6uirq_exit(void);

依据入口中各个模块的初始化和添加顺序,反过来就可以:

static void __exit imx6uirq_exit(void)
{
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
}

中断部分

裸机里处理中断是这样:

  1. 使能中断,初始化对应的寄存器
  2. 注册中断服务函数,也就是向irqtable数值的指定标号处吸入中断服务函数
  3. 中断发生后进入IRQ中断服务函数,然后执行

设备树中断信息节点

Linux中,需要把中断先添加进设备树,然后再编写中断驱动。所以在实际写驱动之前,需要先在设备树内添加对应的中断。例如,需要使用按键KEY0的中断,需要添加:

key{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "Femtomes-key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
	status = "okay"
};

感兴趣的可以自行查阅中断控制器对应的设备树(intc节点)。

中断添加和处理

以下的代码均是需要添加在上面的字符设备驱动框架中的。在添加设备完成后,需要初始化IO。初始化IO的过程比较复杂,写成独立的函数,然后在驱动入口调用即可。
在做处理之前,首先添加一下需要用到的宏定义:

#define KEY_NUM			1
#define KEY0VALUE		0x01
#define INVAKEY			0xFF

然后在字符设备结构体中添加:

struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer;
atomic_t keyvalue;
atomic_t releasekey;

由于可能会有多个按键,每个按键也可能需要不同的irq_keydesc来描述:

struct irq_keydesc{
	int gpio;		//IO编号
	int irqnum;		//中断号
	unsigned char value;	//键值
	char name[10];	//名称
	irqreturn_t(*handler)(int,void*);	//中断处理函数
}

这里注意,irq_keydesc是作为imx6uirq_dev的成员变量存在的!

添加到入口的代码:

ret = keyio_init(&imx6uirq);

函数实现:

static int keyio_init(struct imx6uirq_dev* dev)
{
    int ret = 0;
    int i = 0;
    /*1. 按键初始化 */
    dev->nd = of_find_node_by_path("/key");
    if(dev->nd == NULL){
        ret = -EINVAL;
        goto fail_nd;
    }

    for(i = 0; i < KEY_NUM; i++)
    {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i); 
        if(dev->irqkey[i].gpio < 0){
            printk(" Can't get key%d\r\n",i);
        }
    }

    for(i = 0; i < KEY_NUM ; i++)
    {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        gpio_request(imx6uirq.irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); /* 获取中断号 */
        //dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i); /* 通用获取函数 */
    }

    dev->irqkey[0].handler = key0_handler;
    dev->irqkey[0].value   = KEY0VALUE;

    /*2. 按键中断初始化 */
    for(i = 0; i< KEY_NUM; i++)
    {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, 
                            IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                            dev->irqkey[i].name, &imx6uirq);

        if(ret){
            printk("irq %d request error!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }

    /* 3. 初始化定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;

    return 0;

fail_irq:
    for(i = 0; i < KEY_NUM ;i++){
        gpio_free(imx6uirq.irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

keyio_init中调用的key0_handler,也就是中断服务函数:

static irqreturn_t key0_handler(int irq, void* dev_id)
{
#if 0
    int val = 0;
    struct imx6uirq_dev *dev = dev_id;
    
    val = gpio_get_value(dev->irqkey[0].gpio);
    if(val == 0){           /* 按下 */
        printk("KEY0 push!\r\n");
    } else if(val == 1){    /**/
        printk("KEY0 release!\r\n");
    }
#endif

    struct imx6uirq_dev *dev = dev_id;
    dev->timer.data = (volatile unsigned long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

    return IRQ_HANDLED;
}

keyio_init中调用的timer_func,也就是定时器服务函数:

static void timer_func(unsigned long arg)
{
    int val = 0;
    struct imx6uirq_dev* dev = (struct imx6uirq_dev*)arg;

    val = gpio_get_value(dev->irqkey[0].gpio);
    if(val == 0){           /* 按下 */
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
        printk("KEY0 push!\r\n");
    } else if(val == 1){    /* 释放 */
        atomic_set(&dev->keyvalue, 0x80 | (dev->irqkey[0].value));
        atomic_set(&dev->releasekey, 1);
        printk("KEY0 release!\r\n");
    }
}

这里简单描述一下逻辑。我们要实现定时器消抖,那么在按下按键的时候,定时器开始工作,延迟一段时间后(代码中是10ms)读取键值,如果还是按下,那么就认为是一次有效的按压。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值