
本文共 12236 字,大约阅读时间需要 40 分钟。
文章目录
为什么需要输入子系统框架
我们首先来看字符类驱动框架:
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#include #include #include #include #include #include #include #include #include #include #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驱动开发
硬件原理
软件实现
/* * @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#include #include #include #include #include #include #include #include #include #include #include #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 8static 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");
发表评论
最新留言
关于作者
