900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > linux驱动之输入子系统

linux驱动之输入子系统

时间:2020-01-22 05:06:44

相关推荐

linux驱动之输入子系统

对于驱动开发者来说,对按键 触摸屏 鼠标等设备分别进行文件操作显得很繁琐,他们具有一些相同的规律,即内核负责记录数据,应用负责读取数据,因此,内核开发者为了简化驱动开发者的工作,特地创造了输入子系统。

输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个input_event结构体,并传递给应用程序,这样应用程序就知道硬件发生了什么。

以触摸屏驱动程序为例,大致介绍输入子系统的使用。

触摸屏驱动程序

#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/sched.h>#include <linux/pm.h>#include <linux/sysctl.h>#include <linux/proc_fs.h>#include <linux/delay.h>#include <linux/platform_device.h>#include <linux/input.h>#include <linux/io.h>#include <linux/clk.h>#include <linux/delay.h>struct input_dev *ts_dev;struct timer_list ts_timer;struct adc_regs {unsigned long adccon;unsigned long adctsc;unsigned long adcdly;unsigned long adcdat0;unsigned long adcdat1;unsigned long adcupdn;unsigned long adcclrint;unsigned long reserved;unsigned long adcclrintpndnup;};static struct adc_regs *adc_regs;//等待按下static void wait_for_pen_down(void){adc_regs->adctsc = 0xd3;}//等待抬起static void wait_for_pen_up(void){adc_regs->adctsc = 0x1d3;}//xy坐标自动转换static void measure_xy_mode(void){adc_regs->adctsc = ((1<<7) | (1<<6) | (1<<4) | (1<<3) | (1<<2));}//启动adc转换static void start_adc(void){adc_regs->adccon |= (1<<0);}//触摸中断irqreturn_t ts_irq(int irq, void *dev_id){unsigned long data0, data1;int down;data0 = adc_regs->adcdat0;data1 = adc_regs->adcdat1;down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开if(!down){wait_for_pen_down();//等待按下input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);input_sync(ts_dev);//报告失败事件}else{measure_xy_mode();//xy自动转换start_adc();//开启adc转换}//adc_regs->adcclrint = 0;//清除adc中断标志adc_regs->adcclrintpndnup = 0;//清除触摸中断标志return IRQ_HANDLED;}//adc中断irqreturn_t adc_irq(int irq, void *dev_id){mod_timer(&ts_timer, jiffies + HZ / 100);//10mswait_for_pen_up();//等待抬起adc_regs->adcclrint = 0;//清除adc中断标志//adc_regs->adcclrintpndnup = 0;//清除触摸中断标志return IRQ_HANDLED;}//定时器中断void ts_timer_function(unsigned long data){int x, y;unsigned long data0, data1;int down;data0 = adc_regs->adcdat0;data1 = adc_regs->adcdat1;down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开if(!down){wait_for_pen_down();//等待按下input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);input_sync(ts_dev);//报告失败事件}else{x = data0 & 0xfff;y = data1 & 0xfff;input_event(ts_dev, EV_ABS, ABS_X, x);input_event(ts_dev, EV_ABS, ABS_Y, y);input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1);input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);input_sync(ts_dev);measure_xy_mode();//xy自动转换start_adc();//开启adc转换}}int ts_init(void){struct clk *clk;ts_dev = input_allocate_device();//分配输入结构体set_bit(EV_KEY, ts_dev->evbit);//key事件set_bit(EV_ABS, ts_dev->evbit);//绝对地址set_bit(BTN_TOUCH, ts_dev->keybit);//触摸input_set_abs_params(ts_dev, ABS_X, 0, 0xfff, 0, 0);//设置x坐标的范围input_set_abs_params(ts_dev, ABS_Y, 0, 0xfff, 0, 0);input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);input_register_device(ts_dev);//注册设备adc_regs = ioremap(0x7e00b000, sizeof(struct adc_regs));//虚拟地址映射clk = clk_get(NULL, "adc");clk_enable(clk);adc_regs->adccon = (1 << 16) | (1 << 14) | (65 << 6);adc_regs->adcdly = 0xffff;adc_regs->adcclrintpndnup = 0;//清除触摸中断标志,该标志为1时进入中断,必须软件清零request_irq(IRQ_TC, ts_irq, IRQF_SHARED, "ts", NULL);//申请触摸中断request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, "adc", NULL);//adc转换完成中断init_timer(&ts_timer);ts_timer.function = ts_timer_function;add_timer(&ts_timer);wait_for_pen_down();//等待按下return 0;}void ts_exit(void){del_timer(&ts_timer);free_irq(IRQ_TC, NULL);free_irq(IRQ_ADC, NULL);iounmap(adc_regs);input_unregister_device(ts_dev);input_free_device(ts_dev);}module_init(ts_init);module_exit(ts_exit);MODULE_LICENSE("GPL");

