linux2.6.24输入子系统——触摸屏驱动设计

来源:互联网 发布:淘宝买高达模型推荐 编辑:程序博客网 时间:2024/05/01 23:56

开发平台:Fedora19

嵌入式平台:FL2440+linux2.6.24

1. 驱动程序(ts_driver.c):

#include<linux/module.h> 

#include <linux/kernel.h> 

#include <linux/clk.h> 

#include <linux/init.h> 

#include <linux/input.h> 

#include <linux/serio.h> 

#include <asm-arm/plat-s3c/regs-adc.h> 

#include <asm/irq.h> 

#include <asm/io.h> 

//===============================输入子系统相关=========================================

/*用于保存从平台时钟列表中获取的ADC时钟*/ 

static struct clk *adc_clk; 

/*定义了一个用来保存经过虚拟映射后的内存地址*/ 

static void __iomem *adc_base; 

/*定义一个输入设备来表示我们的触摸屏设备*/ 

static struct input_dev *ts_dev;

/*设备名称*/ 

#define DEVICE_NAME    "my2440_TouchScreen"

/*定义一个WAIT4INT宏,该宏将对ADC触摸屏控制寄存器进行操作

S3C2410_ADCTSC_YM_SEN这些宏都定义在regs-adc.h中*/ 

#define WAIT4INT(x)    (((x)<<8) | S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | \

S3C2410_ADCTSC_XP_SEN| S3C2410_ADCTSC_XY_PST(3))

//===============================ADC和触摸屏操作相关=====================================       

/*定义一个外部的信号量ADC_LOCK,因为ADC_LOCK在ADC驱动程序中已申明

这样就能保证ADC资源在ADC驱动和触摸屏驱动中进行互斥访问*/ 

//extern struct semaphore ADC_LOCK; //当有ADC驱动的时候作互斥使用

/*做为一个标签,只有对触摸屏操作后才对X和Y坐标进行转换*/ 

static int OwnADC = 0; 

/*用于记录转换后的X坐标值和Y坐标值*/ 

static long xp; 

static long yp; 

/*用于计数对触摸屏压下或抬起时模拟输入转换的次数*/ 

static int count; 

/*定义一个AUTOPST宏,将ADC触摸屏控制寄存器设置成自动转换模式*/ 

#define AUTOPST    (S3C2410_ADCTSC_YM_SEN| S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \

S3C2410_ADCTSC_AUTO_PST| S3C2410_ADCTSC_XY_PST(0))

//==============函数声明==========================

static void touch_timer_fire(unsigned longdata) ;

 

/*定义并初始化了一个定时器touch_timer,定时器服务程序为touch_timer_fire*/ 

static struct timer_list touch_timer =TIMER_INITIALIZER(touch_timer_fire, 0, 0);     

 

//======================================================================                                                     

//                                                                           ADC中断服务程序            

//AD转换完成后触发执行

//======================================================================

static irqreturn_t adc_irq(int irq, void*dev_id) 

   /*用于记录这一次AD转换后的值*/ 

   unsigned long data0; 

   unsigned long data1; 

   if(OwnADC) 

   { 

       /*读取这一次AD转换后的值,注意这次主要读的是坐标*/ 

       data0 = readl(adc_base + S3C2410_ADCDAT0); 

       data1 = readl(adc_base + S3C2410_ADCDAT1); 

       /*记录这一次通过AD转换后的X坐标值和Y坐标值,根据数据手册可知,X和Y坐标转换数值

       分别保存在数据寄存器0和1的第0-9位,所以这里与上S3C2410_ADCDAT0_XPDATA_MASK就是取0-9位的值*/ 

       xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; 

       yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; 

       /*计数这一次AD转换的次数*/ 

       count++; 

       if (count < (1<<2))  

       { 

           /*如果转换的次数小于4,则重新启动ADC转换*/ 

           writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST, adc_base + S3C2410_ADCTSC); 

           writel(readl(adc_base + S3C2410_ADCCON)| S3C2410_ADCCON_ENABLE_START,adc_base + S3C2410_ADCCON); 

       }  

       else  

       { 

           /*否则,启动1个时间滴答的定时器,这时就会去执行定时器服务程序上报事件和数据*/ 

           mod_timer(&touch_timer, jiffies + 1); //jiffies系统当前的Tick数

           writel(WAIT4INT(1), adc_base + S3C2410_ADCTSC); 

       } 

   } 

   return IRQ_HANDLED; 

}

//======================================================================                                                     

//                                                                           定时器服务程序          

//定时器溢出后执行

//======================================================================

