I. One of the prerequisites for writing a touch screen DRIVER: Input subsystem
1. Introduction to the input subsystem
The input subsystem is provided in Linux. input devices, such as buttons, touch screens, and mouse, can use input interface functions to drive devices.
2. Composition of the input subsystem
Input subsystemDriver Layer(Drivers ),Input subsystem core layer(Input core) andEvent Processing LayerEvent Handler.
Driver layer:Converts underlying hardware input into a unified event type and reports it to the input core layer.
Input subsystem core layer:Provide the input device registration and operation interfaces for the driver layer, such as input_register_device. Notify the event processing layer to process the event and generate corresponding device information under/proc.
Event Processing Layer:Distribute events reported by hardware devices to user space and kernel.
3. device description
In the Linux kernel, the input device uses the input_dev struct to describe the input device driver, the core work of the driver is to report input events such as buttons, touch screens, keyboards, and mouse to the system. You do not need to worry about file operation interfaces because the input subsystem has completed file operation interfaces. The event reported by the driver passes through the input subsystem core layer and event processing layer to reach the user space.
4. Writing input device drivers
4.1Device registration/Cancellation
Function for registering the input device: int input_register_device (struct input_dev * Dev)
Function for logging out of the input device: void input_unregister_device (struct input_dev * Dev)
4.2Event support
The device driver uses set_bit () or bit () to tell the input subsystem what events and buttons it supports. For example:
Set_bit (ev_key, Dev-> evbit) can also be written as Dev-> evbit [0] = bit (ev_key)
The input subsystem supports key events.
Struct input_dev has two members: evbit indicates the event type, and keybit indicates the key type.
Types of events supported by the input subsystem:
Ev_rst ResetEv_key keyRelative coordinates of ev_rel
Absolute coordinates of ev_absEv_msc other ev_led led
Ev_snd voice ev_rep repeat ev_ff Force Feedback
Ev_syn synchronization event
When the event type is ev_key, you also need to specify the key type:
Btn_left left mouse button btn_0 digit 0 Key
Btn_right right-click btn_1 Number 1
Btn_middleBtn_touch touch screen click
Note: For more event types, see the header file input. h in Linux source code.
4.3 report events
The functions used to report ev_key, ev_rel, and ev_abs events are as follows:
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)
Parameter description:
CodeEvent code. All event codes are included in the input. h header file of the Linux source code.
ValueEvent value. If the event type is ev_key, when the press value is 1, the release value is 0.
4.4 completion event report
Use input_sync (struct input_dev * Dev) to tell the input core that the reporting event has been completed.
II,Preparation of touch screen driver 2: Principles of touch screen driver
Touch screen Workflow
1.1 set the touch screen interface to the waiting interrupt mode and wait for the touch screen to be pressed.
1.2If the interruption occurs (TC interruption, that is, the touch screen is pressed)Select X and Y coordinate conversion modes (X and Y coordinate conversion modes, respectively, or X and Y coordinate automatic conversion modes) to start A/D conversion.
1.3 When A/D is converted,Through interruption (AD interruption, used to indicate X and Y coordinate transformation)Obtain the x/y coordinate, adcdat0 bit [] -- X coordinate, adcdat1 bit [] -- y coordinate.
1.4 set the touch screen interface to the waiting interrupt mode and wait for the touch pen to exit the touch screen.
1.5 return to Step 1 and wait until the next touch screen is pressed.
Tq2440 touch screen driver Flowchart
650) This. width = 650; "src =" http://s3.51cto.com/wyfs02/M01/4C/2C/wKioL1Q3q6Dy88BXAAJaC1g0QdA514.bmp "Title =" Touch Screen driver flow chart .bmp "alt =" wkiol1q3q6dy88bxaajac11_qda514.bmp "/>
3. Source Code analysis of the tq2440 touch screen Driver (because my code annotations are already detailed, I will not analyze them one by one ):
#include <linux/errno.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/init.h>#include <linux/serio.h>#include <linux/delay.h>#include <linux/platform_device.h>#include <linux/clk.h>#include <asm/io.h>#include <asm/irq.h>#include <plat/regs-adc.h>#include <mach/regs-gpio.h>#define S3C2410TSVERSION0x0101#define WAIT4INT(x) (((x)<<8) | S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_XY_PST(3))#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))static char *tq2440ts_name = "TQ2440 TouchScreen";staticstruct input_dev *dev;staticlong xp;staticlong yp;staticint count;extern struct semaphore ADC_LOCK; //定义信号量ADC_LOCK static int OwnADC = 0;static void __iomem *base_addr; //用来保存经过映射后的虚拟地址 /*touch_timer_fire函数所做的工作如下: * 读出触摸屏被按下还是被抬起的状态 * 如果触摸屏被按下并且A/D转换完成就报告事件和数据 * 如果触摸屏被按下但是A/D转换还没开始就启动A/D转换 * 如果触摸屏被抬起首先报告事件然后重新进入等待中断模式并释放触摸屏占用的ADC资源 */ static void touch_timer_fire(unsigned long data){ unsigned long data0; unsigned long data1;int updown; //读取A/D转换后的值 data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); //S3C2410_ADCDAT0_UPDOWN =1000000000000000,updown为1时表示触摸屏被按下,updown为0时表示触摸屏没有被按下 updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); //如果触摸屏被按下,并且A/D转换已完成,就报告事件和数据 if (updown) { if (count != 0) {long tmp; tmp = xp;xp = yp;yp = tmp; //由于A/D转换时对X和Y的坐标采样了4次,所以这里X和Y的坐标就取4次采样值的平均值(坐标各自都除以4) xp >>= 2; yp >>= 2; //报告X,Y坐标数据 input_report_abs(dev, ABS_X, xp); input_report_abs(dev, ABS_Y, yp); //报告按键事件,1表示触摸点被按下 input_report_key(dev, BTN_TOUCH, 1); //报告触摸屏状态,1表示触摸屏被按下 input_report_abs(dev, ABS_PRESSURE, 1);//完成报告 input_sync(dev); } //如果触摸屏被按下,但是A/D转换还没开始,就启动A/D转换 xp = 0; yp = 0; count = 0; //设置触摸屏为自动X/Y轴坐标转换模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); //启动A/D转换 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else //如果触摸屏被抬起 { count = 0; //报告按键事件,0表示触摸点被抬起 input_report_key(dev, BTN_TOUCH, 0); //报告触摸屏状态,0表示触摸屏被抬起 input_report_abs(dev, ABS_PRESSURE, 0); //完成报告 input_sync(dev); //使触摸屏重新进入等待按下的中断模式 ADCTSC = 011010011 iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);if (OwnADC) //触摸屏被抬起,不应该再占用ADC资源,应释放之 {OwnADC = 0;up(&ADC_LOCK); //释放触摸屏所占用的ADC资源 } }}//定义并初始化定时器touch_timer static struct timer_list touch_timer =TIMER_INITIALIZER(touch_timer_fire, 0, 0);/* TC中断,当触摸屏被按下或松开时执行,此函数主要做的工作如下: * 获得ADC资源,因为在ADC驱动里面也使用了ADC资源 * 判断触摸屏是被按下还是被抬起,并对这两种状态做出相应的处理 */ static irqreturn_t stylus_updown(int irq, void *dev_id){unsigned long data0;unsigned long data1;int updown;//触摸屏按下或抬起的标示 //尝试获得信号量ADC_LOCK,若down_trylock返回0表示信号量获得成功,触摸屏可以使用ADC资源,否则不能使用ADC资源 if (down_trylock(&ADC_LOCK) == 0){OwnADC = 1; //表示触摸屏的ADC资源可用 //读取A/D转换后的值 data0 = ioread32(base_addr+S3C2410_ADCDAT0);data1 = ioread32(base_addr+S3C2410_ADCDAT1); //S3C2410_ADCDAT0_UPDOWN =1000000000000000,updown为1时表示触摸屏被按下,updown为0时表示触摸屏没有被按下 updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));if (updown) //如果触摸屏被按下,那么调用touch_timer_fire函数启动A/D转换 {touch_timer_fire(0);}else //如果触摸屏被松开,那么就释放触摸屏所占用的ADC资源 {OwnADC = 0;up(&ADC_LOCK);//释放ADC_LOCK信号量 }}return IRQ_HANDLED;}/* AD中断,当X,Y坐标转换完成时执行 * 负责取得X和Y的坐标值 */static irqreturn_t stylus_action(int irq, void *dev_id){unsigned long data0;unsigned long data1;if (OwnADC) //标示触摸屏资源可用 {//读取ADCDAT0寄存器(读出X轴坐标转换数据值) data0 = ioread32(base_addr+S3C2410_ADCDAT0); //读取ADCDAT1寄存器(读出Y轴坐标转换数据值)data1 = ioread32(base_addr+S3C2410_ADCDAT1); //取得X坐标的值,即读取ADCDAT0寄存器0-9位的值,S3C2410_ADCDAT0_XPDATA_MASK = 0000001111111111 xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;//取得Y坐标的值,即读取ADCDAT1寄存器0-9位的值,S3C2410_ADCDAT1_YPDATA_MASK = 0000001111111111yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;//记录这一次A/D转换的次数 count++;if (count < (1<<2)) //如果转换次数小于4次 {//设置触摸屏为自动X/Y轴坐标转换模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);//重新启动A/D转换 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);}else{//否则,启动1个时间滴答的定时器,这就会去执行touch_timer_fire定时器超时函数的上报事件和数据 mod_timer(&touch_timer, jiffies+1);//停止ADC转换,防止屏幕抖动 iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);}}return IRQ_HANDLED;}static struct clk*adc_clock;/*设备初始化函数,该函数主要做的工作如下: * 获得ADC时钟以及使能ADC时钟 * 映射ADC的IO内存的地址 * 设置ADCCON寄存器使能预分频,设置ADCDLY寄存器(采样的延时值) * 设置ADCTSC寄存器进入等待中断模式(等待触摸屏被按下) * 设置输入设备支持的事件 * 申请ADC,TC中断 * 注册输入设备 */static int __init tq2440ts_init(void){int ret1,ret2;struct input_dev *input_dev; //定义输入设备 adc_clock = clk_get(NULL, "adc"); //获得外设adc的时钟 if (!adc_clock) //如果adc_clock为空 {printk(KERN_ERR "failed to get adc clock source\n");return -ENOENT;}clk_enable(adc_clock); //使能adc时钟//映射ADC的I/O内存地址,S3C2410_PA_ADC是ADC控制器的基地址(0x58000000),0x20是虚拟地址长度大小 base_addr=ioremap(S3C2410_PA_ADC,0x20); if (base_addr == NULL) //如果映射ADC的I/O内存地址失败 {printk(KERN_ERR "Failed to remap register block\n");return -ENOMEM;} //设置ADCCON寄存器使能预分频,预分频系数选择255,ADCCON =0111111111000000iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),base_addr+S3C2410_ADCCON);//ADCDLY= 1111111111111111,采样的延迟值 iowrite32(0xffff, base_addr+S3C2410_ADCDLY);//进入等待按下的中断模式 ADCTSC = 011010011 iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); //分配一个input_dev结构体 input_dev = input_allocate_device();if (!input_dev){printk(KERN_ERR "Unable to allocate the input device !!\n");return -ENOMEM;} //初始化输入设备 dev = input_dev;//支持同步事件,按键事件,绝对位移事件 dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);//set_bit(EV_SYN,dev->evbit); //set_bit(EV_KEY,dev->evbit);//set_bit(EV_ABS,dev->evbit);//支持的按键类型为触摸屏点击 dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);//set_bit(BTN_TOUCH,dev->keybit); //设置触摸屏的x坐标,y坐标,压力 input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); //在驱动挂载后在/proc/bus/input/devices中能看到的输入设备的信息 dev->name = tq2440ts_name;dev->id.bustype = BUS_RS232;dev->id.vendor = 0xDEAD;dev->id.product = 0xBEEF;dev->id.version = S3C2410TSVERSION; //申请ADC中断,X,Y坐标的AD转换完成时即产生此中断 ret1 =request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, tq2440ts_name, dev);if (ret1) //如果申请AD中断失败 {printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n");iounmap(base_addr); //解除ADC控制器的内存地址映射 return -EIO;}//申请TC中断,触摸屏被按下或弹起时即产生此中断 ret2 =request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, tq2440ts_name, dev);if (ret2) //如果申请TC中断失败 {printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n");iounmap(base_addr);//解除ADC控制器的内存地址映射 return -EIO;}printk(KERN_INFO "%s successfully loaded\n", tq2440ts_name);input_register_device(dev);//注册输入设备 return 0;}/*设备退出函数,该函数主要做的工作如下: * 关闭并释放ADC和TC中断 * 关闭并注销ADC时钟 * 注销输入设备 * 解除ADC控制器的内存空间的映射 */static void __exit tq2440ts_exit(void){disable_irq(IRQ_ADC);//关闭ADC中断disable_irq(IRQ_TC); //关闭TC中断 free_irq(IRQ_TC,dev); //释放TC中断 free_irq(IRQ_ADC,dev); //释放ADC中断 if (adc_clock){clk_disable(adc_clock); //关闭时钟ADCclk_put(adc_clock); //注销ADC时钟 adc_clock = NULL;}input_unregister_device(dev);//注销输入设备 iounmap(base_addr); //解除ADC控制器的内存映射 }module_init(tq2440ts_init);module_exit(tq2440ts_exit);MODULE_LICENSE("GPL");
4. Transplantation of touch screen drivers
For how to port the touch screen driver, see the article 《Transplantation of tq2440 touch screen driverSo far, the compilation of the tq2440 touch screen driver has come to an end.
This article is from the "endless thinking" blog. For more information, contact the author!
Analysis of tq2440 four-wire resistive touch screen driver