基于S3C2440的Linux-3.6.6移植——触摸屏应用

在linux-3.6.6中,集成了s3c2440触摸屏的应用层文件——/drivers/input/touchscreen/s3c2410_ts.c。因此我们只需要定义好平台设备,就可以实现触摸屏。

 

s3c2410_ts.c是基于input子系统的,而且它还应用了上一篇文章中介绍过的adc.c文件,因此要理解s3c2410_ts.c文件,还要先理解adc.c文件。下面就介绍s3c2410_ts.c文件。

 

s3c2440触摸屏平台驱动为:

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         = s3c2410ts_remove,

};

 

static struct platform_device_ids3cts_driver_ids[] = {

       { "s3c2410-ts",0 },

       { "s3c2440-ts",0 },

       { "s3c64xx-ts",FEAT_PEN_IRQ },

       { }

};

 

触摸屏的探测函数为:

static int 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 */

       //清空全局变量ts

       memset(&ts, 0, sizeof(struct s3c2410ts));

 

       ts.dev = dev;

 

       info = pdev->dev.platform_data;

       if (!info) {

                dev_err(dev, "no platformdata, cannot attach\n");

                return -EINVAL;

       }

 

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

 

       //获取时钟,触摸屏时钟与ADC时钟有关

       ts.clock = clk_get(dev, "adc");

       if (IS_ERR(ts.clock)) {

                dev_err(dev, "cannot get adcclock 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 resourcefor interrupt\n");

                goto err_clk;

       }

       //获取IO内存资源

       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

       if (!res) {

                dev_err(dev, "no resourcefor registers\n");

                ret = -ENOENT;

                goto err_clk;

       }

       //映射内存虚拟地址

       ts.io = ioremap(res->start, resource_size(res));

       if (ts.io == NULL) {

                dev_err(dev, "cannot mapregisters\n");

                ret = -ENOMEM;

                goto err_clk;

       }

 

       /* inititalise the gpio */

       //初始化触摸屏GPIO

       if (info->cfg_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 toregister adc client\n");

                ret = PTR_ERR(ts.client);

                goto err_iomap;

       }

 

       /* Initialise registers */

       //设置ADC延时寄存器——ADCDLY

       if ((info->delay & 0xffff) > 0)

                writel(info->delay &0xffff, ts.io + S3C2410_ADCDLY);

       //设置ADCTSC寄存器,即设置触摸屏中断模式,INT_DOWN表示按下触发中断

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

 

        //分配一个输入子系统设备

       input_dev = input_allocate_device();

       if (!input_dev) {

                dev_err(dev, "Unable toallocate the input device !!\n");

                ret = -ENOMEM;

                goto err_iomap;

       }

 

       //为触摸屏输入子系统赋值

       ts.input = input_dev;

       //设置事件类型,触摸屏为按键事件和绝对值事件

       ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);

       //设置按键类型,触摸屏为触摸类型

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

       //设置X轴和Y轴坐标范围

       input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);

       input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);

 

       //设置触摸屏信息

       ts.input->name = "S3C24XXTouchScreen";

       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, 0,

                          "s3c2410_ts_pen", ts.input);

       if (ret) {

                dev_err(dev, "cannot getTC interrupt\n");

                goto err_inputdev;

       }

 

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

 

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

       //注册触摸屏为input子系统设备

       ret = input_register_device(ts.input);

       if (ret < 0) {

                dev_err(dev, "failed toregister 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;

}

 

在上面的函数中,调用了s3c_adc_register函数来注册ADC客户,这个函数是在adc.c文件内定义的,具体的介绍请查看我上一篇文章。在注册触摸屏ADC客户时,重点定义了两个回调函数:client->convert_cb的回调函数为s3c24xx_ts_conversion和client->select_cb的回调函数为s3c24xx_ts_select:

static void s3c24xx_ts_conversion(struct s3c_adc_client *client,

                                  unsigneddata0, unsigned data1,

                                  unsigned*left)

{

       dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);

 

       ts.xp += data0;                     //x轴坐标

       ts.yp += data1;                     //y轴坐标

 

       ts.count++;            //转换次数加1

 

       /* 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.

        */

}

 