static void touch_timer_fire(unsigned longdata) 

   /*用于记录这一次AD转换后的值*/ 

     unsigned long data0; 

      unsigned long data1; 

   /*用于记录触摸屏操作状态是按下还是抬起*/ 

   int updown; 

   /*读取这一次AD转换后的值,注意这次主要读的是状态*/ 

   data0 = readl(adc_base + S3C2410_ADCDAT0); 

   data1 = readl(adc_base + S3C2410_ADCDAT1); 

   /*记录这一次对触摸屏是压下还是抬起,该状态保存在数据寄存器的第15位,所以与上S3C2410_ADCDAT0_UPDOWN*/ 

    updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN))&& (!(data1 & S3C2410_ADCDAT0_UPDOWN)); 

   /*判断触摸屏的操作状态*/ 

    if (updown)  

   { 

       /*如果状态是按下,并且ADC已经转换了就报告事件和数据*/ 

       if (count != 0)  

       { 

   //===这里调换X/Y的坐标是为了满足X长Y短的习惯,不换也行===

           long tmp;                                                                  

           tmp = xp; 

           xp = yp; 

           yp = tmp; 

           //===取4次转换平均值===                                                                                       

           xp >>= 2; 

           yp >>= 2; 

#ifdef CONFIG_TOUCHSCREEN_MY2440_DEBUG 

           /*触摸屏调试信息,编译内核时选上此项后,点击触摸屏会在终端上打印出坐标信息*/ 

           struct timeval tv; 

           do_gettimeofday(&tv); 

           printk(KERN_DEBUG "T: %06d, X: %03ld, Y: %03ld/n",(int)tv.tv_usec, xp, yp); 

#endif 

           /*报告X、Y的绝对坐标值*/ 

            input_report_abs(ts_dev, ABS_X, xp); 

            input_report_abs(ts_dev, ABS_Y, yp); 

           /*报告触摸屏的状态,1表明触摸屏被按下*/ 

            input_report_abs(ts_dev, ABS_PRESSURE, 1); 

           /*报告按键事件,键值为1(代表触摸屏对应的按键被按下)*/ 

            input_report_key(ts_dev, BTN_TOUCH, 1); 

           /*等待接收方受到数据后回复确认,用于同步*/ 

            input_sync(ts_dev);  

        } 

       /*如果状态是按下,并且ADC还没有开始转换就启动ADC进行转换*/ 

        xp = 0; 

        yp = 0; 

        count = 0; 

       /*设置触摸屏的模式为自动转换模式*/ 

        writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST, adc_base + S3C2410_ADCTSC); 

       /*启动ADC转换*/ 

        writel(readl(adc_base + S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, adc_base + S3C2410_ADCCON); 

    }  

   else  

   { 

       /*否则是抬起状态*/ 

        count = 0; 

       /*报告按键事件,键值为0(代表触摸屏对应的按键被释放)*/ 

        input_report_key(ts_dev, BTN_TOUCH, 0); 

       /*报告触摸屏的状态,0表明触摸屏没被按下*/ 

        input_report_abs(ts_dev, ABS_PRESSURE, 0); 

       /*等待接收方受到数据后回复确认,用于同步*/ 

        input_sync(ts_dev); 

       /*将触摸屏重新设置为等待中断状态*/ 

        writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC); 

        /*如果触摸屏抬起,就意味着这一次的操作结束,所以就释放ADC资源的占有*/ 

       if (OwnADC)  

       { 

           OwnADC = 0; 

           //up(&ADC_LOCK); 

       } 

    } 

}                                             

//=============================================================                                                                

//触摸屏中断服务程序,对触摸屏按下或提笔时触发执行

//=============================================================

static irqreturn_t tc_irq(int irq, void*dev_id) 

   /*用于记录这一次AD转换后的值*/ 

   unsigned long data0; 

   unsigned long data1; 

   /*用于记录触摸屏操作状态是按下还是抬起*/ 

   int updown; 

   /*ADC资源可以获取,即上锁*/ 

   //if (down_trylock(&ADC_LOCK) == 0) 

   if(1)

   { 

       /*标识对触摸屏进行了操作*/ 

       OwnADC = 1; 

       /*读取这一次AD转换后的值,注意这次主要读的是状态*/ 

       data0 = readl(adc_base + S3C2410_ADCDAT0); 

       data1 = readl(adc_base + S3C2410_ADCDAT1); 

       /*记录这一次对触摸屏是压下还是抬起,该状态保存在数据寄存器的第15位,所以与上S3C2410_ADCDAT0_UPDOWN*/ 

       updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN))&& (!(data1 & S3C2410_ADCDAT0_UPDOWN)); 

       /*判断触摸屏的操作状态*/ 

       if (updown)  

       { 

           /*如果是按下状态,则调用touch_timer_fire函数来启动ADC转换,该函数定义后面再讲*/ 

           touch_timer_fire(0); 

       }  

       else  

       { 

           /*如果是抬起状态,就结束了这一次的操作,所以就释放ADC资源的占有*/ 

           OwnADC = 0; 

          // up(&ADC_LOCK); 

        } 

   } 

   return IRQ_HANDLED; 

 

