注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

嵌入式开发实战4——内核的编译之十二触摸屏的驱动  

2012-06-28 20:10:04|  分类: ARM开发 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
 

嵌入式开发实战4——内核的编译之十二触摸屏的驱动

 

触摸屏是嵌入式开发不可或缺的一种设备,他的驱动移植非常用用,今天就移植一个试试看,首先建立一个s3c2410_ts.c的驱动文件:

 

#include <linux/errno.h>

 

#include <linux/kernel.h>

 

#include <linux/module.h>

 

#include <linux/slab.h>

 

#include <linux/input.h>

 

#include <linux/init.h>

 

#include <linux/serio.h>

 

#include <linux/delay.h>

 

#include <linux/platform_device.h>

 

#include <linux/clk.h>

 

 

 

 

 

#include <linux/gpio.h>

 

#include <asm/io.h>

 

#include <asm/irq.h>

 

 

 

#include <plat/regs-adc.h>

 

#include <mach/regs-gpio.h>

 

 

 

/* For ts.dev.id.version */

 

#define S3C2410TSVERSION  0x0101

 

 

 

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

              S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN |\

              S3C2410_ADCTSC_XY_PST(3))

 

 

 

#define AUTOPST      (S3C2410_ADCTSC_YM_SEN  |  S3C2410_ADCTSC_YP_SEN  |  S3C2410_ADCTSC_XP_SEN |\

         S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))

 

 

 

static char *s3c2410ts_name = "s3c2410 TouchScreen";

 

 

 

static  struct input_dev *dev;

 

static long xp;

 

static long yp;

 

static int count;

 

 

 

extern struct semaphore ADC_LOCK;

 

static int OwnADC = 0;

 

 

 

 

 

static void __iomem *base_addr;

 

 

 

static inline void s3c2410_ts_connect(void)

 

{

 

 s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON);

 

 s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON);

 

 s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON);

 

 s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON);

 

}

 

 

 

 

 

 

 

static void touch_timer_fire(unsigned long data)

 

{

 

    unsigned long data0;

 

    unsigned long data1;

 

 int updown;

 

 

 

    data0 = ioread32(base_addr+S3C2410_ADCDAT0);

 

    data1 = ioread32(base_addr+S3C2410_ADCDAT1);

 

 

 

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

 

 

 

    if (updown) {

 

      if (count != 0) {

 

   long tmp;

 

                                                                                              

 

   tmp = xp;

 

   xp = yp;

 

   yp = tmp;

 

                                                                                             

 

                        xp >>= 2;

 

                        yp >>= 2;

 

 

 

    input_report_abs(dev, ABS_X, xp);

 

    input_report_abs(dev, ABS_Y, yp);

 

 

 

    input_report_key(dev, BTN_TOUCH, 1);

 

    input_report_abs(dev, ABS_PRESSURE, 1);

 

    input_sync(dev);

 

     }

 

 

 

     xp = 0;

 

      yp = 0;

 

      count = 0;

 

 

 

      iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);

 

   iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START,base_addr+S3C2410_ADCCON);

 

    } else {

 

      count = 0;

 

     input_report_key(dev, BTN_TOUCH, 0);

 

     input_report_abs(dev, ABS_PRESSURE, 0);

 

     input_sync(dev);

 

 

 

     iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);

 

  if (OwnADC) {

 

   OwnADC = 0;

 

   up(&ADC_LOCK);

 

  }

 

   }

 

}

 

 

 

static struct timer_list touch_timer =

 

  TIMER_INITIALIZER(touch_timer_fire, 0, 0);

 

 

 

static irqreturn_t stylus_updown(int irq, void *dev_id)

 

