Linux通过GPIO状态实现按键上报(按键功能)

  GPIO是集成电路或电子电路板上未提交的数字信号引脚,用于将微控制器连接到其他电子设备的标准接口。当然,我们也可以通过几个GPIO口编写一个键盘驱动,驱动挂载后拥有按键功能。
  示例驱动基于ARM64架构开发,其中用到的知识将会在文章中讲述。


设备树
platform
工作队列
input


设备树:


  设备树是一种描述硬件的数据结构,它起源于OpenFirmware(OF),设备树宏定义为CONFIG_OF。ARM内核版本3.x之后引入了设备树,其目的是为移除各种硬件的描述性代码,减少驱动代码体积。 设备树由一系列被命名的节点(Node)和属性(Property)组成,而节点本身可包含子节点。
  ARM64架构下设备树文件放置在:arch/arm64/boot/dts目录下,参考下图设备树-altera,不同厂商的设备树文件都创建了自己的文件夹。

设备树-altera

设备树-altera

  .dts文件是一种ASCII文本对Device Tree的描述,一般而言,一个.dts文件对应一个ARM的设备(一个厂家可能拥有许多不同型号的芯片或设备),比如rtd就分为1295*.dts、1298*.dts等。

  .dtsi文件属于描述头文件,dts文件中需要进行include .dtsi文件。当然,dtsi本身也支持include 另一个dtsi文件。


  socfpga_stratix10.dtsi 部分内容:

/*
 * Copyright Altera Corporation (C) 2015. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/dts-v1/;
#include <dt-bindings/reset/altr,rst-mgr-s10.h>
#include <dt-bindings/gpio/gpio.h>

/ {
	compatible = "altr,socfpga-stratix10";
	#address-cells = <2>;
	#size-cells = <2>;

	cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		cpu0: cpu@0 {
			compatible = "arm,cortex-a53", "arm,armv8";
			device_type = "cpu";
			enable-method = "psci";
			reg = <0x0>;
		};

		cpu1: cpu@1 {
			compatible = "arm,cortex-a53", "arm,armv8";
			device_type = "cpu";
			enable-method = "psci";
			reg = <0x1>;
		};

		cpu2: cpu@2 {
			compatible = "arm,cortex-a53", "arm,armv8";
			device_type = "cpu";
			enable-method = "psci";
			reg = <0x2>;
		};

		cpu3: cpu@3 {
			compatible = "arm,cortex-a53", "arm,armv8";
			device_type = "cpu";
			enable-method = "psci";
			reg = <0x3>;
		};
	};

	...
	gpio0: gpio@ffc03200 {
			#address-cells = <1>;
			#size-cells = <0>;
			compatible = "snps,dw-apb-gpio";
			reg = <0xffc03200 0x100>;
			resets = <&rst GPIO0_RESET>;
			status = "disabled";

			porta: gpio-controller@0 {
				compatible = "snps,dw-apb-gpio-port";
				gpio-controller;
				#gpio-cells = <2>;
				snps,nr-gpios = <24>;
				reg = <0>;
				interrupt-controller;
				#interrupt-cells = <2>;
				interrupts = <0 110 4>;
			};
		};

		gpio1: gpio@ffc03300 {
			#address-cells = <1>;
			#size-cells = <0>;
			compatible = "snps,dw-apb-gpio";
			reg = <0xffc03300 0x100>;
			resets = <&rst GPIO1_RESET>;
			status = "disabled";

			portb: gpio-controller@0 {
				compatible = "snps,dw-apb-gpio-port";
				gpio-controller;
				#gpio-cells = <2>;
				snps,nr-gpios = <24>;
				reg = <0>;
				interrupt-controller;
				#interrupt-cells = <2>;
				interrupts = <0 111 4>;
			};
		};

		...
};

  socfpga_stratix10_socdk.dts 部分内容:

/*
 * Copyright Altera Corporation (C) 2015. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "socfpga_stratix10.dtsi"

/ {
	model = "SoCFPGA Stratix 10 SoCDK";

	aliases {
		serial0 = &uart0;
	};

	chosen {
		stdout-path = "serial0:115200n8";
	};

	leds {
		compatible = "gpio-leds";
		hps0 {
			label = "hps_led0";
			gpios = <&portb 20 GPIO_ACTIVE_HIGH>;
		};

		hps1 {
			label = "hps_led1";
			gpios = <&portb 19 GPIO_ACTIVE_HIGH>;
		};

		hps2 {
			label = "hps_led2";
			gpios = <&portb 21 GPIO_ACTIVE_HIGH>;
		};
	};

	...
};

  dtb文件由DTC编译.dts生成的二进制文件(.dtb),bootloader在引导内核时,会预先读取.dtb到内存,进而由内核解析,当然这些不需要我们关心了。

  如果我们想增加未使用的GPIO口,可以这么做:
  仿socfpga_stratix10.dtsi文件中的gpio1: gpio@ffc03300 写法:

	gpio1: gpio@ffc03400 {
		compatible = "snps,dw-port-output";
		//gpio-controller;
		#gpio-cells = <6>; 
		output,gpio_numbers = <5>; 				/* gpio number*/
		output,gpio0 = <8 1 0>;					
		output,gpio1 = <59 1 1>;					
		output,gpio2 = <60 1 1>;					
		output,gpio3 = <63 1 1>;				
		output,gpio4 = <134 0 1>;
	};

  这些端口作为配置使用。

  在这里推荐一本书<Linux设备驱动开发详解-基于最新的Linux4.0内核>,设备树相关驱动讲解的比较详细。