//=============================================================                                                                

//初始化ADC控制寄存器和ADC触摸屏控制寄存器

//=============================================================

static void adc_initialize(void) 

    /*计算结果为(二进制):111111111000000,再根据数据手册得知

    此处是将AD转换预定标器值设为255、AD转换预定标器使能有效*/ 

   writel(S3C2410_ADCCON_PRSCEN| S3C2410_ADCCON_PRSCVL(0xFF),adc_base + S3C2410_ADCCON); 

   /*对ADC开始延时寄存器进行设置,延时值为0xffff*/ 

   writel(0xffff, adc_base + S3C2410_ADCDLY); 

   /*WAIT4INT宏计算结果为(二进制):11010011,再根据数据手册得知

    此处是将ADC触摸屏控制寄存器设置成等待中断模式*/ 

   writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC); 

}                                                    

//============================================================

//                                                                    模块初始化

//1.获取ADC时钟并使能

//2.把IO空间映射到虚拟地址,初始化ADC和触摸屏控制器

//3.申请ADC中断和触摸屏TC中断

//4.注册输入子系统

//============================================================

static int __init ts_init(void) 

     int ret; 

     /*从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,因为ADC的转换频率跟时钟有关。

    系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/ 

   adc_clk = clk_get(NULL, "adc"); 

   if(!adc_clk) 

   { 

       /*错误处理*/ 

       printk(KERN_ERR "falied to find adc clock source/n"); 

       return -ENOENT; 

   } 

   /*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/ 

   clk_enable(adc_clk);

             

    /*将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。

     注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,

    S3C2410_PA_ADC是ADC控制器的基地址,定义在mach-s3c2410/include/mach/map.h中,0x20是虚拟地址长度大小*/ 

   adc_base = ioremap(S3C2410_PA_ADC,0x20); 

   if(adc_base == NULL) 

   { 

       /*错误处理*/ 

       printk(KERN_ERR "failed to remap register block/n"); 

       ret = -EINVAL; 

       goto err_noclk; 

   } 

   /*初始化ADC控制寄存器和ADC触摸屏控制寄存器*/ 

   adc_initialize();           

             

   /*申请ADC中断,AD转换完成后触发。这里使用共享中断IRQF_SHARED是因为该中断号在ADC驱动中也使用了,

    最后一个参数1是随便给的一个值,因为如果不给值设为NULL的话,中断就申请不成功*/ 

   ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED | IRQF_SAMPLE_RANDOM,DEVICE_NAME, 1); 

   if(ret) 

   { 

       printk(KERN_ERR "IRQ%d error %d/n", IRQ_ADC, ret); 

       ret = -EINVAL; 

       goto err_nomap; 

    }           

        /*申请触摸屏中断,对触摸屏按下或提笔时触发*/ 

   ret = request_irq(IRQ_TC, tc_irq, IRQF_SAMPLE_RANDOM, DEVICE_NAME,1); 

   if(ret) 

   { 

       printk(KERN_ERR "IRQ%d error %d/n", IRQ_TC, ret); 

       ret = -EINVAL; 

       goto err_noirq; 

    }

             

   /*给输入设备申请空间,input_allocate_device定义在input.h中*/ 

   ts_dev = input_allocate_device();

   /*下面初始化输入设备,即给输入设备结构体input_dev的成员设置值。

   evbit字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,

   BIT宏实际就是对1进行位操作,定义在linux/bitops.h中*/ 

   ts_dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);

   /*keybit字段用于描述按键的类型,在input.h中定义了很多,这里用BTN_TOUCH类型来表示触摸屏的点击*/ 

   ts_dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);

   /*对于触摸屏来说,使用的是绝对坐标系统。这里设置该坐标系统中X和Y坐标的最小值和最大值(0-1023范围)

   ABS_X和ABS_Y就表示X坐标和Y坐标,ABS_PRESSURE就表示触摸屏是按下还是抬起状态*/ 

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

   /*以下是设置触摸屏输入设备的身份信息,直接在这里写死。

    这些信息可以在驱动挂载后在/proc/bus/input/devices中查看到*/ 

   ts_dev->name          =DEVICE_NAME;   /*设备名称*/ 

   ts_dev->id.bustype    =BUS_RS232;     /*总线类型*/     

   ts_dev->id.vendor     = 0xDEAD;        /*经销商ID号*/ 

   ts_dev->id.product    =0xBEEF;        /*产品ID号*/ 

   ts_dev->id.version    =0x0101;        /*版本ID号*/

   /*好了,一些都准备就绪,现在就把ts_dev触摸屏设备注册到输入子系统中*/ 

   input_register_device(ts_dev); 

             return 0; 

 

              /*下面是错误跳转处理*/ 

              err_noclk: 

                            clk_disable(adc_clk); 

                            clk_put(adc_clk); 

              err_nomap: 

                            iounmap(adc_base); 

              err_noirq: 

                            free_irq(IRQ_ADC,1); 

                            returnret; 