输入子系统分析

大致看一下一个输入设备结构体包含哪些成员,其余成员暂且不介绍

struct input_dev{unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//所支持的事件类型,包括EV_KEY按键 EV_ABS绝对地址,触摸屏 EV_REL相对地址,鼠标unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//对于按键事件,有哪些选项struct device dev;//设备结构体struct list_headnode;};

首先,看一下设备分配函数

struct input_dev *input_allocate_device(void){struct input_dev *dev;dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);if (dev) {dev->dev.type = &input_dev_type;dev->dev.class = &input_class;device_initialize(&dev->dev);mutex_init(&dev->mutex);spin_lock_init(&dev->event_lock);INIT_LIST_HEAD(&dev->h_list);INIT_LIST_HEAD(&dev->node);__module_get(THIS_MODULE);}return dev;}

一般情况下,都会采用设备嵌套的结构,而分配函数也就是对input_dev->dev的初始化以及自身相关的初始化

接下来,看看注册函数

int input_register_device(struct input_dev *dev){list_add_tail(&dev->node, &input_dev_list);//将结点添加到input_dev链表list_for_each_entry(handler, &input_handler_list, node)//遍历链表input_attach_handler(dev, handler);}

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler){const struct input_device_id *id;id = input_match_device(handler, dev);error = handler->connect(handler, dev, id);}

在进行一番比较后,如果设备和处理函数匹配,则连接。input_handler结构体如下,该结构体负责处理对应input_dev的输入事件。

struct input_handler {void *private;void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);bool (*match)(struct input_handler *handler, struct input_dev *dev);int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);void (*disconnect)(struct input_handle *handle);void (*start)(struct input_handle *handle);const struct file_operations *fops;int minor;const char *name;const struct input_device_id *id_table;struct list_headh_list;struct list_headnode;}

接下来,介绍一下输入事件

事件结构体如下

struct input_event {struct timeval time;__u16 type;//事件类型__u16 code;//事件的具体参数类型__s32 value;//参数值};

发送事件函数如下

void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value){if (is_event_supported(type, dev->evbit, EV_MAX)) {//测试是否设置了该事件,一般如下设置set_bit(EV_KEY, ts_dev->evbit);input_handle_event(dev, type, code, value);}}

下面以input_event(ts_dev, EV_ABS, ABS_X, x)具体分析input_handle_event函数

static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value){switch (type) //首先判断类型,为EV_ABS{case EV_ABS:if (is_event_supported(code, dev->absbit, ABS_MAX))//测试EV_ABS事件是否设置了该参数disposition = input_handle_abs_event(dev, code, &value);break;}}

继续调用input_handle_abs_event函数

static int input_handle_abs_event(struct input_dev *dev,unsigned int code, int *pval){if (is_mt_event && dev->slot != input_abs_get_val(dev, ABS_MT_SLOT)) {input_abs_set_val(dev, ABS_MT_SLOT, dev->slot);input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);}}

如下是关于ABS_X的定义以及结构体input_absinfo

#define ABS_MT_SLOT0x2f/* MT slot being modified */#define ABS_X0x00struct input_absinfo {__s32 value;__s32 minimum;__s32 maximum;__s32 fuzz;__s32 flat;__s32 resolution;};

最后一步,在input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);中调用input_handler的event函数,将消息整合成input_event结构体的形式发送给用户,由用户读取信息。具体代码没有深入研究,猜测类似于read、write之类的函数。

触摸屏相关分析

如图所示,是电阻型触摸屏获取坐标值的原理。

对于触摸屏驱动来说,有两个中断,分别是IRQ_TC和IRQ_ADC,前者是触摸中断,后者是ADC转换完成中断。

当进入IRQ_TC中断时,启动adc转换,adc转换的结果x坐标、y坐标分别存放在ADCDAT0和ADCDAT1寄存器中,进入ADC中断后将事件上报给输入子系统。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。