程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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;
}
View Code

全局变量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;
}
View Code

三、修改驱动程序

由于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  //安装到tmp目录下   

需要注意的是编译所使用的的版本要和内核编译的版本保持一致,我用的是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触摸屏驱动移植

posted @ 2023-05-06 01:09  大奥特曼打小怪兽  阅读(702)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步