JZ2440开发板学习------中级(二十四:末)

来源:互联网 发布:淘宝双十一有什么优惠 编辑:程序博客网 时间:2024/04/30 12:37

自己写驱动程序之触摸屏

第一步:驱动程序大框

Input输入子系统驱动程序大框比较简单如下:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>

#include <mach/irqs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/gpio.h>

#include <linux/device.h>
#include <linux/input.h>
#include <linux/platform_device.h>

#include <linux/errno.h>
#include <linux/fb.h>
#include <mach/fb.h>
#include <mach/regs-lcd.h>
#include <linux/dma-mapping.h>
//#include <linux/pm.h>  //POWER Manager 
#include <asm/gpio.h>

#include <linux/clk.h>
#include <asm/irq>
#include <plat/ts.h>
#include <plat/regs-adc.h>
#include <linux/serio.h>

static int s3cts_init(void)
{
return 0;
}

static void s3cts_exit(void)
{
}

module_init(s3cts_init);
module_exit(s3cts_exit);
MODULE_LICENSE("GPL");



第二步:补充初始化init()函数

大框如下:

/* 1. 分配一个input_dev结构体 */

如果不会写,搜一下s3c2410_ts.c,看看它是怎样分配的,之后照着自己写咯:

struct input_dev *s3cts_dev;

s3cts_dev= input_allocate_device();


/* 2. 设置 */

/* 2.1 能产生哪类事件 */

s3cts_dev->evbit[0]=BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);

/* 2.2 能产生这类操作里的哪些事件*/

ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

上面可能不太好理解,所以用以下的写法比较简单易懂:

set_bit(BTN_TOUCH, s3cts_dev->keybit);

input_set_abs_params(s3cts_dev, ABS_X, 0, 0x3FF, 0, 0); //见注释1 这个是设置ad转换的x坐标
input_set_abs_params(s3cts_dev, ABS_Y, 0, 0x3FF, 0, 0); //这个是设置ad转换的y坐标

什么时候按下,什么时候抬起需要有标志来提醒:

input_set_abs_params(s3cts_dev, ABS_PRESSURE, 0, 1, 0, 0);


/* 3. 注册 */

input_register_device(s3cts_dev);


/* 4. 硬件相关的操作 */

WOW,硬件操作来咯,在很多关于硬件操作部分的其实仔细看主芯片手册和硬件相关手册来操作没有那么难,只是麻烦:

1. 首先,如果要实现滑屏,长时间按住,拖拽等动作就要有时间控制,这个时候就需要时钟啦:

struct clk *clock;

clock = clk_get(NULL, "adc");    //见注释2
clk_enable(clock);

2. 其次就是设置寄存器咯,硬件嘛肯定要和寄存器相关了,之前已经相关寄存器截图,现在就是对其赋值啦!


与触摸相关的寄存器如上图所示,既然都是一起的就放在一个结构体中吧:

struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};

static volatile struct s3cts_regs *S3cts_regs;

S3cts_regs= ioremap(0x58000000, sizeof(struct s3cts_regs));

之后就是按部就班的设置啦:

S3cts_regs->adccon=(1<<14) | (49<<6); //49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz  0 1 49 000 0 0 0

查看S3C2440芯片手册如下图:


触摸屏的工作方式是循环工作,一直判断是否有中断产生,首先判断是否有按键按下(触摸按下),如果有按下,则发生INT_TC中断,此中断有什么作用呢?如下图:


它只有判断是否按下和抬起,要是想得到判断位置,判断滑屏等就需要另一个中断操作啦:INT_ADC,模数转换!

综上,就需要初始化两个中断啦:

一般申请irq都是request_irq()函数:request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev),但是具体该怎样写呢?参看S3C2410_ts.c中:irq_tc: The interrupt number for pen up/down interrupt,和Called when the IRQ_TC is fired for a pen up or down event.所以,搜索irq_tc或者IRQ_TC,WOW,内核中暂时没有可以利用的哦!这时候怎么办呢?这时候去内核文件夹中去看看,在drivers中有Input文件夹,触摸屏就是输入子系统的一个应用,所以猜测应该有相应触摸屏的,果不其然,在input中有touchscreen,之后挨个看看吧,瞅的像的就进去看看,最后在tsc2007.c中,我们发现了和我们驱动程序最相近的啦!所以好好利用一下啦!

err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq, IRQF_ONESHOT, client->dev.driver->name, ts);

照葫芦画瓢咯:

request_irq(IRQ_TC, pen_down_up_irq, IRQF_****,"ts_pen" , NULL);  //注释3

request_irq(IRQ_ADC,adc_irq,IRQF_****,"adc_irq",NULL);

通过观察上面的IRQF_ONESHOT,所以它将两个中断声明在一个函数中,查看了注释3,对于我们的驱动程序来说,IRQF_SAMPLE_RANDOM比较好,所以:

request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM,"ts_pen" , NULL); 

request_irq(IRQ_ADC,adc_irq,IRQF_SAMPLE_RANDOM,"adc_irq",NULL);

之后就进入第三步啦:



第三步:实现pen_down_up_irq()函数

查看tsc2007.c中,分析一下:

static irqreturn_t tsc2007_hard_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;

if (!ts->get_pendown_state || likely(ts->get_pendown_state()))
return IRQ_WAKE_THREAD;

if (ts->clear_penirq)
ts->clear_penirq();


return IRQ_HANDLED;
}

可以知道,先是判断是否按下,在我们写的驱动程序中是直接和硬件打交道的,所以看看硬件手册可以得出:

adcdat0是判断x,adcdat1是判断y,其最高位是判断是否按下,所以就写一个就行:

static irqreturn_t pen_down_up_irq(int irq, void *handle)
{
if(S3cts_regs->adcdat0 & (1<<15))    //这里是指抬起
{
}
else     //这里才是按下

{

}
return IRQ_HANDLED;
}

之前我们写Input输入子系统的时候多会用到Input相关函数,不过这里不会写怎么办呢?观察tsc2007_soft_irq()函数,其中有关抬起的input函数:

input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_PRESSURE, 0);
input_sync(input);

我们也这样写啦:

input_report_key(s3cts_dev, BTN_TOUCH, 0);
input_report_abs(s3cts_dev, ABS_PRESSURE, 0);
input_sync(s3cts_dev);

查看手册,ADCTSC寄存器是ADC Touch Screen Control Register,它的作用是等待触摸笔按下或者松开模式,可以通过判断ADCTSC的BIT8是否被设置来知道是等待松开还是等待按下。所以在松开模式时要设置ADCTSC:



0xd3:0 1101 0011 ( BIT8 为 0 = Detect Stylus Down Interrupt Signal.)表示等待发现笔尖按下信号,所以在松开模式里还应该加一句:

if(S3cts_regs->adcdat0 & (1<<15))
{
input_report_key(s3cts_dev, BTN_TOUCH, 0);
input_report_abs(s3cts_dev, ABS_PRESSURE, 0);
input_sync(s3cts_dev);
S3cts_regs->adctsc = 0xd3;
}

接下来就要写else啦:

从手册上得知:


else表示按下,当按下时就不需要等待Touch screen interrupt了,所以XP Pull-up应该设置为1并且AUTO_PST位应该设置为1:

S3cts_regs->adctsc = (1<<3) | (1<<2);

并且在获得坐标位置的同时应该打开ADC采集功能啦,开始ADC转换:

else
{
S3cts_regs->adctsc = (1<<3) | (1<<2);
S3cts_regs->adccon |= (1<<0);
}

至此第三步结束啦!



第四步:完善adc_irq()函数

参看一下tsc2007.c的:

static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;
struct input_dev *input = ts->input;
struct ts_event tc;
u32 rt;


