问题:MDrv_IR_Input_Init在哪里使用的?
一、驱动层编写了Mdrv_ir.c和一个map.c文件,mdrv是驱动文件,map.c是一个供mdrv驱动调用的一个键值映射表。
关于input驱动的基本知识可以参照博客:
输入子系统设备驱动层实现原理
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
1).在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
2).将Input设备注册到input子系统中;
3).在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
驱动设计流程如下图所示
input子系统驱动设计流程
比如驱动中
在模块初始化函数MDrv_IR_Input_Init中做了以下工作:
(1)根据宏定义选择map文件名和要注册的input设备名以及一个vendor_id
char *map_name = RC_MAP_TV;
char *input_name = "TV IR Receiver";
__u16 vendor_id = 0x0019;
(2)根据Linux内核版本定义设备类型
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 20)
struct rc_dev *dev;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
struct input_dev *dev;
#else
struct input_dev *dev;
IR_KEYTAB_TYPE *ir_codes = ir_codes_tv;
#endif
(3)为一个遥控器结构体分配内存
ir = kzalloc(sizeof(struct mstar_ir), GFP_KERNEL);
(4)分配一个输入设备
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 20)
dev = rc_allocate_device();
#else
dev = input_allocate_device();
#endif
(5)前面为ir分配了内存,这里初始化这个input设备。
这里的vendor_id及productid之类的可以用来区分是哪个设备,比如在Android的native层加载和这个设备相关的C++程序的时候,可以通过这些id来区分。
// init input device
ir->dev = dev;
dev->driver_name = MDRV_NAME_IR;
dev->map_name = map_name;
dev->driver_type = RC_DRIVER_IR_RAW;
dev->input_name = input_name;
dev->input_phys = "/dev/ir";
dev->input_id.bustype = BUS_I2C;
dev->input_id.vendor = vendor_id;
dev->input_id.product = 0x0001;
dev->input_id.version = 1;
dev->allowed_protos = RC_TYPE_ALL;
(6)注册一个输入设备
err = rc_register_device(dev);
(7)如果注册有问题就释放内存
if (err)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 20)
rc_free_device(dev);
#else
input_free_device(dev);
#endif
kfree(ir);
return err;
}
(8)这个是对设备结构体进行原子操作
// No auto-repeat.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 20)
clear_bit(EV_REP, ir->dev->input_dev->evbit);
#else
clear_bit(EV_REP, ir->dev->evbit);
#endif
原子整数操作 | 描述 |
---|---|
void set_bit(int nr,void *addr) | 原子地设置addr所指对象的第nr位 |
void clear_bit(int nr,void *addr) | 原子地清空addr所指对象的第nr位 |
void change_bit(int nr,void *addr) | 原子地翻转addr所指对象的第nr位 |
int test_and_set_bit(int nr,void *addr) | 原子地设置addr所指对象的第nr位,并返回原先的值 |
int test_and_clear_bit(int nr,void *addr) | 原子地清空addr所指对象的第nr位,并返回原先的值 |
int test_and_change_bit(int nr,void *addr) | 原子地翻转addr所指对象的第nr位,并返回原先的值 |
int test_bit(int nr,void *addr) | 原子地返回addr所指对象的第nr位 |
init_completion(&key_completion);
(10)创建一个workqueue队列并执行这个任务
key_dispatch_workqueue = create_workqueue("keydispatch_wq");
queue_work(key_dispatch_workqueue, &key_dispatch_thread);
Workqueue编程接口
序号 | 接口函数 | 说明 |
1 | create_workqueue | 用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数: @name:workqueue的名称 |
2 | create_singlethread_workqueue | 用于创建workqueue,只创建一个内核线程。输入参数: @name:workqueue名称 |
3 | destroy_workqueue | 释放workqueue队列。输入参数: @ workqueue_struct:需要释放的workqueue队列指针 |
4 | schedule_work | 调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数: @ work_struct:具体任务对象指针 |
5 | schedule_delayed_work | 延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数: @work_struct:具体任务对象指针 @delay:延迟时间 |
6 | queue_work | 调度执行一个指定workqueue中的任务。输入参数: @ workqueue_struct:指定的workqueue指针 @work_struct:具体任务对象指针 |
7 | queue_delayed_work | 延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。 |
在模块注销函数MDrv_IR_Input_Exit()中做了以下工作:
(1)释放workqueue队列
destroy_workqueue(key_dispatch_workqueue);
(2)注销输入设备
input_free_device(ir->dev);
(3)释放输入设备
kfree(ir);
事件上报:
驱动程序通过在MDrv_IR_Init中注册中断实现,每次发生中断的时候就会触发_MDrv_IR_ISR函数,这样子,就能对键值进行处理了
result = request_irq(E_FIQ_IR, _MDrv_IR_ISR, SA_INTERRUPT, "IR", NULL);->MDrv_IR_ISRParseKey->_MDrv_IR_GetKey
事件支持
Set_bit告诉input子系统它支持哪些事件
Set_bit(EV_KEY,button_dev.evbit)
Struct input_dev中有两个成员,一个是evbit;一个是keybit.分别用来表示设备所支持的事件类型和按键类型。
事件类型linux/input.h:
/*
* Event types
*/
#define EV_FACTKEYREADER 0x19 自定义键值类型,键值过滤的时候可以根据键值类型来进行过滤
#define EV_SYN 0x00 同步事件
#define EV_KEY 0x01 按键事件
#define EV_REL 0x02 相对坐标
#define EV_ABS 0x03 绝对坐标
#define EV_MSC 0x04 其它
#define EV_SW 0x05
#define EV_LED 0x11 LED
#define EV_SND 0x12 声音
#define EV_REP 0x14 Repeat
#define EV_FF 0x15 力反馈
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
报告事件
Void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);//报告指定type,code的输入事件
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);//报告绝对坐标
Void input_sync(struct input_dev *dev);//报告同步事件
在触摸屏驱动设计中,一次坐标及按下状态的整个报告过程如下:
Input_report_abs(input_dev,ABS_X,x);//X坐标
Input_report_abs(input_dev,ABS_Y,y);//Y坐标
Input_report_abs(input_dev,ABS_PRESSURE,pres);//压力
input_sync(struct input_dev *dev);//同步
释放与注销设备
Void input_free_device(struct input_dev *dev);
Void input_unregister_device(struct input_dev *);
在内核中,input_dev表示一个input设备,
input_handler表示input设备的接口
linux内核的drivers/input/keyboard/gpio_keys.c基于input架构实现了一个通用的GPIO按键驱动。该驱动基于platform_driver架构,名为“gpio-keys”。它将硬件相关信息屏蔽在板文件platform_device的platform_data中,因此驱动可应用于各个处理器,具有良好的跨平台性。