• 触摸屏驱动程序框架分析


    触摸屏驱动程序,用于人机交互lcd上的独立的一个屏,这里指的是电阻屏。下面来分析一下内核自带的触摸屏驱动框架,便于我们自已编写触摸屏驱动程序

    触摸屏驱动使用的是Input_subsys系统。我们打开内核的s3c2410_ts.c触摸屏驱动来分析:下面来看一下流程是怎么样 从入口函数开始分析

    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),
    };
    module_platform_driver(s3c_ts_driver);
    View Code

    在这里没有发现所谓的入口函数 init 但我们看到了一条这样的定义 module_platform_driver(s3c_ts_driver); 我们搜索一个这个东西看看是什么,

    #define module_platform_driver(__platform_driver) 
        module_driver(__platform_driver, platform_driver_register, 
                platform_driver_unregister)

    展开看一下

    #define module_driver(__driver, __register, __unregister, ...) 
    static int __init __driver##_init(void) 
    { 
        return __register(&(__driver) , ##__VA_ARGS__); 
    } 
    module_init(__driver##_init); 
    static void __exit __driver##_exit(void) 
    { 
        __unregister(&(__driver) , ##__VA_ARGS__); 
    } 
    module_exit(__driver##_exit);

    ##__VA_ARGS__ 宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错 把参数代进去结果就是 入口函数和出口函数

    static int __init s3c_ts_driver_init(void) 
    { 
        return platform_driver_register(&(s3c_ts_driver));
    } 
    module_init(s3c_ts_driver_init); 
    static void __exit s3c_ts_driver_exit(void) 
    { 
        platform_driver_unregister(&(s3c_ts_driver)); 
    } 
    module_exit(s3c_ts_driver_exit);

    到这里又到了关平平台总线的概念了,之前的驱动有讲过的,如果内核里有相同名了的device就会调用 s3c_ts_driver 里的s3c2410ts_probeb函数,现在我们分析probeb函数的内容就可以得到基本的触摸屏框架了 进去看一下这个函数做了些什么事情:

    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
    ");
            return -EINVAL;
        }
    
        dev_dbg(dev, "initialising touchscreen
    ");
    
        ts.clock = clk_get(dev, "adc");
        if (IS_ERR(ts.clock)) {
            dev_err(dev, "cannot get adc clock source
    ");
            return -ENOENT;
        }
    
        clk_enable(ts.clock);
        dev_dbg(dev, "got and enabled clocks
    ");
    
        ts.irq_tc = ret = platform_get_irq(pdev, 0);
        if (ret < 0) {
            dev_err(dev, "no resource for interrupt
    ");
            goto err_clk;
        }
    
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res) {
            dev_err(dev, "no resource for registers
    ");
            ret = -ENOENT;
            goto err_clk;
        }
    
        ts.io = ioremap(res->start, resource_size(res));
        if (ts.io == NULL) {
            dev_err(dev, "cannot map registers
    ");
            ret = -ENOMEM;
            goto err_clk;
        }
    
        /* inititalise the gpio */
        if (info->cfg_gpio)
            info->cfg_gpio(to_platform_device(ts.dev));
    
        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
    ");
            ret = PTR_ERR(ts.client);
            goto err_iomap;
        }
    
        /* Initialise registers */
        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();
        if (!input_dev) {
            dev_err(dev, "Unable to allocate the input device !!
    ");
            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);
        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 = "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, 0,
                  "s3c2410_ts_pen", ts.input);
        if (ret) {
            dev_err(dev, "cannot get TC interrupt
    ");
            goto err_inputdev;
        }
    
        dev_info(dev, "driver attached, registering input device
    ");
    
        /* 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
    ");
            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;
    }
    View Code

    初始化一块内存 memset(&ts, 0, sizeof(struct s3c2410ts));

    从device里得到 数据 info = pdev->dev.platform_data;

    获得adc时钟 ts.clock = clk_get(dev, "adc");

    使能(打开) adc时钟 clk_enable(ts.clock);

    获得资源 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    等等 一些数据 还有 gpio 映射,这些

    input_dev = input_allocate_device();  分配一个input_dev 结构体,上面说过会用到输入子系统,这里就体现出来了

    一堆设置过后

    ret = request_irq(ts.irq_tc, stylus_irq, 0, 注册触摸屏中断

    ret = input_register_device(ts.input);  注册input_dev结构体。

    看到这些和我们前面说过的输入子系统那功就一模一样了,如果不了解,就去看前面的输入子系统分析

    当触摸屏有触摸动作时就会进入中断处理函数 stylus_irq 注册中断时这个就是参数了 然后 s3c_adc_start(ts.client, 0, 1 << ts.shift); 启动adc转换 当adc转换完成后,会产生adc中断,在adc中断里启动一个定时器,在定时器中断里上报事件 input_report_key(ts.input, BTN_TOUCH, 0); 这个函数上报

    static void touch_timer_fire(unsigned long data)
    {
        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);
    
        if (down) {
            if (ts.count == (1 << ts.shift)) {
                ts.xp >>= ts.shift;
                ts.yp >>= ts.shift;
    
                dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d
    ",
                    __func__, ts.xp, ts.yp, ts.count);
    
                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;
            }
    
            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);
    
            writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
        }
    }
    
    static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0);
    View Code

    到基本框架就出来了,还有没分析的就是输入子系统框架了,前有随笔有分析过,总结我们自已写触摸屏驱动有以下步骤

    1:分配一个input_dev结构体

    2:设置

    3:注册

    4:硬件相关操作,比如,ADC转换的值的优化,对触摸屏的值解析,上报成我们需要的数据即可

    使用tslib测试:

      注意事项:1:在内核中 include/linux/input.h 中 有一项定义版本号 #define EV_VERSION        0x010001 这个值 要和 编译器里的版本一样,编译器里这个定义在根目录下 usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/input.h +32 里有定义,两个版本如果不一样,随便更改一个就可以。

      注意事项:2:如果是我们自已编写的驱动程序 要注意在设置input_dev结构体时要加上压力 input_set_abs_params(ts, ABS_PRESSURE, 0, 1, 0, 0); 同时在 上报事件的时候,在上报同步事件前加上 input_report_abs(ts, ABS_PRESSURE, 1); 这个压力事件

    其它的什么 BTN_TOUCH、ABS_X、ABS_Y 都不能少,少一个tslib就无法识别。

    下面列出tslib的使用方法和步骤:当然首先要获得tslib的源码才行 然后先安装下面的工具

    sudo apt-get install autoconf
    sudo apt
    -get install automake sudo apt-get install libtool 编译: tar xzf tslib-1.4.tar.gz cd tslib ./autogen.sh mkdir tmp echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache ./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp make make install 安装: cd tmp cp * -rf /nfsroot nfsroot是指根文件系统目录 适用于网络文件系统启动 如果是其它的方式
    那么,这条命令最终的结果是 把 tslib下的所有文件 全部拷贝到 根文件系统目录下即可 使用: 先安装ts.ko, lcd.ko
    1. 修改 /etc/ts.conf第1行(去掉#号和第一个空格): # module_raw input 改为: module_raw input 2. export TSLIB_TSDEVICE=/dev/event1 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 ts_test

     列出我自已写的代码 代码一定要多写,才能理解,看再多代码不如自已写代码

    #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>
    
    struct ts_reg {
        unsigned long adccon;
        unsigned long adctsc;
        unsigned long adcdly;
        unsigned long adcdat0;
        unsigned long adcdat1;
        unsigned long adcupdn;
    };
    
    static volatile struct ts_reg *ts_regs;
    static struct input_dev *ts;
    static struct timer_list ts_time;
    
    /* 等待按下模式 */
    void wait_ts_down_mode(void)
    {
        ts_regs->adctsc = 0xd3;
    }
    /* 等待松开模式 */
    void wait_ts_up_mode(void)
    {
        /* 注意看寄存器是第8位 即共9位,我开始一直以为8位,想好好久才明白 */
        ts_regs->adctsc = 0x1d3;
    }
    /* 自动测量模式 */
    void auto_measu_mode(void)
    {
        ts_regs->adctsc = (1<<3) | (1<<2);
    }
    /* 启动ADC */
    static void start_adc(void)
    {
        ts_regs->adccon |= (1<<0);
    }
    static void ts_timer_function(unsigned long data)
    {
        if (ts_regs->adcdat0 & (1<<15))
        {
            /* 已经松开 */
            input_report_abs(ts, ABS_PRESSURE, 0);
            input_report_key(ts, BTN_TOUCH, 0);
            input_sync(ts);
            wait_ts_down_mode();
        }
        else
        {
            /* 测量X/Y坐标 */
            auto_measu_mode();
            start_adc();
        }
    }
    /* 触摸屏中断处理函数 */
    static irqreturn_t ts_irq(int irq, void *dev_id)
    {
        if (ts_regs->adcdat0 & (1<<15))
        {
            //printk("pen up
    ");
    //        input_report_abs(ts, ABS_X, 0);
    //        input_report_abs(ts, ABS_Y, 0);
            input_report_abs(ts, ABS_PRESSURE, 0);
            input_report_key(ts, BTN_TOUCH, 0);
            input_sync(ts);
            wait_ts_down_mode();
        }
        else
        {
    //        printk("pen down
    ");
            auto_measu_mode();        /* 设置为自动转换模式 */
            start_adc();            /* 启动ADC转换 */
        }
        return IRQ_HANDLED;
    }
    /* ADC转换完成中断函数 */
    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
        int res;
        int adcdat0, adcdat1;
        
        /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
        adcdat0 = ts_regs->adcdat0 & 0x3ff;    /* 得到x方向的值 */
        adcdat1 = ts_regs->adcdat1 & 0x3ff;     /* 得到y方向的值 */
        if (ts_regs->adcdat0 & (1<<15))        /* 判断是松开还是按下 */
        {
            input_report_abs(ts, ABS_X, 0);    /* 松开就上报0 */
            input_report_abs(ts, ABS_Y, 0);
            input_report_key(ts, BTN_TOUCH, 0);
            input_report_abs(ts, ABS_PRESSURE, 0);
            input_sync(ts);
            wait_ts_down_mode();                /* 设置为等待按下模式 */
        }
        else
        {
    //        printk("adc_irq cnt = %d, x = %d, y = %d
    ", ++cnt, adcdat0, adcdat1);
            input_report_abs(ts, ABS_X, adcdat0);    /* 上报事件 */
            input_report_abs(ts, ABS_Y, adcdat1);
            input_report_key(ts, BTN_TOUCH, 1);
            input_report_abs(ts, ABS_PRESSURE, 1);
            input_sync(ts);
            wait_ts_up_mode();                    /* 进入等待按下模式 */
            /* 启动定时器处理长按/滑动的情况修改定时器的值为10毫秒中后产生中断 */
            res = mod_timer(&ts_time, jiffies + HZ/100);
        }
        return IRQ_HANDLED;
    }
    
    static int ts_init(void)
    {
        struct clk* clk;
        int res;
        /* 1:分配一个input_dev结构体 */
        ts = input_allocate_device(); 
        if (!ts)
        {
            printk("Unable to allocate the input device !!
    ");
            return 1;
        }
        /* 2:设置 */
        /* 2.1:设置可以产生那类事件 */
        set_bit(EV_KEY,ts->evbit);    /* 可以产生按键类事件 */
        set_bit(EV_ABS,ts->evbit);    /* 可以产生绝对位移类事件 触摸屏 */
        /* 2.2:设置可以产生这类事件的那种事件 */
        set_bit(BTN_TOUCH, ts->keybit);        /* 产生绝对位移类触摸事件 */
        input_set_abs_params(ts, ABS_X, 0, 0x3FF, 0, 0);    /* x方向位移 */
        input_set_abs_params(ts, ABS_Y, 0, 0x3FF, 0, 0);    /* y方向位移 */
        input_set_abs_params(ts, ABS_PRESSURE, 0, 1, 0, 0);/* 压力 tslib测试要用 */
        
        /* 3:注册 */
        res = input_register_device(ts);
        if (res)
        {
            printk("input_register_devicd fila!
    ");
            return res;
        }
        /* 4:硬件相关设置 */
        /* 4.1:获取adc时钟 使能adc时钟 */
        clk = clk_get(NULL, "adc");
        if (IS_ERR(clk)) {
            dev_err(NULL, "cannot get adc clock source
    ");
            return -1;
        }
        clk_enable(clk);
    
        /* 4.2:寄存器映射 查看s3c2440关于触摸屏那一章节 */
        ts_regs = ioremap(0x58000000, sizeof(struct ts_reg));
        if (ts_regs == NULL)
        {
            printk("cannot map registers
    ");
            return -1;
        }
        /* 触摸屏寄存器设置 */
        /*
         * bit[14]   : PRSCEN=1   A/D converter prescaler enable 0-DISABLE  1-ENABLE
         * bit[13:6] : PRSCVL=49  ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]    : A/D conversion starts by enable. 先设为0
         */
        ts_regs->adccon = (1<<14) | (49<<6);
        /* ADC 启动延时间隔 用于等待电压稳定 */
        ts_regs->adcdly = 0xffff;
        
        /* 4.3:注册触摸屏中断 */
        res = request_irq(IRQ_TC, ts_irq, IRQF_SAMPLE_RANDOM, "ts_irq", NULL);
        if (res)
        {
            printk("cannot get TC interrupt
    ");
            return res;
        }
        /* 4.4:注册ADC中断 */
        res = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc_irq", NULL);
        if (res)
        {
            printk("cannot get ADC interrupt
    ");
            return res;
        }
        /* 优化措施5: 使用定时器处理长按,滑动的情况
         * 
         */
        init_timer(&ts_time);                        /* 初始化定时器 */
        ts_time.function = ts_timer_function;        /* 设置定时器处理函数 */
        add_timer(&ts_time);                        /* 加载定时器 */
    
        /* 其它操作 设置ts寄存器进入等待触摸屏动作模式 
         * 如设没有这条操作,触摸屏没反应
         */
        wait_ts_down_mode();
        
        return 0;
    }
    
    static void ts_exit(void)
    {
        free_irq(IRQ_ADC, NULL);    /* 释放中断 */
        free_irq(IRQ_TC, NULL);        /* 释放中断 */
        iounmap(ts_regs);            /* 取消GPIO映射 */
        input_unregister_device(ts);
        input_free_device(ts);        /* 释放input_dev */
        
    }
    
    module_init(ts_init);
    module_exit(ts_exit);
    MODULE_LICENSE("GPL");
    View Code
    钻木取火!拼的是体力?耐心?智慧?
  • 相关阅读:
    zabbix api
    cassandra学习笔记
    loki grafana
    logstash配置 filebeat配置
    k8s prometheus api
    51cto 先超Prometheus+Grafana监控告警系统实战
    51cto 张岩峰 Prometheus企业级监控系统:零基础入门
    imooc 使用Prometheus实践基于Spring Boot监控告警体系 笔记
    python3 django migrate
    python3虚拟环境
  • 原文地址:https://www.cnblogs.com/x2i0e19linux/p/11752187.html
Copyright © 2020-2023  润新知