在linux-3.6.6中,集成了s3c2440触摸屏的应用层文件——/drivers/input/touchscreen/s3c2410_ts.c。因此我们只需要定义好平台设备,就可以实现触摸屏。
s3c2410_ts.c是基于input子系统的,而且它还应用了上一篇文章中介绍过的adc.c文件,因此要理解s3c2410_ts.c文件,还要先理解adc.c文件。下面就介绍s3c2410_ts.c文件。
s3c2440触摸屏平台驱动为:
static struct platform_driver s3c_ts_driver = {
.driver = {
.name = "samsung-ts",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &s3c_ts_pmops,
#endif
},
.id_table = s3cts_driver_ids,
.probe = s3c2410ts_probe,
.remove = s3c2410ts_remove,
};
static struct platform_device_ids3cts_driver_ids[] = {
{ "s3c2410-ts",0 },
{ "s3c2440-ts",0 },
{ "s3c64xx-ts",FEAT_PEN_IRQ },
{ }
};
触摸屏的探测函数为:
static int s3c2410ts_probe(struct platform_device *pdev)
{
struct s3c2410_ts_mach_info*info;
struct device *dev = &pdev->dev;
struct input_dev *input_dev;
struct resource *res;
int ret = -EINVAL;
/* Initialise input stuff */
//清空全局变量ts
memset(&ts, 0, sizeof(struct s3c2410ts));
ts.dev = dev;
info = pdev->dev.platform_data;
if (!info) {
dev_err(dev, "no platformdata, cannot attach\n");
return -EINVAL;
}
dev_dbg(dev, "initialising touchscreen\n");
//获取时钟,触摸屏时钟与ADC时钟有关
ts.clock = clk_get(dev, "adc");
if (IS_ERR(ts.clock)) {
dev_err(dev, "cannot get adcclock source\n");
return -ENOENT;
}
//使能时钟
clk_enable(ts.clock);
dev_dbg(dev, "got and enabled clocks\n");
//获取触摸屏中断号
ts.irq_tc = ret = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_err(dev, "no resourcefor interrupt\n");
goto err_clk;
}
//获取IO内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "no resourcefor registers\n");
ret = -ENOENT;
goto err_clk;
}
//映射内存虚拟地址
ts.io = ioremap(res->start, resource_size(res));
if (ts.io == NULL) {
dev_err(dev, "cannot mapregisters\n");
ret = -ENOMEM;
goto err_clk;
}
/* inititalise the gpio */
//初始化触摸屏GPIO
if (info->cfg_gpio)
info->cfg_gpio(to_platform_device(ts.dev));
//注册ADC客户
ts.client = s3c_adc_register(pdev,s3c24xx_ts_select,
s3c24xx_ts_conversion, 1);
if (IS_ERR(ts.client)) {
dev_err(dev, "failed toregister adc client\n");
ret = PTR_ERR(ts.client);
goto err_iomap;
}
/* Initialise registers */
//设置ADC延时寄存器——ADCDLY
if ((info->delay & 0xffff) > 0)
writel(info->delay &0xffff, ts.io + S3C2410_ADCDLY);
//设置ADCTSC寄存器,即设置触摸屏中断模式,INT_DOWN表示按下触发中断
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
//分配一个输入子系统设备
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(dev, "Unable toallocate the input device !!\n");
ret = -ENOMEM;
goto err_iomap;
}
//为触摸屏输入子系统赋值
ts.input = input_dev;
//设置事件类型,触摸屏为按键事件和绝对值事件
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
//设置按键类型,触摸屏为触摸类型
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
//设置X轴和Y轴坐标范围
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
//设置触摸屏信息
ts.input->name = "S3C24XXTouchScreen";
ts.input->id.bustype = BUS_HOST;
ts.input->id.vendor = 0xDEAD;
ts.input->id.product = 0xBEEF;
ts.input->id.version = 0x0102;
//设置触摸屏的采样次数
ts.shift = info->oversampling_shift;
ts.features = platform_get_device_id(pdev)->driver_data;
//申请中断
ret = request_irq(ts.irq_tc, stylus_irq, 0,
"s3c2410_ts_pen", ts.input);
if (ret) {
dev_err(dev, "cannot getTC interrupt\n");
goto err_inputdev;
}
dev_info(dev, "driver attached, registering input device\n");
/* All went ok, so register to the input system */
//注册触摸屏为input子系统设备
ret = input_register_device(ts.input);
if (ret < 0) {
dev_err(dev, "failed toregister input device\n");
ret = -EIO;
goto err_tcirq;
}
return 0;
err_tcirq:
free_irq(ts.irq_tc, ts.input);
err_inputdev:
input_free_device(ts.input);
err_iomap:
iounmap(ts.io);
err_clk:
del_timer_sync(&touch_timer);
clk_put(ts.clock);
return ret;
}
在上面的函数中,调用了s3c_adc_register函数来注册ADC客户,这个函数是在adc.c文件内定义的,具体的介绍请查看我上一篇文章。在注册触摸屏ADC客户时,重点定义了两个回调函数:client->convert_cb的回调函数为s3c24xx_ts_conversion和client->select_cb的回调函数为s3c24xx_ts_select:
static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
unsigneddata0, unsigned data1,
unsigned*left)
{
dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
ts.xp += data0; //x轴坐标
ts.yp += data1; //y轴坐标
ts.count++; //转换次数加1
/* From tests, it seems that it is unlikely to get a pen-up
* event during the conversion process which means we can
* ignore any pen-up events with less than the requisite
* count done.
*
* In several thousand conversions, no pen-ups where detected
* before count completed.
*/
}
static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
{
if (select) { //启用自动(连续)x轴和y轴坐标转换模式
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
ts.io + S3C2410_ADCTSC);
} else { //启动定时器,并设置抬起为触摸屏中断
//重新注册名为touch_timer的内核定时器,到期时间为此后一个时钟滴答
mod_timer(&touch_timer, jiffies+1);
writel(WAIT4INT | INT_UP, ts.io+ S3C2410_ADCTSC);
}
}
s3c24xx_ts_conversion函数的主要作用是在ADC转换完成后累计x轴和y轴坐标的值,以便在其他的函数内分别求取它们的平均值。s3c24xx_ts_select函数主要是通过select参数来转换触摸屏工作模式。
下面介绍定时器的作用:
//静态创建名为touch_timer的内核定时器,并初始化其function为touch_timer_fire,其他参数为0
static DEFINE_TIMER(touch_timer,touch_timer_fire, 0, 0);
touch_timer定时器超时处理函数touch_timer_fire:
static void touch_timer_fire(unsigned longdata)
{
unsigned long data0;
unsigned long data1;
bool down;
//读取ADC转换数值
data0 = readl(ts.io + S3C2410_ADCDAT0);
data1 = readl(ts.io + S3C2410_ADCDAT1);
//调用get_down函数,判断触摸屏是否被按下
down = get_down(data0, data1);
if (down) { //触摸屏被按下
if (ts.count == (1 << ts.shift)){ //达到设定的采样次数
ts.xp >>=ts.shift; //x轴坐标取平均值
ts.yp >>=ts.shift; //y轴坐标取平均值
dev_dbg(ts.dev,"%s: X=%lu, Y=%lu, count=%d\n",
__func__, ts.xp,ts.yp, ts.count);
//上传x轴和y轴的绝对值坐标
input_report_abs(ts.input, ABS_X, ts.xp);
input_report_abs(ts.input, ABS_Y, ts.yp);
//上传按键类型,即为触摸屏按键
input_report_key(ts.input, BTN_TOUCH, 1);
//同步上传
input_sync(ts.input);
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
}
//启动ADC转换
s3c_adc_start(ts.client, 0, 1 << ts.shift);
} else { //不是触摸屏被按下,也就是触笔抬起
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
//上传按键类型,即为触摸屏按键
input_report_key(ts.input,BTN_TOUCH, 0);
//同步上传
input_sync(ts.input);
//重新定义ADCTSC寄存器,设置触摸屏按下中断
writel(WAIT4INT | INT_DOWN,ts.io + S3C2410_ADCTSC);
}
}
最后我们再来看触摸屏中断函数:
static irqreturn_t stylus_irq(int irq, void*dev_id)
{
unsigned long data0;
unsigned long data1;
bool down;
//读取ADC转换数值
data0 = readl(ts.io + S3C2410_ADCDAT0);
data1 = readl(ts.io + S3C2410_ADCDAT1);
//判断是否为触摸屏被按下
down = get_down(data0, data1);
/* TODO we should never get an interrupt with down set while
* the timer is running, but maybe we ought to verify that the
* timer isn't running anyways. */
if (down) //触摸屏被按下,开启ADC转换
s3c_adc_start(ts.client, 0, 1 << ts.shift);
else //触摸屏不是被按下
dev_dbg(ts.dev, "%s:count=%d\n", __func__, ts.count);
if (ts.features & FEAT_PEN_IRQ) {
/* Clear pen down/up interrupt*/
writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
}
return IRQ_HANDLED;
}
s3c2410_ts.c文件里的函数就介绍到这里,我再来总结一下触摸屏驱动的过程:当触摸屏被按下时,触发了触摸屏中断stylus_irq,在该函数内,当判断是触摸屏被按下后,就调用s3c_adc_start函数开始ADC转换,转换的过程是在s3c_adc_try函数内依次调用s3c_adc_select函数和s3c_adc_convert函数。s3c_adc_select函数的作用是调用回调函数s3c24xx_ts_select,其中第二个参数为1,表示启用自动(连续)x轴和y轴坐标转换模式,s3c_adc_convert函数的作用是启动ADC转换。启动了ADC转换,就会触发ADC转换中断,从而进入中断处理程序s3c_adc_irq。在中断内调用了回调函数s3c24xx_ts_conversion,用于累计x轴和y轴坐标值,然后判断是否达到了设定的采样次数,如果没有达到则继续进行ADC转换,如果达到了采样次数,则调用s3c24xx_ts_select函数,这时设定的该函数的第二个参数为0,则表示启动定时器,并把触摸屏中断设定为抬起触发中断。当所有的采样次数都完成后,退出了ADC转换中断,则进入定时器touch_timer_fire函数内,在该函数内还是首先判断当前触摸屏是否被按下,如果是,则计算x轴和y轴坐标值的平均值,并把结果上传,然后又继续调用s3c_adc_start函数开始ADC转换,如此循环。当触笔被抬起时,进入触摸屏中断stylus_irq,但没有进行实质性的操作,关键是系统也进入了定时器touch_timer_fire函数内,当判断是触笔被抬起时,首先上传相关事件,然后是重新设定触摸屏中断为按下触发。至此整个触摸屏工作流程就结束了。可以看出,只要触笔没有抬起,触笔所经过的坐标值都会被上传。
下面我们再来介绍触摸屏的移植。在arch/arm/mach-s3c24xx/mach-zhaocj2440.c文件内,首先要添加一个头文件:
#include <plat/ts.h>
然后再添加一个触摸屏信息结构体:
static struct s3c2410_ts_mach_info zhaocj2440_ts_info={
.delay = 10000, //延时
.presc = 49, //预分频
.oversampling_shift = 2, //采样次数,2的2次方为4
};
再把zhaocj2440_features_str字符数组增加一个“t”字符,表示触摸屏,即:
static char zhaocj2440_features_str[12]__initdata = "4bt";
修改zhaocj2440_parse_features函数内case 't':的内容:
case 't':
if(features->done & FEATURE_TOUCH)
printk(KERN_INFO"ZHAOCJ2440: '%c' ignored, "
"touchscreenalready set\n", f);
else
features->optional[features->count++]=
&s3c_device_ts;
features->done|= FEATURE_TOUCH;
break;
最后在zhaocj2440_init函数内把刚才定义的zhaocj2440_ts_info结构添加进平台数据内:
s3c24xx_ts_set_platdata(&zhaocj2440_ts_info);
这样触摸屏驱动就移植好了,我们还需要修改menuconfig,配置触摸屏:
Device Drivers --->
Input device support --->
[*] Touchscreens --->
<*> Samsung S3C2410/generictouchscreen input driver
在系统启动的过程中,我们可以从打印出来的信息中看到触摸屏驱动已移植好了:
samsung-ts s3c2440-ts: driver attached, registering input device
input: S3C24XX TouchScreen as /devices/virtual/input/input0
在系统启动后,通过下列命令,可以查看所有input子系统的详细信息:
[root@zhaocj /]#cat proc/bus/input/devices
I: Bus=0019 Vendor=dead Product=beefVersion=0102
N: Name="S3C24XX TouchScreen"
P: Phys=
S: Sysfs=/devices/virtual/input/input0
U: Uniq=
H: Handlers=mouse0 event0
B: PROP=0
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=3
I: Bus=0019 Vendor=001f Product=0001 Version=0100
N: Name="pwm-beeper"
P: Phys=pwm/input0
S: Sysfs=/devices/platform/s3c24xx-pwm.0/pwm-beeper.0/input/input1
U: Uniq=
H: Handlers=kbd event1
B: PROP=0
B: EV=40001
B: SND=6
I: Bus=0019 Vendor=0001 Product=0001Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S:Sysfs=/devices/platform/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2
B: PROP=0
B: EV=3
B: KEY=100000 0 38000000 0
从上面可以看出,触摸屏input子系统是作为第一个input子系统,因此它的驱动文件是/dev/event0。我们写一段应用程序来测试一下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <linux/input.h>
int main()
{
struct input_event data;
int fd;
fd = open("/dev/event0", O_RDONLY)
if(fd<0)
{
printf("Error open%s\n", TS_DEV);
return -1;
}
while(1)
{
read(fd, &data,sizeof(data));
printf("data.type = %d,data.code = %d, data.value = %d\n",data.type,data.code,data.value);
if (data.type == EV_KEY)
{
printf("type:EV_KEY, event = %s, value = %d\n\n",
data.code ==BTN_TOUCH ? "BTN_TOUCH" : "Unkown", data.value);
}
else if(data.type == EV_ABS)
{
printf("type: EV_ABS, event =%s, value = %d\n\n",
data.code ==ABS_X ? "ABS_X" :
data.code ==ABS_Y ? "ABS_Y" :
data.code ==ABS_PRESSURE ? "ABS_PRESSURE" :
"Unkown", data.value);
}
else if (data.type == EV_SYN)
{
printf("type:EV_SYN, event = %s, value = %d\n\n",
data.code ==SYN_REPORT ? "SYN_REPORT" : "Unkown", data.value);
}
else
{
printf("type:0x%x, event = 0x%x, value = %d\n\n", data.type, data.code, data.value);
}
}
return 0;
}
下面是一个完整的触笔从按下到抬起的测试结果:
data.type = 3, data.code = 0, data.value =670
type: EV_ABS, event = ABS_X, value = 670
data.type = 3, data.code = 1, data.value =795
type: EV_ABS, event = ABS_Y, value = 795
data.type = 1, data.code = 330, data.value= 1
type: EV_KEY, event = BTN_TOUCH, value = 1
data.type = 0, data.code = 0, data.value =0
type: EV_SYN, event = SYN_REPORT, value = 0
data.type = 3, data.code = 0, data.value =669
type: EV_ABS, event = ABS_X, value = 669
data.type = 3, data.code = 1, data.value =799
type: EV_ABS, event = ABS_Y, value = 799
data.type = 0, data.code = 0, data.value =0
type: EV_SYN, event = SYN_REPORT, value = 0
data.type = 3, data.code = 1, data.value =798
type: EV_ABS, event = ABS_Y, value = 798
data.type = 0, data.code = 0, data.value =0
type: EV_SYN, event = SYN_REPORT, value = 0
data.type = 3, data.code = 0, data.value =670
type: EV_ABS, event = ABS_X, value = 670
data.type = 0, data.code = 0, data.value =0
type: EV_SYN, event = SYN_REPORT, value = 0
data.type = 3, data.code = 1, data.value =800
type: EV_ABS, event = ABS_Y, value = 800
data.type = 0, data.code = 0, data.value =0
type: EV_SYN, event = SYN_REPORT, value = 0
data.type = 1, data.code = 330, data.value= 0
type: EV_KEY, event = BTN_TOUCH, value = 0
data.type = 0, data.code = 0, data.value =0
type: EV_SYN, event = SYN_REPORT, value = 0
从测试的结果可以看出,在触笔按下时,会完整的得到四个事件:ABS_X、ABS_Y、BTN_TOUCH和SYN_REPORT。在触笔抬起时,会得到两个事件:BTN_TOUCH和SYN_REPORT。在两者之间,只要这次检测的X轴或Y轴坐标中的任意一个与上次检测的不同,就会把不同的坐标值报告给用户,当然仍然是以SYN_REPORT作为结束。