{

 

 unsigned long data0;

 

 unsigned long data1;

 

 int updown;

 

 

 

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

 

  OwnADC = 1;

 

  data0 = ioread32(base_addr+S3C2410_ADCDAT0);

 

  data1 = ioread32(base_addr+S3C2410_ADCDAT1);

 

 

 

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

 

S3C2410_ADCDAT0_UPDOWN));

 

 

 

  if (updown) {

 

   touch_timer_fire(0);

 

  } else {

 

   OwnADC = 0;

 

   up(&ADC_LOCK);

 

  }

 

 }

 

 

 

 return IRQ_HANDLED;

 

 

 

 

 

}

 

 

 

 

 

static irqreturn_t stylus_action(int irq, void *dev_id)

 

{

 

 unsigned long data0;

 

 unsigned long data1;

 

 

 

 if (OwnADC) {

 

  data0 = ioread32(base_addr+S3C2410_ADCDAT0);

 

  data1 = ioread32(base_addr+S3C2410_ADCDAT1);

 

 

 

  xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;

 

    yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;

 

  count++;

 

 

 

      if (count < (1<<2)) {

 

   iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE  |  AUTOPST,

 

base_addr+S3C2410_ADCTSC);

 

   iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START,

 

base_addr+S3C2410_ADCCON);

 

  } else {

 

   mod_timer(&touch_timer, jiffies+1);

 

   iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);

 

  }

 

 }

 

 

 

 return IRQ_HANDLED;

 

}

 

 

 

static struct clk  *adc_clock;

 

 

 

static int __init s3c2410ts_init(void)

 

{

 

  struct input_dev *input_dev;

 

 

 

  adc_clock = clk_get(NULL, "adc");

 

  if (!adc_clock) {

 

    printk(KERN_ERR "failed to get adc clock source\n");

 

 

 

 

 

  return -ENOENT;

 

 }

 

 clk_enable(adc_clock);

 

 

 

 base_addr=ioremap(S3C2410_PA_ADC,0x20);

 

  if (base_addr == NULL) {

 

    printk(KERN_ERR "Failed to remap register block\n");

 

  return -ENOMEM;

 

 }

 

 

 

  /* Configure GPIOs */

 

 s3c2410_ts_connect();

 

 

 

  iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\

 

         base_addr+S3C2410_ADCCON);

 

  iowrite32(0xffff,  base_addr+S3C2410_ADCDLY);

 

 iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);

 

 

 

  /* Initialise input stuff */

 

 input_dev = input_allocate_device();

 

 

 

  if (!input_dev) {

 

    printk(KERN_ERR "Unable to allocate the input device !!\n");

 

  return -ENOMEM;

 

 }

 

 

 

 dev = input_dev;

 

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

 

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

 

  input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);

 

  input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);

 

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

 

 

 

  dev->name = s3c2410ts_name;

 

  dev->id.bustype = BUS_RS232;

 

  dev->id.vendor = 0xDEAD;

 

  dev->id.product = 0xBEEF;

 

  dev->id.version = S3C2410TSVERSION;

 

 

 

 

 

 

 

 

 

  /* Get irqs */

 

  if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,

 

  "s3c2410_action", dev)) {

 

    printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n");

 

  iounmap(base_addr);

 

  return -EIO;

 

 }

 

  if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,

 

   "s3c2410_action", dev)) {

 

    printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");

 

  iounmap(base_addr);

 

  return -EIO;

 

 }

 

 

 

  printk(KERN_INFO "%s successfully  loaded\n", s3c2410ts_name);

 

 

 

  /* All went ok, so register to the input system */

 

 input_register_device(dev);

 

 

 

 return 0;

 

}

 

 

 

static void __exit s3c2410ts_exit(void)

 

{

 

 disable_irq(IRQ_ADC);

 

 disable_irq(IRQ_TC);

 

 free_irq(IRQ_TC,dev);

 

 free_irq(IRQ_ADC,dev);

 

 

 

 if (adc_clock) {

 

  clk_disable(adc_clock);

 

  clk_put(adc_clock);

 

  adc_clock = NULL;

 

 }

 

 

 

 input_unregister_device(dev);

 

 iounmap(base_addr);

 

}