platform:


  使用设备树后,驱动需要与.dts中描述的设备节点进行匹配,从而使驱动的probe()函数执行。对于 platform_driver而言,需要添加一个OF匹配表:

static const struct of_device_id gpio_input[] = {
	{.compatible = "snps,dw-port-output",},
};

static struct platform_driver rtk_gpio_driver = {
	.driver = {
			   .name = KEYBOARD_NAME,
			   .owner = THIS_MODULE,
			   .of_match_table = gpio_input,
			   },
	.probe = keyboard_probe,
	.remove = keyboard_remove,
};

  在驱动代码中,我们可以这样获取节点信息:

#define CONFIG_GPIO         "output,gpio"
u32 gpio_numbers = 0;
 
//首先获取到到gpio数量
if (of_property_read_u32(node, "output,gpio_numbers", &gpio_numbers))
{
	printk(KERN_ERR "Don't know gpio group number.\n");
	return -EINVAL;
}

if (0 >= gpio_numbers) {
    return INFO_ERROR;
}

//通过索引获取配置
for(i = 0; i < gpio_numbers; i++)
{
	   char strdata[128] = {0};
	   int ngpio = 0;
	   sprintf(strdata,"%s%d",CONFIG_GPIO,i);
	  
	   //通过索引获取配置,  我们的GPIO节点由3个参数组成,分别表示GPIO号,中断值,GPIO状态
	   if (of_property_read_u32_index(node, strdata, 0, &ngpio))
	   {
	       printk(KERN_ERR "read_u32_index id: Don't know gpio group number.\n");
	       return -EINVAL;
	   }
	   
	   if (of_property_read_u32_index(node, strdata, 1, &ngpio))
	   {
	       printk(KERN_ERR "read_u32_index in:Don't know gpio group number.\n");
	       return -EINVAL;
	   }
	   
	   if (of_property_read_u32_index(node, strdata, 2, &ngpio))
	   {
	       printk(KERN_ERR "read_u32_index out:Don't know gpio group number.\n");
	       return -EINVAL;
	   }
}


工作队列:


  在多按键的情况下,使用中断触发方式无法做到正确按键判断,在效率方面也不理想。比较合理的做法就是使用工作队列,工作队列采用定时唤醒方式实现,参考<linux内核驱动工作队列用法>


input:


  当驱动执行到这一步的时候,基本上完成我们实现的功能了,这时候只需要把我们扫描到的按钮上报到输入子系统就行了,系统会自动把按键信息(比如我们定义0-7)展示到我们想要显示的位置!
  input_event(key_data->input,EV_KEY,MSC_SCAN,value); //上报输入事件
  input_report_key(key_data->input,code,value); //上报值
  input_sync(key_data->input); //同步信息,调用这个函数才能看到我们输入的信息。

下面是完整代码:


.h:
/**
* 李坤昱
* [email protected]
*/

#ifndef KEYBOARD_H
#define KEYBOARD_H

#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>								
#include <linux/input.h>

#define  GPIOLEN 24
#define KDEBUG 0


//gpio口对应状态
struct gpio_info
{
    int id;
    int req;
    int write;
    int flags;
    int irq;
    int gpio_data;
};

