简介
这篇文章分析 linux 中 touch screen 的驱动,这里拿 s3c2410 中的触摸屏作为例子, 主要内容包括:
- 触摸屏原理
- 代码分析
原理
当我们按下触摸屏时,我们必须知道所按下的区域的位置, 如果把水平和垂直作为坐标系的话,那么位置信息也就是触摸点的坐标值 (x,y).
首先来看下一般阻性触摸屏的构成:
触摸屏包含上下叠合的两个透明层阻性材料,中间由一种弹性材料隔开. 当触摸屏表面受到压力时,顶层和底层之间会产生触碰。所用的电阻式触摸屏都采用分压器原理来产生代表 X 坐标和 Y 坐标的电压. 如下图所示,分压器是通过将两个电阻进行串联来实现的. 上面的电阻 R1 连接正参考电压 Vref, 下面的电阻 R2 接地。连个电阻连接点处的电压测量值与下面那个电阻的阻值成正比.
这样,根据接触点处的电压值,便可以计算出对应的位置. 当然,如果在触摸屏上进行滑动,那么就会有一系列连续的电压信息, 不过,这些信息是模拟信号,但是计算机只认识数字信息, 所以,必须将这些模拟信号转换成对应的数字信号,所以, 这里还必须利用模数转换器 (ADC) 的帮忙:
代码分析
经过上述的分析,我们可以看出,这里有两个关键点, 一个是 ADC, 一个是坐标信息的反馈。下面我们分别来看.
ADC
由于并不是触摸屏一个设备可能会使用 ADC,
所以每个使用 ADC 的设备,对于 ADC 来说就是一个客户端,
所以在 probe
函数中会创建一个 adc client:
ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
s3c24xx_ts_conversion, 1);
if (IS_ERR(ts.client)) {
dev_err(dev, "failed to register adc client\n");
ret = PTR_ERR(ts.client);
goto err_iomap;
}
看一下它的参数:
struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,
void (*select)(struct s3c_adc_client *client,
unsigned int selected),
void (*conv)(struct s3c_adc_client *client,
unsigned d0, unsigned d1,
unsigned *samples_left),
unsigned int is_ts)
select:
当开始 / 结束一次 ADC 转换时的回调函数conv
: 当获得一个采样点信息时的回调函数is_ts
: 该设备是否为触摸屏,因为触摸屏相对于其他设备有更高的优先级.
先来看 select
回调函数:
/**
* s3c24xx_ts_select - ADC selection callback.
* @client: The client that was registered with the ADC core.
* @select: The reason for select.
*
* Called when the ADC core selects (or deslects) us as a client.
*/
static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
{
if (select) {
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
ts.io + S3C2410_ADCTSC);
} else {
mod_timer(&touch_timer, jiffies+1);
writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
}
}
当 select
不为 0 时,表示一次 adc 转换的开始,
这是只是设置 adc 的模式.
反之为 adc 转换的结束,这时需要将计算得出的位置信息通知上层系统.
这里是通过一个定时器来完成的
当每个采样点完成计算时,调用 conv
回调函数:
/**
* s3c24xx_ts_conversion - ADC conversion callback
* @client: The client that was registered with the ADC core.
* @data0: The reading from ADCDAT0.
* @data1: The reading from ADCDAT1.
* @left: The number of samples left.
*
* Called when a conversion has finished.
*/
static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
unsigned data0, unsigned data1,
unsigned *left)
{
dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
ts.xp += data0;
ts.yp += data1;
ts.count++;
/* 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.
*/
}
可以看出这里只是将得到的坐标值累加.
这样当结束一次 adc 转换时,最终通知 input
子系统的坐标值是一个平均值:
if (ts.count == (1 << ts.shift)) {
ts.xp >>= ts.shift;
ts.yp >>= ts.shift;
下面来看下 ADC 触发源,这里有 2 个触发源:
- 当在触摸屏上按下时触发一次 adc 转换过程
- 一次 ADC 转换过程的结束
先来看第一个,这里,当在触摸屏上按下时,会触发一个中断 在中断回调函数中,对于非触摸屏设备,会将其挂入到 ADC 的等待处理队列
list_add_tail(&client->pend, &adc_pending);
如果是触摸屏,由于它具有较高的优先级, 如果 adc 当前正在处理另一个触摸屏处理请求,那么将直接返回
if (client->is_ts && adc->ts_pend)
return -EAGAIN;
否则,adc 将直接处理该请求.
当一次 ADC 完成一次转换时,同样会触发一个中断, 这里首先判断采样点的个数是否达到要求,如果没有,将继续进行采样:
if (client->nr_samples > 0) {
/* fire another conversion for this */
client->select_cb(client, 1);
s3c_adc_convert(adc);
反之,将结束当前的采样,然后开始处理下一个请求:
} else {
spin_lock(&adc->lock);
(client->select_cb)(client, 0);
adc->cur = NULL;
s3c_adc_try(adc);
spin_unlock(&adc->lock);
}
信息的反馈
信息的反馈主要是基于 linux input 子系统, 首先是注册需要反馈的事件的类型,这里主要有位置事件和按键事件
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
以及定义位置信息的范围
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
当在触摸屏上按下时,会同时反馈按下的点的位置事件和一个按键信息:
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);
当在触摸屏上释放是,同样会反馈一个按键信息:
input_report_key(ts.input, BTN_TOUCH, 0);
input_sync(ts.input);
对于 input 子系统的相关细节,不在本篇的讨论范围,下一篇文章会有详解.
FIN.