linux设备树-LCD触摸屏设备驱动

Posted 大奥特曼打小怪兽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux设备树-LCD触摸屏设备驱动相关的知识,希望对你有一定的参考价值。

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

在上一节我们已经移植了LCD驱动,那么本节将会移植LCD触摸屏驱动。有关触摸屏的原理,以及硬件接线,我们在linux驱动移植-LCD触摸屏设备驱动章节已经介绍的非常清楚了。同时在这一篇博客,我们也详细介绍了触摸屏驱动的实现,并进行了代码演示。

linux 5.2.8内核已经自带了s3c2440触摸屏驱动,该驱动还依赖于ADC驱动,相当于把我们在linux驱动移植-LCD触摸屏设备驱动中写的驱动程序拆成了两个部分,但是代码整体逻辑大致是一样的。

这一节,我们将尝试引入设备树,通过设备树来实现触摸屏驱动程序。

一、触摸屏驱动

linux 5.2.8自带的s3c2440触摸屏驱动,其采用platform设备驱动模型。

1.1 platform device

名字为"s3c2410-ts"的platform device定义在arch/arm/plat-samsung/devs.c文件:

static struct resource s3c_ts_resource[] = 
        [0] = DEFINE_RES_MEM(S3C24XX_PA_ADC, S3C24XX_SZ_ADC),   // 0x58000000  SZ_1M
        [1] = DEFINE_RES_IRQ(IRQ_TC),                           // IRQ_TC为子中断 子中断控制器硬件中断号9,对应的主中断控制器硬件中断号31
;

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

void __init s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *hard_s3c2410ts_info)

        s3c_set_platdata(hard_s3c2410ts_info,
                         sizeof(struct s3c2410_ts_mach_info), &s3c_device_ts);

其中函数s3c24xx_ts_set_platdata用于设置platform设备的私有数据,数据类型为struct s3c2410_ts_mach_info,s3c_device_ts.dev.platform_data会被设置为&default_ts_data :

static struct s3c2410_ts_mach_info default_ts_data __initdata = 
        .delay                  = 10000,
        .presc                  = 49,
        .oversampling_shift     = 2,
;
default_ts_data定义在arch/arm/plat-samsung/devs.c文件中的,我们需要把s3c_device_ts成员里初始化的这些常量数据抽离到设备树中。

1.2 platform driver

名字为"s3c2410-ts"的platform driver定义在drivers/input/touchscreen/s3c2410_ts.c文件:

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

static const 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",
#ifdef CONFIG_PM
                .pm     = &s3c_ts_pmops,
#endif
        ,
        .id_table       = s3cts_driver_ids,
        .probe          = s3c2410ts_probe,
        .remove         = s3c2410ts_remove,
;

module_platform_driver(s3c_ts_driver);

1.3 s3c2410ts_probe

当platform设备和驱动匹配后,将会调用s3c2410ts_probe进行input设备的注册。函数定义在drivers/input/touchscreen/s3c2410_ts.c:

/**
 * 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 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 = dev_get_platdata(dev);          
        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");        
        if (IS_ERR(ts.clock)) 
                dev_err(dev, "cannot get adc clock source\\n");
                return -ENOENT;
        

        ret = clk_prepare_enable(ts.clock);    
        if (ret) 
                dev_err(dev, "Failed! to enabled clocks\\n");
                goto err_clk_get;
        
        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));
        if (ts.io == NULL) 
                dev_err(dev, "cannot map registers\\n");
                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\\n");
                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 !!\\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);
        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\\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:
        clk_disable_unprepare(ts.clock);
        del_timer_sync(&touch_timer);
 err_clk_get:
        clk_put(ts.clock);
        return ret;

全局变量ts定义如下:

#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 - driver touchscreen state.
 * @client: The ADC client we registered with the core driver.
 * @dev: The device we are bound to.
 * @input: The input device we registered with the input subsystem.
 * @clock: The clock for the adc.
 * @io: Pointer to the IO base.
 * @xp: The accumulated X position data.
 * @yp: The accumulated Y position data.
 * @irq_tc: The interrupt number for pen up/down interrupt
 * @count: The number of samples collected.
 * @shift: The log2 of the maximum count to read in one go.
 * @features: The features supported by the TSADC MOdule.
 */
struct s3c2410ts 
        struct s3c_adc_client *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;

二、ADC驱动

触摸屏驱动依赖于ADC驱动,ADC驱动也是采用的其采用platform设备驱动模型。

2.1 platform device

名字为"s3c24xx-adc"的platform device定义在arch/arm/plat-samsung/devs.c文件:

static struct resource s3c_adc_resource[] = 
        [0] = DEFINE_RES_MEM(S3C24XX_PA_ADC, S3C24XX_SZ_ADC),     // 0x58000000  SZ_1M
        [1] = DEFINE_RES_IRQ(IRQ_TC),                             // IRQ_TC为子中断 子中断控制器硬件中断号9,对应的主中断控制器硬件中断号31  
        [2] = DEFINE_RES_IRQ(IRQ_ADC),                            // IRQ_ADC为子中断 子中断控制器硬件中断号10,对应的主中断控制器硬件中断号31 
;

struct platform_device s3c_device_adc = 
        .name           = "s3c24xx-adc",
        .id             = -1,
        .num_resources  = ARRAY_SIZE(s3c_adc_resource),
        .resource       = s3c_adc_resource,
;

2.2 platform driver

名字为"s3c24xx-adc"的platform driver定义在arch/arm/plat-samsung/adc.c文件:

static const struct platform_device_id s3c_adc_driver_ids[] = 
        
                .name           = "s3c24xx-adc",
                .driver_data    = TYPE_ADCV1,
        , 
                .name           = "s3c2443-adc",
                .driver_data    = TYPE_ADCV11,
        , 
                .name           = "s3c2416-adc",
                .driver_data    = TYPE_ADCV12,
        , 
                .name           = "s3c64xx-adc",
                .driver_data    = TYPE_ADCV2,
        , 
                .name           = "samsung-adc-v3",
                .driver_data    = TYPE_ADCV3,
        ,
         
;
MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);

static const struct dev_pm_ops adc_pm_ops = 
        .suspend        = s3c_adc_suspend,
        .resume         = s3c_adc_resume,
;

static struct platform_driver s3c_adc_driver = 
        .id_table       = s3c_adc_driver_ids,
        .driver         = 
                .name   = "s3c-adc",
                .pm     = &adc_pm_ops,
        ,
        .probe          = s3c_adc_probe,
        .remove         = s3c_adc_remove,
;

2.3 s3c_adc_probe

当platform设备和驱动匹配后,将会调用s3c_adc_probe进行ADD相关的初始化工作。函数定义在arch/arm/plat-samsung/adc.c:

static int s3c_adc_probe(struct platform_device *pdev)

        struct device *dev = &pdev->dev;
        struct adc_device *adc;
        struct resource *regs;
        enum s3c_cpu_type cpu = platform_get_device_id(pdev)->driver_data;
        int ret;
        unsigned tmp;

        adc = devm_kzalloc(dev, sizeof(*adc), GFP_KERNEL);
        if (!adc)
                return -ENOMEM;

        spin_lock_init(&adc->lock);

        adc->pdev = pdev;
        adc->prescale = S3C2410_ADCCON_PRSCVL(49);

        adc->vdd = devm_regulator_get(dev, "vdd");
        if (IS_ERR(adc->vdd)) 
                dev_err(dev, "operating without regulator \\"vdd\\" .\\n");
                return PTR_ERR(adc->vdd);
        

        adc->irq = platform_get_irq(pdev, 1);
        if (adc->irq <= 0) 
                dev_err(dev, "failed to get adc irq\\n");
                return -ENOENT;
        

        ret = devm_request_irq(dev, adc->irq, s3c_adc_irq, 0, dev_name(dev),
                                adc);
        if (ret < 0) 
                dev_err(dev, "failed to attach adc irq\\n");
                return ret;
        

        adc->clk = devm_clk_get(dev, "adc");
        if (IS_ERR(adc->clk)) 
                dev_err(dev, "failed to get adc clock\\n");
                return PTR_ERR(adc->clk);
        

        regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        adc->regs = devm_ioremap_resource(dev, regs);
        if (IS_ERR(adc->regs))
                return PTR_ERR(adc->regs);

        ret = regulator_enable(adc->vdd);
        if (ret)
                return ret;

        clk_prepare_enable(adc->clk);

        tmp = adc->prescale | S3C2410_ADCCON_PRSCEN;

        /* Enable 12-bit ADC resolution */
        if (cpu == TYPE_ADCV12)
                tmp |= S3C2416_ADCCON_RESSEL;
        if (cpu == TYPE_ADCV2 || cpu == TYPE_ADCV3)
                tmp |= S3C64XX_ADCCON_RESSEL;

        writel(tmp, adc->regs + S3C2410_ADCCON);

        dev_info(dev, "attached adc driver\\n");

        platform_set_drvdata(pdev, adc);
        adc_dev = adc;

        return 0;

三、修改驱动程序

由于linux 5.2.8已经自带了s3c2440触摸屏、以及ADC驱动,因此我们直接在内核源码上改造,使其支持设备树即可。

3.1 修改设备树

3.1.1 新增myts设备节点

在内核arch/arm/boot/dts/s3c2440-smdk2440.dts文件中添加myts设备节点,用于触摸屏驱动:

myts: myts@5800000 
    compatible = "myts";
    reg = <0x58000000 0x100>;
    reg-names = "adc_ts_physical";
    interrupts = <1 31 9 3>;
    interrupt-names = "int_tc";
    clocks = <&clocks PCLK_ADC>;
    clock-names = "adc";
    delay = <0xffff>;
    presc = <49>;
    oversampling_shift = <2>;     
;
3.1.2 新增myadc设备节点

在内核arch/arm/boot/dts/s3c2440-smdk2440.dts文件中添加myadc设备节点,用于ADC驱动:

myadc: myadc@5800000 
    compatible = "myadc";
    reg = <0x58000000 0x100>;
    reg-names = "adc_physical";
    interrupts = <1 31 9 3>,<1 31 10 3>;
    interrupt-names = "int_tc","int_adc_s";
    clocks = <&clocks PCLK_ADC>;
    clock-names = "adc";
;

3.2 修改触摸屏驱动

3.2.1 修改s3c_ts_driver

为了支持设备树,所以我们需要修改s3c_ts_driver变量添加设备树匹配项,变量定义在arch/arm/plat-samsung/devs.c文件。修改完成后代码如下:

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

static const struct of_device_id  s3cts_dt_match[] =    // 用于设备树匹配
     .compatible = "myts", .data = (void *)0 ,
    ,
;

static const 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",
                .of_match_table = of_match_ptr(s3cts_dt_match),
#ifdef CONFIG_PM
                .pm     = &s3c_ts_pmops,
#endif
        ,
        .id_table       = s3cts_driver_ids,
        .probe          = s3c2410ts_probe,
        .remove         = s3c2410ts_remove,
;
3.2.2  修改s3c2410ts_probe

当platform设备和驱动匹配后,将会调用s3c2410ts_probe进行input设备的注册,函数位于drivers/input/touchscreen/s3c2410_ts.c文件,修改完之后代码如下:

/**
 * 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 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;
        struct device_node *np;
        
        
        np = dev->of_node;                // 获取myts设备节点
        if (!np) 
            dev_err(dev, "could not find device info\\n");
            return -EINVAL;
        

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

        ts.dev = dev;

        info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);       // 动态分配struct s3c2410_ts_mach_info
        if (!info) 
                dev_err(dev, "no mem for info\\n");
                return -ENOMEM;
        
        
        // 获取设备节点中的各个属性值,用来设置驱动参数
        of_property_read_u32(np, "delay", &info->delay);
        of_property_read_u32(np, "presc", &info->presc);
        of_property_read_u32(np, "oversampling_shift", &info->oversampling_shift);

        dev_dbg(dev, "%s:                delay:  0x%lx\\n", __func__, info->delay);
        dev_dbg(dev, "%s:                presc:  0x%1x\\n", __func__, info->presc);
        dev_dbg(dev, "%s:   oversampling_shift:  0x%1x\\n", __func__, info->oversampling_shift);
        
        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;
        

        ret = clk_prepare_enable(ts.clock);    // 使能时钟
        if (ret) 
                dev_err(dev, "Failed! to enabled clocks\\n");
                goto err_clk_get;
        
        dev_dbg(dev, "got and enabled clocks\\n");

        ts.irq_tc = ret = platform_get_irq(pdev, 0);     // 获取第一个IRQ编号 
        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));      // 将ADC相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址
        if (ts.io == NULL) 
                dev_err(dev, "cannot map registers\\n");
                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\\n");
                ret = PTR_ERR(ts.client);
                goto err_iomap;
        

        /* Initialise registers */
        if ((info->delay & 0xffff) > 0)
                writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);        // 设置ADC启动延时时间

        writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);        // 设置ADCTSC 1 << 7 | 1<<6 | 1 << 4 | (3&3) << 0 = 0xD3   
                                                                       开启INT_TC中断,笔尖按下触发

        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;
        

        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);     // s3c2440手册ADC是10位,所以第四个参数设置为0x3FF
        input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);     // s3c2440手册ADC是10位,所以第四个参数设置为0x3FF 

        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;
         ts.features = of_device_get_match_data(&pdev->dev);    // 获取私有数据


        ret = request_irq(ts.irq_tc, stylus_irq, 0,                 // 申请中断   
                          "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);                  // 注册input_dev
        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:
        clk_disable_unprepare(ts.clock);
        del_timer_sync(&touch_timer);
 err_clk_get:
        clk_put(ts.clock);
        return ret;

同时需要引入头文件:

#include <linux/of_device.h>

3.3 修改ADC驱动

3.3.1 修改s3c_adc_driver

为了支持设备树,所以我们需要修改s3c_adc_driver变量添加设备树匹配项,变量定义在arch/arm/plat-samsung/adc.c文件。修改完成后代码如下:

static const struct of_device_id  s3c_adc_dt_match[] =    // 用于设备树匹配
     .compatible = "myadc", .data = (void *)TYPE_ADCV1 ,
    ,
;

static const struct platform_device_id s3c_adc_driver_ids[] = 
        
                .name           = "s3c24xx-adc",
                .driver_data    = TYPE_ADCV1,
        , 
                .name           = "s3c2443-adc",
                .driver_data    = TYPE_ADCV11,
        , 
                .name           = "s3c2416-adc",
                .driver_data    = TYPE_ADCV12,
        , 
                .name           = "s3c64xx-adc",
                .driver_data    = TYPE_ADCV2,
        , 
                .name           = "samsung-adc-v3",
                .driver_data    = TYPE_ADCV3,
        ,
         
;
MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);