//按键信息
struct keyboard_data
{
    int gpio_len;                     
    struct gpio_info gpio[GPIOLEN];
    struct input_dev *input;
};


//计算gpio相对端口有几次触发
struct gpio_irq_info
{
    int id;
    int irq;
    int write;
    int bIs;
    int count;
};

static void gpio_clear(void);
static void gpio_query_value(void);
static void gpio_query_state(void);
static void queue_timeout(struct work_struct *work);
static void queue_delayed(unsigned long delay);
static void gpio_input(int number,int value);
static int gpio_init(struct keyboard_data *gpio_grp);
static int keyboard_init(void);
static void keyboard_exit(void);
static int keyboard_probe(struct platform_device *pdev);
static int keyboard_remove(struct platform_device *pdev);
static int keyboard_reg(void);
static void keyboard_unreg(void);

#endif

.cpp:
#include "keyboard.h"

#define KEYBOARD_NAME       "keyboard-reg"
#define INFO_ERROR        	-1
#define INFO_NAME         "output,gpio"


struct keyboard_data *key_data;

struct gpio_irq_info  irq_info[8] = {0};
#define irq_infoLen 8

//工作队列
struct delayed_work work;
int work_run = 0;

static int gpio_alls_idata[8] = {
    14,
    6,
    10,
    2,
    12,
    4,
    8,
    0
};

static int gpio_alls_dataLen = 8;

//记录发送的哪个按键并且关闭
struct buttonInput {
    int nbutton;
    int nbIs;
};
struct buttonInput g_button;

struct platform_device *pdev_temp;


//清除gpio信息
static void gpio_clear(void)
{
    int i = 0;
    for(; i < irq_infoLen; i++)
    {
        irq_info[i].bIs = 1;
        irq_info[i].count = 0;
    } 
}

//轮寻数据
static void gpio_query_value(void)
{
    int i = 0;
    for(; i < irq_infoLen && 0 != irq_info[i].id; i++)
    {
        if (1 == irq_info[i].write) {
            int nIs = gpio_get_value(irq_info[i].id);
            if (nIs != irq_info[i].bIs)
            {
                irq_info[i].bIs = nIs;
                irq_info[i].count = 0;
            }
            else
                irq_info[i].count++;
        }
    }
    gpio_query_state();
}

//检测是否符合提交按键信息,并提交
static void gpio_query_state(void)
{
    int nbutton = 0;
    int i = 0;
    int nval = 0;
    for(; i < irq_infoLen && 0 != irq_info[i].id; i++)
    {
        if (1 == irq_info[i].write && 3 <= irq_info[i].count) {
            nval |= (irq_info[i].bIs << (3 - nbutton++));
        }
        else if (1 == irq_info[i].write && 3 > irq_info[i].count) {
            nval = -1;
            break;
        }
    }
    if (-1 != nval) {
        for(i = 0; i < gpio_alls_dataLen; i++)
        {
            if (nval == gpio_alls_idata[i]) {
                if (g_button.nbutton != i) {
                    gpio_input(i,1);
                    g_button.nbutton = i;
                    g_button.nbIs = 1;
                    printk(KERN_INFO "button:%d,nval:%d.\n",i,nval);
                }
                break;
            }
        }
    }
    else if (-1 != g_button.nbutton && 1 == g_button.nbIs)
    {
        gpio_input(g_button.nbutton,0);
	    printk(KERN_INFO "button clean:%d.\n",g_button.nbutton);
        g_button.nbIs = 0;
        g_button.nbutton = -1;
        gpio_clear();
    }
}

/******队列函数**************/

static void queue_timeout(struct work_struct *work)
{

    if (1 == work_run) {
        gpio_query_value();
        queue_delayed(14);
    }
}

static void queue_delayed(unsigned long delay)
{
    unsigned long delay_ = msecs_to_jiffies(delay);
    if (delay_ >= HZ)
		delay_ = round_jiffies_relative(delay_);
    schedule_delayed_work(&work, delay_);
}

static void gpio_input(int number,int value)
{	
    int code = 0;
    switch(number)
    {
    case 0:{code = KEY_1;break;}
    case 1:{code = KEY_2;break;}
    case 2:{code = KEY_3;break;}
    case 3:{code = KEY_4;break;}
    case 4:{code = KEY_5;break;}
    case 5:{code = KEY_6;break;}
    case 6:{code = KEY_7;break;}
    case 7:{code = KEY_8;break;}
    }

    input_event(key_data->input,EV_KEY,MSC_SCAN,value);    
    input_report_key(key_data->input,code,value);
    input_sync(key_data->input);
}

