TQ2440四线电阻式触摸屏驱动程序的分析

一,编写触摸屏驱动程序的准备知识之一:输入子系统 

1.输入子系统简单介绍

linux系统提供了input子系统,按键、触摸屏、鼠标等输入型设备都可以利用input接口函数来实现设备驱动。

2.输入子系统的组成

输入子系统由驱动层(drivers)、输入子系统核心层(input core)和事件处理层(Event Handler)三部分组成。

驱动层:将底层的硬件输入转化为统一的事件型式,向输入核心层汇报。

输入子系统核心层:为驱动层提供输入设备注册与操作接口,如input_register_device ;通知事件处理层对事件进行处理;在/proc下产生相应的设备信息。

事件处理层:将硬件设备上报的事件分发到用户空间和内核。

3.设备描述

在linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动编写的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件,不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过输入子系统核心层和事件处理层最终到达用户空间。

4.input设备驱动的编写

4.1 设备注册/注销

注册输入设备的函数为:int input_register_device(struct input_dev*dev)

注销输入设备的函数为: void input_unregister_device(struct input_dev*dev)

4.2 事件支持

设备驱动通过set_bit()或者BIT()告诉input子系统它支持哪些事件,哪些按键。例如下面的例子:

set_bit(EV_KEY,dev->evbit) 也可以写成dev->evbit[0] = BIT(EV_KEY)

表示输入子系统支持按键事件。

struct input_dev 中有两个成员:evbit表示事件类型,Keybit表示按键类型。

输入子系统支持的事件类型:

EV_RST Reset       EV_KEY 按键       EV_REL 相对坐标

EV_ABS 绝对坐标   EV_MSC 其它        EV_LED LED

EV_SND 声音        EV_REP Repeat      EV_FF  力反馈

EV_SYN 同步事件

当事件类型为EV_KEY时,还需要指明按键类型:

BTN_LEFT 鼠标左键      BTN_0  数字0键

BTN_RIGHT 鼠标右键     BTN_1  数字1键

BTN_MIDDLE 鼠标中键    BTN_TOUCH 触摸屏点击

注:更多的事件类型请看linux源码中的input.h这个头文件。


4.3 报告事件 

   用于报告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)

参数说明:

   code 事件的代码,所有事件的代码都在linux源码中的input.h头文件中。

value 事件的值。如果事件的类型是EV_KEY,当按键按下时值为1,松开时值为0。

4.4 完成事件的报告

   使用input_sync(struct input_dev *dev)来告诉input core 报告事件已经完成。

二,编写触摸屏驱动程序的准备知识之二:触摸屏驱动的原理

  1. 触摸屏的工作流程

    1.1 设置触摸屏接口为等待中断模式,等待触摸屏被按下。

    1.2 如果中断发生(TC中断,即触摸屏被按下),选择X、Y坐标转换模式(X、Y坐标分别转换模式或者X、Y坐标自动转换模式),启动A/D转换。

    1.3 当A/D转换完成后,通过中断(AD中断,用来表示X、Y 坐标转换完成)获取X/Y坐标,ADCDAT0 bit[9:0]--X坐标,ADCDAT1 bit[9:0]--Y坐标。

    1.4 设置触摸屏接口为等待中断模式,等待触摸笔离开触摸屏。

    1.5 返回步骤1,等待下次触摸屏被按下。

  2. TQ2440触摸屏驱动程序整体流程图

三,TQ2440触摸屏驱动程序源码分析(由于我代码注释已经比较详细了,所以就不逐个函数进行分析了):

#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 S3C2410TSVERSION	0x0101

#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";

static	struct input_dev *dev;
static	long xp;
static	long yp;
static	int 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 = 0000001111111111
		yp += 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 =0111111111000000
	iowrite32(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); //关闭时钟ADC
		clk_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");


四,触摸屏驱动的移植

关于触摸屏驱动程序的移植请看我前面写的一篇文章《TQ2440触摸屏驱动程序的移植》 ,至此有关TQ2440触摸屏驱动程序的编写就告一段落了。 

时间: 2024-08-03 10:05:46

TQ2440四线电阻式触摸屏驱动程序的分析的相关文章

TQ2440触摸屏驱动程序的移植