while (!ts->stopped && tsc2007_is_pen_down(ts)) {


/* pen is down, continue with the measurement */
tsc2007_read_values(ts, &tc);


rt = tsc2007_calculate_pressure(ts, &tc);


if (rt == 0 && !ts->get_pendown_state) {
/*
* If pressure reported is 0 and we don't have
* callback to check pendown state, we have to
* assume that pen was lifted up.
*/
break;
}


if (rt <= ts->max_rt) {
dev_dbg(&ts->client->dev,
"DOWN point(%4d,%4d), pressure (%4u)\n",
tc.x, tc.y, rt);


input_report_key(input, BTN_TOUCH, 1);
input_report_abs(input, ABS_X, tc.x);
input_report_abs(input, ABS_Y, tc.y);
input_report_abs(input, ABS_PRESSURE, rt);


input_sync(input);


} else {
/*
* Sample found inconsistent by debouncing or pressure is
* beyond the maximum. Don't report it to user space,
* repeat at least once more the measurement.
*/
dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
}


wait_event_timeout(ts->wait, ts->stopped,
  msecs_to_jiffies(ts->poll_period));
}


dev_dbg(&ts->client->dev, "UP\n");


input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_PRESSURE, 0);
input_sync(input);


if (ts->clear_penirq)
ts->clear_penirq();


return IRQ_HANDLED;
}

从最开始模仿,最开始它的意思是判断是否一直按着,分两种情况考虑,按着,松开。我们先判断松开的吧,再判断按下的。松开,其实也有种情况要考虑,就是当AD转换未完成的时候已经松开了,此时就不需要再判断是否按着呢,此时就需要重新判断是否按下了。

判断按键状态松开还是按下(先判断松开),那就跟之前抬起的代码是一样的,都表示松开的意思:

if(S3cts_regs->adcdat0 & (1<<15))
{
input_report_key(s3cts_dev, BTN_TOUCH, 0);
input_report_abs(s3cts_dev, ABS_PRESSURE, 0);
input_sync(s3cts_dev);
S3cts_regs->adctsc = 0xd3;
}

这里并没有上述所说的丢弃数据的动作,那就先往下写,之后在回头写,接下来就是按下的动作,我们通过通篇阅读tsc2007.c,可以得出:当按下触摸板后产生中断,中断里面调用延时函数进入底半部程序。在调度函数里面,先读取坐标,计算压力,然后根据压力大小上报坐标。若触摸板一直按下,那么就重新调用,并通过压力值来看触摸板是否释放。这就是tsc2007.c的大概意思,而我们的程序大概也是这个道理,当继续按下时,就统计几次的adcdat0(坐标x的值)和adcdat1(坐标y的值),之后求取平均值,作为反映出来(即反映到secureCRT串口工具的屏幕上)。所以需要计数器:

static int cnt =0;

之前我们说如果在AD转换的时候抬起(或者说没有到达我们要统计几次的次数时)就舍弃数据,通过将cnt清零就可以办到:

if(S3cts_regs->adcdat0 & (1<<15))
{
cnt = 0;
input_report_key(s3cts_dev, BTN_TOUCH, 0);
input_report_abs(s3cts_dev, ABS_PRESSURE, 0);
input_sync(s3cts_dev);
S3cts_regs->adctsc = 0xd3;
}

现在要记录按下的,也就是在else里写:

else

{

}

我们知道,要统计数据就需要借助数组啦,定义:

static int x[6]; //统计五次

static int y[6]; 

如何记录数据呢?我们可以这样处理:

x[cnt] = S3cts_regs->adcdat0 & 0x3ff; //其实可以直接为S3cts_regs->adcdat0,至于& 0x3ff是为了防止跑偏。

但是我们程序是先判断松开的情况,或者你也可以这样想当你做判断的时候,其实S3cts_regs->adcdat0早就不是最初的值了,为了保存住最初的值,就需要我们最开始就将值赋值给一个变量:

int adcdat0,adcdat1;

adcdat0 = S3cts_regs->adcdat0 & 0x3ff;

adcdat1 = S3cts_regs->adcdat1 & 0x3ff;

每次给数组赋值之后就可以将计量器自增1:

++cnt;

当为6次时,进入求平均值的阶段,但是你想,加入你按着你自己没有感觉动了一些位置,可是数据统计时,统计到你有一次数据和其他五次数据偏差特别大,这样的数据就不应该留着,所以要有优化的措施:

我们定义一个过滤函数:

static int s3c_filter_ts(int x[], int y[])
{
#define ERR_LIMIT 10    //定义一个范围,求平均数,如果对比之后有超出范围的

int avr_x, avr_y;
int det_x, det_y;

avr_x = (x[0] + x[1])/2;
avr_y = (y[0] + y[1])/2;

det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);

if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))    //立马退出过滤函数
return 0;

avr_x = (x[1] + x[2])/2;
avr_y = (y[1] + y[2])/2;

det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);

if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))     //立马退出过滤函数
return 0;

avr_x = (x[2] + x[3])/2;
avr_y = (y[2] + y[3])/2;

det_x = (x[4] > avr_x) ? (x[4] - avr_x) : (avr_x - x[4]);
det_y = (y[4] > avr_y) ? (y[4] - avr_y) : (avr_y - y[4]);

if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))     //立马退出过滤函数
return 0;

avr_x = (x[3] + x[4])/2;
avr_y = (y[3] + y[4])/2;

det_x = (x[5] > avr_x) ? (x[5] - avr_x) : (avr_x - x[5]);
det_y = (y[5] > avr_y) ? (y[5] - avr_y) : (avr_y - y[5]);

if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))     //立马退出过滤函数
return 0;

return 1;
}

如果过滤成功后就要将平均值反映出来啦:

if (cnt == 6)
{
if (s3c_filter_ts(x, y))
{
input_report_abs(s3cts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3]+x[4]+x[5])/6);
input_report_abs(s3cts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])+x[4]+x[5]/6);
input_report_abs(s3cts_dev, ABS_PRESSURE, 1);
input_report_key(s3cts_dev, BTN_TOUCH, 1);
input_sync(s3c_ts_dev);
}

cnt =0;

S3cts_regs->adctsc = 0x1d3;//与之前的相比,这里的意思是等待松开的中断信号

      /*我们知道有种情况是长时间按着,或者滑屏,这个时候就要用到定时器,为什么呢?如果没有定时器,则处理完S3cts_regs->adctsc = 0x1d3;之后就跳出adc_irq(),则长时间按着或者滑屏怎样处理呢?所以要用到定时器:*/

     mod_timer(&ts_timer, jiffies + HZ/100);   //延时10ms之后启动定时器

}

else

{

S3cts_regs->adctsc = (1<<3) | (1<<2);
S3cts_regs->adccon |= (1<<0);
     //当cnt不够6的时候,肯定要ad转换啦

}



第五步:实现定时器函数

在之前总结的写法是:

 timer_list xxx_timer;

 init_timer(&dev->xxx_timer);

dev->xxx_timer.function=&xxx_do_timer;

 add_timer(&dev->xxx_timer);

 mod_timer(&buttons_timer, jiffies+HZ/100); 

对于初始化添加定时器都要在初始化函数init()中实现:

static struct timer_list ts_timer;

init_timer(&ts_timer);
ts_timer.function = ts_timer_function;
add_timer(&ts_timer);

static void ts_timer_function(unsigned int data)

{

}

之所以要用到定时器之前分析了,其实用到定时器就是再次去查看是否还在按着,如果没有则松开,如果有则继续启动AD转换,启动AD转换就会调用adc_irq():

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);
S3cts_regs->adctsc = 0xd3;     
}
else
{
enter_measure_xy_mode();
S3cts_regs->adctsc = (1<<3)|(1<<2);
}
return IRQ_HANDLED;



