linux驱动篇-touchscreen-完整版

Touchscreen

本篇文章为触摸屏驱动完整版本,为的是给时间充裕的同学详细讲解。如要时间有限可以看精简版,传送门在下面。

https://blog.csdn.net/chichi123137/article/details/89256532

前言

在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

 

也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

 

针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

 

最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

 

总体目标

本篇文章的目标是介绍如何从自顶向下从零编写linux下的触摸屏驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

 

总体思路

总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

 

中年润在写代码的的总体思路如下:

需求描述—能够详细完整的描述一个需求。

需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

具体代码—根据编写框架,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

Makefile—用来编译驱动代码。

目录结构—用来说明当完成编码后的结果。

测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

执行结果—观察执行结果是否符合预期。

结果总结—回顾本节的思路,知识点,api,结构体。

实战目标—说明如何根据本文档训练。

请大家尽量按照自顶向下的学习思路来学习和实战,因为我们所有工作的动力都是我们心中的需求。这些步骤仅仅是我们达到目标所要走过的路。目录起到提纲挈领的重要作用,写的时候要实时看下提纲,看有没有偏离自己的方向。

 

需求描述

编写触摸屏驱动,要求如下:

1能够在按下时在串口上连续输出按下的坐标值;

2能够在松开时在串口上输出已经松开。

 

需求分析

要分析出需要实现哪些功能,经过分析,可以得出需要实现以下功能。

1能够控制ADC数模转换器将模拟信号转换成数字信号;

2能够通过按下时的电阻值来确定当前按下的坐标值;

3需要利用linux提供的input输入子系统模块相关api。

 

需求分解

根据需求分析的结果,分解出需要实现的功能:

1需要向内核注册和卸载input设备

2配置adc和触摸屏接口寄存器

3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开

4需要启动ADC转换,将电阻值转换为数字值

 

编写思路

编写思路主要用来搭建代码框架,解决在宏观上如何用代码实现驱动的功能。Touchscreen驱动的核心是注册input设备,并注册adc和触摸中断处理函数。

0编写代码框架,头文件,出入口函数,声明LICENSE

 

在入口函数中所做的工作如下:

1需要向内核注册input设备

1.1需要注册input device

1.1.1需要申请struct input_dev结构体

1.1.2需要设置能产生哪类事件,产生哪些事件

1.1.3需要注册struct input_dev结构体

1.2需要卸载input device

2配置adc和触摸屏接口寄存器

2.1获取并使能adc时钟

2.2构造adc和触摸屏相关寄存器结构体

2.3映射寄存器

2.4配置AD转换器的预分频使能和预分频值

 

3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开

3.1需要注册触摸屏中断处理函数

3.1.1编写触摸屏中断处理函数

3.2进入等待触摸笔按下模式

 

4需要启动ADC转换,将电阻值转换为数字值

4.1需要注册adc中断处理函数

4.1.1需要编写adc中断处理函数

 

在出口函数中所做的工作如下

5在出口函数中释放资源

 

详细步骤

详细步骤主要用来在代码框架里填充必要的细节代码,解决在微观上如何用代码实现驱动各个小功能。Touchscreen驱动的总体核心是注册input设备,并注册adc和触摸中断处理函数。具体小功能则为实现中断处理函数和消除触摸抖动。

0编写代码框架,头文件,出入口函数,声明LICENSE

1需要向内核注册和卸载input设备

1.1需要注册input device

1.1.1需要申请struct input_dev结构体

1.1.2需要设置能产生哪类事件,产生哪些事件

1.1.3需要注册struct input_dev结构体

1.2需要卸载input device

2配置adc和触摸屏接口寄存器

2.1获取并使能adc时钟

2.2构造adc和触摸屏相关寄存器结构体

2.3映射寄存器

2.4配置AD转换器的预分频使能和预分频值

 

3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开

3.1需要注册触摸屏中断处理函数

3.1.1编写触摸屏中断处理函数

3.1.2通过检测笔尖的落下和抬起状态来进行操作

3.1.3进入自动测量模式,启动adc,将电压值转换为坐标值

3.2进入等待触摸笔按下模式

 

4需要启动ADC转换,将电阻值转换为数字值

4.1需要注册adc中断处理函数

4.1.1需要编写adc中断处理函数

4.1.2在按下时获取坐标值,并进入等待松开状态

 

在出口函数中所做的工作如下

5在出口函数中释放资源

5.1释放申请的中断资源

5.2取消寄存器的地址映射

5.3释放申请的时钟资源

5.4卸载input设备

5.5释放input设备资源

 

编写框架

/* Touchscreen_skeleton.c */
/* 本文件是依照touchscreen 驱动<编写思路>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
/* 2.2构造adc和触摸屏相关寄存器结构体 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
 
static void enter_wait_pen_down_mode(void)
{
 
}
 
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
       return 0;
}
 
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       return 0;
}
 
static int s3c_touchscreen_init(void)
{
       int ret;
       struct clk * adc_clk;
 
       /* 1.1需要注册input device */
       /* 1.1.1需要申请struct input_dev结构体 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要设置能产生哪类事件,产生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要注册struct input_dev结构体 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和触摸屏接口寄存器 */
       /* 2.1获取并使能adc时钟 */
       adc_clk = clk_get(NULL,"adc");
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD转换器的预分频使能和预分频值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先设为0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开 */
       /* 2.1需要注册触摸屏中断处理函数 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request_irq failed ts_pen\n");
       }
       /* 2.2进入等待触摸笔按下模式 */
       enter_wait_pen_down_mode();
 
       /* 3需要启动ADC转换,将电阻值转换为数字值 */
       /* 3.1需要注册adc中断处理函数 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request_irq failed adc\n");
       }
       return 0;
}
 
static void s3c_touchscreen_exit(void)
{
       iounmap(s3c_ts_regs);
       /* 1.2需要卸载input device */
       input_unregister_device(s3c_ts_inputdev);
}
 
/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

 

驱动代码

《驱动代码未优化》对应touchscreen.c主要用来做触摸屏中断实验,adc中断实验。

《驱动代码优化措施1》提出了一种通过配置延时寄存器来优化驱动代码的方法。

《驱动代码优化措施2》提出了一种应对当adc转换完成时已经松开触摸屏的方法。

《驱动代码优化措施3》提出了一种多次测量求平均坐标值的方法。

《驱动代码优化措施4》提出了一种通过软件过滤所获得的坐标值的方法。

《驱动代码优化措施5》提出了一种通过增加定时器和定时器处理函数来处理长按触摸屏和滑动触摸屏的处理方法。

《驱动代码最终版》加入了上报事件的操作,是将5种优化措施结合后的代码。

 

驱动代码代码未优化

/* Touchscreen.c可以用来做触摸屏中断实验,adc中断实验,驱动代码优化措施1实验 */
 
