文章目录
为什么需要输入子系统框架
我们首先来看字符类驱动框架:
1)写file_operations结构体的成员函数: .open()、.read()、.write()
2)在入口函数里通过register_chrdev()创建驱动名,生成主设备号,赋入file_operations结构体
3)在出口函数里通过unregister_chrdev() 卸载驱动
若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦?
所以需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现。
输入子系统框架
驱动层
将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。
输入子系统核心层
它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。
事件处理层
主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
设备描述
input_dev结构是实现设备驱动核心工作:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。
注册输入设备函数:
int input_register_device(struct input_dev *dev)
注销输入设备函数:
void input_unregister_device(struct input_dev *dev)
驱动实现——初始化(事件支持)
set_bit()告诉input输入子系统支持哪些事件,哪些按键。例如:
set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev类型)
struct input_dev中有两个成员为:
1)evbit事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)。
2)keybit按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)。
驱动实现——报告事件
用于报告EV_KEY,EV_REL,EV_ABS事件的函数分别为:
void input_report_key(struct input_dev *dev,unsigned int code,int value)
void input_report_rel(struct input_dev *dev,unsigned int code,int value)
void input_report_abs(struct input_dev *dev,unsigned int code,int value)
驱动实现——报告结束
input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下:
input_reprot_abs(input_dev,ABS_X,x); //x坐标
input_reprot_abs(input_dev,ABS_Y,y); // y坐标
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步结束
实例分析(按键中断程序):
//按键初始化
static int __init button_init(void)
{//申请中断
if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
return –EBUSY;
set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
set_bit(BTN_0,button_dev.keybit); //支持设备两个键
set_bit(BTN_1,button_dev.keybit); //
input_register_device(&button_dev);//注册input设备
}
/*在按键中断中报告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值
input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
input_sync(&button_dev);
}
总结:input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。
关于事件报告的实现方法
中断实现 常用于实体按键
按键输入示例:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <../arch/arm/mach-mx28/mx28_pins.h>
#define DEVICE_NAME "input_key"
struct input_dev *inputdev;
struct imx28x_key_struct {
int key_code; /* 按键能产生的键值*/
int gpio; /* 按键连接的 GPIO */
struct work_struct work; /* 按键的工作队列 */
};
struct imx28x_key_struct keys_list[] ={
{.key_code = KEY_A, .gpio = MXS_PIN_TO_GPIO(PINID_LCD_D17)},
{.key_code = KEY_B, .gpio = MXS_PIN_TO_GPIO(PINID_LCD_D18)},
{.key_code = KEY_C, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA4)},
{.key_code = KEY_D, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA5)},
{.key_code = KEY_E, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA6)}
};
static irqreturn_t imx28x_key_intnerrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int i = (int)dev_id;
int gpio = keys_list[i].gpio; /* 获取按键的 GPIO */
int code = keys_list[i].key_code; /* 获取按键的键值 */
/*
* 延迟 20uS,看按键是不是按下,如果不是,就是抖动
*/
printk(KERN_INFO "%d was pressed",code);
udelay(20);
if (gpio_get_value(gpio)) {
return IRQ_HANDLED;
}
input_report_key(inputdev, code, 1); /* 先报告键按下事件 */
input_sync(inputdev);
schedule_work(&(keys_list[i].work)); /* 提交工作队列,实现中断的下半部处理 */
return IRQ_HANDLED;
}
static void imx28x_scankeypad(struct work_struct *_work)
{
/* 通过工作队列指针而获得它所属的 imx28x_key_struct 类型的对象 */
struct imx28x_key_struct *key_tmp = container_of(_work, struct imx28x_key_struct, work);
int gpio = key_tmp->gpio;
int code = key_tmp->key_code;
/* 每隔 10mS 检查按键是否已经提起,如果没有提起就一直等待 */
while(!gpio_get_value(gpio)){
mdelay(10);
}
input_report_key(inputdev, code, 0); /* 报告按键提起事件 */
input_sync(inputdev);
}
static int __devinit iMX28x_key_init(void)
{
int i = 0, ret = 0;
int irq_no = 0;
int code, gpio;
inputdev = input_allocate_device(); /* 为输入设备驱动对象申请内存空间*/
if (!inputdev) {
return -ENOMEM;
}
inputdev->name = DEVICE_NAME;
set_bit(EV_KEY, inputdev->evbit); /* 设置输入设备支持按键事件 */
for (i = 0; i < sizeof(keys_list)/sizeof(keys_list[0]); i++) {
code = keys_list[i].key_code;
gpio = keys_list[i].gpio;
/* 为每个按键都初始化工作队列 */
INIT_WORK(&(keys_list[i].work), imx28x_scankeypad);
set_bit(code, inputdev->keybit); /* 设置输入设备支持的键值 */
/* 为每个按键都初始化 GPIO */
gpio_free(gpio);
ret = gpio_request(gpio, "key_gpio");
if (ret) {
printk("request gpio failed %d \n", gpio);
return -EBUSY;
}
/* 当 GPIO 被设置为输入工作状态后,就可以检测中断信号 */
gpio_direction_input(gpio);
/* 把每个 GPIO 中断响应方式都设置为下降沿响应 */
irq_no = gpio_to_irq(gpio);
set_irq_type(gpio, IRQF_TRIGGER_FALLING);
/* 为每个按键的中断都安装中断处理函数,其私有数据为按键信息在 keys_list 数组下的索引 */
ret = request_irq(irq_no, imx28x_key_intnerrupt, IRQF_DISABLED, "imx28x_key", (void *)i);
if (ret) {
printk("request irq faile %d!\n", irq_no);
return -EBUSY;
}
}
input_register_device(inputdev); /* 注册设备驱动 */
printk("EasyARM-i.MX28x key driver up \n");
return 0;
}
static void __exit iMX28x_key_exit(void)
{
int i = 0;
int irq_no;
for (i = 0; i < sizeof(keys_list)/sizeof(keys_list[0]); i++) {
irq_no = gpio_to_irq(keys_list[i].gpio); /* 为每个按键释放 GPIO */
free_irq(irq_no, (void *)i); /* 为每个按键卸载中断处理函数 */
}
input_unregister_device(inputdev); /* 注销输入设备驱动 */
printk(" key driver remove \n");
}
module_init(iMX28x_key_init);
module_exit(iMX28x_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YangYue");
内核定时器实现,用于没有触发信号的输入事件
例如没有触发的触摸屏事件。
动态 timer 由内核自身使用,其实也是其他 Timer 的实现基础。使用动态 Timer 的接口函数有三个:
add_timer()
del_timer()
init_timer()
使用时,先调用 init_timer() 初始化一个定时器,指定到期时间和到期处理函数;初始化完成后,内核代码可以用 add_timer() 启动定时器,或者用 del_timer() 来取消一个已经启动的定时器。
add_timer 采用时间轮算法将定时器加入 per CUP 变量 tvec_bases 中,根据其 expire 时间,可能被加入 5 个 Timer Vector 之一。此后,tick 中断将根据时间轮算法处理。当本 timer 到期时,触发其处理函数。
动态 Timer 有两个方面的用途:一是内核自己使用,比如某些驱动程序需要定时服务的时候使用它;二是用来实现用户层 Timer。下面首先讲解间隔 Timer。
比如要实现一个100ms的中断
可以这样设置:
ps2timer.expires=jiffies +HZ100/1000;
mod_timer(&ps2timer,jiffies+ HZ100/1000 );
PS2驱动开发
硬件原理
DI/DAT:信号流向,从手柄到主机,此信号是一个 8bit 的串行数据,同步传送于时钟
的下降沿。信号的读取在时钟由高到低的变化过程中完成。
DO/CMD:信号流向,从主机到手柄,此信号和 DI 相对,信号是一个 8bit 的串行数据,
同步传送于时钟的下降沿。
NC:空端口;
GND:电源地;
VDD:接收器工作电源,电源范围 3~5V;
CS/SEL:用于提供手柄触发信号。在通讯期间,处于低电平;
CLK:时钟信号,由主机发出,用于保持数据同步;
NC:空端口;
ACK:从手柄到主机的应答信号。此信号在每个 8bits 数据发送的最后一个周期变低并
且 CS 一直保持低电平,如果 CS 信号不变低,约 60 微秒 PS 主机会试另一个外设。在编程
时未使用 ACK 端口。
软件实现
/*
* @Author: your name
* @Date: 2021-04-11 18:25:16
* @LastEditTime: 2021-04-11 23:46:33
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /DriverDefine/PS2/PS2.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/timer.h>
#define DEVICE_NAME "joystick"
#define PSB_SELECT 1
#define PSB_L3 2
#define PSB_R3 3
#define PSB_START 4
#define PSB_PAD_UP 5
#define PSB_PAD_RIGHT 6
#define PSB_PAD_DOWN 7
#define PSB_PAD_LEFT 8
#define PSB_L2 9
#define PSB_R2 10
#define PSB_L1 11
#define PSB_R1 12
#define PSB_GREEN 13
#define PSB_RED 14
#define PSB_BLUE 15
#define PSB_PINK 16
#define PSB_TRIANGLE 13
#define PSB_CIRCLE 14
#define PSB_CROSS 15
#define PSB_SQUARE 16
#define PSS_RX 5
#define PSS_RY 6
#define PSS_LX 7
#define PSS_LY 8
static struct timer_list ps2timer;
u16 Handkey;
u8 Comd[2]={0x01,0x42};
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //Êý¾Ý´æ´¢Êý×é
u16 MASK[]={
PSB_SELECT,
PSB_L3,
PSB_R3 ,
PSB_START,
PSB_PAD_UP,
PSB_PAD_RIGHT,
PSB_PAD_DOWN,
PSB_PAD_LEFT,
PSB_L2,
PSB_R2,
PSB_L1,
PSB_R1 ,
PSB_GREEN,
PSB_RED,
PSB_BLUE,
PSB_PINK
};
typedef struct
{
int data_pin;
int cmd_pin;
int cs_pin;
int clk_pin;
}ps2_handler;
ps2_handler ps_define={
2*32+5,2*32+7,2*32+13,2*32+15,
};
#define DI gpio_get_value(ps_define.data_pin)
#define DO_H gpio_direction_output(ps_define.cmd_pin,1)
#define DO_L gpio_direction_output(ps_define.cmd_pin,0)
#define CS_H gpio_direction_output(ps_define.cs_pin,1)
#define CS_L gpio_direction_output(ps_define.cs_pin,0)
#define CLK_H gpio_direction_output(ps_define.clk_pin,1)
#define CLK_L gpio_direction_output(ps_define.clk_pin,0)
typedef struct
{
int PHYKEY;
int LOGICKEY;
}KEY_MAP;
KEY_MAP key_map[16]={
{PSB_SELECT, KEY_1},
{PSB_L3, KEY_A},
{PSB_R3, KEY_B},
{PSB_START, KEY_C},
{PSB_PAD_UP, KEY_A},
{PSB_PAD_RIGHT, KEY_A},
{PSB_PAD_DOWN, KEY_A},
{PSB_PAD_LEFT, KEY_A},
{PSB_L2, KEY_1},
{PSB_R2, KEY_2},
{PSB_L1, KEY_3},
{PSB_R1, KEY_4},
{PSB_GREEN, KEY_A},
{PSB_RED, KEY_ENTER},
{PSB_BLUE, KEY_7},
{PSB_PINK, KEY_8},
};
struct input_dev *inputdev;
void PS2_Cmd(u8 CMD)
{
volatile u16 ref=0x01;
Data[1] = 0;
for(ref=0x01;ref<0x0100;ref<<=1)
{
if(ref&CMD)
{
DO_H;
}
else DO_L;
CLK_H;
udelay(50);
CLK_L;
udelay(50);
CLK_H;
if(DI)
Data[1] = ref|Data[1];
}
}
void PS2_ReadData(void)
{
volatile u8 byte=0;
volatile u16 ref=0x01;
CS_L;
PS2_Cmd(Comd[0]);
PS2_Cmd(Comd[1]);
for(byte=2;byte<9;byte++)
{
for(ref=0x01;ref<0x100;ref<<=1)
{
CLK_H;
CLK_L;
udelay(50);
CLK_H;
if(DI)
Data[byte] = ref|Data[byte];
}
udelay(50);
}
CS_H;
}
void PS2_ClearData()
{
u8 a;
for(a=0;a<9;a++)
Data[a]=0x00;
}
u8 PS2_DataKey()
{
u8 index;
PS2_ClearData();
PS2_ReadData();
Handkey=(Data[4]<<8)|Data[3];
for(index=0;index<16;index++)
{
if((Handkey&(1<<(MASK[index]-1)))==0)
return index+1;
}
return 0;
}
u8 PS2_AnologData(u8 button)
{
return Data[button];
}
void PS2_ShortPoll(void)
{
CS_L;
udelay(16);
PS2_Cmd(0x01);
PS2_Cmd(0x42);
PS2_Cmd(0X00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
CS_H;
udelay(16);
}
void PS2_EnterConfing(void)
{
CS_L;
udelay(16);
PS2_Cmd(0x01);
PS2_Cmd(0x43);
PS2_Cmd(0X00);
PS2_Cmd(0x01);
PS2_Cmd(0x00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
CS_H;
udelay(16);
}
void PS2_TurnOnAnalogMode(void)
{
CS_L;
PS2_Cmd(0x01);
PS2_Cmd(0x44);
PS2_Cmd(0X00);
PS2_Cmd(0x01);
PS2_Cmd(0xEE);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
CS_H;
udelay(16);
}
void PS2_VibrationMode(void)
{
CS_L;
udelay(16);
PS2_Cmd(0x01);
PS2_Cmd(0x4D);
PS2_Cmd(0X00);
PS2_Cmd(0x00);
PS2_Cmd(0X01);
CS_H;
udelay(16);
}
void PS2_ExitConfing(void)
{
CS_L;
udelay(16);
PS2_Cmd(0x01);
PS2_Cmd(0x43);
PS2_Cmd(0X00);
PS2_Cmd(0x00);
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
CS_H;
udelay(16);
}
void PS2_SetInit(void)
{
PS2_ShortPoll();
PS2_ShortPoll();
PS2_ShortPoll();
PS2_EnterConfing();
PS2_TurnOnAnalogMode();
PS2_VibrationMode();
PS2_ExitConfing();
}
void PS2_Vibration(u8 motor1, u8 motor2)
{
CS_L;
udelay(16);
PS2_Cmd(0x01);
PS2_Cmd(0x42);
PS2_Cmd(0X00);
PS2_Cmd(motor1);
PS2_Cmd(motor2);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
CS_H;
udelay(16);
}
void ps2_timer_function(unsigned long data)
{
u8 val;
val=PS2_DataKey();
printk("%d \t\n",val);
if(val==0)
{
input_event(inputdev,EV_KEY,key_map[val].LOGICKEY, 0); //上报EV_KEY类型,button按键,0(没按下)
input_sync(inputdev);
}else
{
input_event(inputdev,EV_KEY,key_map[val].LOGICKEY, 1); //上报EV_KEY类型,button按键,0(没按下)
input_event(inputdev,EV_REL,PS2_AnologData(), 1);
input_sync(inputdev);
//printk("%d \t\n",key_map[val].LOGICKEY);
}
mod_timer(&ps2timer,jiffies+ HZ*100/1000 );
}
static int PS2_init(void)
{
int i = 0;
inputdev = input_allocate_device(); /* 为输入设备驱动对象申请内存空间*/
if (!inputdev) {return -ENOMEM;}
inputdev->name = DEVICE_NAME;
set_bit(EV_KEY, inputdev->evbit); /* 设置输入设备支持按键事件 */
set_bit(EV_REL, inputdev->evbit);
set_bit(KEY_A,inputdev->keybit); //支持按键 L
set_bit(KEY_B,inputdev->keybit); //支持按键 S
set_bit(KEY_C,inputdev->keybit); //支持按键 空格
set_bit(KEY_ENTER,inputdev->keybit);
//GPIO
gpio_request(ps_define.data_pin, "data");
gpio_direction_input(ps_define.data_pin);
gpio_request(ps_define.cs_pin, "cs");
gpio_direction_output(ps_define.cs_pin,0);
gpio_request(ps_define.clk_pin, "clk");
gpio_direction_output(ps_define.clk_pin,0);
gpio_request(ps_define.cmd_pin, "cmd");
gpio_direction_output(ps_define.cmd_pin,0);
PS2_SetInit();
input_register_device(inputdev); /* 注册设备驱动 */
init_timer(&ps2timer);
ps2timer.function=ps2_timer_function;
ps2timer.expires=jiffies +HZ*100/1000;
add_timer(&ps2timer);
printk("YURI PS2 driver up \n");
return 0;
}
static void PS2_exit(void)
{
del_timer(&ps2timer);
input_unregister_device(inputdev); /* 注销输入设备驱动 */
input_free_device(inputdev);
printk("PS2 driver remove \n");
}
module_init(PS2_init);
module_exit(PS2_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YURI YANG");