第六步:补全初始化函数和添加注销函数s3cts_ts()

在初始化函数init()最后,应该是判断是否有按下的动作:

S3cts_regs->adctsc = 0xd3;

return 0;

之后就是exit()函数啦:

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);

其实注销函数很简单的!



好啦!附上自己写的驱动程序:

/******************************************************************************************************************

s3c_ts.c

******************************************************************************************************************/

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>


#include <mach/irqs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/gpio.h>


#include <linux/device.h>
#include <linux/input.h>
#include <linux/platform_device.h>


#include <linux/errno.h>
#include <linux/fb.h>
#include <mach/fb.h>
#include <mach/regs-lcd.h>
#include <linux/dma-mapping.h>
//#include <linux/pm.h>  //POWER Manager 
#include <asm/gpio.h>


#include <linux/clk.h>
#include <asm/irq.h>
#include <plat/ts.h>
#include <plat/regs-adc.h>
#include <linux/serio.h>


static struct input_dev *ts_dev;
struct ts_regs{
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
static volatile struct ts_regs* Ts_regs;
struct clk *clock;
static struct timer_list ts_timer;


static void start_adc(void)
{
Ts_regs->adctsc = (1<<2) | (1<<3);
Ts_regs->adccon |= 1<<0;
}


static void ts_timer_function(unsigned long data)
{
if(Ts_regs->adcdat0 & (1<<15))
{
input_report_key(ts_dev, BTN_TOUCH, 0);
input_report_abs(ts_dev, ABS_PRESSURE, 0);
input_sync(ts_dev);
Ts_regs->adctsc = 0xd3;
}
else
{
start_adc();
}
}


static int filter_ts(int x[], int y[])
{
#define LIMIT 10
int avr_x, avr_y;
int xx, yy;


avr_x = (x[0] + x[1])/2;
avr_y = (y[0] + y[1])/2;
xx = (x[2] > avr_x) ? (x[2] - avr_x ) : (avr_x - x[2]);
yy = (y[2] > avr_y) ? (y[2] - avr_y ) : (avr_y - y[2]);
if((xx > LIMIT) || (yy > LIMIT))
return 0;


avr_x =(x[1] + x[2])/2;
avr_y =(y[1] + y[2])/2;
xx = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
yy = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
if((xx > LIMIT) || (yy > LIMIT))
return 0;


avr_x =(x[2] + x[3])/2;
avr_y =(y[2] + y[3])/2;
xx = (x[4] > avr_x) ? (x[4] - avr_x) : (avr_x - x[4]);
yy = (y[4] > avr_y) ? (y[4] - avr_y) : (avr_y - y[4]);
if((xx > LIMIT) || (yy > LIMIT))
return 0;


avr_x =(x[3] + x[4])/2;
avr_y =(y[3] + y[4])/2;
xx = (x[5] > avr_x) ? (x[5] - avr_x) : (avr_x - x[5]);
yy = (y[5] > avr_y) ? (y[5] - avr_y) : (avr_y - y[5]);
if((xx > LIMIT) || (yy > LIMIT))
return 0;


return 1;
}


static irqreturn_t ts_pen_updown_irq(int irq, void *handle)
{
if(Ts_regs->adcdat0 & (1<<15))
{
input_report_key(ts_dev, BTN_TOUCH, 0);
input_report_abs(ts_dev, ABS_PRESSURE, 0);
input_sync(ts_dev);
Ts_regs->adctsc = 0xd3;
}
else
{
start_adc();

}


return IRQ_HANDLED;
}


static irqreturn_t adc_irq(int irq, void *handle)
{
static int cnt =0;
static int x[6],y[6];
int adcdat0,adcdat1;


adcdat0 = Ts_regs->adcdat0 & 0x3ff;
adcdat1 = Ts_regs->adcdat1 & 0x3ff;

if(Ts_regs->adcdat0 & (1<<15))
{
cnt = 0;
input_report_key(ts_dev, BTN_TOUCH, 0);
input_report_abs(ts_dev, ABS_PRESSURE, 0);
input_sync(ts_dev);
Ts_regs->adctsc = 0xd3;
}
else
{
x[cnt] =adcdat0 & 0x3ff;
y[cnt] =adcdat1 & 0x3ff;
cnt ++;
if(cnt ==6)
{
if(filter_ts(x,y))
{
input_report_key(ts_dev, BTN_TOUCH, 1);
input_report_abs(ts_dev, ABS_X, (x[0] + x[1] + x[2] + x[3] + x[4] + x[5])/6);
input_report_abs(ts_dev, ABS_Y, (y[0] + y[1] + y[2] + y[3] + y[4] + y[5])/6);
input_report_abs(ts_dev, ABS_PRESSURE, 1);
input_sync(ts_dev);
}
cnt =0;
Ts_regs->adctsc = 0x1d3;
mod_timer(&ts_timer, jiffies + HZ/100);
}
else
{
start_adc();
}


}

return IRQ_HANDLED;
}




static int ts_init(void)
{
ts_dev = input_allocate_device();
ts_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
set_bit(BTN_TOUCH, ts_dev->keybit);

input_set_abs_params(ts_dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);


input_register_device(ts_dev);


clock = clk_get(NULL, "adc");
clk_enable(clock);


Ts_regs=ioremap(0x58000000, sizeof(struct ts_regs));
Ts_regs->adccon = (1<<14) | (49<<6);
request_irq(IRQ_TC, ts_pen_updown_irq,  IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc_irq", NULL);


init_timer(&ts_timer);
ts_timer.function = ts_timer_function;
add_timer(&ts_timer);


Ts_regs->adcdly = 0xffff;


Ts_regs->adctsc = 0xd3;


return 0;
}


static void ts_exit(void)
{
del_timer(&ts_timer);
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
iounmap(Ts_regs);
input_unregister_device(ts_dev);
input_free_device(ts_dev);
}


module_init(ts_init);
module_exit(ts_exit);
MODULE_LICENSE("GPL");

其中碰到的错误之一:

Ts_regs->adccon |= 1<<0; 如果写成 Ts_regs->adccon |  = 1<<0; 就会出现以下的错误:



结果:







************************************************************************************************************************************************************************************************************************************************************************

注释1:

input_set_abs_params()

 :

有过linux下触摸屏开发经验的,应该知道通常驱动是把采集到的原始坐标(A/D值),直接通过input系统上报。对于12位的A/D,触摸屏的范围是:0~0xFFF。在驱动中表示如下:

    input_set_abs_params(ts->dev, ABS_X, 0, 0xFFF, 0, 0);
        input_set_abs_params(ts->dev, ABS_Y, 0, 0xFFF, 0, 0);

而Android需要驱动直接上报液晶屏坐标。如我们屏的分辨率是480*272,驱动中需要设置触摸屏的范围如下:

input_set_abs_params(ts->dev, ABS_X, 0, 480, 0, 0);
        input_set_abs_params(ts->dev, ABS_Y, 0, 272, 0, 0);

大家在移植别人的触摸屏驱动时一定要注意上面范围的设置。必须要匹配你的屏分辨率。否则本文后面的方法就不适用了。

接下来需要思考的是,如何把我们采集到A/D数据转换为屏坐标。可以用下面的公式来转换。

int a0,a1,a2,a3,a4,a5,a6;
        x=(int) ts->xp;
        y=(int) ts->yp;
        ts->xp=(long) ((a2+(a0*x)+(a1*y))/a6);
        ts->yp=(long) ((a5+(a3*x)+(a4*y))/a6);

开始的ts->xp、ts->yp为原始的A/D数据,通过公式计算后的ts->xp、ts->yp为屏坐标。

最后的问题就是如何确定a0~a6的值了。这7个参数的值最好的获取方法就是通过tslib了。Tslib的移植可以不在android上,搭建一个普通的根文件系统即可。移植完tslib后,运行校准工具ts_calibrate,生成pointercal文件。打开文件,里面会放着7个参数。这7个参数即是我们要的a0~a6。如:

11062 -38 -7188384 69 13570 -19408864 65536

set_bit(EV_ABS, idev->evbit);
set_bit(ABS_X, idev->absbit);
set_bit(ABS_Y, idev->absbit);
set_bit(ABS_Z, idev->absbit);
前四行说的是设备支持坐标事件,包括X坐标,Y坐标事件,Z坐标事件。
input_set_abs_params(idev, ABS_X, -64, 64, 4, 0);
对于X轴范围是-64到+64,数据误差是-4到+4,中心平滑位置是0 
input_set_abs_params(idev, ABS_Y, -64, 64, 4, 0);
input_set_abs_params(idev, ABS_Z, -64, 64, 4, 0);
对于Y和Z轴,和X轴一样


 input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);   //这个是设置ad转换的x坐标
 input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);   //这个是设置ad转换的y坐标
 input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); //这个是设置触摸屏是否按下的标志
 设置ABS_X编码值范围为0-0x3ff,因为mini2440的AD转换出的数据最大为10位,所以不会超过0x3ff