static const struct dev_pm_ops adc_pm_ops = 
        .suspend        = s3c_adc_suspend,
        .resume         = s3c_adc_resume,
;

static struct platform_driver s3c_adc_driver = 
        .id_table       = s3c_adc_driver_ids,
        .driver         = 
                .name   = "s3c-adc",
                .of_match_table = of_match_ptr(s3c_adc_dt_match),
                .pm     = &adc_pm_ops,
        ,
        .probe          = s3c_adc_probe,
        .remove         = s3c_adc_remove,
;
3.3.2  修改s3c_adc_probe

当platform设备和驱动匹配后,将会调用s3c_adc_probe进行ADD相关的初始化工作。函数位于arch/arm/plat-samsung/adc.c文件,修改完之后代码如下:

static int s3c_adc_probe(struct platform_device *pdev)

        struct device *dev = &pdev->dev;
        struct adc_device *adc;
        struct resource *regs;
        //enum s3c_cpu_type cpu = platform_get_device_id(pdev)->driver_data;
        enum s3c_cpu_type cpu = (int)of_device_get_match_data(&pdev->dev);    // 获取私有数据
        int ret;
        unsigned tmp;

        adc = devm_kzalloc(dev, sizeof(*adc), GFP_KERNEL);
        if (!adc)
                return -ENOMEM;

        spin_lock_init(&adc->lock);

        adc->pdev = pdev;
        adc->prescale = S3C2410_ADCCON_PRSCVL(49);                     // 设置预分频器的值  (49&0xff) << 6           

        adc->vdd = devm_regulator_get(dev, "vdd");
        if (IS_ERR(adc->vdd)) 
                dev_err(dev, "operating without regulator \\"vdd\\" .\\n");
                return PTR_ERR(adc->vdd);
        

        adc->irq = platform_get_irq(pdev, 1);             // 获取第2个IRQ编号   IRQ_ADC  ADC转换成功后,会进入IRQ_ADC中断函数
        if (adc->irq <= 0) 
                dev_err(dev, "failed to get adc irq\\n");
                return -ENOENT;
        

        ret = devm_request_irq(dev, adc->irq, s3c_adc_irq, 0, dev_name(dev),  // 申请中断
                                adc);
        if (ret < 0) 
                dev_err(dev, "failed to attach adc irq\\n");
                return ret;
        

        adc->clk = devm_clk_get(dev, "adc");                   // 获取adc时钟
        if (IS_ERR(adc->clk)) 
                dev_err(dev, "failed to get adc clock\\n");
                return PTR_ERR(adc->clk);
        

        regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);  // 获取第一个内存资源  ADC相关寄存器基地址
        adc->regs = devm_ioremap_resource(dev, regs);           // 将ADC相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址
        if (IS_ERR(adc->regs))
                return PTR_ERR(adc->regs);

        ret = regulator_enable(adc->vdd);
        if (ret)
                return ret;

        clk_prepare_enable(adc->clk);        // 使能时钟

        tmp = adc->prescale | S3C2410_ADCCON_PRSCEN;     // 49 <<6 | 1<<14

        /* Enable 12-bit ADC resolution */
        if (cpu == TYPE_ADCV12)
                tmp |= S3C2416_ADCCON_RESSEL;
        if (cpu == TYPE_ADCV2 || cpu == TYPE_ADCV3)
                tmp |= S3C64XX_ADCCON_RESSEL;

        writel(tmp, adc->regs + S3C2410_ADCCON);       // 预分频器使能,分频值设置为49   

        dev_info(dev, "attached adc driver\\n");

        platform_set_drvdata(pdev, adc);    // 保存驱动私有数据
        adc_dev = adc;

        return 0;

同时需要引入头文件:

#include <linux/of_device.h>

此外,还需要将查找文件中如下代码:

enum s3c_cpu_type cpu = platform_get_device_id(adc->pdev)->driver_data;

全部修改为:

enum s3c_cpu_type cpu = (int)of_device_get_match_data(&pdev->dev);    // 获取私有数据

四、烧录开发板测试

4.1 配置内核

执行如下命令:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# make menuconfig

配置内核,将内核自带的触摸屏驱动编译进内核:

Device Drivers  --->
    Input device support  -->
        [*] Touchscreens  -->
               <*> Samsung S3C2410/generic touchscreen input driver
    Graphics support  --->
        [ ] Bootup logo  --->      

保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# mv s3c2440_defconfig ./arch/arm/configs/

4.2 编译内核

此时重新执行:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# make distclean
root@zhengyang:/work/sambashare/linux-5.2.8-dt# make s3c2440_defconfig    
root@zhengyang:/work/sambashare/linux-5.2.8-dt# make uImage V=1

将uImage复制到tftp服务器路径下:

root@zhengyang:/work/sambashare/linux-5.2.8-dt#  cp /work/sambashare/linux-5.2.8-dt/arch/arm/boot/uImage /work/tftpboot/

4.3  编译dts

在linux内核根目录执行如下命令:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# make dtbs
  DTC     arch/arm/boot/dts/s3c2416-smdk2416.dtb
  DTC     arch/arm/boot/dts/s3c2440-smdk2440.dtb

编译设备树文件,把前面配置过的arch/arm/boot/dts里的dts文件编译成dtb文件。

将s3c2440-smdk2440.dtb复制到tftp服务器路径下:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# cp /work/sambashare/linux-5.2.8-dt/arch/arm/boot/dts/s3c2440-smdk2440.dtb /work/tftpboot/

4.4 启动内核

uboot启动后,将dtb下载到内存地址0x30001000中:

SMDK2440 # tftp 0x30001000 s3c2440-smdk2440.dtb

注意:我们可以修改uboot源码,扩展一个device_tree分区,然后将dtb文件存储在该分组取中。

然后将内核镜像加载到内存0x30008000地址,并烧录内核到Nand Flash:

SMDK2440 # tftp 30008000 uImage
SMDK2440 # nand erase.part kernel
SMDK2440 # nand write 30008000 kernel

然后可以使用如下命令启动内核:

SMDK2440 # bootm 0x30008000 - 0x30001000   // 无设备树时,直接bootm 0x30008000
//bootm  uImage地址  ramdisk地址  设备树镜像地址

内核启动打印有关LCD触摸屏信息如下:

....
samsung-ts 58000000.myts: no pinctrl handle
OF: no dma-ranges found for node(/myts@5800000)
samsung-ts 58000000.myts: device is not dma coherent
samsung-ts 58000000.myts: device is not behind an iommu
samsung-ts 58000000.myts: s3c2410ts_probe:                delay:  0xffff
samsung-ts 58000000.myts: s3c2410ts_probe:                presc:  0x31
samsung-ts 58000000.myts: s3c2410ts_probe:   oversampling_shift:  0x2
samsung-ts 58000000.myts: initialising touchscreen
clock-names adc in index 0
samsung-ts 58000000.myts: got and enabled clocks
OF: of_irq_parse_one: dev=/myts@5800000, index=0
OF:  parent=/interrupt-controller@4a000000, intsize=4
OF:  intspec=1
of_irq_parse_raw:  /interrupt-controller@4a000000:00000001,0000001f,00000009,00000003
OF: of_irq_parse_raw: ipar=/interrupt-controller@4a000000, size=4
OF:  -> addrsize=1
OF:  -> got it !
samsung-ts 58000000.myts: driver attached, registering input device
PM: Adding info for No Bus:input0
input: S3C24XX TouchScreen as /devices/virtual/input/input0
....

内核启动打印有关ADC信息如下:

....
s3c-adc 58000000.myadc: no pinctrl handle
OF: no dma-ranges found for node(/myadc@5800000)
s3c-adc 58000000.myadc: device is not dma coherent
s3c-adc 58000000.myadc: device is not behind an iommu
OF: of_irq_parse_one: dev=/myadc@5800000, index=1
OF:  parent=/interrupt-controller@4a000000, intsize=4
OF:  intspec=1
of_irq_parse_raw:  /interrupt-controller@4a000000:00000001,0000001f,0000000a,00000003
OF: of_irq_parse_raw: ipar=/interrupt-controller@4a000000, size=4
OF:  -> addrsize=1
OF:  -> got it !
clock-names adc in index 0
s3c-adc 58000000.myadc: attached adc driver
....

运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:

[root@zy:/]# cat /proc/interrupts
           CPU0
  7:       1304  s3c-eint   7 Edge      eth0
  8:          0       s3c   8 Edge      s3c2410-rtc tick
 13:      10445       s3c  13 Edge      samsung_time_irq
 22:          0       s3c  16 Edge      4d000000.fb
 27:          0       s3c  27 Edge      54000000.i2c
 30:          0       s3c  30 Edge      s3c2410-rtc alarm
 35:          8  s3c-level  35 Level     50004000.serial
 36:         56  s3c-level  36 Level     50004000.serial
 41:          0  s3c-level  41 Edge      s3c2410_ts_pen             // IRQ_TC
 42:          0  s3c-level  42 Edge      58000000.myadc             // IRQ_ADC
 59:          0  s3c-level  59 Edge      53000000.watchdog

查看设备节点文件:

[root@zy:/]# ls /dev/input -l
total 0
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0

五、使用tslib进行测试

5.1 下载tslib

直接到github上下载:

root@zhengyang:/work/sambashare/drivers/#git clone https://github.com/kergoth/tslib

下载完成后,我直接上传到ubuntu服务器如下路径:/work/sambashare/drivers。

跳转到tslib文件夹:

root@zhengyang:/work/sambashare/drivers# cd tslib/

5.2 编译

首先运行:

root@zhengyang:/work/sambashare/drivers/tslib# ./autogen.sh  
root@zhengyang:/work/sambashare/drivers/tslib# mkdir tmp

然后配置:

root@zhengyang:/work/sambashare/drivers/tslib# CC=arm-linux-gcc ./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp CFLAGS="-march=armv4t -O2 -Wall -W"

编译安装:

root@zhengyang:/work/sambashare/drivers/tslib# make   //编译
root@zhengyang:/work/sambashare/drivers/tslib# make install  //安装到temp目录下   

需要注意的是编译所使用的的版本要和内核编译的版本保持一致,我是用的是arm-linux-gcc4.8.3版本。

可以通过如下命令查看可执行文件的平台属性信息:

root@zhengyang:/work/sambashare/drivers/tslib# cd tmp
root@zhengyang:/work/sambashare/drivers/tslib/tmp# arm-linux-readelf -A bin/ts_test
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "4T"
  Tag_CPU_arch: v4T
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_rounding: Needed
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_enum_size: int

5.3 配置nfs文件系统

将tmp里面的bin ,etc,include,lib4个目录下的文件拷贝到文件系统的bin ,etc,include,lib4个目录下 :

root@zhengyang:/work/sambashare/drivers/tslib/tmp# cp * /work/nfs_root/rootfs/ -rfd

进入nfs文件系统,修改etc/inittab文件:

root@zhengyang:/work/sambashare/drivers/tslib/tmp# cd /work/nfs_root/rootfs/
root@zhengyang:/work/sambashare/drivers/tslib/tmp# vim etc/inittab

检查是否会启动:

tty1: tty1::askfirst:-/bin/sh  #在虚拟终端tty1启动askfirst动作的shell,也就是在LCD上会出现Please press Enter to active this console.

若有,前面加#,屏蔽掉,这条命令。

5.4  安配置LCD和触摸屏环境

[root@zy:/]# export TSLIB_TSDEVICE=/dev/input/event0    #ts设备文件(触摸屏):event0
[root@zy:/]# export TSLIB_CALIBFILE=/etc/pointercal     #校验文件(calibrate file),存放校验值
[root@zy:/]# export TSLIB_CONFFILE=/etc/ts.conf         #配置文件
[root@zy:/]# export TSLIB_PLUGINDIR=/lib/ts             #插件文件
[root@zy:/]# export TSLIB_CONSOLEDEVICE=none            #终端控制台设为NULL
[root@zy:/]# export TSLIB_FBDEVICE=/dev/fb0             #fb设备文件(LCD):fb0

或者直接写入nfs根文件系统路径下etc/profile文件中。

5.5 测试

运行校准程序,触摸屏依次出现5个点,依次点击之:

[root@zy:/]# ts_calibrate
xres = 240, yres = 320
Took 5 samples...
Top left : X =  267 Y =  783
Took 5 samples...
Top right : X =  782 Y =  774
Took 3 samples...
Bot right : X =  769 Y =  185
Took 2 samples...
Bot left : X =  289 Y =  212
Took 19 samples...
Center : X =  532 Y =  497
-29.351410 0.281104 0.002008
352.980225 -0.013406 -0.379243
Calibration constants: -1923574 18422 131 23132912 -878 -24854 65536

生成的校准文件名为pointercal,位于/etc目录下。

在开发板上执行如下名,就可以在lcd上绘图了:

[root@zy:/]# ts_test

屏幕最上方会出现三个按钮,分别为“Drag”、“Draw”和“Quit”,默认是第一个,因此,用触摸笔点击任何一处,十字光标便会到那里。

下面是点击Draw按钮并用触摸笔写字的效果图:

同时控制台输出大量信息,其中一小部分:

194.859177:    125    192    255
194.889177:    131    196    255
194.919155:    135    199    255
194.949226:    138    196    255
194.979155:    141    189    255
195.009152:    143    183    255
195.039166:    144    178    255
195.069162:    146    175    255
195.099161:    150    176    255
195.129166:    154    175    255
195.159257:    157    175    255
195.189178:    158    179    255
195.219176:    155    187    255
195.249176:    152    197    255
195.279150:    150    206    255
195.309160:    151    209    255
195.339161:    154    212    255
195.369205:    160    213      0

 第一列为timeval结构体的两个成员:tv_sec和tv_usec,中间两列分别是X和Y的坐标,最后为pressure,这里可以理解成“触摸事件”,为255表示触摸笔点击了(接触)屏幕,为0表示触摸笔离开了屏幕(这里出现很多的255是正常的,因为写字过程中笔没有离开触摸屏)。点击屏幕上“Quit”或按Ctrl+C可退出该程序。

参考文章

[1]linux驱动移植-LCD驱动基础

[2]linux驱动移植-LCD设备驱动

[3]linux驱动移植-LCD触摸屏设备驱动

[4]基于设备树的TQ2440触摸屏驱动移植

八 s3c2440 linux 触摸屏 驱动代分析

LCD触摸屏的移植

原文地址:

http://blog.csdn.net/woshidahuaidan2011/article/details/52054949


1、对触摸屏添加设备信息

触摸屏的设备信息的添加跟lcd很像,这里就仿照lcd的platform设备信息给触摸屏添加设备信息。可以在Touchscreen-s3c2410.h(include\\linux\\platform_data)文件中看到,其定义了有关触摸屏信息的结构体s3c2410_ts_mach_info,因此我们只需要填充该结构体就可以。

struct s3c2410_ts_mach_info原型为:

struct s3c2410_ts_mach_info

       int             delay;   //延时时间

       int             presc;  //预分频,adc时钟=PCLK/(presc+1)adc时钟最大为2.5M

       int             oversampling_shift;  //采样次数,2的2次方为4

   void    (*cfg_gpio)(struct platform_device *dev);

;

 

于是在要在mach-smdk2440.c文件中定义中该结构体并且加入设备平台设备就好,在mach-smdk2440.c定义:

#include<linux/platform_data/touchscreen-s3c2410.h> /*ts add*/

(注意这里已经改了,在plat/ts.h目录不存在了)

 

static struct s3c2410_ts_mach_info s3c _touch_data= //跟默认的一样

     .delay=1000,   //延时时间

     .presc=49,  //预分频,

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

  

;

然后加入设备平台链表,在

static struct platform_device*smdk2440_devices[] __initdata =

        &s3c_device_ohci,

        &s3c_device_lcd,

        &s3c_device_wdt,

        &s3c_device_i2c0,

        &s3c_device_iis,

        &smdk2440_device_dm9k,

        &s3c_device_rtc,

       &s3c_device_adc, //adc 触摸屏需要

       &s3c_device_ts,   //新加

;

注意:假如没有添加数模转换&s3c_device_adc,则内核会无法启动。

最后一步将设备信息添加到平台设备中,在smdk2440_machine_init函数中写入:

static void  __init smdk2440_machine_init(void)

       s3c24xx_fb_set_platdata(&smdk2440_fb_info);

        s3c24xx_ts_set_platdata(&s3c_touch_data);   //新加

        s3c_i2c0_set_platdata(NULL);

 

        platform_add_devices(smdk2440_devices,ARRAY_SIZE(smdk2440_devices));

        smdk_machine_init();

 

至于s3c24xx_ts_set_platdata函数,其定义在Devs.c(arch\\arm\\plat-samsung)文件中,作用跟lcd对应的函数一样就是把s3c_touch_data添加到平台设备s3c_device_ts中。其函数原型为:

 void __inits3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *hard_s3c2410ts_info)

      s3c_set_platdata(hard_s3c2410ts_info,

                     sizeof(struct s3c2410_ts_mach_info),&s3c_device_ts);

至于平台设备s3c_device_ts,这个定义在/arch/arm/plat-samsung/devs.c有:

struct platform_device s3c_device_ts =

      .name            = "s3c2410-ts",    //

      .id          = -1,

      .dev.parent    = &s3c_device_adc.dev,   //调用adc的平台设备的dev

      .num_resources   = ARRAY_SIZE(s3c_ts_resource),

      .resource       = s3c_ts_resource,

;

 

2、对触摸屏驱动的测试

假如你够细心的话,那么在内核启动的时候会看到:

samsung-ts s3c2440-ts: driverattached, registering input device

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

同时也会看到:

input: gpio-keys as/devices/platform/gpio-keys/input/input1

可以看到作为输入设备,触摸屏被设置为input0,之前被设置的key变成了input0。

 

因此我们可以写测试代码(只需要修改前面所写的按键的测试的代码就可以):

 

 

/*ts.c */

#include<stdint.h>

#include<string.h>

#include<fcntl.h>

#include<unistd.h>

#include<stdio.h>

#include<linux/input.h>

 

int main(void)

  int fd;

  int flag=0;

 structinput_event ev_ts;

  fd=open("/dev/input/event0", O_RDWR);

  if(fd < 0)

   

    perror("Error:open device ts");

     close(fd);

     return -1;

   

  while(1)

   

 

      read(fd,&ev_ts,sizeof(structinput_event));

 

    if(ev_ts.type) //下面的三句的意思是当连续读到的ev_ts.type0,那么只上报一次0

       flag=ev_ts.type;

    if(flag!=0) 

printf("type:%d,code:%d,value:%d,sec:%d,nsec:%d\\n",ev_ts.type,ev_ts.code,ev_ts.value,ev_ts.time.tv_sec,ev_ts.time.tv_usec);

 

      flag=ev_ts.type;

 

   

 close(fd);

 return 0;

 

对应的Makefile 文件为:

CC=arm-linux-2440-gcc

ts:ts.c

        $CC -o $@  $<

        cp ts /work/root/work/  #这里是我的跟文件系统的路径

clean:

        rm -rf ts

 

测试结果如下,下面给出一个触笔从按下到抬起过程测试所得到的数据:

 

type:3,code:0,value:660,sec:3138,nsec:430227

type:3,code:1,value:473,sec:3138,nsec:430227

type:1,code:330,value:1,sec:3138,nsec:430227

type:0,code:0,value:0,sec:3138,nsec:430227

type:3,code:0,value:656,sec:3138,nsec:435283

type:0,code:0,value:0,sec:3138,nsec:435283

type:3,code:0,value:668,sec:3138,nsec:440267

type:3,code:1,value:476,sec:3138,nsec:440267

type:0,code:0,value:0,sec:3138,nsec:440267

type:1,code:330,value:0,sec:3138,nsec:450234

type:0,code:0,value:0,sec:3138,nsec:45023

type类型为3表示本次上报的是绝对坐标类型,可以看到本次我随机按下的坐标x=660 ,y=473

随后上传的类型为1表示上传的是按键的类型,通过编码 code:330可以判断该按键类型为触摸屏被按下。

然后上传了一次上报事件结束标志,其type类型为0

此时一次完整的触摸屏按下的动作上传完毕。

但是通过返回的数字可以看到有上传了一次X的坐标信息和同步信息:

type:3,code:0,value:656,sec:3138,nsec:435283

type:0,code:0,value:0,sec:3138,nsec:435283