static int gpio_init(struct keyboard_data *gpio_grp)
{
    //首先注册gpio
    int i = 0;
    int ret = 0;
    if(KDEBUG)printk(KERN_ALERT ":  gpio_init(void)! \n");
    for(; i < gpio_grp->gpio_len; i++)
    {
        char strdata[128] = {0};
        sprintf(strdata,"%s_%d",KEYBOARD_NAME,i);

        if (1 == gpio_grp->gpio[i].req) {
            ret = gpio_request(gpio_grp->gpio[i].id, /*KEYBOARD_NAME*/strdata);
            if (ret) {
                printk(KERN_ALERT "get led FAILED!\n");
                return ret;
            }
        }
        
        //注册输入或输出 以及默认值
        if (1 == gpio_grp->gpio[i].write) {
            ret = gpio_direction_input(gpio_grp->gpio[i].id);
            if (ret) {
                printk(KERN_ALERT "get led FAILED!\n");
                return ret;
            }
        }

        irq_info[i].id = gpio_grp->gpio[i].id;
        irq_info[i].irq = gpio_grp->gpio[i].irq;
        irq_info[i].write = gpio_grp->gpio[i].write;
        
    }  
    
    work_run = 1;
    INIT_DELAYED_WORK(&work, queue_timeout);
    queue_delayed(14);
    
    g_button.nbutton = -1;
    g_button.nbIs = 0;
    
    if(KDEBUG)printk(KERN_ALERT ":  gpio_init(void) of end !  \n");
    return ret;
}


static int keyboard_init(void)
{
    int error = 0;
    struct keyboard_data *gpio_grp = 0;
    printk(KERN_INFO "keyboard_init\n");

    if (keyboard_reg())
    {
        printk(KERN_INFO "platform_driver_register of fail\n");
        return -1;
    }
    
    if (0 == key_data) {
       key_data = kzalloc(sizeof(struct keyboard_data), GFP_KERNEL);
        if (!key_data)
        {
            printk(KERN_ERR "failed to allocate device structure.\n");
            return -ENOMEM;
        }
        memset(key_data, 0, sizeof(struct keyboard_data));
    }
    gpio_grp = key_data;

    gpio_grp->input = input_allocate_device();
    if(gpio_grp->input == NULL)
    {
	printk(KERN_ERR "key_input.c: Not enough memory\n");
	return -ENOMEM;
    }

    // 2.1 type of the event
    set_bit(EV_KEY,gpio_grp->input->evbit);	

    // 2.2 event code
    set_bit(KEY_1,gpio_grp->input->keybit);			    
    set_bit(KEY_2,gpio_grp->input->keybit);			
    set_bit(KEY_3,gpio_grp->input->keybit);			
    set_bit(KEY_4,gpio_grp->input->keybit);			
    set_bit(KEY_5,gpio_grp->input->keybit);
    set_bit(KEY_6,gpio_grp->input->keybit);
    set_bit(KEY_7,gpio_grp->input->keybit);
    set_bit(KEY_8,gpio_grp->input->keybit);		
	
    
    gpio_grp->input->id.bustype = BUS_HOST;
    gpio_grp->input->phys = "gpio-keys/input0";
    gpio_grp->input->id.vendor = 0x0001;
    gpio_grp->input->id.product = 0x0001;
    gpio_grp->input->id.version = 0x100;
    if (pdev_temp)
    {
	    gpio_grp->input->name = pdev_temp->name;
	    gpio_grp->input->dev.parent = &pdev_temp->dev;
        if(KDEBUG)printk("pdev_temp->name :%s",pdev_temp->name);
    }
	    
	// 注册input device
	error = input_register_device(gpio_grp->input);
	if(error)
		printk(KERN_ERR "keyboard_init: input_register_device error\n");

	printk(KERN_INFO "keyboard_init of end ...\n");
	return 0;	
}

