海思3531DV100按键驱动实现,参考https://blog.csdn.net/cfl927096306/article/details/88842398
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/timer.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
//按键映射
//KEY1 - GPIO0_0
//KEY2 - GPIO0_1
//KEY3 - GPIO0_2
//KEY4 - GPIO0_3
//KEY5 - GPIO0_4
//KEY6 - GPIO0_5
//KEY7 - GPIO0_6
//KEY8 - GPIO0_7
typedef enum
{
MY_KEY1,
MY_KEY2,
MY_KEY3,
MY_KEY4,
MY_KEY5,
MY_KEY6,
MY_KEY7,
MY_KEY8,
MY_KEY_MAX,
}my_key_e;
static int io_offset[MY_KEY_MAX]={0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
#define WRITE_REG(Addr, Value) ((*(volatile unsigned int *)(Addr)) = (Value))
#define READ_REG(Addr) (*(volatile unsigned int *)(Addr))
//Hi3531 GPIO0基地址
#define GPIO_0_BASE_ADDR 0x12150000
//方向控制寄存器,配置输入或输出
#define GPIO_DIR_OFFSET_ADDR 0x400
//中断触发寄存器,配置边沿或电平触发
#define GPIO_IS_OFFSET_ADDR 0x404
//双沿触发中断寄存器,配置单边沿或双边沿触发方式
#define GPIO_IBE_OFFSET_ADDR 0x408
//触发中断条件寄存器,配置下降沿/低电平或上升沿/高电平触发
#define GPIO_IEV_OFFSET_ADDR 0x40C
//中断屏蔽寄存器,用来屏蔽或使能中断
#define GPIO_IE_OFFSET_ADDR 0x410
//原始中断状态寄存器,用来查询 GPIO 管脚是否发生中断(0:未发生,1:发生)
#define GPIO_RIS_OFFSET_ADDR 0x414
//屏蔽状态中断寄存器,用来查询 GPIO 管脚屏蔽后的中断是否有效
#define GPIO_MIS_OFFSET_ADDR 0x418
//中断清除寄存器,用来清除管脚产生的中断,同时清除GPIO_RIS和GPIO_MIS
#define GPIO_IC_OFFSET_ADDR 0x41C
unsigned int gpio_0_virtual_addr = 0;
//定义一个结构体用来对输入按键进行描述
struct my_buttons_desc {
int gpio; // 表示对应的按键引脚
int irq; // 表示对应的中断位
char *name; // 表示对应的按键请求中断时的中断名
int key_code; // 表示按键在输入子系统中对应的键值
};
//定义一个描述按键的数组
static struct my_buttons_desc buttons_desc[] = {
{MY_KEY1, 89, "my_buttons_A", KEY_A},
{MY_KEY2, 89, "my_buttons_B", KEY_B},
{MY_KEY3, 89, "my_buttons_C", KEY_C},
{MY_KEY4, 89, "my_buttons_D", KEY_D},
{MY_KEY5, 89, "my_buttons_E", KEY_E},
{MY_KEY6, 89, "my_buttons_F", KEY_F},
{MY_KEY7, 89, "my_buttons_G", KEY_G},
{MY_KEY8, 89, "my_buttons_H", KEY_H},
};
//定义一个输入子系统的结构体指针变量
static struct input_dev *buttons_dev;
static struct my_buttons_desc *irq_buttons_desc = NULL;
static struct timer_list buttons_timer;
//地址映射
static int hi3531_virtual_addr_map(void)
{
gpio_0_virtual_addr = (unsigned int)ioremap_nocache(GPIO_0_BASE_ADDR, 0x10000);
if(!gpio_0_virtual_addr)
{
printk("GPIO_0_BASE_ADDR ioremap addr failed !\n");
return -1;
}
return 0;
}
//取消地址映射
static void hi3531_virtual_addr_unmap(void)
{
iounmap((void*)gpio_0_virtual_addr);
}
//海思官方提供的中断操作
//如果要产生中断,且避免假中断,则必须按照下面的初始化顺序:
//1. 配置 GPIO_IS,选择边沿触发或电平触发。
//2. 配置 GPIO_IEV,选择下降沿/上升沿触发和高电平/低电平触发。
//3. 如果选择边沿触发,需配置 GPIO_IBE,选择单沿或双沿触发方式。
//4. 保证 GPIO 数据线在以上操作过程中保持稳定。
//5. 向寄存器 GPIO_IC 写 0xFF,清中断。
//6. 配置 GPIO_IE 为 1,使能中断。
static int hi3531_button_gpio_config(void)
{
unsigned int u32Reg = 0;
//配置为输入
u32Reg = READ_REG(gpio_0_virtual_addr + GPIO_DIR_OFFSET_ADDR);
u32Reg &= (~0xFF);
WRITE_REG(gpio_0_virtual_addr + GPIO_DIR_OFFSET_ADDR, u32Reg);
//配置中断
u32Reg = READ_REG(gpio_0_virtual_addr + GPIO_IS_OFFSET_ADDR);
u32Reg &= (~0xFF);
WRITE_REG(gpio_0_virtual_addr + GPIO_IS_OFFSET_ADDR, u32Reg); //边沿触发中断
u32Reg = READ_REG(gpio_0_virtual_addr + GPIO_IBE_OFFSET_ADDR);
u32Reg |= (0xFF);
WRITE_REG(gpio_0_virtual_addr + GPIO_IBE_OFFSET_ADDR, u32Reg); //单边沿触发中断
WRITE_REG(gpio_0_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF); //清除中断
u32Reg = READ_REG(gpio_0_virtual_addr + GPIO_IE_OFFSET_ADDR);
u32Reg |= (0xFF);
WRITE_REG(gpio_0_virtual_addr + GPIO_IE_OFFSET_ADDR, u32Reg); //使能中断
return 0;
}
//GPIO按键中断处理函数
static irqreturn_t my_buttons_irq(int irq, void *dev_id)
{
unsigned int u32Reg = 0;
int i = 0;
struct my_buttons_desc *tmp_desc = (struct my_buttons_desc *)dev_id;
//因为是一组GPIO(8个pin)共享一个中断号,所以这里一开始就要判断到底是哪个中断来了
//通过读中断状态寄存器来判断
u32Reg = READ_REG(gpio_0_virtual_addr + GPIO_RIS_OFFSET_ADDR);
if(!(u32Reg & 0xFF))
{
//interrupt not happened
return IRQ_HANDLED;
}
else
{
for(i =0; i < MY_KEY_MAX; i++)
{
if(io_offset[i] == (u32Reg & 0xFF))
{
tmp_desc->gpio = i;
}
}
}
WRITE_REG(gpio_0_virtual_addr + GPIO_IC_OFFSET_ADDR, 0xFF); //GPIO0: 清除中断
//按键IO发生边沿中断时重新设置定时间隔,用于按键消抖
//40ms之后触发定时器中断,执行my_buttons_timer_function(),并将buttons_timer.data传过去
irq_buttons_desc = (struct my_buttons_desc *)dev_id;
buttons_timer.data = irq_buttons_desc->gpio;
mod_timer(&buttons_timer, jiffies+msecs_to_jiffies(40));
return IRQ_HANDLED;
}
static unsigned int my_buttons_read_gpio(unsigned int gpio)
{
unsigned int gpio_level = 0;
gpio_level = READ_REG(gpio_0_virtual_addr + (io_offset[gpio] << 2));
gpio_level = gpio_level >> gpio;
return gpio_level;
}
//定时器中断处理函数
static void my_buttons_timer_function(unsigned long data)
{
unsigned int gpio_level;
if (!irq_buttons_desc)
{
// 初始化定时器会走进该function一次
printk("irq_buttons_desc == NULL, return\n");
return;
}
//获取按键IO状态
gpio_level = my_buttons_read_gpio((unsigned int)data);
printk("my_buttons_timer_function: gpio = %ld, gpio_level = %d\n", data, gpio_level);
//根据按键IO状态上报按键事件
if (gpio_level)
{
//上报按键弹起
input_event(buttons_dev, EV_KEY, irq_buttons_desc->key_code, 0);
input_sync(buttons_dev);
}
else
{
//上报按键按下
input_event(buttons_dev, EV_KEY, irq_buttons_desc->key_code, 1);
input_sync(buttons_dev);
}
}
//入口函数
static int __init my_buttons_init(void)
{
int i = 0;
int ret = 0;
printk("my_buttons_init start\n");
//1、分配一个input_dev结构体
buttons_dev = input_allocate_device();
if(!buttons_dev)
{
printk("input_allocate_device error!\n");
return -ENOMEM;
}
//2、设置input_dev结构体
//2.1、设置支持的事件类型
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit); //支持长按
//2.2、设置支持该类事件中的事件码
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
set_bit(buttons_desc[i].key_code, buttons_dev->keybit);
}
//2.3、硬件相关的操作
hi3531_virtual_addr_map();
hi3531_button_gpio_config();
//3、中断相关的操作
//为每个按键申请一个中断,共用中断处理函数my_buttons_irq()
//按键触发方式为双边沿触发
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
//Hi3531的一组GPIO只有一个中断号,一组GPIO有8个pin,所以这里得是共享中断
ret = request_irq(buttons_desc[i].irq, my_buttons_irq, IRQF_SHARED, buttons_desc[i].name, (void*)&buttons_desc[i]);
printk("request_irq %s\n", ret==0?"succeed":"failed");
}
//4、注册input_dev结构体
input_register_device(buttons_dev);
//初始化定时器,用于按键消抖
init_timer(&buttons_timer);
buttons_timer.function = my_buttons_timer_function;
add_timer(&buttons_timer);
printk("my_buttons_init end\n");
return 0;
}
//出口函数
static void __exit my_buttons_exit(void)
{
int i;
printk("my_buttons_exit start\n");
hi3531_virtual_addr_unmap();
//释放申请的按键中断
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
free_irq(buttons_desc[i].irq, (void*)&buttons_desc[i]);
}
//删除定时器
del_timer(&buttons_timer);
//注销输入设备
input_unregister_device(buttons_dev);
//释放输入设备内存空间
input_free_device(buttons_dev);
printk("my_buttons_exit end\n");
}
module_init(my_buttons_init);
module_exit(my_buttons_exit);
MODULE_LICENSE("GPL");
将以上驱动程序编译成kernel模块,需事先完整编译一遍kernel代码,如:
该文件命名为xxx.c
在 linux-3.0.y/drivers/input/keyboard/Makefile 加一句
obj-m += xxx.o
把该文件放于 linux-3.0.y/drivers/input/keyboard 目录下
cd linux-3.0.y
make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux- SUBDIRS=./drivers/input/keyboard modules