/* 本文件是依照touchscreen 驱动<详细步骤>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define TOUCH_SCREEN_INTERRUPT_TEST             1
#define ADC_INTERRUPT_TEST                        1
 
#define OPTIMIZED_METHOD1                       1
#define OPTIMIZED_METHOD2                       1    
#define OPTIMIZED_METHOD3                       1
#define OPTIMIZED_METHOD4                       1
#define OPTIMIZED_METHOD5                       1
 
/* 2.2构造adc和触摸屏相关寄存器结构体 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
 
/* 进入等待触摸笔按下模式 */
static void enter_wait_pen_down_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]0:     检测笔尖落下中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0xd3;
}
 
/* 进入等待触摸笔松开模式 */
static void enter_wait_pen_up_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]1:     检测笔尖抬起中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0x1d3;
}
 
/* 进入自动测量xy坐标模式 */
static void enter_measure_xy_mode(void)
{
       /* 6 = (1<<3)|(1<<2)
         * bit[8]0:     检测笔尖抬起落下信号
         * bit[7]0:     YM输出驱动器禁止
         * bit[6]0:     YP输出驱动器使能
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]0:     XP输出驱动器使能
         * bit[3]1:     XP上拉禁止
         * bit[2]1:     自动顺序X方向和Y方向测量
         * bit[0-1]00:       无操作模式
         */
       s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
 
/* 启动adc */
static void start_adc(void)
{
       /*
         * bit[0]:A/D转换启动且此位在启动后被清零
         */
       s3c_ts_regs->adccon |= (1<<0);
}
 
/* 3.1.1编写触摸屏中断处理函数 */
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
#if TOUCH_SCREEN_INTERRUPT_TEST
       /* 3.1.2通过检测笔尖的落下和抬起状态来进行操作 */
       /* bit[15]:       笔尖抬起态 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              printk("pen up\n");
              enter_wait_pen_down_mode();
       }
       else
       {
              /* 笔尖落下态 */
              printk("pen down\n");
              enter_wait_pen_up_mode();
#if ADC_INTERRUPT_TEST
              /* 3.1.3进入自动测量模式,启动adc,将电压值转换为坐标值 */
              enter_measure_xy_mode();
              start_adc();
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
/* 4.1.1需要编写adc中断处理函数 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST
       /* 4.1.2在按下时获取坐标值,并进入等待松开状态 */
       printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
              ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
       enter_wait_pen_up_mode();
#endif
       return IRQ_HANDLED;
}
 
static struct clk * adc_clk;
/*入口函数*/
static int s3c_touchscreen_init(void)
{
       int ret;
      
 
       /* 1.1需要注册input device */
       /* 1.1.1需要申请struct input_dev结构体 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要设置能产生哪类事件,产生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要注册struct input_dev结构体 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和触摸屏接口寄存器 */
       /* 2.1获取并使能adc时钟 */
       adc_clk = clk_get(NULL,"adc");
       if (!adc_clk) {
              printk("failed to get adc clock source\n");
              return -ENOENT;
       }
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD转换器的预分频使能和预分频值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先设为0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开 */
       /* 3.1需要注册触摸屏中断处理函数 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request touchscreen irq failed error code %d\n",ret);
       }
      
       /* 3.2进入等待触摸笔按下模式 */
       enter_wait_pen_down_mode();
      
       /* 4需要启动ADC转换,将电阻值转换为数字值 */
       /* 4.1需要注册adc中断处理函数 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request adc irq failed error code %d\n",ret);
       }
#if OPTIMIZED_METHOD1
       /* 优化错施1:
        * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif
       return 0;
}
/*出口函数*/
static void s3c_touchscreen_exit(void)
{
       /*
       * 5.1释放申请的中断资源
       * 5.2取消寄存器的地址映射
       * 5.3是否申请的时钟资源
       * 5.4卸载input设备
       * 5.5是否input设备资源
       */
       free_irq(IRQ_ADC,NULL);
       free_irq(IRQ_TC,NULL);
       iounmap(s3c_ts_regs);
       if (adc_clk) {
              clk_disable(adc_clk);
              clk_put(adc_clk);
              adc_clk = NULL;
       }
 
       /* 1.2需要卸载input device */
       input_unregister_device(s3c_ts_inputdev);
       input_free_device(s3c_ts_inputdev);
}
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

 

驱动代码优化措施1

基于《驱动代码未优化》开启OPTIMIZED_METHOD1后可以用来做优化措施1实验。实验结果见《执行结果-优化措施1》章节。

#if OPTIMIZED_METHOD1
       /* 优化错施1:
        * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif

 

驱动代码优化措施2

/* 优化措施2如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果,下面为优化措施2的代码 */
 
/* 基于touchscreen.c编写,重新命名为touchscreen_optmized.c,主要用来对优化措施2进行实验 */
 
/* 本文件是依照touchscreen 驱动<详细步骤>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define TOUCH_SCREEN_INTERRUPT_TEST             1
#define ADC_INTERRUPT_TEST                        1
 
#define OPTIMIZED_METHOD1                       1
#define OPTIMIZED_METHOD2                       1    
#define OPTIMIZED_METHOD3                       1
#define OPTIMIZED_METHOD4                       1
#define OPTIMIZED_METHOD5                       1
 
/* 2.2构造adc和触摸屏相关寄存器结构体 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
 
/* 进入等待触摸笔按下模式 */
static void enter_wait_pen_down_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]0:     检测笔尖落下中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0xd3;
}
 
/* 进入等待触摸笔松开模式 */
static void enter_wait_pen_up_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]1:     检测笔尖抬起中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0x1d3;
}
 
/* 进入自动测量xy坐标模式 */
static void enter_measure_xy_mode(void)
{
       /* 6 = (1<<3)|(1<<2)
         * bit[8]0:     检测笔尖抬起落下信号
         * bit[7]0:     YM输出驱动器禁止
         * bit[6]0:     YP输出驱动器使能
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]0:     XP输出驱动器使能
         * bit[3]1:     XP上拉禁止
         * bit[2]1:     自动顺序X方向和Y方向测量
         * bit[0-1]00:       无操作模式
         */
       s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
 
/* 启动adc */
static void start_adc(void)
{
       /*
         * bit[0]:A/D转换启动且此位在启动后被清零
         */
       s3c_ts_regs->adccon |= (1<<0);
}
 
/* 3.1.1编写触摸屏中断处理函数 */
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
#if TOUCH_SCREEN_INTERRUPT_TEST
       /* 3.1.2通过检测笔尖的落下和抬起状态来进行操作 */
       /* bit[15]:       笔尖抬起态 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              printk("pen up\n");
              enter_wait_pen_down_mode();
       }
       else
       {
              /* 笔尖落下态 */
              printk("pen down\n");
              enter_wait_pen_up_mode();
#if ADC_INTERRUPT_TEST
              /* 3.1.3进入自动测量模式,启动adc,将电压值转换为坐标值 */
              enter_measure_xy_mode();
              start_adc();
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
/* 4.1.1需要编写adc中断处理函数 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST && OPTIMIZED_METHOD2
       /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              /* 已经松开 */
              cnt = 0;
              enter_wait_pen_down_mode();
       }
       else
       {
              /* 4.1.2在按下时获取坐标值,并进入等待松开状态 */
              printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                     ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
              enter_wait_pen_up_mode();
       }