module_init(s3c2410ts_init);

 

module_exit(s3c2410ts_exit);

这里面涉及很多和前面的AD共用转换器的部分以及相应的硬件的设置,有时间在后面详细的说明一下。

下来照例修改makefile和Kconfig两个文件,前者增加一行:

obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o

 

后者增加:

menuconfig INPUT_TOUCHSCREEN

         bool "Touchscreens"

         help

           Say Y here, and a list of supported touchscreens will be displayed.

           This option doesn't affect the kernel.

 

           If unsure, say Y.

 

if INPUT_TOUCHSCREEN

 

config TOUCHSCREEN_S3C2410

        tristate "Samsung S3C2410 touchscreen input driver"

        depends on MACH_MINI2440 && INPUT && INPUT_TOUCHSCREEN && MINI2440_ADC

        help

          Say Y here if you have the s3c2410 touchscreen.

          If unsure, say N. 

          To compile this driver as a module, choose M here: the

          module will be called s3c2410_ts.

 

config TOUCHSCREEN_ADS7846

         tristate "ADS7846/TSC2046 and ADS7843 based touchscreens"

         depends on SPI_MASTER

         depends on HWMON = n || HWMON

         help

           Say Y here if you have a touchscreen interface using the

           ADS7846/TSC2046 or ADS7843 controller, and your board-specific

           setup code includes that in its table of SPI devices.

 

           If HWMON is selected, and the driver is told the reference voltage

           on your board, you will also get hwmon interfaces for the voltage

           (and on ads7846/tsc2046, temperature) sensors of this chip.

 

           If unsure, say N (but it's safe to say "Y").

 

           To compile this driver as a module, choose M here: the

           module will be called ads7846.

 

然后又是:make menuconfig

Device Drivers  --->

    Input device support  --->  

[*]   Touchscreens  --->

在这里选中前面设置的触摸屏选项。

最后:make  zImage

同时转载一篇对触屏的说明的BLOG:

 

以下内容来自链接(里面包含作者,但原文出处我们无处得知,如果作者看到此文档,

请和我们联系,我们将会改正链接):

原文出处:

http://www.arm9home.net/read.php?tid-2406-keyword-%B4%A5%C3%FE%C6%C1%B3%CC%D0%F2.html 

 

mini2440 触摸屏驱动程序分析

By JeefJiang July ,8th,2009

这是mini2440驱动分析系列的第三篇文章,本文分为三个部分,第一部分讲叙硬件知识,包括触摸屏

的原理以及SCC2440 SOC 上的触摸屏是如何工作的。第二部分分析输入设备子系统的框架,并进行相应

的代码分析。第三部分利用上述的原理来分析mini2440的触摸屏驱动。第四部分介绍了测试和校准。

1. 需要准备的硬件知识

1.1 电阻式触摸屏工作原理原理 

触摸屏附着在显示器的表面,与显示器相配合使用,如果能测量出触摸点在屏幕上的坐标位置,则可根

据显示屏上对应坐标点的显示内容或图符获知触摸者的意图。触摸屏按其技术原理可分为五类:矢量压力

传感式、电阻式、电容式、红外线式、表面声波式,其中电阻式触摸屏在嵌入式系统中用的较多。电阻触

摸屏是一块4 层的透明的复合薄膜屏,如图2 所示,最下面是玻璃或有机玻璃构成的基层,最上面是一层

外表面经过硬化处理从而光滑防刮的塑料层,中间是两层金属导电层,分别在基层之上和塑料层内表面,

在两导电层之间有许多细小的透明隔离点把它们隔开。当手指触摸屏幕时,两导电层在触摸点处接触。 

触摸屏的两个金属导电层是触摸屏的两个工作面,在每个工作面的两端各涂有一条银胶,称为该工作面

的一对电极,若在一个工作面的电极对上施加电压,则在该工作面上就会形成均匀连续的平行电压分布。

如图4 所示,当在 X 方向的电极对上施加一确定的电压,而 Y 方向电极对上不加电压时,在 X 平行电压场

中,触点处的电压值可以在 Y+(或Y- )电极上反映出来,通过测量 Y+电极对地的电压大小,便可得知触点的

 X 坐标值。同理,当在 Y 电极对上加电压,而 X 电极对上不加电压时,通过测量 X+电极的电压,便可得知

触点的Y 坐标。电阻式触摸屏有四线和五线两种。四线式触摸屏的 X 工作面和Y 工作面分别加在两个导电

层上,共有四根引出线,分别连到触摸屏的 X 电极对和Y 电极对上。五线式触摸屏把 X 工作面和 Y 工作面

都加在玻璃基层的导电涂层上,但工作时,仍是分时加电压的,即让两个方向的电压场分时工作在同一工

作面上,而外导电层则仅仅用来充当导体和电压测量电极。因此,五线式触摸屏的引出线需为5 根。 

 

1.2  在S3C2440中的触摸屏接口

SOC S3C2440 的触摸屏接口是与ADC接口结合在一起的,框图如下:

转换速率:当PCLK=50MHz时,分频设为 49,则10位的转换计算如下:

When the GCLK frequency is 50MHz and the prescaler value is 49,

A/D converter freq. = 50MHz/(49+1) = 1MHz

Conversion time = 1/(1MHz /  5cycles) = 1/200KHz = 5 us

This A/D converter was designed to operate at maxi mum 2.5MHz clock, so the conversion rate can

go up to 500 KSPS.

触摸屏接口的模式有以下几种:

普通ADC转换模式

独立X/Y 位置转换模式

自动X/Y 位置转换模式

等待中断模式

我们主要接受触摸屏接口的等待中断模式和自动X/Y 位置转换模式(驱动程序中会用到):

自动转换模式操作流程如下:触摸屏控制器自动转换 X,Y 的触摸位置,当转换完毕后将数据分别存放在

寄存器ADCDAT0 和ADCDAT1. 并产生INT_ADC 中断通知转换完毕。

等待中断模式:

Touch Screen Controller generates interrupt (INT_TC)  signal when the Stylus is down. Waiting for

Interrupt Modesetting value is rADCTSC=0xd3;  // XP_PU, XP_Dis, XM_Dis, YP_Dis, YM_En.

当触摸后,触摸屏控制器产生INT_TC 中断,四个引脚设置应该为:

引脚    XP    XM    YP    YM

状态    PULL UP/XP Disable    Disable ( 初始值即是)    Disable    Enable

设置    1    0    1    1

当中断产生后,X/Y 的位置数据可以选择独立 X/Y 位置转换模式,和自动 X/Y 位置转换模式进行读取,

采用自动X/Y 位置转换模式进行读取需要对我们已经设置的 TSC寄存器进行更改,在原有的基础上或上

S3C2410_ADCTSC_PULL_UP_DISABLE  | S3C2410_ADCTSC_AUTO_PST |

S3C2410_ADCTSC_XY_PST(0)。

数据转换完毕后,也会产生中断。

2. 输入子系统模型分析

2.1  整体框架:

输入子系统包括三个部分设备驱动、输入核心、事件处理器。

第一部分是连接在各个总线上的输入设备驱动,在我们的SOC上,这个总线可以使虚拟总线

platformbus,他们的作用是将底层的硬件输入转化为统一事件型式,向输入核心(Input core )汇报.

 第二部分输入核心的作用如下:

(1)    调用 input_register_device() used to  添加设备,调用input_unregister_device()  除去设备。(下

面会结合触摸屏驱动讲述)

(2)    在/PROC 下产生相应的设备信息,下面这个例子即是:

/proc/bus/input/devices  showing a USB mouse:

I: Bus=0003 Vendor=046d Product=c002 Version=0120

N: Name="Logitech USB-PS/2 Mouse M-BA47"

P: Phys=usb-00:01.2-2.2/input0

H: Handlers=mouse0 event2

B: EV=7

B: KEY=f0000 0 0 0 0 0 0 0 0

B: REL=103

(3)    通知事件处理器对事件进行处理

第三部分是事件处理器:

输入子系统包括了您所需要的大所属处理器,如鼠标、键盘、joystick ,触摸屏,也有一个通用的处理

器被叫做event handler(对于内核文件evdev.C ). 需要注意的是随着内核版本的发展,event handler将用

来处理更多的不同硬件的输入事件。在Linux2.6.29版本中,剩下的特定设备事件处理就只有鼠标和joystick 。

这就意味着越来越多的输入设备将通过event handler来和用户空间打交道。事件处理层的主要作用就是和

用户空间打交道,我们知道Linux 在用户空间将所有设备当成文件来处理,在一般的驱动程序中都有提供

fops 接口,以及在/dev 下生成相应的设备文件nod ,而在输入子系统的驱动中,这些动作都是在事件处理

器层完成的,我们看看evdev.C 相关代码吧。

static int __init evdev_init(void)

{

    return input_register_handler(&evdev_handler);

}

这是该模块的注册程序,将在系统初始化时被调用。

初始化得过程很简单,就一句话,不过所有的秘密都被保藏在evdev_handler 中了:

static struct input_handler evdev_handler = {

    .event        = evdev_event,

    .connect    = evdev_connect,

    .disconnect    = evdev_disconnect,

    .fops        = &evdev_fops,

    .minor        = EVDEV_MINOR_BASE,

    .name        = "evdev",

    .id_table    = evdev_ids,

};

先看connect 函数中如下的代码:

snprintf(evdev->name, sizeof(evd ev->name), "event%d", minor);

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

 evdev->handle.dev = i nput_get_device(dev);

    evdev->handle.name = evdev->name;

    dev_set_name(&evdev->dev, evdev->name);

    evdev->dev.devt = MKDEV(INPU T_MAJOR, EVDEV_MINOR_BASE + minor);

evdev->dev.class = &input_class;    evdev

->dev.parent = &dev->dev;

    evdev->dev.release = evdev_free;

    device_initialize(&evdev->dev);

      error = device_add(&evdev->dev);

注意黑色的部分这将会在/sys/device/viture /input/input0/event0 这个目录就是在这里生成的,在event

下会有一个dev 的属性文件,存放着设备文件的设备号,,这样 udev  就能读

取该属性文件获得设备号,从而在/dev 目录下创建设备节点/dev/event0

再看evdev_fops成员:

static const struct fil e_operations evdev_fops = {

    .owner        = THIS_MODULE,

    .read        = evdev_read,

    .write        = evdev_write,

    .poll        = evdev_poll,

    .open        = evdev_open,

    .release    = evdev_release,     .unlocked_ioctl    = evdev_ioctl,

#ifdef CONFIG_COMPAT

    .compat_ioctl    = evdev_ioctl_compat,

#endif

    .fasync        = evdev_fasync,    .flush        = evdev_flush

};

看过LDD3 的人都知道,这是设备提供给用户空间的接口,用来提供对设备的操作,其中evdev_ioctl

提供了很多命令,相关的命令使用参照《Using the Input Subsystem, Part II》

3    mini2440 的触摸屏驱动

3.1  初始化:

static int __init s3c2410ts_init(void)

{

    struct input_dev *input_dev;

 

    adc_clock = clk_get(NULL, "adc");

    if (!adc_clock) {

        printk(KERN_ERR "fa iled to get adc clock source\n");

        return -ENOENT;

    }

    clk_enable(adc_clock);

 // 获取时钟,挂载 APB BUS上的外围设备,需要时钟控制,ADC就是这样的设备。

    base_addr=ioremap(S3C2410_PA_ADC,0x20);

I/O 内存是不能直接进行访问的,必须对其进行映射,为 I/O 内存分配虚拟地址,这些虚拟地址以

__iomem进行说明,但不能直接对其进行访问,需要使用专用的函数,如iowrite32

    if (base_addr == NULL) {

        printk(KERN_ERR "Failed to remap register block\n");

        return -ENOMEM;

    }

 

    /* Configure GPIOs */

    s3c2410_ts_connect();

    iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\

              base_addr+S3C2410_ADCCON);// 使能预分频和设置分频系数

    iowrite32(0x ffff,  base_ addr+S3C2410_ADCDLY);//设置ADC延时,在等待中断

模式下表示产生INT_TC 的间隔时间

    iowrite32(WAIT4INT(0),  base_addr+S3C2410_ADCTSC);

按照等待中断的模式设置TSC

接下来的部分是注册输入设备

    /* Initialise input stuff */

    input_dev = input_allocate_device();

//allocate memory for new input device, 用来给输入设备分配空间,并做一些输入设备通用的初始的设

    if (!input_dev) {

        printk(KERN_ERR "Unable  to allocate the input device !!\n");

        return -ENOMEM;

    }

    dev = input_dev;

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

// 设置事件类型

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

    input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);

    input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);

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