static void keyboard_exit(void)
{
    int i = 0;
    if (0 != work_run)
        work_run = 0;

    cancel_delayed_work_sync(&work);
    //释放gpio资源
    
    for(; i < key_data->gpio_len; i++)
    {
        if (1 == key_data->gpio[i].req) {
            gpio_free(key_data->gpio[i].id);
        }
    }
    if (key_data)
    {
        if (key_data->input) {
            // 取消注册 
            input_unregister_device(key_data->input);
            // 释放input dev
            input_free_device(key_data->input);
        }
        kfree(key_data),key_data = 0;
    }
 
    keyboard_unreg();
}

//探测函数
static int keyboard_probe(struct platform_device *pdev)
{
	u32 gpio_numbers = 0;
	struct device_node *node = NULL;
        int i = 0;

	if(KDEBUG)printk(KERN_INFO "[%s]", __FUNCTION__);
	node = pdev->dev.of_node;
	if (!node)
	{
		printk(KERN_ERR "failed to allocate device structure.\n");
		return -ENODEV;
	}

	if(KDEBUG)printk(KERN_INFO "[%s] node name = [%s]", __FUNCTION__, node->name);


    if (0 == key_data) {
       key_data = kzalloc(sizeof(struct keyboard_data), GFP_KERNEL);
        if (!key_data)
        {
            printk(KERN_ERR "failed to allocate device structure.\n");
            return -ENOMEM;
        }
        memset(key_data, 0, sizeof(struct keyboard_data));
    }
    
    //首先获取到到gpio数量
	if (of_property_read_u32(node, "output,gpio_numbers", &gpio_numbers))
	{
		printk(KERN_ERR "Don't know gpio group number.\n");
		return -EINVAL;
	}
    if(KDEBUG)printk(KERN_INFO "[gpio_numbers :%d]",gpio_numbers);

    //读取配置并保存
    if (0 >= gpio_numbers) {
        return INFO_ERROR;
    }

    //初始化
    for(; i < gpio_numbers; i++)
    {
        memset(&key_data->gpio[i], 0, sizeof(struct gpio_info));
    }
    key_data->gpio_len = gpio_numbers;

    for(i = 0; i < gpio_numbers; i++)
    {
	char strdata[128] = {0};
	int ngpio = 0;
        sprintf(strdata,"%s%d",INFO_NAME,i);
        if(KDEBUG)printk(KERN_INFO "config of node info :%s.\n",strdata);
        
        
        //通过索引获取配置
        if (of_property_read_u32_index(node, strdata, 0, &ngpio))
        {
            printk(KERN_ERR "read_u32_index id: Don't know gpio group number.\n");
            return -EINVAL;
        }
        else
            key_data->gpio[i].id = ngpio;
        
        if (of_property_read_u32_index(node, strdata, 1, &ngpio))
        {
            printk(KERN_ERR "read_u32_index in:Don't know gpio group number.\n");
            return -EINVAL;
        }
        else
            key_data->gpio[i].req = ngpio;
        
        if (of_property_read_u32_index(node, strdata, 2, &ngpio))
        {
            printk(KERN_ERR "read_u32_index out:Don't know gpio group number.\n");
            return -EINVAL;
        }
        else
            key_data->gpio[i].write = ngpio;
       
        if(KDEBUG)printk(KERN_INFO "[rtk_gpio_grp->gpio : req:%d,in:%d,write:%d]",key_data->gpio[i].id,
        key_data->gpio[i].req,key_data->gpio[i].write);
    }
    platform_set_drvdata(pdev, key_data);
    pdev_temp = pdev;
    gpio_init(key_data);
    
    return 0;
}

static int keyboard_remove(struct platform_device *pdev)
{
    if (0 != work_run)
        work_run = 0;
    
    platform_set_drvdata(pdev, NULL);
    return 0;
}

static const struct of_device_id gpio_input_device[] = {
	{.compatible = "snps,dw-port-output",},
};

static struct platform_driver rtk_gpio_driver = {
	.driver = {
			   .name = KEYBOARD_NAME,
			   .owner = THIS_MODULE,
			   .of_match_table = gpio_input_device,
			   },
	.probe = keyboard_probe,
	.remove = keyboard_remove,
};

static int keyboard_reg(void)
{
	return platform_driver_register(&rtk_gpio_driver);
}

static void keyboard_unreg(void)
{
    platform_driver_unregister(&rtk_gpio_driver);
}

module_init(keyboard_init);
module_exit(keyboard_exit);
MODULE_AUTHOR(KEYBOARD_NAME);
MODULE_LICENSE("GPL");
发布了88 篇原创文章 · 获赞 195 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/a29562268/article/details/104054886