#endif
       return IRQ_HANDLED;
}
 
static struct clk * adc_clk;
/*入口函数*/
static int s3c_touchscreen_init(void)
{
       int ret;
      
 
       /* 1.1需要注册input device */
       /* 1.1.1需要申请struct input_dev结构体 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要设置能产生哪类事件,产生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要注册struct input_dev结构体 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和触摸屏接口寄存器 */
       /* 2.1获取并使能adc时钟 */
       adc_clk = clk_get(NULL,"adc");
       if (!adc_clk) {
              printk("failed to get adc clock source\n");
              return -ENOENT;
       }
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD转换器的预分频使能和预分频值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先设为0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开 */
       /* 3.1需要注册触摸屏中断处理函数 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request touchscreen irq failed error code %d\n",ret);
       }
      
       /* 3.2进入等待触摸笔按下模式 */
       enter_wait_pen_down_mode();
      
       /* 4需要启动ADC转换,将电阻值转换为数字值 */
       /* 4.1需要注册adc中断处理函数 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request adc irq failed error code %d\n",ret);
       }
#if OPTIMIZED_METHOD1
       /* 优化错施1:
        * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif
       return 0;
}
/*出口函数*/
static void s3c_touchscreen_exit(void)
{
       /*
       * 5.1释放申请的中断资源
       * 5.2取消寄存器的地址映射
       * 5.3是否申请的时钟资源
       * 5.4卸载input设备
       * 5.5是否input设备资源
       */
       free_irq(IRQ_ADC,NULL);
       free_irq(IRQ_TC,NULL);
       iounmap(s3c_ts_regs);
       if (adc_clk) {
              clk_disable(adc_clk);
              clk_put(adc_clk);
              adc_clk = NULL;
       }
 
       /* 1.2需要卸载input device */
       input_unregister_device(s3c_ts_inputdev);
       input_free_device(s3c_ts_inputdev);
}
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

驱动代码优化措施3

/* 优化措施3: 多次测量求平均值,基于《驱动代码优化措施2》并更新adc_irq函数,打开OPTIMIZED_METHOD3开关 */
/* 4.1.1需要编写adc中断处理函数 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       static int x[4],y[4];
       unsigned long x_meanvalue,y_meanvalue;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST && OPTIMIZED_METHOD2
       /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              /* 已经松开 */
              cnt = 0;
              enter_wait_pen_down_mode();
       }
       else
       {
#if OPTIMIZED_METHOD3
              /* 优化措施3: 多次测量求平均值 */
              x[cnt] = adcdat0 & 0x3ff;
              y[cnt] = adcdat1 & 0x3ff;
              /* 4.1.2在按下时获取坐标值,并进入等待松开状态 */
              printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                     cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
                    
              ++cnt;
              if (cnt == 4)
              {
                     x_meanvalue = (x[0] + x[1] + x[2] + x[3])/cnt;
                     y_meanvalue = (y[0] + y[1] + y[2] + y[3])/cnt;
                     printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                            cnt, x_meanvalue,y_meanvalue);
                     cnt = 0;                
                     enter_wait_pen_up_mode();
              }
              else
              {
                     enter_measure_xy_mode();
                     start_adc();
              }
#endif
       }
#endif
       return IRQ_HANDLED;
}

驱动代码优化措施4

/* 优化措施4: 软件过滤,基于《驱动代码优化措施3》并打开OPTIMIZED_METHOD4开关以及1、2、3的开关 */
static int s3c_filter_ts(int x[],int y[])
{
#if OPTIMIZED_METHOD4
/* 优化措施4: 软件过滤,核心思想是判断当前的值
  * 对于均值是否超出了一个偏差,如果超出了就抛弃
  */
#define ERR_LIMIT 10
 
       int avr_x,avr_y;
       int delt_x,delt_y;
 
       avr_x = x[0] + x[1];
       avr_y = y[0] + y[1];
 
       delt_x = (x[2] - avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
       delt_y = (y[2] - avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
 
       if ((delt_x > ERR_LIMIT) || (delt_y > ERR_LIMIT))
       {
              return 0;
       }
      
       avr_x = x[0] + x[1] + x[2];
       avr_y = y[0] + y[1] + y[2];
 
       delt_x = (x[3] - avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
       delt_y = (y[3] - avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
 
       if ((delt_x > ERR_LIMIT) || (delt_y > ERR_LIMIT))
       {
              return 0;
       }
#endif
       return 1;
}
 
/* 4.1.1需要编写adc中断处理函数 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       static int x[4],y[4];
       unsigned long x_meanvalue,y_meanvalue;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST && OPTIMIZED_METHOD2
       /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              /* 已经松开 */
              cnt = 0;
              enter_wait_pen_down_mode();
       }
       else
       {
#if OPTIMIZED_METHOD3
              /* 优化措施3: 多次测量求平均值 */
              x[cnt] = adcdat0 & 0x3ff;
              y[cnt] = adcdat1 & 0x3ff;
              /* 4.1.2在按下时获取坐标值,并进入等待松开状态 */
              printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                     cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
                    
              ++cnt;
              if (cnt == 4)
              {
                     /* 优化措施4: 软件过滤 */
                     if (s3c_filter_ts(x,y))
                     {
                            x_meanvalue = (x[0] + x[1] + x[2] + x[3])/cnt;
                            y_meanvalue = (y[0] + y[1] + y[2] + y[3])/cnt;
                            printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                                   cnt, x_meanvalue,y_meanvalue);
                     }
                     cnt = 0;                
                     enter_wait_pen_up_mode();
              }
              else
              {
                     enter_measure_xy_mode();
                     start_adc();
              }
#endif
       }
#endif
       return IRQ_HANDLED;
}

驱动代码优化措施5

/* 优化措施5: 使用定时器处理长按,滑动的情况,基于《驱动代码优化措施4》并更新软件定时器部分代码,整理好的整体代码如下 */
 
/* 基于touchscreen.c编写,重新命名为touchscreen_optmized.c,主要用来对优化措施2-5进行实验 */
 
/* 本文件是依照touchscreen 驱动<详细步骤>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define TOUCH_SCREEN_INTERRUPT_TEST             1
#define ADC_INTERRUPT_TEST                        1
 
#define OPTIMIZED_METHOD1                       1
#define OPTIMIZED_METHOD2                       1    
#define OPTIMIZED_METHOD3                       1
#define OPTIMIZED_METHOD4                       1
#define OPTIMIZED_METHOD5                       1
 
/* 2.2构造adc和触摸屏相关寄存器结构体 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
static struct clk * adc_clk;
 
#if OPTIMIZED_METHOD5
static struct timer_list ts_timer;
#endif
 
/* 进入等待触摸笔按下模式 */
static void enter_wait_pen_down_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]0:     检测笔尖落下中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0xd3;
}
 
