三,TQ2440触摸屏驱动程序源代码分析(因为我的代码注释已经详细了,所以不分析一个函数):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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,updown1时表示触摸屏被按下,updown0时表示触摸屏未按下
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)
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);
//报告触摸屏状态
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,updown1时表示触摸屏被按下,updown0时表示触摸屏未按下
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;
}
* 负责取得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触摸屏驱动程序的编写就告一段落了。