static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)

{

       if (select) {            //启用自动(连续)x轴和y轴坐标转换模式

                writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,

                       ts.io + S3C2410_ADCTSC);

       } else {                 //启动定时器,并设置抬起为触摸屏中断

                //重新注册名为touch_timer的内核定时器,到期时间为此后一个时钟滴答

mod_timer(&touch_timer, jiffies+1);

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

       }

}

 

s3c24xx_ts_conversion函数的主要作用是在ADC转换完成后累计x轴和y轴坐标的值,以便在其他的函数内分别求取它们的平均值。s3c24xx_ts_select函数主要是通过select参数来转换触摸屏工作模式。

 

下面介绍定时器的作用:

//静态创建名为touch_timer的内核定时器,并初始化其function为touch_timer_fire,其他参数为0

static DEFINE_TIMER(touch_timer,touch_timer_fire, 0, 0);

 

touch_timer定时器超时处理函数touch_timer_fire:

static void touch_timer_fire(unsigned longdata)

{

       unsigned long data0;

       unsigned long data1;

       bool down;

 

       //读取ADC转换数值

       data0 = readl(ts.io + S3C2410_ADCDAT0);

       data1 = readl(ts.io + S3C2410_ADCDAT1);

 

       //调用get_down函数,判断触摸屏是否被按下

       down = get_down(data0, data1);

 

       if (down) {                   //触摸屏被按下

                if (ts.count == (1 << ts.shift)){           //达到设定的采样次数

                        ts.xp >>=ts.shift;                 //x轴坐标取平均值

                        ts.yp >>=ts.shift;                 //y轴坐标取平均值

 

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

                        //同步上传

                        input_sync(ts.input);

 

                        ts.xp = 0;

                        ts.yp = 0;

                        ts.count = 0;

                }

                //启动ADC转换

                s3c_adc_start(ts.client, 0, 1 << ts.shift);

       } else {                 //不是触摸屏被按下,也就是触笔抬起

                ts.xp = 0;

                ts.yp = 0;

                ts.count = 0;

                //上传按键类型,即为触摸屏按键

                input_report_key(ts.input,BTN_TOUCH, 0);

                //同步上传

                input_sync(ts.input);

 

                //重新定义ADCTSC寄存器,设置触摸屏按下中断

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

       }

}

 

最后我们再来看触摸屏中断函数:

static irqreturn_t stylus_irq(int irq, void*dev_id)

{

       unsigned long data0;

       unsigned long data1;

       bool down;

 

       //读取ADC转换数值

       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)               //触摸屏被按下,开启ADC转换

                s3c_adc_start(ts.client, 0, 1 << ts.shift);

       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;

}

 

s3c2410_ts.c文件里的函数就介绍到这里,我再来总结一下触摸屏驱动的过程:当触摸屏被按下时,触发了触摸屏中断stylus_irq,在该函数内,当判断是触摸屏被按下后,就调用s3c_adc_start函数开始ADC转换,转换的过程是在s3c_adc_try函数内依次调用s3c_adc_select函数和s3c_adc_convert函数。s3c_adc_select函数的作用是调用回调函数s3c24xx_ts_select,其中第二个参数为1,表示启用自动(连续)x轴和y轴坐标转换模式,s3c_adc_convert函数的作用是启动ADC转换。启动了ADC转换,就会触发ADC转换中断,从而进入中断处理程序s3c_adc_irq。在中断内调用了回调函数s3c24xx_ts_conversion,用于累计x轴和y轴坐标值,然后判断是否达到了设定的采样次数,如果没有达到则继续进行ADC转换,如果达到了采样次数,则调用s3c24xx_ts_select函数,这时设定的该函数的第二个参数为0,则表示启动定时器,并把触摸屏中断设定为抬起触发中断。当所有的采样次数都完成后,退出了ADC转换中断,则进入定时器touch_timer_fire函数内,在该函数内还是首先判断当前触摸屏是否被按下,如果是,则计算x轴和y轴坐标值的平均值,并把结果上传,然后又继续调用s3c_adc_start函数开始ADC转换,如此循环。当触笔被抬起时,进入触摸屏中断stylus_irq,但没有进行实质性的操作,关键是系统也进入了定时器touch_timer_fire函数内,当判断是触笔被抬起时,首先上传相关事件,然后是重新设定触摸屏中断为按下触发。至此整个触摸屏工作流程就结束了。可以看出,只要触笔没有抬起,触笔所经过的坐标值都会被上传。

 