以上四句都是设置事件类型中的code,如何理解呢,先说明事件类型,常用的事件类型EV_KEY 、

EV_MOSSE, EV_ABS(用来接收像触摸屏这样的绝对坐标事件),而每种事件又会有不同类型的编码 code,

比方说ABS_X,ABS_Y,这些编码又会有相应的value

    dev->name = s3c2410ts_name;

    dev->id.bustype = BUS_RS232;

    dev->id.vendor = 0xDEAD;

 dev->id.product = 0xBEEF;

    dev->id.version = S3C2410TSVERSION;

// 以上是输入设备的名称和 id ,这些信息时输入设备的身份信息了,在用户空间如何看到呢,

cat /proc/bus/input/devices ,下面是我的截图  

    /* Get irqs */

    if (request_irq(IRQ_ADC, st ylus_action, IRQF_SAMPLE_RANDOM,

        "s3c2410_action", dev)) {

        printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n");

        iounmap(base_addr);

        return -EIO;

    }

    if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,

            "s3c2410_action", dev)) {

        printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");

        iounmap(base_addr);

        return -EIO;

    }

 

    printk(KERN_INFO "%s succe ssfully loaded\n", s3c2410ts_name);

 

    /* All went ok, so register to the input system */

    input_register_device(dev);

 

 

