linux-3.0中的触摸屏驱动讲解

来源:互联网 发布:cdn网络加速技术 编辑:程序博客网 时间:2024/05/14 23:03

一.ADC及触摸屏接口图

S3C2440有8路的ADC通道其中触摸屏控制器接口XP,XM,YP,YM与四路ADC通道复用四个IO引脚。从原理图看出8路ADC只有一个A/D转换器,通过一个8选1开关MUX来选通哪一路A/D通道进行转换。触摸屏控制会产生两个中断,一个触摸屏中断INT_TC,一个ADC_转换完成中断INT_ADC。ADC需要时钟才能工作,因为它需要设置采样率。

触摸屏工作流程:

1、选择模式

2、设置触摸屏接口到等待接口状态

3、如果中断发生,激活转换模式

4、获取坐标后,返回等待中断状态

(INT_TC中断用于按下或弹起触摸屏)

(INT_ADC用于坐标转换完成)

二.AD转换时间

当全局时钟频率为50MHz和预分频值为49时,总共10位转换时间如下:

AD转换器频率=50MHz/(49+10=1MHz

转换时间=1/(1MHz/5cycles)=1/200KHz=5us

三.ADC及触摸屏接口特殊寄存器

(1)控制寄存器(ADCCON)

(2)触摸屏控制寄存器(ADCTSC)

(3)开始延时寄存器(ADCDLY)

(4)转换数据寄存器0(ADCDAT0)

(5)转换数据寄存器1(ADCDATA1)

(6)触摸屏指针上下中断检测寄存器(ADCUPDN)

四.触摸屏接口模式:

(1)正常转换模式:通过设置ADCCON来初始化对ADCDATA0的读写操作。

(2)分离XY坐标转换模式:X坐标模式写X坐标转换数据到ADCDAT0,触摸屏接口产生中断源到中断控制器.Y坐标模式写Y坐标转换数据到ADCDAT1,触摸屏接口产生中断源到中断控制器

(3)自动XY坐标转换模式:触摸屏控制器连续转换触摸X坐标和Y坐标.在触摸控制器写X测量数据到ADCDAT0且写Y测量数据到ADCDAT1后,触摸屏接口产生中断源到自动转换模式下的中断控制器.

(4)等待中断模式:当光标按下产生中断信号(INT_TC)。触摸屏控制器的等待中断模式必须设定为触摸屏接口中触点的状态(XP,XM,YP,YM)

 

触摸屏可以看成是输入设备,工作原理是底层在触摸动作发送时产生一个中断,然后CPU通过外部存储器总线读取坐标,放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取坐标.

  Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。下面分析input输入子系统的结构,以及功能实现。
一. Input子系统结构与功能实现


  1. Input子系统是分层结构的,总共分为三层: 硬件驱动层,子系统核心层,事件处理层。 
    (1)其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
    (2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
    (3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
  2. 各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),Input子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是 硬件驱动层-->子系统核心-->事件处理层-->用户空间

3.输入子系统设备驱动层实现原理:

   在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
①、在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
②、将Input设备注册到input子系统中;
③、在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。

Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):

  1. EV_SYN 0x00 同步事件
  2. EV_KEY 0x01 按键事件
  3. EV_REL 0x02 相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移)
  4. EV_ABS 0x03 绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置)
  5. EV_MSC 0x04 其它
  6. EV_LED 0x11 LED
  7. EV_SND 0x12 声音
  8. EV_REP 0x14 Repeat
  9. EV_FF 0x15 力反馈

 

4. 以触摸屏为例说明输入子系统的工作流程:
     注:s3c2440的触摸屏驱动所用驱动层对应的模块文件为:s3c2410_ts.c,事件处理层对应的模块文件为 evdev.c
    (1)s3c2410_ts模块初始化函数中将触摸屏注册到了输入子系统中,于此同时,注册函数在事件处理层链表中寻找事件处理器,这里找到的是evdev,并且将驱动与事件处理器挂载。并且在/dev/input中生成设备文件event0,以后我们访问这个文件就会找的我们的触摸屏驱动程序。
    (2)应用程序打开设备文件/dev/input/event0,读取设备文件,调用evdev模块中read,如果没有事件进程就会睡眠。  
    (3)当触摸屏按下,驱动层通过子系统核心将事件(就是X,Y坐标),传给事件处理层也就是evdev,evdev唤醒睡眠的进程,将事件传给进程处理。

 

触摸屏代码分析

在arch/arm/plat-s3c24xx/devs.c定义了触摸屏的资源和platform_device结构体