/* 进入等待触摸笔松开模式 */
static void enter_wait_pen_up_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]1:     检测笔尖抬起中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
        * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0x1d3;
}
 
/* 进入自动测量xy坐标模式 */
static void enter_measure_xy_mode(void)
{
       /* 6 = (1<<3)|(1<<2)
         * bit[8]0:     检测笔尖抬起落下信号
         * bit[7]0:     YM输出驱动器禁止
         * bit[6]0:     YP输出驱动器使能
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]0:     XP输出驱动器使能
         * bit[3]1:     XP上拉禁止
         * bit[2]1:     自动顺序X方向和Y方向测量
         * bit[0-1]00:       无操作模式
         */
       s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
 
/* 启动adc */
static void start_adc(void)
{
       /*
         * bit[0]:A/D转换启动且此位在启动后被清零
         */
       s3c_ts_regs->adccon |= (1<<0);
}
 
/* 3.1.1编写触摸屏中断处理函数 */
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
#if TOUCH_SCREEN_INTERRUPT_TEST
       /* 3.1.2通过检测笔尖的落下和抬起状态来进行操作 */
       /* bit[15]:       笔尖抬起态 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              printk("pen up\n");
              enter_wait_pen_down_mode();
       }
       else
       {
              /* 笔尖落下态 */
              printk("pen down\n");
              enter_wait_pen_up_mode();
#if ADC_INTERRUPT_TEST
              /* 3.1.3进入自动测量模式,启动adc,将电压值
                * 转换为坐标值
                */
              enter_measure_xy_mode();
              start_adc();
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
static int s3c_filter_ts(int x[],int y[])
{
#if OPTIMIZED_METHOD4
/* 优化措施4: 软件过滤,核心思想是判断当前的值
  * 对于均值是否超出了一个偏差,如果超出了就抛弃
  */
#define ERR_LIMIT 10
 
       int avr_x,avr_y;
       int delt_x,delt_y;
 
       avr_x = x[0] + x[1];
       avr_y = y[0] + y[1];
 
       delt_x = (x[2] - avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
       delt_y = (y[2] - avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
 
       if ((delt_x > ERR_LIMIT) || (delt_y > ERR_LIMIT))
       {
              return 0;
       }
      
       avr_x = x[0] + x[1] + x[2];
       avr_y = y[0] + y[1] + y[2];
 
       delt_x = (x[3] - avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
       delt_y = (y[3] - avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
 
       if ((delt_x > ERR_LIMIT) || (delt_y > ERR_LIMIT))
       {
              return 0;
       }
#endif
       return 1;
}
 
/* 4.1.1需要编写adc中断处理函数 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       static int x[4],y[4];
       unsigned long x_meanvalue,y_meanvalue;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST && OPTIMIZED_METHOD2
       /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              /* 已经松开 */
              cnt = 0;
              enter_wait_pen_down_mode();
       }
       else
       {
#if OPTIMIZED_METHOD3
              /* 优化措施3: 多次测量求平均值 */
              x[cnt] = adcdat0 & 0x3ff;
              y[cnt] = adcdat1 & 0x3ff;
              /* 4.1.2在按下时获取坐标值,并进入等待松开状态 */
              printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                     cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
                    
              ++cnt;
              if (cnt == 4)
              {
                     /* 优化措施4: 软件过滤 */
                     if (s3c_filter_ts(x,y))
                     {
                            x_meanvalue = (x[0] + x[1] + x[2] + x[3])/cnt;
                            y_meanvalue = (y[0] + y[1] + y[2] + y[3])/cnt;
                            printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                                   cnt, x_meanvalue,y_meanvalue);
                     }
                     cnt = 0;
                     enter_wait_pen_up_mode();
#if OPTIMIZED_METHOD5
                     mod_timer(&ts_timer,jiffies + HZ/100);
#endif
              }
              else
              {
                     enter_measure_xy_mode();
                     start_adc();
              }
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
#if OPTIMIZED_METHOD5
void s3c_ts_timer_func(unsigned long data)
{
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              enter_wait_pen_down_mode();
       }
       else
       {
              enter_measure_xy_mode();
              start_adc();
       }
}
#endif
 
/*入口函数*/
static int s3c_touchscreen_init(void)
{
       int ret;
      
 
       /* 1.1需要注册input device */
       /* 1.1.1需要申请struct input_dev结构体 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要设置能产生哪类事件,产生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要注册struct input_dev结构体 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和触摸屏接口寄存器 */
       /* 2.1获取并使能adc时钟 */
       adc_clk = clk_get(NULL,"adc");
       if (!adc_clk) {
              printk("failed to get adc clock source\n");
              return -ENOENT;
       }
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD转换器的预分频使能和预分频值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先设为0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开 */
       /* 3.1需要注册触摸屏中断处理函数 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request touchscreen irq failed error code %d\n",ret);
       }
      
       /* 4需要启动ADC转换,将电阻值转换为数字值 */
       /* 4.1需要注册adc中断处理函数 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request adc irq failed error code %d\n",ret);
       }
#if OPTIMIZED_METHOD1
       /* 优化错施1:
        * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif
 
       /* 优化措施5: 使用定时器处理长按,滑动的情况*/
#if OPTIMIZED_METHOD5
       init_timer(&ts_timer);
       ts_timer.function = s3c_ts_timer_func;
       add_timer(&ts_timer);
#endif
       /* 3.2进入等待触摸笔按下模式 */
       enter_wait_pen_down_mode();
       return 0;
}
/*出口函数*/
static void s3c_touchscreen_exit(void)
{
       /*
       * 5.1释放申请的中断资源
       * 5.2取消寄存器的地址映射
       * 5.3是否申请的时钟资源
       * 5.4卸载input设备
       * 5.5是否input设备资源
       */
       free_irq(IRQ_ADC,NULL);
       free_irq(IRQ_TC,NULL);
       iounmap(s3c_ts_regs);
       if (adc_clk) {
              clk_disable(adc_clk);
              clk_put(adc_clk);
              adc_clk = NULL;
       }
 
       /* 1.2需要卸载input device */
       input_unregister_device(s3c_ts_inputdev);
       input_free_device(s3c_ts_inputdev);
}
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

驱动代码最终版

在上述驱动代码的基础上,分别在按下和松开的地方加上上报事件的操作。

/* 基于touchscreen_optmized.c编写,重新命名为touchscreen_final.c,主要用来最终版进行实验 */
 
/* 本文件是依照touchscreen 驱动<详细步骤>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define TOUCH_SCREEN_INTERRUPT_TEST             1
#define ADC_INTERRUPT_TEST                        1
 
#define OPTIMIZED_METHOD1                       1
#define OPTIMIZED_METHOD2                       1    
#define OPTIMIZED_METHOD3                       1
#define OPTIMIZED_METHOD4                       1
#define OPTIMIZED_METHOD5                       1
#define FINAL                                                1
 
/* 2.2构造adc和触摸屏相关寄存器结构体 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
static struct clk * adc_clk;
 
#if OPTIMIZED_METHOD5
static struct timer_list ts_timer;
#endif
 
/* 进入等待触摸笔按下模式 */
static void enter_wait_pen_down_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]0:     检测笔尖落下中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0xd3;
}
 
/* 进入等待触摸笔松开模式 */
static void enter_wait_pen_up_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]1:     检测笔尖抬起中断信号
         * bit[7]1:     YM输出驱动器使能
         * bit[6]1:     YP输出驱动器禁止
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]1:     XP输出驱动器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc转换
         * bit[0-1]11:       进入等待中断模式
         */
       s3c_ts_regs->adctsc = 0x1d3;
}
 