// 前面已经设置了设备的基本信息和所具备的能力,所有的都准备好了,现在就可以注册了

    return 0;

}

  中断处理

stylus_action 和stylus_updown 两个中断处理函数,当笔尖触摸时,会进入到stylus_updown ,

static irqreturn_t stylus_updown(int irq, void *dev_id)

{

    unsigned long data0;

    unsigned long data1;

    int updown;

// 注意在触摸屏驱动模块中,这个ADC_LOCK的作用是保证任何时候都只有一个驱动程序使用ADC的

中断线,因为在 mini2440adc 模块中也会使用到ADC,这样只有拥有了这个锁,才能进入到启动 ADC,注意

尽管LDD3 中说过信号量因为休眠不适合使用在ISR 中,但 down_trylock 是一个例外,它不会休眠。

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

        OwnADC = 1;

        data0 = ioread32(base_addr+S3C2410_ADCDAT0);

        data1 = ioread32(base_addr+S3C2410_ADCDAT1);

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

S3C2410_ADCDAT0_UPDOWN));

        if (updown) {//means down

            touch_timer_fire(0);// 这是一个定时器函数,当然在这里是作为普通函数调用,用来启动 ADC

        } else {

            OwnADC = 0;

            up(&ADC_LOCK);//注意红色的部分是基本不会执行的,除非你触摸后以飞快的速度是否,还来

不及启动ADC,当然这种飞快的速度一般是达不到的,笔者调试程序时发现这里是进入不了的

          }

    }       

 

    return IRQ_HANDLED;

}