这是由于在上传同步的信息的时候,实际上传的是:

static inlinevoid input_sync(struct input_dev *dev)

      input_event(dev, EV_SYN, SYN_REPORT,0);

可以看到实习上传了编码为SYN_REPORT的信号,该信号的作用在Documentation\\input\\ event-codes.txt有介绍:

当多个输入数据在同一时间发生变化时,SYN_REPORT用于把这些数据进行打包和包同步。例如,一次鼠标的移动可以上报REL_X和REL_Y两个数值,然后上报出同步信号,在下次动作的时候再次发出REL_X和REL_Y两个数值和同步信号。

此外,这个信号比较特殊,每当系统检测到x或y的其中一个坐标或者x、y两个坐标都有变化的时候,在发射完x或y或(x.y)坐标之后都会产生SYN_REPORT信号,同时在触笔接触或者离开屏幕的时候也会发射出SYN_REPORT信号其值为0。用图示意就是:

检测到触笔按下:

ABS_X=坐标值

ABS_Y=坐标值

BTN_TOUCH=1

SYN_REPORT=0

检测到X坐标有变化Y坐标无变化:

ABS_X=新的坐标值

SYN_REPORT=0

检测到X坐标Y坐标都变化:

ABS_X=新的坐标值

ABS_Y=新的坐标值

SYN_REPORT=0

。。。。。。。

检测到触笔离开:

BTN_TOUCH=0

SYN_REPORT=0

  所以,最后上传的

type:1,code:330,value:0,sec:3138,nsec:450234

type:0,code:0,value:0,sec:3138,nsec:45023

3、对触摸屏驱动分析

触摸屏的驱动代码定义在S3c2410_ts.c (drivers\\input\\touchscreen)  文件中,接下来仍然从probe开始介绍:

static ints3c2410ts_probe(struct platform_device *pdev)

      struct s3c2410_ts_mach_info*info;

      struct device *dev = &pdev->dev; //这里得到的是平台设备的device成员

      struct input_dev *input_dev;//这里跟之前的按键的驱动一样,输入设备。

      struct resource *res;

      int ret = -EINVAL;

 

      /* Initialise input stuff */

      memset(&ts, 0, sizeof(structs3c2410ts));

/*********************************************************************************

 这里是将ts初始化为0。对于tsstructs3c2410ts的实例。我们之前了解到,对于一个输入设备,我们是用struct input_dev来描述一个输入设备;同样的道理,对于一个触摸屏设备,内核用struct s3c2410ts来描述它。下面来看一下structs3c2410ts结构体。

struct s3c2410ts

      structs3c_adc_client *client;注册一个adc设备的客户记录

/*********************************************************************************

struct s3c_adc_client

      structplatform_device       *pdev;

      structlist_head    pend; //客户请求列表

      wait_queue_head_t    *wait; //等待队列,用来睡眠

 

      unsigned int         nr_samples; //记录需要采样的个数

      int                 result;

      unsigned char             is_ts;   //是否支持触摸屏

      unsigned char             channel; //通道数

 

      void       (*select_cb)(struct s3c_adc_client *c,unsigned selected);触摸屏选择回调函数

      void       (*convert_cb)(struct s3c_adc_client*c,  触摸屏转换回调函数

                          unsigned val1, unsigned val2,

                          unsigned *samples_left);

;

***********************************************************************************/

 

      struct device*dev;  触摸屏对应的设备

      structinput_dev *input;  指向要注册的输入子系统

      struct clk*clock;  ADC的时钟

      void __iomem*io;  对应io引脚的基地址

      unsigned longxp;   X的坐标

      unsigned longyp;   Y的坐标

      int irq_tc;          按下触摸屏和抬笔对应的中断号

      int count;          每次采样的adc的个数

      int shift;           记录每次采样的adc的最大个数  

      int features;        标记着ADC对应引脚用在触摸屏模式时所拥有的特性

;

***********************************************************************************/

      ts.dev = dev; 设置触摸屏的设备为定义的平台所定义设备

 

      info =dev_get_platdata(&pdev->dev);得到平台设备定义的s3c2410_ts_mach_info

      if (!info)

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

             return -EINVAL;

     

 

      dev_dbg(dev, "initialisingtouchscreen\\n");

 

      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 enabledclocks\\n");

 

      ts.irq_tc = ret = platform_get_irq(pdev,0);得到中断号

      if (ret < 0)

             dev_err(dev, "no resource forinterrupt\\n");

             goto err_clk;

     

 

      res = platform_get_resource(pdev,IORESOURCE_MEM, 0);//得到内存资源

      if (!res)

             dev_err(dev, "no resource forregisters\\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 */

      if (info->cfg_gpio)//假如有定义cfg_gpio,就通过container_of函数获得platform_device

             info->cfg_gpio(to_platform_device(ts.dev));

     

ts.client =s3c_adc_register(pdev, s3c24xx_ts_select,

                                s3c24xx_ts_conversion, 1); //最后的参数数字1表示支持触摸屏

/*********************************************************************************

s3c_adc_register用来注册一个ADC设备,主要的工作就是:

client->is_ts = 1;

client->select_cb =s3c24xx_ts_select;

client->convert_cb =s3c24xx_ts_conversion;

其参数比较特殊,看一下这些参数的含义:

对于s3c24xx_ts_select,这是选择回调函数,看一下定义。

static void s3c24xx_ts_select(structs3c_adc_client *client, unsigned select)

       if(select)

              writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST,

                     ts.io + S3C2410_ADCTSC);

       else

              mod_timer(&touch_timer,jiffies+1); //介绍完probe函数后,将介绍定时器中断函数。

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

      

这里假如select=1,那么就写入寄存器ADCTSC1101 1100,也就是中自动转换模式

假如select=0,就设定定时器中断的中断时间.然后写入寄存器ADCTSC1 1101 0011把模式变为等待中断模式,并设置触笔抬起触发中断

之前我们有介绍过定时器的使用步骤如下;

定义一个定时器timer_list mytimer

初始化定时器并赋值成员 setup_timer(mytimer, timer_func, data);//datatimer_func形参

增加定时器add_timer(mytimer)

该修改定时器的定时时间mod_timer(mytimer,expire)

取消定时器,有两个可选择

del_timer(mytimer)  直接删除定时器

del_timer_sync(mytimer) 等待本次定时器处理完毕再取消(不适用中断上下文)

不过在S3c2410_ts.c(drivers\\input\\touchscreen)     文件中并没有看到有关定时器的定义,但是有一个宏:

static DEFINE_TIMER(touch_timer,touch_timer_fire, 0, 0);此时定时器计时通过这个宏来定义并初始化的。

事实上,初始化定时器内核留下了很多接口:

init_timer(struct timer_list* timer);
TIMER_INITIALIZER(_function, _expires, _data);
DEFINE_TIMER(_name, _function, _expires, _data);  //
定义并初始化。

 

对于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 */

      if ((info->delay & 0xffff) > 0) //设置延时时间

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

 

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

//写入寄存器ADCTSC01101 0011把模式变为等待中断模式,并设置触笔按下时触发中断

 

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

//支持触摸屏事件,更多解释可以看Documentationinputevent-codes.txt文档,或者查看博客:

http://blog.csdn.net/droidphone/article/details/8432055

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

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

/*********************************************************************************

这里是设置有关坐标的一些参数:

void input_set_abs_params(struct input_dev *dev, unsignedint axis,

                      int min, int max, int fuzz,intflat)

unsigned int axis,表示坐标轴,如: ABS_X ABS_Y ABS_Z等等

int min, int max 表示对应坐标轴的最大值和最小值。

int fuzz 表示滤波器用来消除噪声的制定毛刺值

int flat 对于像joydev这样的驱动,flat可看做阈值,当小于该值时,上报的都是0

********************************************************************************/

      ts.input->name = "S3C24XXTouchScreen"; // 执行cat /proc/bus/input/devices打印的信息

      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;//每次采样的个数,其个数等于=2^ oversampling_shift

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

/*********************************************************************************

得到数值0 .其定义为S3c2410_ts.c(drivers\\input\\touchscreen)

staticstruct platform_device_id s3cts_driver_ids[] =

      "s3c2410-ts", 0 ,

      "s3c2440-ts", 0 ,

      "s3c64xx-ts", FEAT_PEN_IRQ ,

     

;********************************************************************************/

 

      ret = request_irq(ts.irq_tc, stylus_irq,0, //申请中断,中断函数为stylus_irq

                      "s3c2410_ts_pen", ts.input);

      if (ret)

             dev_err(dev, "cannot get TCinterrupt\\n");

             goto err_inputdev;

     

 

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

 

      /* All went ok, so register to the inputsystem */

      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;

前面提到s3c24xx_ts_select函数的时候,提到,一个定时器中断函数,接下来看一下该函数:

static voidtouch_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);//检测是否触笔按下。假如按下返回true,否则返回fause

 

      if (down) //假如有触笔按下

             if (ts.count == (1 <<ts.shift)) //假如到达采样个数预定值,这里设定是2^2=4

                    ts.xp >>= ts.shift; //这是在s3c24xx_ts_conversion累加的,这里求均值

                    ts.yp >>= ts.shift;

                    dev_dbg(ts.dev, "%s:X=%lu, Y=%lu, count=%d\\n",

                           __func__, ts.xp,ts.yp, ts.count);

                    input_report_abs(ts.input,ABS_X, ts.xp);//上报x坐标

                    input_report_abs(ts.input,ABS_Y, ts.yp); //上报y坐标

 

                    input_report_key(ts.input,BTN_TOUCH, 1);//上报触摸屏被按下事件

                    input_sync(ts.input);//上报完毕

                    ts.xp = 0;//清0

                    ts.yp = 0;

                    ts.count = 0;

            

 

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

