概述
GPIO是集成电路或电子电路板上未提交的数字信号引脚,用于将微控制器连接到其他电子设备的标准接口。当然,我们也可以通过几个GPIO口编写一个键盘驱动,驱动挂载后拥有按键功能。
示例驱动基于ARM64架构开发,其中用到的知识将会在文章中讲述。
设备树
platform
工作队列
input
设备树:
设备树是一种描述硬件的数据结构,它起源于OpenFirmware(OF),设备树宏定义为CONFIG_OF。ARM内核版本3.x之后引入了设备树,其目的是为移除各种硬件的描述性代码,减少驱动代码体积。 设备树由一系列被命名的节点(Node)和属性(Property)组成,而节点本身可包含子节点。
ARM64架构下设备树文件放置在:arch/arm64/boot/dts目录下,参考下图设备树-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:
/**
* 李坤昱
* 326087275@qq.com
*/
#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_initn");
if (keyboard_reg())
{
printk(KERN_INFO "platform_driver_register of failn");
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 memoryn");
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 errorn");
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");
最后
以上就是健康铅笔为你收集整理的Linux通过GPIO状态实现按键上报(按键功能)的全部内容,希望文章能够帮你解决Linux通过GPIO状态实现按键上报(按键功能)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复