static struct resource s3c_ts_resource[] = {
    [0] = {
        .start = SAMSUNG_PA_ADC,               /*控制寄存器的起始地址0X58000000*/
        .end   = SAMSUNG_PA_ADC + SZ_ADC - 1,
        .flags = IORESOURCE_MEM,
    },

  [1] = {
        .start = IRQ_TC,
        .end   = IRQ_TC,
        .flags = IORESOURCE_IRQ,
    },
};
struct platform_device s3c_device_ts = {
    .name       = "s3c2410-ts",
    .id     = -1,

    .dev.parent = &s3c_device_adc.dev,
    .num_resources  = ARRAY_SIZE(s3c_ts_resource),
    .resource   = s3c_ts_resource,
};

EXPORT_SYMBOL(s3c_device_ts);

还有一个void __init s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *hard_s3c2410ts_info)函数,该函数的作用是将struct s3c2410_ts_mach_info保存到触摸屏的平台数据中

s3c2410_ts_indo结构体的初始化在/arch/arm/mach-s3c2440/mach-smdk2440.c里

static struct s3c2410_ts_mach_info smdk2440_ts_cfg __initdata = {
    .delay = 10000,         /*ADC的延迟时间*/
    .presc = 49,               /*ADC的预分频系数*/
    .oversampling_shift = 2,  /*采样次数log2的值*/
};
在smdk2440_devices[]这个platform_device结构体数组里面添加s3c_device_ts和s3c_device_adc。内核会将该结构体了的成员全部加到platform总线上.

在smdk2440_machine_init中调用s3c24xx_ts_set_platdata(&smdk2440_ts_cfg),这样就把上面的结构体信息保存到了平台数据中.

下面分析drivers/input/touchscreen/s3c2410_ts.c

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>

#include <plat/adc.h>
#include <plat/regs-adc.h>
#include <plat/ts.h>

#define TSC_SLEEP  (S3C2410_ADCTSC_PULL_UP_DISABLE | S3C2410_ADCTSC_XY_PST(0))

#define INT_DOWN    (0)
#define INT_UP      (1 << 8)

#define WAIT4INT    (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))

#define FEAT_PEN_IRQ    (1 << 0)    /* HAS ADCCLRINTPNDNUP */

struct s3c2410ts {
    struct s3c_adc_client *client;    /*触摸屏驱动是利用ADC转换坐标值 所以 要将触摸屏驱动挂接到ADC上 即 我们这里要说的将ADC作为server 将触摸屏做为client */

   struct device *dev;
    struct input_dev *input;
    struct clk *clock;
    void __iomem *io;
    unsigned long xp;
    unsigned long yp;
    int irq_tc;
    int count;
    int shift;
    int features;
};

static struct s3c2410ts ts;

/* 

#define S3C2410_ADCDAT0_UPDOWN     (1<<15)

 取ADCDAT0的第15位与ADCDAT1的第15位相与的结果。只有当两个寄存器的第15位t同时为0时才返回1,否则返回0.从s3c2440的手册查得ADCDAT的第15位表示对与等待中断模式的光标按下或提起状态,0表示光标被按下状态,1表示光标被提起状态.所以此函数返回1表示按下.

*/

static inline bool get_down(unsigned long data0, unsigned long data1)
{
    /* returns true if both data values show stylus down */
    return (!(data0 & S3C2410_ADCDAT0_UPDOWN) &&
        !(data1 & S3C2410_ADCDAT0_UPDOWN));
}

 