/* 进入自动测量xy坐标模式 */
static void enter_measure_xy_mode(void)
{
       /* 6 = (1<<3)|(1<<2)
         * bit[8]0:     检测笔尖抬起落下信号
         * bit[7]0:     YM输出驱动器禁止
         * bit[6]0:     YP输出驱动器使能
         * bit[5]0:     XM输出驱动器禁止
         * bit[4]0:     XP输出驱动器使能
         * bit[3]1:     XP上拉禁止
         * bit[2]1:     自动顺序X方向和Y方向测量
         * bit[0-1]00:       无操作模式
         */
       s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
 
/* 启动adc */
static void start_adc(void)
{
       /*
         * bit[0]:A/D转换启动且此位在启动后被清零
         */
       s3c_ts_regs->adccon |= (1<<0);
}
 
/* 3.1.1编写触摸屏中断处理函数 */
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
#if TOUCH_SCREEN_INTERRUPT_TEST
       /* 3.1.2通过检测笔尖的落下和抬起状态来进行操作 */
       /* bit[15]:       笔尖抬起态 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              //printk("pen up\n");
#if FINAL
              input_report_abs(s3c_ts_inputdev,ABS_PRESSURE,0);
              input_report_key(s3c_ts_inputdev,BTN_TOUCH,0);
              input_sync(s3c_ts_inputdev);
              enter_wait_pen_down_mode();
#endif
       }
       else
       {
              /* 笔尖落下态 */
              //printk("pen down\n");
              enter_wait_pen_up_mode();
#if ADC_INTERRUPT_TEST
              /* 3.1.3进入自动测量模式,启动adc,将电压值
                * 转换为坐标值
                */
              enter_measure_xy_mode();
              start_adc();
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
static int s3c_filter_ts(int x[],int y[])
{
#if OPTIMIZED_METHOD4
/* 优化措施4: 软件过滤,核心思想是判断当前的值
  * 对于均值是否超出了一个偏差,如果超出了就抛弃
  */
#define ERR_LIMIT 10
 
       int avr_x,avr_y;
       int delt_x,delt_y;
 
       avr_x = x[0] + x[1];
       avr_y = y[0] + y[1];
 
       delt_x = (x[2] - avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
       delt_y = (y[2] - avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
 
       if ((delt_x > ERR_LIMIT) || (delt_y > ERR_LIMIT))
       {
              return 0;
       }
      
       avr_x = x[0] + x[1] + x[2];
       avr_y = y[0] + y[1] + y[2];
 
       delt_x = (x[3] - avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
       delt_y = (y[3] - avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
 
       if ((delt_x > ERR_LIMIT) || (delt_y > ERR_LIMIT))
       {
              return 0;
       }
#endif
       return 1;
}
 
/* 4.1.1需要编写adc中断处理函数 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       static int x[4],y[4];
       unsigned long x_meanvalue,y_meanvalue;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST && OPTIMIZED_METHOD2
       /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              /* 已经松开 */           
              cnt = 0;
#if FINAL
              input_report_abs(s3c_ts_inputdev,ABS_PRESSURE,0);
              input_report_key(s3c_ts_inputdev,BTN_TOUCH,0);
              input_sync(s3c_ts_inputdev);
#endif
              enter_wait_pen_down_mode();
       }
       else
       {
#if OPTIMIZED_METHOD3
              /* 优化措施3: 多次测量求平均值 */
              x[cnt] = adcdat0 & 0x3ff;
              y[cnt] = adcdat1 & 0x3ff;
              /* 4.1.2在按下时获取坐标值,并进入等待松开状态 */
              //printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                     //cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
                    
              ++cnt;
              if (cnt == 4)
              {
                     /* 优化措施4: 软件过滤 */
                     if (s3c_filter_ts(x,y))
                     {
                            x_meanvalue = (x[0] + x[1] + x[2] + x[3])/cnt;
                            y_meanvalue = (y[0] + y[1] + y[2] + y[3])/cnt;
                            //printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
                                   //cnt, x_meanvalue,y_meanvalue);
#if FINAL
                            input_report_abs(s3c_ts_inputdev,ABS_X,x_meanvalue);
                            input_report_abs(s3c_ts_inputdev,ABS_Y,y_meanvalue);
                            input_report_abs(s3c_ts_inputdev,ABS_PRESSURE,1);
                            input_report_key(s3c_ts_inputdev,BTN_TOUCH,1);
                            input_sync(s3c_ts_inputdev);      
#endif
                     }
                     cnt = 0;
                     enter_wait_pen_up_mode();
#if OPTIMIZED_METHOD5
                     mod_timer(&ts_timer,jiffies + HZ/100);
#endif
              }
              else
              {
                     enter_measure_xy_mode();
                     start_adc();
              }
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
#if OPTIMIZED_METHOD5
void s3c_ts_timer_func(unsigned long data)
{
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
#if FINAL
              input_report_abs(s3c_ts_inputdev,ABS_PRESSURE,0);
              input_report_key(s3c_ts_inputdev,BTN_TOUCH,0);
              input_sync(s3c_ts_inputdev);
#endif
              enter_wait_pen_down_mode();
       }
       else
       {
              enter_measure_xy_mode();
              start_adc();
       }
}
#endif
 
/*入口函数*/
static int s3c_touchscreen_init(void)
{
       int ret;
      
 
       /* 1.1需要注册input device */
       /* 1.1.1需要申请struct input_dev结构体 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要设置能产生哪类事件,产生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要注册struct input_dev结构体 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和触摸屏接口寄存器 */
       /* 2.1获取并使能adc时钟 */
       adc_clk = clk_get(NULL,"adc");
       if (!adc_clk) {
              printk("failed to get adc clock source\n");
              return -ENOENT;
       }
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD转换器的预分频使能和预分频值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先设为0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能够检测触摸屏中断,以及检测触摸屏按下还是松开 */
       /* 3.1需要注册触摸屏中断处理函数 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request touchscreen irq failed error code %d\n",ret);
       }
      
       /* 4需要启动ADC转换,将电阻值转换为数字值 */
       /* 4.1需要注册adc中断处理函数 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request adc irq failed error code %d\n",ret);
       }
#if OPTIMIZED_METHOD1
       /* 优化错施1:
        * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif
 
       /* 优化措施5: 使用定时器处理长按,滑动的情况*/
#if OPTIMIZED_METHOD5
       init_timer(&ts_timer);
       ts_timer.function = s3c_ts_timer_func;
       add_timer(&ts_timer);
#endif
       /* 3.2进入等待触摸笔按下模式 */
       enter_wait_pen_down_mode();
       return 0;
}
/*出口函数*/
static void s3c_touchscreen_exit(void)
{
       /*
       * 5.1释放申请的中断资源
       * 5.2取消寄存器的地址映射
       * 5.3是否申请的时钟资源
       * 5.4卸载input设备
       * 5.5是否input设备资源
       */
       free_irq(IRQ_ADC,NULL);
       free_irq(IRQ_TC,NULL);
       iounmap(s3c_ts_regs);
       if (adc_clk) {
              clk_disable(adc_clk);
              clk_put(adc_clk);
              adc_clk = NULL;
       }
 
       /* 1.2需要卸载input device */
       input_unregister_device(s3c_ts_inputdev);
       input_free_device(s3c_ts_inputdev);
}
 
/* 0编写代码框架,头文件,出入口函数,声明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

 

测试代码

本示例无需测试代码,可以通过按下或者松开触摸屏测试。

 

Makefile

KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
all:
       make -C $(KERN_DIR) M=`pwd` modules
clean:
       make -C $(KERN_DIR) M=`pwd` modules clean
       rm -rf modules.order
obj-m += touchscreen.o

如果名字不同,请更换obj-m后面.o的名字。比如中年润这里使用touchscreen_optimized.o,touchscreen_final.o等。

 

目录结构

代码编写完成的目录结构如下所示。直接执行make即可生成.ko文件。

.
├── Makefile
├── touchscreen.c
├── touchscreen_final.c
├── touchscreen_optmized.c
└── touchscreen_skeleton.c

 

测试步骤

0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(或者arm-angstrom-linux-gnueabi-)。

1在menuconfig中配置好内核源码的目标系统为s3c2440。

2 移植好的linux默认是有s3c2440 adc模块的,如果我们的驱动也加载进去,会调用原有的驱动。因此需要在menuconfig中去掉原先的adc驱动。

make menuconfig

Location:                                                                                                                                                    

Device driver->Character devices->ADC driver for S3C2440 development boards

Device driver->Character devices-> TX2440 ADC Driver

3 make uImage 生成uImage,并使用新的内核启动。

4 在pc上将驱动程序编译生成.ko,命令:make。

5 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件。

mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

6 加载ko。

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input2

7按下或者松开触摸屏,观察现象

以上是基本的测试流程,如有要测试优化措施,则需要开启对应的优化措施开关。开启方法就是把相应的宏置1。具体优化措施请参考《驱动代码-驱动代码优化措施n》等章节。

 

执行结果

以下执行结果是按照驱动代码的试验和优化步骤完成,是一一对应的。

 

触摸屏中断触发测试结果

基于《驱动代码未优化》只打开TOUCH_SCREEN_INTERRUPT_TEST宏,其他宏置0。测试结果如下:

 

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input0

request_irq failed adc

[root@TX2440A 11touchscreen]# pen down

pen up

pen down

pen down

pen up

pen down

pen up

pen down

pen up

pen down

pen down

pen up

 

触摸屏中断和adc中断触发测试结果

基于《驱动代码未优化》同时打开TOUCH_SCREEN_INTERRUPT_TEST宏和ADC_INTERRUPT_TEST宏,测试结果如下:

[root@TX2440A 11touchscreen]# pen down

adc_irq cnt = 1, x = 160, y = 735

pen up

pen down

adc_irq cnt = 2, x = 89, y = 748

pen up

pen down

adc_irq cnt = 3, x = 139, y = 733

pen up

pen down

adc_irq cnt = 4, x = 70, y = 825

pen up

pen up

pen down

 

存在的问题是,有很大概率出现按下时产生两次中断,松开时产生两次中断。为了解决这个问题提出了几种优化措施。

 

驱动代码优化措施1结果

/* 优化错施1: 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断 */

开启相应开关后,测试结果如下,要比刚开始要好一点,少了很多重复中断。

#define TOUCH_SCREEN_INTERRUPT_TEST             1

#define ADC_INTERRUPT_TEST                        1

#define OPTIMIZED_METHOD1                       1

 

pen down

adc_irq cnt = 77, x = 152, y = 498

pen up

pen down

adc_irq cnt = 78, x = 246, y = 837

pen up

pen down

adc_irq cnt = 79, x = 213, y = 710

pen down

adc_irq cnt = 80, x = 215, y = 709

依然存在的问题是,还是有重复触发中断的问题。

 

驱动代码优化措施2结果

/* 优化措施2如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */

adc_irq cnt = 449, x = 696, y = 533

pen up

pen down

adc_irq cnt = 450, x = 697, y = 534

pen up

pen down

adc_irq cnt = 451, x = 695, y = 510

pen up

pen down

adc_irq cnt = 452, x = 696, y = 530

pen up

pen down

adc_irq cnt = 453, x = 697, y = 544

存在的问题是,针对同一点所测试的坐标值略有不同。

 

驱动代码优化措施3结果

/* 优化措施3: 多次测量求平均值,需要在《驱动代码优化措施2》的基础上更新adc_irq函数,并打开

可以看到,这次的结果比之前的结果又好一点,最起码坐标值变的比较稳定了

pen down

adc_irq cnt = 0, x = 384, y = 715

adc_irq cnt = 1, x = 383, y = 696

adc_irq cnt = 2, x = 387, y = 700

adc_irq cnt = 3, x = 465, y = 770

adc_irq cnt = 4, x = 404, y = 720

pen up

pen down

adc_irq cnt = 0, x = 384, y = 716

adc_irq cnt = 1, x = 386, y = 683

adc_irq cnt = 2, x = 387, y = 753

adc_irq cnt = 3, x = 384, y = 782

adc_irq cnt = 4, x = 385, y = 733

但是存在的问题是坐标值抖动比较大,387和465的差值有78,所以引出优化措施4,软件消抖。

 

驱动代码优化措施4结果

/* 优化措施4: 软件过滤,核心思想是判断当前的值对于均值是否超出了一个偏差,如果超出了就抛弃 */

pen down

adc_irq cnt = 0, x = 321, y = 414

adc_irq cnt = 1, x = 317, y = 428

adc_irq cnt = 2, x = 321, y = 428

adc_irq cnt = 3, x = 321, y = 427

adc_irq cnt = 4, x = 320, y = 424

pen up

pen down

adc_irq cnt = 0, x = 349, y = 384

adc_irq cnt = 1, x = 349, y = 359

adc_irq cnt = 2, x = 349, y = 382

adc_irq cnt = 3, x = 348, y = 383

adc_irq cnt = 4, x = 348, y = 377

pen up

可以看到坐标值偏差基本不大了,但是当我们滑动的时候还无法实时检测滑动的坐标值,由此引出优化措施5。

 

驱动代码优化措施5结果

/* 优化措施5: 使用定时器处理长按,滑动的情况 */

如下log为长按触摸屏的时候输出的log,可以看到随着按下的时间增加会不停的输出坐标值。

pen down

adc_irq cnt = 0, x = 614, y = 276

adc_irq cnt = 1, x = 605, y = 206

adc_irq cnt = 2, x = 612, y = 276

adc_irq cnt = 3, x = 609, y = 287

adc_irq cnt = 4, x = 610, y = 261

adc_irq cnt = 0, x = 596, y = 224

adc_irq cnt = 1, x = 592, y = 245

adc_irq cnt = 2, x = 589, y = 297

adc_irq cnt = 3, x = 586, y = 230

adc_irq cnt = 4, x = 590, y = 249

adc_irq cnt = 0, x = 588, y = 291

adc_irq cnt = 1, x = 586, y = 236

adc_irq cnt = 2, x = 587, y = 224

adc_irq cnt = 3, x = 590, y = 283

adc_irq cnt = 4, x = 587, y = 258

adc_irq cnt = 0, x = 590, y = 284

adc_irq cnt = 1, x = 583, y = 253

adc_irq cnt = 2, x = 588, y = 292

 

如下log为滑动触摸屏后的log,可以看到随着滑动坐标值在不停的变化

pen down

adc_irq cnt = 0, x = 544, y = 417

adc_irq cnt = 1, x = 539, y = 457

adc_irq cnt = 2, x = 536, y = 375

adc_irq cnt = 3, x = 541, y = 415

adc_irq cnt = 4, x = 540, y = 416

adc_irq cnt = 0, x = 539, y = 396

adc_irq cnt = 1, x = 539, y = 364

adc_irq cnt = 2, x = 540, y = 480

adc_irq cnt = 3, x = 536, y = 363

adc_irq cnt = 4, x = 538, y = 400

adc_irq cnt = 0, x = 550, y = 541

adc_irq cnt = 1, x = 519, y = 734

adc_irq cnt = 2, x = 463, y = 684

adc_irq cnt = 3, x = 519, y = 604

adc_irq cnt = 4, x = 512, y = 640

adc_irq cnt = 0, x = 547, y = 362

adc_irq cnt = 1, x = 562, y = 725

adc_irq cnt = 2, x = 480, y = 765

adc_irq cnt = 3, x = 456, y = 760

adc_irq cnt = 4, x = 511, y = 653

 

驱动代码优化最终版结果

同《驱动代码优化措施5结果》,因为这个只是加了上报事件,对坐标的精度没有影响。

 

结果总结

在本篇文章中,中年润跟读者分享了触摸屏驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构,它们分别是:

 

分配一个input_dev结构体

struct input_dev *input_allocate_device(void)

释放input设备

void input_free_device(struct input_dev *dev)

能产生哪类事件

static inline void set_bit(int nr, unsigned long *addr)

能产生这类事件的哪些事件

static inline void set_bit(int nr, unsigned long *addr)

设置abs事件的参数

void input_set_abs_params(struct input_dev *dev, unsigned int axis,

                      int min, int max, int fuzz, int flat)

注册input设备

int input_register_device(struct input_dev *dev)

卸载input设备

void input_unregister_device(struct input_dev *dev)

每个普通事件的上报离不开同步事件

上报普通事件

void input_event(struct input_dev *dev,

             unsigned int type, unsigned int code, int value)

上报同步事件

static inline void input_sync(struct input_dev *dev)

上报abs事件

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)

上报key事件

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)

 

申请一个中断

static inline int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

          const char *name, void *dev)

释放一个中断

void free_irq(unsigned int, void *)

请读者尽力去了解这些函数的作用,入参,返回值。

 

问题汇总

安装触摸屏驱动报错-22

代码如下所示

ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED | IRQF_SAMPLE_RANDOM, "adc", NULL);

 

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input4

