注:我的硬件平台是Tiny6410,触摸屏部分把默认的一线触摸改成了标准的四线触摸,硬件上就是把屏幕背面四个零欧电阻R34/R35/R36/R37取了,把取了的零欧电阻焊接到R28/R29/R30/R31。这样,软件上就可以用内核自带的触摸屏驱动了。
一、触摸屏驱动移植与tslib移植部分
(一). 触摸屏驱动移植
1. Kconfig修改
arch/arm/mach-s3c64xx/Kconfig里配置用到arch/arm/plat-samsung/devs.c里的平台设备,如下配置:
- @arch/arm/mach-s3c64xx/Kconfig
- about line 98 add:
- select SAMSUNG_DEV_ADC
- select SAMSUNG_DEV_TS
- then, it like this:
- config MACH_JASON6410
- bool "JASON6410"
- select CPU_S3C6410
- select S3C_DEV_FB
- select S3C64XX_SETUP_FB_24BPP
- select SAMSUNG_DEV_ADC
- select SAMSUNG_DEV_TS
- help
- Machine support for the JASON6410
下面是截取自devs.c里的用到的平台设备:- #if defined(CONFIG_SAMSUNG_DEV_ADC)
- static struct resource s3c_adc_resource[] = {
- [0] = DEFINE_RES_MEM(SAMSUNG_PA_ADC, SZ_256),
- [1] = DEFINE_RES_IRQ(IRQ_TC),
- [2] = DEFINE_RES_IRQ(IRQ_ADC),
- };
- struct platform_device s3c_device_adc = {
- .name = "samsung-adc",
- .id = -1,
- .num_resources = ARRAY_SIZE(s3c_adc_resource),
- .resource = s3c_adc_resource,
- };
- #endif /* CONFIG_SAMSUNG_DEV_ADC */
-
- #ifdef CONFIG_SAMSUNG_DEV_TS
- static struct resource s3c_ts_resource[] = {
- [0] = DEFINE_RES_MEM(SAMSUNG_PA_ADC, SZ_256),
- [1] = DEFINE_RES_IRQ(IRQ_TC),
- };
- static struct s3c2410_ts_mach_info default_ts_data __initdata = {
- .delay = 10000,
- .presc = 49,
- .oversampling_shift = 2,
- };
- struct platform_device s3c_device_ts = {
- .name = "s3c64xx-ts",
- .id = -1,
- .num_resources = ARRAY_SIZE(s3c_ts_resource),
- .resource = s3c_ts_resource,
- };
- void __init s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *pd)
- {
- if (!pd)
- pd = &default_ts_data;
- s3c_set_platdata(pd, sizeof(struct s3c2410_ts_mach_info),
- &s3c_device_ts);
- }
- #endif /* CONFIG_SAMSUNG_DEV_TS */
2. 内核配置
配置Event interface是因为tslib的TSLIB_TSDEVICE环境变量需要用到/dev/input/event0设备节点。
- Device Drivers --->
- Input device support --->
- [*] Touchscreens --->
- <*> Samsung S3C2410/generic touchscreen input driver
- Device Drivers --->
- Input device support --->
- <*> Event interface
3. BSP部分修改
如下绿色部分,是针对触摸屏驱动,在BSP上添加的代码。
- /* linux/arch/arm/mach-s3c64xx/mach-jason6410.c
- *
- * Copyright 2012 Jason Lu <gfvvz@yahoo.com.cn>
- * http://jason2012.blog.chinaunix.net
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- */
- #include <linux/init.h>
- #include <linux/interrupt.h>
- #include <linux/fb.h>
- #include <linux/gpio.h>
- #include <linux/kernel.h>
- #include <linux/list.h>
- #include <linux/dm9000.h>
- #include <linux/mtd/mtd.h>
- #include <linux/mtd/partitions.h>
- #include <linux/serial_core.h>
- #include <linux/types.h>
- #include <asm/mach-types.h>
- #include <asm/mach/arch.h>
- #include <asm/mach/map.h>
- #include <mach/map.h>
- #include <mach/regs-gpio.h>
- #include <mach/regs-modem.h>
- #include <mach/regs-srom.h>
- #include <plat/s3c6410.h>
- #include <plat/adc.h>
- #include <plat/cpu.h>
- #include <plat/devs.h>
- #include <plat/fb.h>
- #include <plat/nand.h>
- #include <plat/regs-serial.h>
- #include <plat/ts.h>
- #include <plat/regs-fb-v4.h>
- #include <plat/iic.h>
- #include <video/platform_lcd.h>
- #define UCON S3C2410_UCON_DEFAULT
- #define ULCON (S3C2410_LCON_CS8 | S3C2410_LCON_PNONE | S3C2410_LCON_STOPB)
- #define UFCON (S3C2410_UFCON_RXTRIG8 | S3C2410_UFCON_FIFOMODE)
- static struct s3c2410_uartcfg jason6410_uartcfgs[] __initdata = {
- [0] = {
- .hwport = 0,
- .flags = 0,
- .ucon = UCON,
- .ulcon = ULCON,
- .ufcon = UFCON,
- },
- [1] = {
- .hwport = 1,
- .flags = 0,
- .ucon = UCON,
- .ulcon = ULCON,
- .ufcon = UFCON,
- },
- [2] = {
- .hwport = 2,
- .flags = 0,
- .ucon = UCON,
- .ulcon = ULCON,
- .ufcon = UFCON,
- },
- [3] = {
- .hwport = 3,
- .flags = 0,
- .ucon = UCON,
- .ulcon = ULCON,
- .ufcon = UFCON,
- },
- };
- /* Framebuffer. */
- static struct s3c_fb_pd_win jason6410_fb_win0 = {
- /* this is to ensure we use win0 */
- .win_mode = {
- #if 0
- .pixclock = 115440,
- #endif
- .left_margin = 0x03,
- .right_margin = 0x02,
- .upper_margin = 0x01,
- .lower_margin = 0x01,
- .hsync_len = 0x28,
- .vsync_len = 0x01,
- .xres = 480,
- .yres = 272,
- },
- .max_bpp = 32,
- .default_bpp = 16,
- };
- /* 405566 clocks per frame => 60Hz refresh requires 24333960Hz clock */
- static struct s3c_fb_platdata jason6410_lcd_pdata __initdata = {
- .setup_gpio = s3c64xx_fb_gpio_setup_24bpp,
- .win[0] = &jason6410_fb_win0,
- .vidcon0 = VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,
- .vidcon1 = VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,
- };
- /* Nand flash */
- static struct mtd_partition jason6410_nand_part[] = {
- {
- .name = "u-boot-2011.06",
- .offset = 0,
- .size = (4 * 128 *SZ_1K),
- .mask_flags = MTD_CAP_NANDFLASH,
- },
- {
- .name = "Linux Kernel 3.2.8",
- .offset = MTDPART_OFS_APPEND,
- .size = (5*SZ_1M) ,
- .mask_flags = MTD_CAP_NANDFLASH,
- },
- {
- .name = "UBI File System",
- .offset = MTDPART_OFS_APPEND,
- .size = MTDPART_SIZ_FULL,
- }
- };
- static struct s3c2410_nand_set jason6410_nand_sets[] = {
- [0] = {
- .name = "nand",
- .nr_chips = 1,
- .nr_partitions = ARRAY_SIZE(jason6410_nand_part),
- .partitions = jason6410_nand_part,
- },
- };
- static struct s3c2410_platform_nand jason6410_nand_info = {
- .tacls = 25,
- .twrph0 = 55,
- .twrph1 = 40,
- .nr_sets = ARRAY_SIZE(jason6410_nand_sets),
- .sets = jason6410_nand_sets,
- };
- static struct map_desc jason6410_iodesc[] = {
- };
- static struct platform_device *jason6410_devices[] __initdata = {
- &s3c_device_nand,
- &s3c_device_i2c0,
- &s3c_device_fb,
- &s3c_device_ts,
- &s3c_device_adc,
- };
- static struct i2c_board_info i2c_devs0[] __initdata = {
- { I2C_BOARD_INFO("24c08", 0x50), },
- };
- static struct i2c_board_info i2c_devs1[] __initdata = {
- /* Add your i2c device here */
- };
- static struct s3c2410_ts_mach_info s3c_ts_platform __initdata = {
- .delay = 0xFFFF,
- .presc = 0xFF,
- .oversampling_shift = 2,
- };
- static void __init jason6410_map_io(void)
- {
- u32 tmp;
- s3c64xx_init_io(jason6410_iodesc, ARRAY_SIZE(jason6410_iodesc));
- s3c24xx_init_clocks(12000000);
- s3c24xx_init_uarts(jason6410_uartcfgs, ARRAY_SIZE(jason6410_uartcfgs));
- /* set the LCD type */
- tmp = __raw_readl(S3C64XX_SPCON);
- tmp &= ~S3C64XX_SPCON_LCD_SEL_MASK;
- tmp |= S3C64XX_SPCON_LCD_SEL_RGB;
- __raw_writel(tmp, S3C64XX_SPCON);
- /* remove the lcd bypass */
- tmp = __raw_readl(S3C64XX_MODEM_MIFPCON);
- tmp &= ~MIFPCON_LCD_BYPASS;
- __raw_writel(tmp, S3C64XX_MODEM_MIFPCON);
- }
- static void __init jason6410_machine_init(void)
- {
- s3c_device_nand.name = "s3c6410-nand";
- s3c_nand_set_platdata(&jason6410_nand_info);
- s3c_i2c0_set_platdata(NULL);
- s3c_fb_set_platdata(&jason6410_lcd_pdata);
- s3c24xx_ts_set_platdata(&s3c_ts_platform);
- if (ARRAY_SIZE(i2c_devs0)) {
- i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
- }
- if (ARRAY_SIZE(i2c_devs1)) {
- i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
- }
- platform_add_devices(jason6410_devices, ARRAY_SIZE(jason6410_devices));
- }
- MACHINE_START(JASON6410, "JASON6410")
- /* Maintainer: Darius Augulis <augulis.darius@gmail.com> */
- .atag_offset = 0x100,
- .init_irq = s3c6410_init_irq,
- .map_io = jason6410_map_io,
- .init_machine = jason6410_machine_init,
- .timer = &s3c24xx_timer,
- MACHINE_END
(二). tslib移植及测试
1. 为虚拟机里的Linux系统安装工具:
- sudo apt-get install autoconf
- sudo apt-get install automake
- sudo apt-get install libtool
2. 解压后编译- mv xxx(解压后名字) tslib //名字改为tslib
- cd tslib
- ./autogen.sh
- mkdir tmp
- echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
- ./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
- make
- make install
3. 打开tmp目录,里面有四个文件夹,分别是bin、etc、include、lib。将etc目录下的ts.conf里的第2行去掉注释。即:- # module_raw input
- 改为:
- module_raw input
4. 将bin/etc/lib目录下的文件及连接拷贝到文件系统下对应同名目录。include目录估计是编译应用程序时用的,在此可以不使用。
5. 修改etc/init.d/rcS,注释掉启动QT界面的代码:
6. 重新生成文件系统镜像,并且重新烧写新的内核与文件系统。
7. 在串口终端,顺序输入下列环境变量。
- export TSLIB_TSDEVICE=/dev/input/event0
- export TSLIB_CALIBFILE=/etc/pointercal
- export TSLIB_CONFFILE=/etc/ts.conf
- export TSLIB_PLUGINDIR=/lib/ts
- export TSLIB_CONSOLEDEVICE=none
- export TSLIB_FBDEVICE=/dev/fb0
8. 终端输入命令ts_calibrate,LCD如下显示类似如下界面,触摸校准触摸屏。
9. 终端输入命令ts_test,LCD显示类似如下界面,可以触摸屏幕,十字光标跟随触摸点移动,按Draw按钮,可以画出触摸轨迹。
二、触摸屏及ADC驱动分析
(一)代码详细分析
1. 触摸屏驱动中的probe函数分析
- /**
- * 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 __devinit s3c2410ts_probe(struct platform_device *pdev)
- {
- struct s3c2410_ts_mach_info *info;
- struct device *dev = &pdev->dev;
- struct input_dev *input_dev;
- struct resource *res;
- int ret = -EINVAL;
- /* Initialise input stuff */
- memset(&ts, 0, sizeof(struct s3c2410ts)); //清零ts结构体
- ts.dev = dev; //ts的设备成员指向匹配的平台设备
- info = pdev->dev.platform_data; //info指向平台设备的平台数据
- 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"); //获取ADC时钟
- if (IS_ERR(ts.clock)) {
- dev_err(dev, "cannot get adc clock source\n");
- return -ENOENT;
- }
- clk_enable(ts.clock); //使能ADC时钟
- dev_dbg(dev, "got and enabled clocks\n");
- ts.irq_tc = ret = platform_get_irq(pdev, 0); //获取中断号并赋给irq_tc成员
- 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) //如果定义了GPIO,就在这里初始化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); //写入0xd3,看下图
- 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成员指向新分配的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); //X轴
- input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0); //Y轴
- 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; //驱动数据
- static struct 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, //注册触摸屏中断
- "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:
- del_timer_sync(&touch_timer);
- clk_put(ts.clock);
- return ret;
- }
2. 触摸屏驱动中的stylus_irq函数分析- /**
- * stylus_irq - touchscreen stylus event interrupt
- * @irq: The interrupt number
- * @dev_id: The device ID.
- *
- * Called when the IRQ_TC is fired for a pen up or down event.
- */
- static irqreturn_t stylus_irq(int irq, void *dev_id)
- {
- unsigned long data0;
- unsigned long data1;
- bool down;
-
- //读取ADCDAT0寄存器中数据,最高位UPDOWN是按下/释放标志
- data0 = readl(ts.io + S3C2410_ADCDAT0);
- data1 = readl(ts.io + S3C2410_ADCDAT1);
- down = get_down(data0, data1); //根据ADCDAT0/ADCDAT1最高位值判断按下还是释放触摸屏
- /* TODO we should never get an interrupt with down set while
- * the timer is running, but maybe we ought to verify that the
- * timer isn't running anyways. */
- if (down)
- s3c_adc_start(ts.client, 0, 1 << ts.shift); //如果是按下,启动ADC采样
- else
- dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);
- if (ts.features & FEAT_PEN_IRQ) {
- /* Clear pen down/up interrupt */
- writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP); //清零触摸中断
- }
- return IRQ_HANDLED;
- }
3. ADC驱动中的probe函数分析 stylus_irq中的s3c_adc_start函数位于ADC驱动中,在分析它之前,还是先分析ADC驱动的probe函数。有一点要说明的是,在devs.c中定义的ADC平台设备,名称是"samsung-adc",然而在ADC平台驱动结构体的id_table里没有这个名称,只有"s3c64xx-adc",启动信息里可以看到,匹配名就是这个,那么这个名称是从哪来的呢?通过搜索内核代码,发现在s3c6410.c里,有如下代码:
- void __init s3c6410_map_io(void)
- {
- /* initialise device information early */
- s3c6410_default_sdhci0();
- s3c6410_default_sdhci1();
- s3c6410_default_sdhci2();
- /* the i2c devices are directly compatible with s3c2440 */
- s3c_i2c0_setname("s3c2440-i2c");
- s3c_i2c1_setname("s3c2440-i2c");
- s3c_adc_setname("s3c64xx-adc");
- s3c_device_nand.name = "s3c6400-nand";
- s3c_onenand_setname("s3c6410-onenand");
- s3c64xx_onenand1_setname("s3c6410-onenand");
- s3c_cfcon_setname("s3c64xx-pata");
- }
如上s3c_adc_setname函数的源码如下,给函数就是重命名结构体名字用的!- /* re-define device name depending on support. */
- static inline void s3c_adc_setname(char *name)
- {
- #if defined(CONFIG_SAMSUNG_DEV_ADC) || defined(CONFIG_PLAT_S3C24XX)
- s3c_device_adc.name = name;
- #endif
- }
接下来还有一个问题,就是调用s3c_adc_setname函数的s3c6410_map_io函数在内核启动的什么时候被调用?被哪个函数调用?通过代码追踪,发下如下关系:- MACHINE_START(JASON6410, "JASON6410")
- /* Maintainer: Darius Augulis <augulis.darius@gmail.com> */
- .atag_offset = 0x100,
- .init_irq = s3c6410_init_irq,
- .map_io = jason6410_map_io,
- .init_machine = jason6410_machine_init,
- .timer = &s3c24xx_timer,
- MACHINE_END
-
- jason6410_map_io
- s3c64xx_init_io
- s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));
- static struct cpu_table cpu_ids[] __initdata = {
- {
- .idcode = S3C6400_CPU_ID,
- .idmask = S3C64XX_CPU_MASK,
- .map_io = s3c6400_map_io,
- .init_clocks = s3c6400_init_clocks,
- .init_uarts = s3c6400_init_uarts,
- .init = s3c6400_init,
- .name = name_s3c6400,
- }, {
- .idcode = S3C6410_CPU_ID,
- .idmask = S3C64XX_CPU_MASK,
- .map_io = s3c6410_map_io,
- .init_clocks = s3c6410_init_clocks,
- .init_uarts = s3c6410_init_uarts,
- .init = s3c6410_init,
- .name = name_s3c6410,
- },
- };
好了,圆规正传,开始分析probe函数。- 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 = kzalloc(sizeof(struct adc_device), GFP_KERNEL); //分配一个adc_device结构体
- if (adc == NULL) {
- dev_err(dev, "failed to allocate adc_device\n");
- return -ENOMEM;
- }
- spin_lock_init(&adc->lock); //初始化自旋锁
- adc->pdev = pdev;
- adc->prescale = S3C2410_ADCCON_PRSCVL(49); //预分频
- adc->vdd = regulator_get(dev, "vdd");
- if (IS_ERR(adc->vdd)) {
- dev_err(dev, "operating without regulator \"vdd\" .\n");
- ret = PTR_ERR(adc->vdd);
- goto err_alloc;
- }
- adc->irq = platform_get_irq(pdev, 1); //获取ADC中断
- if (adc->irq <= 0) {
- dev_err(dev, "failed to get adc irq\n");
- ret = -ENOENT;
- goto err_reg;
- }
- ret = request_irq(adc->irq, s3c_adc_irq, 0, dev_name(dev), adc); //注册ADC中断
- if (ret < 0) {
- dev_err(dev, "failed to attach adc irq\n");
- goto err_reg;
- }
- adc->clk = clk_get(dev, "adc"); //获取ADC时钟
- if (IS_ERR(adc->clk)) {
- dev_err(dev, "failed to get adc clock\n");
- ret = PTR_ERR(adc->clk);
- goto err_irq;
- }
- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取ADC寄存器地址范围
- if (!regs) {
- dev_err(dev, "failed to find registers\n");
- ret = -ENXIO;
- goto err_clk;
- }
- adc->regs = ioremap(regs->start, resource_size(regs)); //寄存器地址映射到内存
- if (!adc->regs) {
- dev_err(dev, "failed to map registers\n");
- ret = -ENXIO;
- goto err_clk;
- }
- ret = regulator_enable(adc->vdd);
- if (ret)
- goto err_ioremap;
- clk_enable(adc->clk); //使能ADC时钟
- 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;
-
- //ADCCON寄存器配置:12位、预分频使能、预分频值49
- writel(tmp, adc->regs + S3C2410_ADCCON);
- dev_info(dev, "attached adc driver\n");
- platform_set_drvdata(pdev, adc);
- adc_dev = adc; //adc_dev指向这里分配和设置的adc_device结构体,下面的函数要用到
- return 0;
- err_ioremap:
- iounmap(adc->regs);
- err_clk:
- clk_put(adc->clk);
- err_irq:
- free_irq(adc->irq, adc);
- err_reg:
- regulator_put(adc->vdd);
- err_alloc:
- kfree(adc);
- return ret;
- }
4. ADC驱动中的s3c_adc_start函数分析
- int s3c_adc_start(struct s3c_adc_client *client,
- unsigned int channel, unsigned int nr_samples)
- {
- struct adc_device *adc = adc_dev;
- unsigned long flags;
-
- /* 先判断adc所指向的adc_device是否有效,可以作为设备驱动是否匹配的判断依据;
- * 因为adc_dev是在上面的probe函数里被复制的
- */
- if (!adc) {
- printk(KERN_ERR "%s: failed to find adc\n", __func__);
- return -EINVAL;
- }
- if (client->is_ts && adc->ts_pend) //如果是触摸屏并且是当前有客户,则返回
- return -EAGAIN;
- spin_lock_irqsave(&adc->lock, flags); //获取自旋锁,并把中断状态存入flags
- client->channel = channel; //采样通道
- client->nr_samples = nr_samples; //采样次数
- if (client->is_ts) //如果是触摸屏,将client保存在adc->ts_pend中
- adc->ts_pend = client;
- else //如果不是触摸屏,将client->pend插入adc_pending链表中
- list_add_tail(&client->pend, &adc_pending);
- if (!adc->cur) //如果当前没有处理其他用户请求,则执行新的用户请求
- s3c_adc_try(adc);
- spin_unlock_irqrestore(&adc->lock, flags); //释放自旋锁,恢复中断状态
- return 0;
- }
- EXPORT_SYMBOL_GPL(s3c_adc_start); //将函数导出,让所有遵循GPL协议的模块都可以使用
5. ADC驱动中的s3c_adc_try函数分析
- static void s3c_adc_try(struct adc_device *adc)
- {
- struct s3c_adc_client *next = adc->ts_pend;
- if (!next && !list_empty(&adc_pending)) {
- next = list_first_entry(&adc_pending,
- struct s3c_adc_client, pend);
- list_del(&next->pend);
- } else
- adc->ts_pend = NULL; //去除客户
- if (next) { //如果触摸屏有客户请求,则执行下列函数
- adc_dbg(adc, "new client is %p\n", next);
- adc->cur = next;
- s3c_adc_select(adc, next);
- s3c_adc_convert(adc);
- s3c_adc_dbgshow(adc); //打印ADCCON,ADCTSC,ADCDLY三个寄存器值
- }
- }
6. ADC驱动中的s3c_adc_select函数分析
- static inline void s3c_adc_select(struct adc_device *adc,
- struct s3c_adc_client *client)
- {
- unsigned con = readl(adc->regs + S3C2410_ADCCON); //读取ADCCON寄存器当前值
- enum s3c_cpu_type cpu = platform_get_device_id(adc->pdev)->driver_data; //CPU类型
-
- //执行客户定义的select回调函数,也就是触摸屏驱动中的s3c24xx_ts_select函数
- client->select_cb(client, 1);
- /**
- * s3c24xx_ts_select - ADC selection callback.
- * @client: The client that was registered with the ADC core.
- * @select: The reason for select.
- *
- * Called when the ADC core selects (or deslects) us as a client.
- */
- static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
- {
- if (select) {//启用自动(连续)x/y轴坐标转换模式
- writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
- ts.io + S3C2410_ADCTSC);
- } else {//将touch_timer定时器定时时间设置为此后一个时钟滴答,并进入中断等待模式
- mod_timer(&touch_timer, jiffies+1); //1 jiffies = 1/Hz s Hz可取100
- writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
- }
- }
- if (cpu == TYPE_ADCV1 || cpu == TYPE_ADCV2)
- con &= ~S3C2410_ADCCON_MUXMASK;
- con &= ~S3C2410_ADCCON_STDBM;
- con &= ~S3C2410_ADCCON_STARTMASK;
- if (!client->is_ts) {
- if (cpu == TYPE_ADCV3)
- writel(client->channel & 0xf, adc->regs + S5P_ADCMUX);
- else if (cpu == TYPE_ADCV11 || cpu == TYPE_ADCV12)
- writel(client->channel & 0xf,
- adc->regs + S3C2443_ADCMUX);
- else
- con |= S3C2410_ADCCON_SELMUX(client->channel);
- }
- writel(con, adc->regs + S3C2410_ADCCON);
- }
7. ADC驱动中的s3c_adc_convert函数分析
- static inline void s3c_adc_convert(struct adc_device *adc)
- {
- unsigned con = readl(adc->regs + S3C2410_ADCCON);
- con |= S3C2410_ADCCON_ENABLE_START; //启动ADC转换
- writel(con, adc->regs + S3C2410_ADCCON);
- }
8. ADC驱动中的s3c_adc_irq函数分析
- static irqreturn_t s3c_adc_irq(int irq, void *pw)
- {
- struct adc_device *adc = pw;
- struct s3c_adc_client *client = adc->cur;
- enum s3c_cpu_type cpu = platform_get_device_id(adc->pdev)->driver_data;
- unsigned data0, data1;
- if (!client) {
- dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__);
- goto exit;
- }
- data0 = readl(adc->regs + S3C2410_ADCDAT0);
- data1 = readl(adc->regs + S3C2410_ADCDAT1);
- adc_dbg(adc, "read %d: 0x%04x, 0x%04x\n", client->nr_samples, data0, data1);
- client->nr_samples--; //s3c_adc_start函数中被首次复制
- if (cpu == TYPE_ADCV1 || cpu == TYPE_ADCV11) {
- data0 &= 0x3ff;
- data1 &= 0x3ff;
- } else {
- /* S3C2416/S3C64XX/S5P ADC resolution is 12-bit */
- data0 &= 0xfff;
- data1 &= 0xfff;
- }
-
- //回调函数,在触摸屏驱动中定义
- if (client->convert_cb)
- (client->convert_cb)(client, data0, data1, &client->nr_samples);
- /**
- * s3c24xx_ts_conversion - ADC conversion callback
- * @client: The client that was registered with the ADC core.
- * @data0: The reading from ADCDAT0.
- * @data1: The reading from ADCDAT1.
- * @left: The number of samples left.
- *
- * Called when a conversion has finished.
- */
- static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
- unsigned data0, unsigned data1,
- unsigned *left)
- {
- dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
- ts.xp += data0;
- ts.yp += data1;
- ts.count++;
- /* From tests, it seems that it is unlikely to get a pen-up
- * event during the conversion process which means we can
- * ignore any pen-up events with less than the requisite
- * count done.
- *
- * In several thousand conversions, no pen-ups where detected
- * before count completed.
- */
- }
- if (client->nr_samples > 0) { //如果大于零,表示还未达到多次采样的次数,继续启动
- /* fire another conversion for this */
- client->select_cb(client, 1);
- s3c_adc_convert(adc);
- } else { //如果等于零,表示已到达多次采样次数,启动最后一次采样
- spin_lock(&adc->lock);
- //将touch_timer定时器定时时间设置为此后一个时钟滴答,并进入中断等待模式
- (client->select_cb)(client, 0);
- adc->cur = NULL;
- s3c_adc_try(adc); //启动最后一次采样
- spin_unlock(&adc->lock);
- }
- exit:
- if (cpu == TYPE_ADCV2 || cpu == TYPE_ADCV3) {
- /* Clear ADC interrupt */
- writel(0, adc->regs + S3C64XX_ADCCLRINT);
- }
- return IRQ_HANDLED;
- }
9. 触摸屏驱动中的touch_timer_fire函数分析 当上面select_cb回调函数中设置的定时器定时时间到到达时,会调用如下函数。- static void touch_timer_fire(unsigned long data)
- {
- unsigned long data0;
- unsigned long data1;
- bool down;
- data0 = readl(ts.io + S3C2410_ADCDAT0);
- data1 = readl(ts.io + S3C2410_ADCDAT1);
- down = get_down(data0, data1);
- if (down) { //按下触摸屏
- if (ts.count == (1 << ts.shift)) { //如果到达多次采样数
- ts.xp >>= ts.shift; //除以多次采样数
- ts.yp >>= ts.shift;
- dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\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; 清零
- ts.yp = 0;
- ts.count = 0;
- }
- s3c_adc_start(ts.client, 0, 1 << ts.shift); //启动采样
- } else { //释放触摸屏
- ts.xp = 0;
- ts.yp = 0;
- ts.count = 0;
- input_report_key(ts.input, BTN_TOUCH, 0); //向上报告释放
- input_sync(ts.input); //同步
- writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC); //等待中断模式
- }
- }
(二)总结过程
首先probe函数执行完后,触摸屏进入等待中断模式。当有触摸屏中断发生时,即检测到被触摸时,进入触摸屏中断处理函数。中断函数判断是否被按下,如果是,调用s3c_adc_start,s3c_adc_start完成一些初始化工作后,调用s3c_adc_try,该函数检测等待处理的客户(client),然后调用s3c_adc_select,该函数会调用客户定义的select回调函数选择客户(client)。s3c_adc_try执行完s3c_adc_select后,又会调用s3c_adc_convert,该函数启动AD转换,转换结束后,触发中断,中断处理函数s3c_adc_irq会首先调用s3c24xx_ts_conversion,累加x,y坐标值及采样次数,如果采样次数未到达多次采样次数,继续采样,否者调用client->select_cb(client, 0)设置定时器和重新进入等待中断模式。
- stylus_irq ->
- s3c_adc_start ->
- s3c_adc_try ->
- s3c_adc_select ->
- client->select_cb(client, 1)
- s3c_adc_convert ->
- s3c_adc_irq ->
- (client->convert_cb)(client, data0, data1, &client->nr_samples)
- (client->select_cb)(client, 0)
阅读(6448) | 评论(0) | 转发(4) |