static void touch_timer_fire(unsigned long data)
{
    unsigned long data0;
    unsigned long data1;
    bool down;

    data0 = readl(ts.io + S3C2410_ADCDAT0);      /*获取ADCDAT0里的值*/
    data1 = readl(ts.io + S3C2410_ADCDAT1);      /*获取ADCDAT1里的值*/

    down = get_down(data0, data1);

    if (down) {                                                             /*如果光标为按下状态*/
        if (ts.count == (1 << ts.shift)) {                      /*这里的ts.shift=2,在下面的s3c2410ts_probe里有ts.shift = info->oversampling_shift,我们前面在初始化s3c2410_ts_mach_info是将oversampling_shift设置为2,所以这里的条件是采样次数等于4时*/
            ts.xp >>= ts.shift;                                     /*求平均值*/
            ts.yp >>= ts.shift;

            dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
                __func__, ts.xp, ts.yp, ts.count);

            /*提交X,Y的绝对坐标*/

            input_report_abs(ts.input, ABS_X, ts.xp);
            input_report_abs(ts.input, ABS_Y, ts.yp);

            input_report_key(ts.input, BTN_TOUCH, 1);      /*提交按键事件,键值为1表示触摸屏对应的按键被按下*/
            input_report_abs(ts.input, ABS_PRESSURE, 1); /* 提交触摸屏的状态,1表示触摸屏被按下Add by guowenxue, 2012.03.30 */
            input_sync(ts.input);  /*等待接受方收到数据后回复确认,用于同步*/

            ts.xp = 0;   
            ts.yp = 0;             
            ts.count = 0;      /*当转换次数为4时,将count重新置为0*/
        }

             s3c_adc_start(ts.client, 0, 1 << ts.shift);   /*开启AD转换*/
    } else {
        ts.xp = 0;
        ts.yp = 0;
        ts.count = 0;

        input_report_key(ts.input, BTN_TOUCH, 0);      
        input_report_abs(ts.input, ABS_PRESSURE, 0); /* Add by guowenxue, 2012.03.30 */
        input_sync(ts.input);

        writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);      /*设置成等待中断模式*/
    }
}

/*这里为什么要添加这个内核定时器.因为当我们按下一次,只会产生一个中断,如果我们滑动的话,就不会产生任何效果.为了处理滑动的情况,我们就启用定时器,每隔一段时间就启用一次AD转换,这样就可以得到按下点的信息了。*/

static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0);   /*创建内核定时器*/

/**
 * stylus_irq - touchscreen stylus event interrupt
 * @irq: The interrupt number
 * @dev_id: The device ID.
 *
 * Called when the IRQ_TC is fired for a pen up or down event.
 */
static irqreturn_t stylus_irq(int irq, void *dev_id)
{
    unsigned long data0;
    unsigned long data1;
    bool down;

    data0 = readl(ts.io + S3C2410_ADCDAT0);
    data1 = readl(ts.io + S3C2410_ADCDAT1);

    down = get_down(data0, data1);

    /* TODO we should never get an interrupt with down set while
     * the timer is running, but maybe we ought to verify that the
     * timer isn't running anyways. */

    if (down)                      /*如果触摸屏被按下开启AD转换*/
        s3c_adc_start(ts.client, 0, 1 << ts.shift);  /*设置client中的channel(使用通道为)0,nr_samples为4*/
    else
        dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);

    if (ts.features & FEAT_PEN_IRQ) {
        /* Clear pen down/up interrupt */
        writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
    }

    return IRQ_HANDLED;
}

**
 * s3c24xx_ts_conversion - ADC conversion callback
 * @client: The client that was registered with the ADC core.
 * @data0: The reading from ADCDAT0.
 * @data1: The reading from ADCDAT1.
 * @left: The number of samples left.
 *
 * Called when a conversion has finished.
 */

/*s3c24xx_ts_conversion的作用就是累加每次采样后得到的x,y坐标值,并记录采样次数,

在其它函数中,可以取ts.xp和ts.yp的平均值,得到传递给用户空间的x,y坐标值。*/
static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
                  unsigned data0, unsigned data1,
                  unsigned *left)
{
    dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);

    ts.xp += data0;
    ts.yp += data1;

    ts.count++;

    /* From tests, it seems that it is unlikely to get a pen-up
     * event during the conversion process which means we can
     * ignore any pen-up events with less than the requisite
     * count done.
     *
     * In several thousand conversions, no pen-ups where detected
     * before count completed.
     */
}

/**
 * s3c24xx_ts_select - ADC selection callback.
 * @client: The client that was registered with the ADC core.
 * @select: The reason for select.
 *
 * Called when the ADC core selects (or deslects) us as a client.
 */
static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
{

/*select为非0时设置为自动连续测量x坐标和y坐标模式*/
    if (select) {
        writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
               ts.io + S3C2410_ADCTSC);                            /*设置成自动连续测量X坐标和Y坐标*/
    } else {
        mod_timer(&touch_timer, jiffies+1);
        writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);            /*设置成抬起触发中断*/
    }
}

/**
 * s3c2410ts_probe - device core probe entry point
 * @pdev: The device we are being bound to.
 *
 * Initialise, find and allocate any resources we need to run and then
 * register with the ADC and input systems.
 */