request adc irq failed error code -22

 

原因分析如下

manage.c 957行request_threaded_irq函数中

if ((irqflags & IRQF_SHARED) && !dev_id)

       return -EINVAL;

当所注册的irq中断中有IRQF_SHARED标志,并且dev_id为空则返回-EINVAL。

-EINVAL        /* Invalid argument */         无效的参数

 

安装触摸屏驱动报错-16

代码如下所示

ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);

 

报错log如下

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input5

request adc irq failed error code -16

 

原因分析如下

manage.c 732行__setup_irq函数中有ret = -EBUSY;           

-EBUSY  /* Device or resource busy */

 

调用关系如下

request_irq

request_threaded_irq

__setup_irq

              ret = -EBUSY;

这个问题是因为中年润所用的linux里已经选择了adc的驱动,注册了adc的中断(代码在s3c2440_adc dev_init函数中)(menuconfig开关在Device driver->Character devices->ADC driver for S3C2440 development boards)

 

实际应用-tslib移植和测试

下载tslib

git clone https://github.com/kergoth/tslib

https://sourceforge.net/projects/tslib.berlios/files/

都可以下载,看大家喜好了。

 

编译tslib

采用的是tslib-1.4.tar.gz

我们想用的触摸屏,就是想点击LCD屏上显示的内容,并对应起来。要用到库“tslib”,下面为编译的步骤:

tar xzf tslib-1.4.tar.gz

cd tslib

./autogen.sh

mkdir temp 这个临时目录以后放编译结果。

echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache

./configure --host=arm-angstrom-linux-gnueabi --cache-file=arm-linux.cache --prefix=$(pwd)/temp

make && make install

make install是把编译的结果安装到“临时目录”temp中,make编译生成的文件如下所示:

.
├── bin
│   ├── ts_calibrate
│   ├── ts_harvest
│   ├── ts_print
│   ├── ts_print_raw
│   └── ts_test
├── etc
│   └── ts.conf
├── include
│   └── tslib.h
└── lib
    ├── libts-0.0.so.0 -> libts-0.0.so.0.1.1
    ├── libts-0.0.so.0.1.1
    ├── libts.la
    ├── libts.so -> libts-0.0.so.0.1.1
    ├── pkgconfig
    │   └── tslib-0.0.pc
    └── ts
        ├── arctic2.la
        ├── arctic2.so
        ├── collie.la
        ├── collie.so
        ├── corgi.la
        ├── corgi.so
        ├── dejitter.la
        ├── dejitter.so
        ├── h3600.la
        ├── h3600.so
        ├── input.la
        ├── input.so
        ├── linear_h2200.la
        ├── linear_h2200.so
        ├── linear.la
        ├── linear.so
        ├── mk712.la
        ├── mk712.so
        ├── pthres.la
        ├── pthres.so
        ├── ucb1x00.la
        ├── ucb1x00.so
        ├── variance.la
        └── variance.so
 