下面我们再来介绍触摸屏的移植。在arch/arm/mach-s3c24xx/mach-zhaocj2440.c文件内,首先要添加一个头文件:

#include <plat/ts.h>

 

然后再添加一个触摸屏信息结构体:

static struct s3c2410_ts_mach_info zhaocj2440_ts_info={

 .delay = 10000,         //延时

 .presc = 49,               //预分频

 .oversampling_shift = 2,           //采样次数,2的2次方为4

};

 

再把zhaocj2440_features_str字符数组增加一个“t”字符,表示触摸屏,即:

static char zhaocj2440_features_str[12]__initdata = "4bt";

 

修改zhaocj2440_parse_features函数内case 't':的内容:

case 't':

       if(features->done & FEATURE_TOUCH)

              printk(KERN_INFO"ZHAOCJ2440: '%c' ignored, "

                            "touchscreenalready set\n", f);

       else

              features->optional[features->count++]=

                            &s3c_device_ts;

       features->done|= FEATURE_TOUCH;

       break;

 

最后在zhaocj2440_init函数内把刚才定义的zhaocj2440_ts_info结构添加进平台数据内:

s3c24xx_ts_set_platdata(&zhaocj2440_ts_info);

 

这样触摸屏驱动就移植好了,我们还需要修改menuconfig,配置触摸屏:

Device Drivers --->

 Input device support --->

   [*] Touchscreens --->

         <*> Samsung S3C2410/generictouchscreen input driver

 

在系统启动的过程中,我们可以从打印出来的信息中看到触摸屏驱动已移植好了:

samsung-ts s3c2440-ts: driver attached, registering input device

 

input: S3C24XX TouchScreen as /devices/virtual/input/input0

 

在系统启动后,通过下列命令,可以查看所有input子系统的详细信息:

[root@zhaocj /]#cat proc/bus/input/devices

 

I: Bus=0019 Vendor=dead Product=beefVersion=0102

N: Name="S3C24XX TouchScreen"

P: Phys=

S: Sysfs=/devices/virtual/input/input0

U: Uniq=

H: Handlers=mouse0 event0

B: PROP=0

B: EV=b

B: KEY=400 0 0 0 0 0 0 0 0 0 0

B: ABS=3

 

I: Bus=0019 Vendor=001f Product=0001 Version=0100

N: Name="pwm-beeper"

P: Phys=pwm/input0

S: Sysfs=/devices/platform/s3c24xx-pwm.0/pwm-beeper.0/input/input1

U: Uniq=

H: Handlers=kbd event1

B: PROP=0

B: EV=40001

B: SND=6

 

I: Bus=0019 Vendor=0001 Product=0001Version=0100

N: Name="gpio-keys"

P: Phys=gpio-keys/input0

S:Sysfs=/devices/platform/gpio-keys/input/input2

U: Uniq=

H: Handlers=kbd event2

B: PROP=0

B: EV=3

B: KEY=100000 0 38000000 0

 