//========================================        

//                                               模块卸载

//========================================

static void __exit ts_exit(void) 

   /*屏蔽和释放中断*/ 

   disable_irq(IRQ_ADC); 

   disable_irq(IRQ_TC); 

   free_irq(IRQ_ADC, 1); 

   free_irq(IRQ_TC, 1); 

   /*释放虚拟地址映射空间*/ 

   iounmap(adc_base); 

   /*屏蔽和销毁时钟*/ 

   if(adc_clk)  

   { 

       clk_disable(adc_clk); 

       clk_put(adc_clk); 

       adc_clk = NULL; 

   } 

   /*将触摸屏设备从输入子系统中注销*/ 

   input_unregister_device(ts_dev); 

}

 

module_init(ts_init); 

module_exit(ts_exit); 

MODULE_LICENSE("GPL"); 

MODULE_AUTHOR("DavidZhang"); 

MODULE_DESCRIPTION("My2440 TouchScreen Driver"); 

//=====================================================================

//(1)如果触摸屏感觉到触摸,则触发触摸屏中断即进入tc_irq,获取ADC_LOCK

//后判断触摸屏状态为按下,则调用touch_timer_fire启动ADC转换;

//(2)当ADC转换启动后,触发ADC中断即进入adc_irq,如果这一次转换的次数小

//于4,则重新启动ADC进行转换,如果4次完毕后,启动1个时间滴答的定时器,

//停止ADC转换,也就是说在这个时间滴答内,ADC转换是停止的;

//(3)这里为什么要在1个时间滴答到来之前停止ADC的转换呢?这是为了防止屏

//幕抖动。

//(4)如果1个时间滴答到来则进入定时器服务程序touch_timer_fire,判断触摸

//屏仍然处于按下状态则上报事件和转换的数据,并重启ADC转换,重复第(2)步;

//(5)如果触摸抬起了,则上报释放事件,并将触摸屏重新设置为等待中断状态。   

//=====================================================================

 

2.测试程序(tc_test.c):

#include <stdio.h> 

#include <linux/input.h> 

 

static int event0_fd = -1; 

struct input_event ev0[64]; 

 

static int handle_event0() 

    int button = 0, realx=0, realy=0, i, rd; 

   //============读出64个结构放在ev0中======================

   rd = read(event0_fd, ev0, sizeof(struct input_event)* 64); 

   if(rd < sizeof(struct input_event)) return 0; 

   for(i=0;i<rd/sizeof(struct input_event); i++) 

   { 

       if(EV_ABS == ev0[i].type) 

       { 

           if(ev0[i].code == 0) { 

                realx = ev0[i].value;  //X坐标

           } else if(ev0[i].code == 1) { 

                realy = ev0[i].value;  //Y坐标

           } 

        } 

       //=====打印:读取次数,类型,特征码,值,X坐标,Y坐标

       printf("event(%d):type:%d; code:%3d; value:%3d; realx:%3d;realy:%3d\n",i,ev0[i].type,ev0[i].code,ev0[i].value,realx,realy); 

         

   } 

   return 1; 

 

int main(void) 

   int done = 1; 

   event0_fd = open("/dev/input/event0",02);  //打开设备文件

   if(event0_fd <0) { 

       printf("open input device error\n"); 

       return -1; 

   } 

   while (done) 

   { 

       printf("begin handle_event0...\n"); 

       done = handle_event0();  //读取64个坐标并打印

       printf("end handle_event0...\n"); 

   } 

   if(event0_fd > 0) 

   { 

       close(event0_fd); 

       event0_fd = -1; 

   } 

   return 0; 

 

3.测试

(1) 编译得出ts_driver.ko模块文件和tc_test可执行文件。

(2)安装模块文件:

 

(3)查看输入设备信息


注意:如果Hhandlers=  处没有event0,则还需要配置内核。(在内核配置中勾选Devicesdriver -> Input device -> event interface ,然后重新编译内核)!

(3)创建设备节点

输入设备也是属于字符设备,在/dev 下创建一个目录input,然后在input目录下创建event0设备节点


(3)运行测试







0 0
原创粉丝点击