6 directories, 36 files

 

 

拷贝文件

拷贝temp下的所有文件和目录到被挂载的nfs目录下

cd temp

cp * ~/nfs/440/201903-train/11touchscreen/ -rfd

再将所有东西拷贝到根目录下对应的目录中去。

[root@TX2440A 11touchscreen]# cp include/ / -rfd

[root@TX2440A 11touchscreen]# cp etc / -rfd

[root@TX2440A 11touchscreen]# cp bin / -rfd

[root@TX2440A 11touchscreen]# cp lib / -rfd

 

安装lcd驱动

[root@TX2440A 201903-train]# cd 10lcd/

[root@TX2440A 10lcd]# ls

Makefile        cfbfillrect.ko  lcd.c           lcd_skeleton.c

cfbcopyarea.ko  cfbimgblt.ko    lcd.ko

[root@TX2440A 10lcd]# insmod cfbfillrect.ko

[root@TX2440A 10lcd]# insmod cfbimgblt.ko

[root@TX2440A 10lcd]# insmod cfbcopyarea.ko

[root@TX2440A 10lcd]# insmod lcd.ko

Console: switching to colour frame buffer device 60x34

[root@TX2440A 10lcd]#

 

event0是触摸屏

[root@tx2440a 10lcd]# ls /dev/event0

/dev/event0

fb0是lcd屏

[root@TX2440A 10lcd]# ls /dev/fb0

/dev/fb0

[root@TX2440A 10lcd]#

 

修改配置文件

[root@TX2440A /]# vi etc/ts.conf

# Uncomment if you wish to use the linux input layer event interface

module_raw input

 

配置环境变量

[root@TX2440A /]# export TSLIB_TSDEVICE=/dev/event0 //指定触摸屏设备

[root@TX2440A /]# export TSLIB_CALIBFILE=/etc/pointercal //指定触摸屏校准文件 pintercal 的存放位置

[root@TX2440A /]# export TSLIB_CONFFILE=/etc/ts.conf //指定 TSLIB 配置文件的位置

[root@TX2440A /]# export TSLIB_PLUGINDIR=/lib/ts //指定触摸屏插件所在路径

[root@TX2440A /]# export TSLIB_CONSOLEDEVICE=none //设定控制台设备为 none ,否则默认为 /dev/tty

[root@TX2440A /]# export TSLIB_FBDEVICE=/dev/fb0 //指定帧缓冲设备

export TSLIB_TSDEVICE=/dev/event0

export TSLIB_CALIBFILE=/etc/pointercal

export TSLIB_CONFFILE=/etc/ts.conf

export TSLIB_PLUGINDIR=/lib/ts

export TSLIB_CONSOLEDEVICE=none

export TSLIB_FBDEVICE=/dev/fb0

 

校验坐标

ts_calibrate //是指校验,在SHELL中输入这个程序后,这时屏幕上会出现一些文字,左上角会有个“十字架”。点击十字架,后续还有4个,点击。串口输出的log如下所示:

[root@TX2440A 11touchscreen]# ts_calibrate

xres = 480, yres = 272

Took 11 samples...

Top left : X =  831 Y =  295

Took 1 samples...

Top right : X =  191 Y =  401

Took 1 samples...

Bot right : X =  123 Y =  773

Took 2 samples...

Bot left : X =  777 Y =  696

Took 1 samples...

Center : X =  495 Y =  574

580.221924 -0.599737 -0.091836

-141.668213 0.063053 0.451238

Calibration constants: 38025424 -39304 -6018 -9284368 4132 29572 65536

 

校验成功后会生成一个配置文件

[root@TX2440A bin]# ls /etc/pointercal

/etc/pointercal

[root@TX2440A bin]# cat /etc/pointercal

-39304 -6018 38025424 4132 29572 -9284368 65536

 

光标测试

ts_test //在SHELL上输入这个命令,就可以测试了。这时点一下鼠标或滑动一下,触摸屏上的光标就会跟着走。

991550578.260205:    368    106      1

991550578.280216:    361    103      1

991550578.300209:    354    100      1

991550578.320208:    348     97      1

991550578.340210:    342     94      1

991550578.360397:    339     91      1

991550578.380587:    336     90      1

991550578.389291:    334     90      0

991550578.407462:    340     92      1

991550578.425279:    344     92      1

991550578.445213:    349     92      1

991550578.465213:    353     94      1

991550578.485214:    361     97      1

 

其他测试

ts_print:打印坐标,log如下所示:

[root@TX2440A 11touchscreen]# ts_print

991551189.058950:    293    139      1

991551189.076173:    286    136      1

991551189.081869:    281    134      0

 

ts_print_raw:打印原始坐标,log如下所示

[root@TX2440A /bin]# ts_print_raw

991551236.083744:    582    526      1

991551236.101215:    602    484      1

991551236.121243:    595    476      1

991551236.141213:    594    476      1

 

ts_harvest:校验整个屏幕,log如下,此时屏幕上的光标会总左上角开始移动,一直点一直移动,知道右下角。

[root@TX2440A /bin]# ts_harvest

Took 1 samples...

Took 7 samples...

Took 14 samples...

Took 11 samples...

Took 9 samples...

Took 13 samples...

Took 13 samples...

 

实战目标

1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

3请读者根据编写思路,独立写出编写框架。

4请读者根据详细步骤,独立编写驱动代码和测试代码。

5请读者根据《Makefile》章节,独立编写Makefile。

6请读者根据《测试步骤》章节,独立进行测试。

7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

 

参考资料

《linux设备驱动开发祥解》

《TX2440开发手册及代码》

《韦东山嵌入式教程》

《鱼树驱动笔记》

《s3c2440a》芯片手册英文版和中文版

 

致谢

感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

 

为了省去驱动开发者搜集各种资料来写驱动,中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

 

联系方式

微信群:见文章最底部,因微信群有效期只有7天,感兴趣的同学可以加下。微信群里主要是为初学者答疑解惑,也可以进行技术和非技术的交流。如果微信群失效了,大家可以加qq群,我会在qq群里分享微信群的二维码。同时也欢迎和中年润志同道合的中年人尤其是中年码农的加入。

微信订阅号:自顶向下学嵌入式

公众号微信:EmbeddedAIOT

CSDN博客:chichi123137

CSDN博客网址:https://blog.csdn.net/chichi123137?utm_source=blog_pc_recommand

QQ邮箱:[email protected]

QQ群:766756075

更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者咨询。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎大家选购哦。

linux驱动篇-touchscreen-完整版_第1张图片

linux驱动篇-touchscreen-完整版_第2张图片

你可能感兴趣的:(linux驱动)