static int __devinit s3c2410ts_probe(struct platform_device *pdev)
{
    struct s3c2410_ts_mach_info *info;
    struct device *dev = &pdev->dev;
    struct input_dev *input_dev;
    struct resource *res;
    int ret = -EINVAL;

    /* Initialise input stuff */
    memset(&ts, 0, sizeof(struct s3c2410ts));

    ts.dev = dev;

    info = pdev->dev.platform_data;
    if (!info) {
        dev_err(dev, "no platform data, cannot attach\n");
        return -EINVAL;
    }

    dev_dbg(dev, "initialising touchscreen\n");

    ts.clock = clk_get(dev, "adc");   /*获取ADC时钟*/
    if (IS_ERR(ts.clock)) {
        dev_err(dev, "cannot get adc clock source\n");

      return -ENOENT;
    }

    clk_enable(ts.clock);       /*时钟使能*/
    dev_dbg(dev, "got and enabled clocks\n");

    ts.irq_tc = ret = platform_get_irq(pdev, 0);      /*获取中断号*/
    if (ret < 0) {
        dev_err(dev, "no resource for interrupt\n");
        goto err_clk;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   /*获取资源*/
    if (!res) {
        dev_err(dev, "no resource for registers\n");
        ret = -ENOENT;
        goto err_clk;
    }

    ts.io = ioremap(res->start, resource_size(res));       /*I/O内存映射*/
    if (ts.io == NULL) {
        dev_err(dev, "cannot map registers\n");
        ret = -ENOMEM;
        goto err_clk;
    }

    /* inititalise the gpio */

if (info->cfg_gpio)  /*如果有cfg_gpio,就调用,初始化gpio*/
        info->cfg_gpio(to_platform_device(ts.dev));

/*注册ADC设备*/

    ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
                     s3c24xx_ts_conversion, 1);
    if (IS_ERR(ts.client)) {
        dev_err(dev, "failed to register adc client\n");
        ret = PTR_ERR(ts.client);
        goto err_iomap;
    }

    /* Initialise registers */

/*设置ADC的延时寄存器*/
    if ((info->delay & 0xffff) > 0)
        writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);

    writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);      /*设置成等待中断模式*/

    input_dev = input_allocate_device();     /分配一个input_dev*/
    if (!input_dev) {
        dev_err(dev, "Unable to allocate the input device !!\n");
        ret = -ENOMEM;
        goto err_iomap;
    }

/*初始化input*/

    ts.input = input_dev;
    ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | BIT(EV_SYN); /* Modify by    
guowenxue, 2012.03.30, 每一种类型的事件都在input_dev.evbit中用一位来表示,构成一个位图,如果某一位为1,表示支持该事件,如果该位为0,表示不支持该事件*/
    ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);    /*设置所支持的按键,触摸屏可以看成是只有一个按键的设备*/

/* 设置ad转换的XY坐标,范围是0-0x3FF

因为mini2440的AD转换出的数据最大为10位,所以不会超过0x3ff。
*/
    input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);   

    input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);       
    input_set_abs_params(ts.input, ABS_PRESSURE, 0, 1, 0, 0);  /* Add by guowenxue, 2012.03.
30 */

    ts.input->name = "S3C24XX TouchScreen";
    ts.input->id.bustype = BUS_HOST;
    ts.input->id.vendor = 0xDEAD;
    ts.input->id.product = 0xBEEF;
    ts.input->id.version = 0x0102;

    ts.shift = info->oversampling_shift;
    ts.features = platform_get_device_id(pdev)->driver_data;

    ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
              "s3c2410_ts_pen", ts.input);
    if (ret) {
        dev_err(dev, "cannot get TC interrupt\n");
        goto err_inputdev;
    }

    dev_info(dev, "driver attached, registering input device\n");

    /* All went ok, so register to the input system */
    ret = input_register_device(ts.input);
    if (ret < 0) {
        dev_err(dev, "failed to register input device\n");
        ret = -EIO;

   goto err_tcirq;
    }

    return 0;

 err_tcirq:
    free_irq(ts.irq_tc, ts.input);
 err_inputdev:
    input_free_device(ts.input);
 err_iomap:
    iounmap(ts.io);
 err_clk:
    del_timer_sync(&touch_timer);
    clk_put(ts.clock);
    return ret;
}

static int __devexit s3c2410ts_remove(struct platform_device *pdev)
{
    free_irq(ts.irq_tc, ts.input);
    del_timer_sync(&touch_timer);

    clk_disable(ts.clock);
    clk_put(ts.clock);

    input_unregister_device(ts.input);
    iounmap(ts.io);

    return 0;
}

