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, };
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 //安装到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可退出该程序。
参考文章