static void touch_timer_fire(unsigned long data)

{

      unsigned long data0;

      unsigned long data1;

       int updown;

      data0 = ioread32(base_addr+S3C2410_ADCDAT0);

      data1 = ioread32(base_addr+S3C2410_ADCDAT1);

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

S3C2410_ADCDAT0_UPDOWN));

 

      if (updown) {//means down

          转换四次后进行事件汇报

          if (count != 0) {

            long tmp;

                                                                                                 

            tmp = xp;

            xp = yp;

            yp = tmp;

      //这里进行转换是因为我们的屏幕使用时采用的是 240*320 ,相当于把原来的屏幕的X,Y 轴变换。

个人理解,不只是否正确                                                                                            

                      xp >>= 2;

                      yp >>= 2;

           input_report_abs(dev, ABS_X, xp);

                 input_report_abs(dev, ABS_Y, yp);

// 设备X,Y 值

              input_report_key(dev, BTN_TOUCH, 1);

              input_report_abs(dev, ABS_PRESSURE, 1);

              input_sync(dev);

// 这个表明我们上报了一次完整的触摸屏事件,用来间隔下一次的报告

          }

        xp = 0;

          yp = 0;

          count = 0;

 

          iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,

base_addr+S3C2410_ADCTSC);

          iowrite32(ioread32(base_addr+S3C2410_ADCCON) |

S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);