#ifdef CONFIG_PM
static int s3c2410ts_suspend(struct device *dev)
{
    writel(TSC_SLEEP, ts.io + S3C2410_ADCTSC);
    disable_irq(ts.irq_tc);
    clk_disable(ts.clock);

    return 0;
}


static int s3c2410ts_resume(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct s3c2410_ts_mach_info *info = pdev->dev.platform_data;

    clk_enable(ts.clock);
    enable_irq(ts.irq_tc);

    /* Initialise registers */
    if ((info->delay & 0xffff) > 0)
        writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);

    writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);

    return 0;
}

static struct dev_pm_ops s3c_ts_pmops = {
    .suspend    = s3c2410ts_suspend,
    .resume     = s3c2410ts_resume,
};
#endif

static struct platform_device_id s3cts_driver_ids[] = {
    { "s3c2410-ts", 0 },
    { "s3c2440-ts", 0 },
    { "s3c64xx-ts", FEAT_PEN_IRQ },
    { }
};

MODULE_DEVICE_TABLE(platform, s3cts_driver_ids);

static struct platform_driver s3c_ts_driver = {
    .driver         = {
        .name   = "samsung-ts",
        .owner  = THIS_MODULE,
#ifdef CONFIG_PM
        .pm = &s3c_ts_pmops,
#endif
    },
    .id_table   = s3cts_driver_ids,
    .probe      = s3c2410ts_probe,
    .remove     = __devexit_p(s3c2410ts_remove),
};

static int __init s3c2410ts_init(void)
{
    return platform_driver_register(&s3c_ts_driver);
}

static void __exit s3c2410ts_exit(void)
{
    platform_driver_unregister(&s3c_ts_driver);
}

module_init(s3c2410ts_init);
module_exit(s3c2410ts_exit);

MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>, "
          "Ben Dooks <ben@simtec.co.uk>, "
          "Simtec Electronics <linux@simtec.co.uk>");
MODULE_DESCRIPTION("S3C24XX Touchscreen driver");
MODULE_LICENSE("GPL v2");

下面总结一下驱动原理:当触摸屏被按下时,首先会触发触摸屏中断,该中断是在s3c2410ts_probe函数中申请的ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
              "s3c2410_ts_pen", ts.input);中断处理函数为stylus_irq,在该函数内首先判断屏幕是否被按下,按下则调用s3c_adc_start函数开始AD转换。转换完成后又会触发ADC中断,进入ADC的中断服务程序s3c_adc_irq.在ADC的中断服务程序中会调用客服端的convert_cb函数,即上面的s3c24xx_ts_conversion函数,该函数的作用就是累计X,Y坐标的值。然后判断转换次数到了没,前面我们把转换次数设置成了4,每次进入这个函数时就将转换次数减一,没到就调用client->select_cb(client, 1),即上面的s3c24xx_ts_select,设置成自动连续测量X坐标和Y坐标函数,然后进行ADC转换,转换后又产生ADC中断,又进入到ADC中断服务程序,如此循环直到进行了4次AD转换.最后调用(client->select_cb)(client, 0),进入定时器处理函数,这里说明一下为什么能调用到s3c24xx_ts_conversion和convert_cb的,因为在触摸屏驱动中我们向server端注册了一个client sc32410_ts.c中的probe函数中ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,s3c24xx_ts_conversion, 1);。定时器处理函数 首先判断触摸屏是按下还是抬起 如果是按下则算出x轴和y轴坐标值的平均值(ts.xp >>= ts.shift;ts.yp >>= ts.shift;注意右移2位表示除以4) 并把结果上报给input子系统 然后又继续调用s3c_adc_start函数开始ADC转换 这样一直循环 如果是抬起 则进入触摸屏中断服务程序stylus_irq(因为前面已经把触摸屏中断触发方式改为抬起了)

到这里 触摸屏驱动基本上就分析完了 其实 如果要扩展的话 东西太多 大家有兴趣也可以自己研究 个人认为 原理很简单 代码花点时间也能看明白 而最难的最关键的我认为还是驱动架构 为什么linux要这么做这个驱动 这么做有什么好处 就触摸屏驱动而言 因为他要用到ADC 而还有很多地方也要用到ADC 比如 HWMON驱动 那么如果把ADC单独拿出来作为server的话 其他使用ADC的设备作为client 这样就不会形成累赘的代码 可移植性也越好 PWM也是一样 LCD 背光 蜂鸣器都要用到PWM 因此也就采用这种server和client结构

0 0
原创粉丝点击