/*********************************************************************************

假如有触摸屏被按下但是还没有达到采样的次数,或者没有开启adc转换,

;********************************************************************************/

 

      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);//进入等待中断模式

     

家下来看一下中断服务函数,也就是当触笔按下屏幕的时候,会进入此中断函数中:

 

staticirqreturn_t stylus_irq(int irq, void *dev_id)

      unsigned long data0;

      unsigned long data1;

      bool down;

 

      data0 = readl(ts.io + S3C2410_ADCDAT0);//读取ADCDAT0的值

      data1 = readl(ts.io + S3C2410_ADCDAT1);

 

      down = get_down(data0, data1);//是否屏幕被按下

 

      /* TODO we should never get an interruptwith 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) // 对于s3c64xx-ts,if才可判定成功

             /* Clear pen down/up interrupt */

             writel(0x0, ts.io +S3C64XX_ADCCLRINTPNDNUP);

     

 

      return IRQ_HANDLED;

有关触摸屏的代码比较简单,这里就介绍到这里,现在总结一下其工作的流程:

首先在驱动初始化的时候回调用probe函数,在该函数中

调用ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,    s3c24xx_ts_conversion, 1);注册一个adc设备;然后调用input_dev =input_allocate_device();申请一个输入设备,并且调用ret = input_register_device(ts.input);注册成为一个输入设备;再此之间又调用ret=request_irq(ts.irq_tc, stylus_irq, 0,

"s3c2410_ts_pen",ts.input);函数注册一个中断,并且有函数writel(WAIT4INT| INT_DOWN, ts.io + S3C2410_ADCTSC);设置寄存器,使其处于等待中断模式。等到中断到来的时候(触摸屏被按下的时候),则调用中断函数stylus_irq,在该中断函数中,则调用s3c_adc_start(ts.client, 0, 1 << ts.shift);开始进行adc转换;函数s3c_adc_start调用s3c_adc_start函数,s3c_adc_start函数则调用s3c_adc_select和s3c_adc_convert函数;s3c_adc_select就会调用s3c24xx_ts_select函数用来将模式转换为自动转换模式,然后s3c_adc_convert会读取转换的数字。于此同时,在adc每次进入中断adc中断的时候,s3c_adc_irq会通过判断client->nr_samples的数值(即触摸屏需要采样的次数),来判定是否通过s3c24xx_ts_select函数将自动转换模式转化为等待中断模式。

一般情况下,触摸屏将处于等待中断模式下,一旦触摸屏被按下,则触发stylus_irq中断函数,该中断函数将调用s3c_adc_start函数,进而调用s3c_adc_try函数,继而调用s3c_adc_select函数,最后调用s3c24xx_ts_select(client,1),s3c24xx_ts_select函数将模式设置为自动转化模式。此时,cpu则开始转化ADC,等到转化完毕,则会进入ADC中断函数s3c_adc_irq中,在该函数中,则读取转化后AD的数值,并设置client->nr_samples--;来判断是否达到每次取值求平均的个数,然后调用s3c24xx_ts_conversion,将每次读取的AD值进行累加(以便后面计算平均值),假如没有到达需要每次取值求平均的个数,则重新调用s3c24xx_ts_select(client,1),仍设置为自动转化模式,然后等到AD转换完毕,则重新进去AD中断函数s3c_adc_irq就这么周而复始,等到达需要每次取值求平均的个数s3c_adc_irq则会调用(client->select_cb)(client,0);和s3c_adc_try(adc)函数,(client->select_cb)(client, 0)将会把模式设置为等待中断模式并社会下次触发是抬笔触发,且设置定时器为下一个jiffies时间,在定时器服务函数touch_timer_fire里,在touch_timer_fire函数里,假如判定触笔按下,在会上报坐标值,假如判定触笔抬起,则会上报触笔抬起信号并把下次触发中断设置为触笔按下触发中断。就这样周而复始的轮训这工作。

 

4、安装tslib校正触摸屏

本次安装的版本是网络上最常见的tslib1.4.

Tslib是一个开源的程序,能够为触摸屏驱动获得的采样提供诸如滤波、去抖、校准等功能,通常作为触摸屏驱动的适配层,为上层的应用提供了一个统一的接口。

首先将下载来的tslib1.4拷贝到linux系统下,解压:

也可以使用git下载:

git clonehttps://github.com/kergoth/tslib