注释2:

struct clk *clk_get(struct device *dev, const char *con_id)  //clk_get从一个时钟list链表中以字符id名称来查找一个时钟clk结构体

                                          并且返回
{
const char *dev_id = dev ? dev_name(dev) : NULL;

return clk_get_sys(dev_id, con_id);
}

static inline const char *dev_name(const struct device *dev)      //在新版本的内核中struct device 已经没有bus_id成员,取而代之的是通过dev_name和dev_set_name对设备的名字进行操作。
{
/* Use the init name until the kobject becomes available */
if (dev->init_name)
return dev->init_name;

return kobject_name(&dev->kobj);

}

struct clk * clk_get_sys(const char *dev_id, const char *con_id)    
{
struct clk_lookup *cl;

mutex_lock(&clocks_mutex);
cl = clk_find(dev_id, con_id);
if (cl && !__clk_get(cl->clk))
cl = NULL;
mutex_unlock(&clocks_mutex);

return cl ? cl->clk : ERR_PTR(-ENOENT);
}

static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)  
{
struct clk_lookup *p, *cl = NULL;
int match, best = 0;


list_for_each_entry(p, &clocks, node) {
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}


if (match > best) {
cl = p;
if (match != 3)
best = match;
else
break;
}
}
return cl;
}


http://blog.csdn.net/luckywang1103/article/details/39031363



注释3:

中断标志位

    新版本中的IRQF_XXX替代了老版本中的SA_XXX,主要标志包括以下:

    IRQF_DISABLED:快速中断标志,对应老版本中的SA_INTERRUPT标志,表明在处理本中断时屏蔽所有中断,而在没设置此标志位的程序中,都是开中断处理的,可以进行中断嵌套。

    IRQF_SAMPLE_RANDOM:用于随机数据产生;

    IRQF_SHARED:用于共享中断,设置此标志时,request_irq最后一个参数dev不能为NULL,对应老版本内核的SA_SHIRQ;

    IRQF_PROBE_SHARED:探测共享中断;

    IRQF_TIMER:专用于定时中断;

    IRQF_PERCPU:Interrupt is per cpu

    IRQF_NOBALANCING:Flag to exclude this interrupt from irq balancing

    IRQF_IRQPOLL:Interrupt is used for polling(only the interrupt that is registered first in an shared interrupt is considered for performance reasons)

    IRQF_ONESHOT:Interrupt is not reenabled after the hardirq handler finished. Used by threaded interrupts which need to keep the irq line disabled until the threaded handler has been run.


0 0
原创粉丝点击