从上面可以看出,触摸屏input子系统是作为第一个input子系统,因此它的驱动文件是/dev/event0我们写一段应用程序来测试一下:

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <fcntl.h>

#include <linux/input.h>

 

int main()

{

       struct input_event data;

       int fd;

       fd = open("/dev/event0", O_RDONLY)

if(fd<0)

{

              printf("Error open%s\n", TS_DEV);

           return -1;

       }

 

       while(1)

       {

                read(fd, &data,sizeof(data));

             printf("data.type = %d,data.code = %d, data.value = %d\n",data.type,data.code,data.value);

 

                if (data.type == EV_KEY)

                {

                       printf("type:EV_KEY, event = %s, value = %d\n\n",

                                data.code ==BTN_TOUCH ? "BTN_TOUCH" : "Unkown", data.value);

                }

                else if(data.type == EV_ABS)

                {

                        printf("type: EV_ABS, event =%s, value = %d\n\n",

                                data.code ==ABS_X ? "ABS_X" :

                                data.code ==ABS_Y ? "ABS_Y" :

                                data.code ==ABS_PRESSURE ? "ABS_PRESSURE" :

                               "Unkown", data.value);

                }

                else if (data.type == EV_SYN)

                {

                        printf("type:EV_SYN, event = %s, value = %d\n\n",

                                data.code ==SYN_REPORT ? "SYN_REPORT" : "Unkown", data.value);

                }

                else

                {

                        printf("type:0x%x, event = 0x%x, value = %d\n\n", data.type, data.code, data.value);

                }

       }

        return 0;

}

 

下面是一个完整的触笔从按下到抬起的测试结果:

data.type = 3, data.code = 0, data.value =670

type: EV_ABS, event = ABS_X, value = 670

 

data.type = 3, data.code = 1, data.value =795

type: EV_ABS, event = ABS_Y, value = 795

 

data.type = 1, data.code = 330, data.value= 1

type: EV_KEY, event = BTN_TOUCH, value = 1

 

data.type = 0, data.code = 0, data.value =0

type: EV_SYN, event = SYN_REPORT, value = 0

 

data.type = 3, data.code = 0, data.value =669

type: EV_ABS, event = ABS_X, value = 669

 

data.type = 3, data.code = 1, data.value =799

type: EV_ABS, event = ABS_Y, value = 799

 

data.type = 0, data.code = 0, data.value =0

type: EV_SYN, event = SYN_REPORT, value = 0

 

data.type = 3, data.code = 1, data.value =798

type: EV_ABS, event = ABS_Y, value = 798

 

data.type = 0, data.code = 0, data.value =0

type: EV_SYN, event = SYN_REPORT, value = 0

 

data.type = 3, data.code = 0, data.value =670

type: EV_ABS, event = ABS_X, value = 670

 

data.type = 0, data.code = 0, data.value =0

type: EV_SYN, event = SYN_REPORT, value = 0

 

data.type = 3, data.code = 1, data.value =800

type: EV_ABS, event = ABS_Y, value = 800

 

data.type = 0, data.code = 0, data.value =0

type: EV_SYN, event = SYN_REPORT, value = 0

 

data.type = 1, data.code = 330, data.value= 0

type: EV_KEY, event = BTN_TOUCH, value = 0

 

data.type = 0, data.code = 0, data.value =0

type: EV_SYN, event = SYN_REPORT, value = 0

 

从测试的结果可以看出,在触笔按下时,会完整的得到四个事件:ABS_X、ABS_Y、BTN_TOUCH和SYN_REPORT。在触笔抬起时,会得到两个事件:BTN_TOUCH和SYN_REPORT。在两者之间,只要这次检测的X轴或Y轴坐标中的任意一个与上次检测的不同,就会把不同的坐标值报告给用户,当然仍然是以SYN_REPORT作为结束。

你可能感兴趣的:(基于S3C2440的Linux-3.6.6移植——触摸屏应用)