(假如没有安装git先安装:sudo apt-get install get

下载完毕后解压:

$tar -xf tslib-1.4.tar.gz

进入talib加压后的目录:

执行:

./autogen.sh

然后执行配置命令:

./configure  --host=arm-inux\\

ac_cv_func_malloc_0_nonnull=yes  --cache-file=arm-linux-2440.cache  --prefix=/work/tools/tslib

其中--prefix是指定安装路径,把tslib安装到/work/tools/tslib目录

  --host=arm-inux的具体是交叉编译器的名字(假如没有的话建立软连接就可以)

完毕后,执行:

make

makeinstall

安装完成后进入安装的tslib目录会生成

可以看到在lib里面就是生成的一些库文件,我们把lib/生成的*so*拷贝到根文件系统目录下:

在tslab目录下执行:

cp ./lib/*so* /work/root/lib/

cp ./l     ib/ts  /work/root/lib/  -r

tslib/bin文件下所有文件复制到根文件系统的/bin目录下:

cp bin/* /work/root/bin/

/tslib/etc下的ts.conf文件复制到根文件系统的/etc下

cp etc/ts.conf  /work/root/etc/

然后修改刚拷贝的ts.conf文件:

去掉# module_raw input前面的注释以及空格(注意修改完是前面是没有空格的)

 

 

然后设置一下环境变量,可以在根文件系统的etc/profile (假如没有,新建文件即可)加入:

进入根文件系统目录,执行:

sudovi etc/profile 

 写入下列语句:

export TSLIB_TSDEVICE=/dev/input/event0

export TSLIB_CONFFILE=/etc/ts.conf

export TSLIB_PLUGINDIR=/lib/ts

export TSLIB_CALIBFILE=/tmp/pointercal

export TSLIB_CONSOLEDEVICE=none

export TSLIB_FBDEVICE=/dev/fb0

TSLIB_TSDEVICE——触摸屏设备文件,因人而异;

TSLIB_CONFFILE——配置文件名,就是前面复制并修改的ts.conf文件;

TSLIB_PLUGINDIR——插件目录

TSLIB_CALIBFILE——执行ts_calibrate会得到一个校准文件,指定的目录就是这个校正文件存储的目录,当需要判断触屏输入的位置时候,后调用本文件。

TSLIB_CONSOLEDEVICE——控制台设备文件名;

TSLIB_FBDEVICE——LCD设备节点,也要根据自己开发板的情况填写。

一切就绪就可以让开发板挂载上文件系统了,然后就可以校验:

输入:ts_calibrate

提示:

xres = 480, yres= 272

selecteddevice is not atouchscreen I understand

这是因为在tslib/plugins/input-raw.c的文件中有:

static intcheck_fd(struct tslib_input *i)

    

    structtsdev *ts =i->module.dev;

    intversion;

    u_int32_tbit;

    u_int64_tabsbit;

 

    if (!((ioctl(ts->fd,EVIOCGVERSION, &version) >= 0) &&

        (version== EV_VERSION) &&

        (ioctl(ts->fd,EVIOCGBIT(0, sizeof(bit) *8), &bit) >= 0) &&

        (bit& (1 << EV_ABS)) &&

        (ioctl(ts->fd,EVIOCGBIT(EV_ABS,sizeof(absbit) * 8), &absbit) >= 0) &&

        (absbit& (1 << ABS_X)) &&

        (absbit& (1 << ABS_Y)) &&(absbit & (1 << ABS_PRESSURE))))

        fprintf(stderr,"selecteddevice is not a touchscreen I understand ");

        return-1;

    

 

    if(bit & (1 <<EV_SYN))

        i->using_syn=1;

 

    return0;

这个函数,主要是个if语句。这里if中的判定的这八个条件只要有一个不满足(有取反符号)就会打印:

"selected device is not a touchscreen Iunderstand "

先看这八个条件:

(ioctl(ts->fd, EVIOCGVERSION, &version) >=0)  //读取驱动版本号

(version == EV_VERSION)                      //比较驱动版本号

 (ioctl(ts->fd, EVIOCGBIT(0, sizeof(bit) *8),&bit) >= 0)  //获取事件

 (bit & (1 << EV_ABS))                            //比较EV_ABS事件

 (ioctl(ts->fd,EVIOCGBIT(EV_ABS,sizeof(absbit) * 8), &absbit) >= 0)   //获取EV_ABS事件相关位

   (absbit & (1 <<ABS_X))                                     //比较ABS_X

  (absbit & (1 << ABS_Y))                                     //比较ABS_Y

(absbit & (1 << ABS_PRESSURE)))                             //比较ABS_PRESSURE

首先这里的驱动版本号,就是安装交叉编译工具链的时候用的内核版本是否跟现在内核版本是否相同,由于很多人下载别人的交叉编译工具链,所以这里的版本号很多时候不相同的。

我的交叉编译工具链的版本号在:

crosstool/arm-jason-linux-gnueabi/sysroot/usr/include/linux/input.h

我的内核的触摸屏驱动版本号在

/include/uapi/linux/input.h

 

不一致的话要修改一致即可

还有一个就是在linux3.14中并没有上报ABS_PRESSURE事件,这里需要添加到内核使之可以支持ABS_PRESSURE(压力值)事件。

这要修改三个地方:在drivers/input/touchscreen/s3c2410_ts.c文件中:

touch_timer_fire函数中:

  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_report_abs(ts.input, ABS_PRESSURE,  1);   //新加第一句

  input_sync(ts.input);

 

 

  input_report_key(ts.input, BTN_TOUCH, 0);

  input_report_abs(ts.input, ABS_PRESSURE,  0);  //新加第二句

  input_sync(ts.input);

在s3c2410ts_probe函数里:

  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);//新加的第三句

  ts.input->name = "S3C24XX TouchScreen";

注意第一句跟第二句的参数不一样,最后一个参数一个是1一个是0。

假如设置错误的话,运行tslib的测试程序会提示:

random: nonblocking pool is initialized

修改完毕后就可以编译下载内核到开发板,假如需要修改环境变量的话还需要修改环境变量。

运行ts_calibrate

屏幕出现十字符,点击校正。

校正后会在TSLIB_CALIBFILE=/tmp/pointercal

定义的目录里面生成pointercal校正文件。

然后运行ts_test

此时可以画点或者画线等等,如此表示校验成功。

可以进行测试

参考:

http://hi.baidu.com/396954504/item/45d171f027327014cf9f3226

http://www.latelee.org/embedded-linux/porting-linux-tslib.html

http://blog.csdn.net/zhaocj/article/details/37522115

以上是关于linux设备树-LCD触摸屏设备驱动的主要内容,如果未能解决你的问题,请参考以下文章

linux设备树-LCD驱动程序

编程\_LCD驱动程序框架\_使用设备树

Linux MIPI DSI驱动调试笔记-设备树DCS格式序列之配置LCD初始化代码

Linux MIPI DSI驱动调试笔记-设备树DCS格式序列之配置LCD初始化代码

基于瑞芯微RV1109 Linux触摸屏GT911驱动调试心得-设备树刷厂商给的触摸屏固件...

基于瑞芯微RV1109 Linux触摸屏GT911驱动调试心得-设备树刷厂商给的触摸屏固件...