如果还没有启动ADC或者ACD转换四次完毕后则启动ADC

 

      }      else {

如果是up状态,则提出报告并让触摸屏处在等待触摸的阶段

          count = 0;

 

          input_report_key(dev, BTN_TOUCH, 0);

 

 

          input_repor t_abs(dev, ABS_PRESSURE, 0);

          input_sync(dev);

 

          iowrite32(WAIT4I NT(0), base_addr+S3C2410_ADCTSC);

        if (OwnADC) {

            OwnADC = 0;

            up(&ADC_LOCK);

        }

      }

}

static irqreturn_t stylus_action(int irq, void *dev_id)

{

    unsigned long data0;

    unsigned long data1;

 

    if (OwnADC) {

        data0 = ioread32(base_addr+S3C2410_ADCDAT0);

        data1 = ioread32(base_addr+S3C2410_ADCDAT1);

 

        xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;

        yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;

        count++;

读取数据

        if (count < (1<<2)) { 如果小如四次重新启动转换

            iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,

base_addr+S3C2410_ADCTSC);

            iowrite32(ioread32(base_addr+S3C2410_ADCCON) |

S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);

        } else {如果超过四次,则等待1ms 后进行数据上报

 

            mod_timer(&touch_timer, jiffies+1);

            iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);

 

        }

    }

 

    return IRQ_HANDLED;

}

我们从整体上描述转换的过程:  

(1)    如果触摸屏感觉到触摸,则进入 updown ISR, 如果能获取ADC_LOCK则调用touch_timer_fire,

启动ADC,

(2)    ADC 转换,如果小于四次继续转换,如果四次完毕后,启动1 个时间滴答的定时器,停止ADC,

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

(3)    这样可以防止屏幕抖动。

(4)    如果 1 个时间滴答到时候,触摸屏仍然处于触摸状态则上报转换数据,并重启 ADC,重复(2)

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

4  测试与校准

关于应用程序的编写,请参照《Using the Input Subsystem, Part II》,讲解了 input 设备的API,

触摸屏的校准时使触摸屏的坐标与LCD 得坐标进行对应,这种对应需要映射,这个映射的过程即为校准,我们提供了一种线性算法的映射方法,具体的代码见附件。

要学会站在高手的基础上,不断的向上前进,向下挖掘。

  评论这张
 
阅读(1289)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017