触摸屏驱动分析
一.入口函数
static int s3c_ts_init(void)
1.分配一个input_dev结构体
static struct input_dev s3c_ts_dev;
分配该结构体:s3c_ts_dev=input_allocate_device();
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)];//设置哪一类事件
unsigned long keybit[NBITS(KEY_MAX)];//按键类事件中的哪一件事
unsigned long relbit[NBITS(REL_MAX)];
unsigned long absbit[NBITS(ABS_MAX)];
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int state;
int sync;
int abs[ABS_MAX + 1];
int rep[REP_MAX + 1];
unsigned long key[NBITS(KEY_MAX)];
unsigned long led[NBITS(LED_MAX)];
unsigned long snd[NBITS(SND_MAX)];
unsigned long sw[NBITS(SW_MAX)];
int absmax[ABS_MAX + 1];
int absmin[ABS_MAX + 1];
int absfuzz[ABS_MAX + 1];
int absflat[ABS_MAX + 1];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle *grab;
struct mutex mutex;/* serializes open and close operations */
unsigned int users;
struct class_device cdev;
union {/* temporarily so while we switching to struct device */
struct device *parent;
} dev;
struct list_headh_list;
struct list_headnode;
};
2.设置input_dev结构体
(1)设置能产生哪类事件
按键类事件,绝对位移事件
set_bit(EV_KEY,s3c_ts_dev);//按键类事件
set_bit(EV_ABS,s3c_ts_dev);//绝对位移事件
(2)设置能产生一类事件中具体哪个事件
按键类事件:触摸屏事件
绝对位移事件:x方向,y方向,绝对位移方向
set_bit(BTN_TOUCH,s3c_ts_dev->keybit);//按键类事件中的触摸屏事件
input_set_abs_params(s3c_ts_dev,ABS_X,0,0x3ff,0,0);//绝对位移事件中x方向位移事件
参数1:input_dev
参数2:坐标方向(x,y,压力方向)
参数3:坐标最小值:0
参数4:坐标最大值:0x3ff(分辨率为10位,最大值刚好为0x3ff)
参数5,6:填0
Input_set_abs_params(s3c_ts_dev,ABS_Y,0,0X3FF,0,0);//Y方向位移事件
Iniput_set_abs_params(s3c_ts_dev,ABS_PRESSURE,0,1,0,0);//压力方向位移事件
3.注册input_dev结构体
input_register_device(s3c_ts_dev);
4.硬件操作
(1)使能ADC的PCLK时钟(clkcon[15])
struct clk *clk;
clk_get(NULL,”adc”);
clk_enable(clk);
(2)设置触摸屏相关的寄存器
① 在设置前需要先映射为虚拟地址
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};//将所有寄存器写成一个结构体,只需要对结构体映射即可
ioremap(0x58000000,sizeof(struct s3c_ts_regs));//建立映射
② 设置ADC相关的寄存器(基本上只需设置adccon,adctsc,adcdly三个)
a.ADCCON
bit[14]:1—使能预分频器
bit[13-6]:49—预分频值设置为49
其余各位设置为0
s3c_ts_regs->adccon=(1<<14)|(49<<6);
③ 注册中断
因为触摸屏工作的基本原理是当笔尖按下或提起时会产生中断,所以需要注册一个IRQ_TC,当笔尖按下时,会启动ADC转换;当ADC转换完成后,会触发ADC中断,进行事件上报等处理,因此还需要注册一个ADC中断(IRQ_ADC)
request_irq(IRQ_TC,pen_down_up_irq, IRQF_SAMPLE_RANDOM,”ts_pen”,NULL)
request_irq(IRQ_ADC,adc_irq,IRQF_SAMPLE_RANDOM,”adc”,NULL)
参数1:中断名
参数2:中断处理函数
参数3:irqflags
参数4:设备名
参数5:dev_id
④等待笔尖按下触发中断(IRQ_TC)
enter_wait_pen_down_mode();//需要设置ADCTSC寄存器的值为0xd3,然后才能等待笔尖按下中断
void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc=0xd3;
}
补充一个优化:因为如果电压还未稳定就触发了IRQ_TC中断,则此时的测量值是不可靠的,因此需要延时一段时间等电压稳定后再进行测量,即设置ADCDLY为最大值(0xffff)
⑤当笔尖按下时会触发中断(IRQ_TC),并触发中断处理函数pen_down_up_irq
在中断处理函数中首先判断触发原因是笔尖按下,还是笔尖抬起(因为这两者的中断处理函数是同一个);通过ADCDAT0的第15位来判断,0代表笔尖落下,1代表笔尖抬起
如果是笔尖抬起所触发的中断,则需要上报事件:
input_report_abs(s3c_ts_dev,ABS_PRESSURE,0);
input_report_abs(s3c_ts_dev,BTN_TOUCH,0);
input_sync(s3c_ts_dev);//上报完需要添加该语句
上报完事件后(即已经处理完)再次等待笔尖落下
如果是笔尖落下所触发的中断,则进入测量x,y坐标的模式(enter_measure_xy_mode()),该模式主要设置ADC的x,y测量方式,并启动ADC转换(start_adc)
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2);//设置为x,y自动转换模式
}
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);//启动ADC转换
}
⑥当ADC转换完成会触发IRQ_ADC中断,并执行中断处理函数adc_irq
由于在ADC转换完成前可能触摸笔就已经松开,此时的测量值就没有意义,需要舍弃, 因此在adc_irq中首先判断触摸笔是否已经松开,如果已经松开,则上报事件并等待笔 尖再次落下
static irqreturn_t adc_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
cnt = 0;
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
}
如果笔尖尚未抬起就读取x,y的测量值,并进行一些求平均值等优化操作以提高精度,
再将处理好的结果结果进行上报,最后继续等待笔尖抬起
这里的软件优化措施包括:求平均值,软件滤波(static int s3c_filter_ts(int x[], int y[])) 等
还需要处理长按或者滑动等情况,使用定时器在处理,基本原理是:使用定时器延时10ms后,再次判断笔尖是否抬起,如果抬起了(不是长按或者滑动),则上报事件;如果还没抬起(长按或者滑动,再次测量x,y的值)
——要使用定时器需要有如下步骤:
static struct timer_list ts_timer;
init_timer(&ts_timer);//初始化定时器
ts_timer.function = s3c_ts_timer_function;//定时器时间到执行此函数
add_timer(&ts_timer);//添加一个定时器
上述步骤就在入口函数中设置
mod_timer(&ts_timer, jiffies + HZ/100);//启动定时器10ms,在adc_irq中处理数据时启动
定时时间到,执行s3c_ts_timer_function
static void s3c_ts_timer_function(unsigned long data)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
/* 测量X/Y坐标 */
enter_measure_xy_mode();
start_adc();
}
}
2.出口函数
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);//注销中断
free_irq(IRQ_ADC, NULL);
iounmap(s3c_ts_regs);//取消映射关系
input_unregister_device(s3c_ts_dev);//注销设备
input_free_device(s3c_ts_dev);//释放设备所占的内存
del_timer(&ts_timer);//关闭定时器
}