按照天嵌官方的<linux移植之step by step>手册上的方法,做linux2.6.30.4触摸屏驱动程序的移植没有成功,经过一番摸索发现是2.6.30.4没有合适的ADC驱动,所以就着手自己做ADC和触摸屏驱动的移植,下面是我解决问题的详细过程: 1.找到天嵌给的linux2.6.30.4的源码,进入drivers/char 目录,把ADC驱动程序的源码抠出来,注意还有那个ADC驱动程序要调用的头文件,我的是直接把头文件加到ADC驱动程序的源码里面,下面是加入头文件修改后的ADC驱动

四线电阻触摸屏采样滤波算法C语言代码

四线电阻触摸屏的采样滤波算法,实践证明很好用的! void TsAdcStart(void) // 自己实现 { } bool IsTsAdcOver(void) // 自己实现 { bool iRes; return iRes; } u16 TsAdcGetX(void) // 自己实现 { u16 iXPhy = 0; return iXPhy; } u16 TsAdcGetY(void) // 自己实现 { u16 iYPhy = 0; return iYPhy; } void delay

网卡触摸屏驱动程序

一.Linux网络体系架构 二.网卡驱动设计 三.Dm9000网卡驱动分析 四.Linux输入子系统 五.触摸屏驱动设计 网卡触摸屏驱动程序

005触摸屏驱动程序

一. 触摸屏驱动程序之概念介绍(第十六课/第一节) 内核会带有(s3c2410_ts.c)触摸屏的驱动程序(平台驱动),大概浏览,然后仿造 注册一个平台驱动 若内核里面有同名的平台设备驱动程序的时候,probe函数就会被调用 分配一个input_dev结构体 设置它 注册 当有事情发生时会上报事件 1th.现在开始写触摸屏驱动代码** 先从入口函数开始 第一步:分配一个input_dev结构体 第二步:设置 设置能产生哪类事件 设置能产生该类事件里哪些事件 第三步:注册 触摸屏原理: 触摸屏使用

触摸屏驱动程序

触摸屏驱动程序框架与上一片文章的输入子系统类似,只是底层驱动由按键变成了触摸屏. S3C2440的ADC相关寄存器: struct s3c_ts_regs { unsigned long adccon; unsigned long adctsc; unsigned long adcdly; unsigned long adcdat0; unsigned long adcdat1; unsigned long adcupdn; }; 1.分配input_dev结构体 struct input_de

列式数据库的简单分析

转自:列式数据库的简单分析 这些天看数据仓库的内容,发现一个新内容——列式存储.曾经有想过把数据库行列转置作成索引,不过没有深想,没想到列式数据库已经开始发展起来了.首先看下WIKI上对列式数据库的解释: 列式数据库是以列相关存储架构进行数据存储的数据库,主要适合与批量数据处理和即席查询.相对应的是行式数据库,数据以行相关的存储体系架构进行空间分配,主要适合与小批量的数据处理,常用于联机事务型数据处理.数据库以行.列的二维表的形式存储数据,但是却以一维字符串的方式存储,例如以下的一个表:EmpI

Linux DM9000网卡驱动程序完全分析

Linux DM9000网卡驱动程序完全分析http://blog.csdn.net/ypoflyer/article/details/6209922

电阻触摸屏驱动程序(二)

首先来看一下,硬件操作需要干哪些事情.看一下TSYP.TSXP.TSYM.TSXM四个引脚接到了哪里? 搜索TSYP引脚 TSYM.TSYP.TSXM.TSXP分别接到了AIN4.AIN5.AIN6.AIN7上,它们不是普通的GPIO管脚. 在2440的芯片手册里搜索AIN4,发现要么用作模拟输入,要么用作触摸屏的,因此不需要 配置寄存器. 1 #include <linux/errno.h> 2 #include <linux/kernel.h> 3 #include <l

驱动06.触摸屏驱动程序

1.触摸屏的简介 触摸屏是标准的输入设备,在写驱动程序时采用的之前讲过的输入子系统那套框架.我们无需关心对设备文件的操作,只需关心对硬件寄存器的操作和上报事件即可. 触摸屏是附在LCD上的一层薄膜,并不是我们平时认识的触摸屏,它只是起到确定坐标的作用. S3C2440提供的触摸屏接口有4种处理模式,分别是:正常转换模式.单独的X/Y位置转换模式.自动X/Y位置转换模式和等待中断模式.本例子中用的是等待中断模式 2.以s3c2410_ts.c为例分析整体框架 2.1 s3c2410ts_init函