1.1 本节阅读前提
本节的说明建立在前两节的基础之上,需要先阅读如下两篇章:
linux input输入子系统分析《一》:初识input输入子系统
linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析
1.2 触摸屏工作原理
S3C2440的触摸屏接口是4线电阻式触摸屏接口,可以控制x、y方向上的引脚(XP、XM、YP、YM)的变换,S3C2440触摸屏的硬件资源包括触摸屏引脚和ADC转换接口,可以使用寄存器组中的ADCTSC寄存器来操作触摸屏引脚资源,ADCCON寄存器来控制AD转换功能,由linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析中介绍的触摸屏接口工作模式为4种,这里我们只是用x/y方向自动转换模式和等待中断模式。自动转换模式用于转换x方向和y方向的值到ADCDAT0和ADCDAT1中,等待中断模式用于检测触摸屏的按下和抬起,一般使用上升沿和下降沿触发获得触摸屏事件。
触摸屏使用引脚的4个引脚XP、XM、YP、YM分别对应S3C2440芯片的AIN7、AIN6、AIN5、AIN4模拟输入源,其中按照linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析中的图4和图5可以看出AIN7接XP,AIN5接YP,由此可得x和y坐标的模拟信号由AIN5和AIN7引脚通过ADC转换器产生,产生的数据保存在ADCDAT0和ADCDAT1中。
1.3 驱动程序组成结构
分析代码前,有必要了解驱动程序的组成结构。
s3c2440ts_init()完成的功能:
使能adc的PCLK时钟源
映射操作触摸屏寄存器的地址
初始化寄存器
初始化输入设备
填充输入子系统设备结构体input_dev
申请中断IRQ_TS和IRQ_ADC
注册输入设备到输入子系统中
s3c2440ts_exit()完成的功能:
注销使用的系统资源
stylus_updown()完成的功能:
中断处理程序,完成对触摸屏按下和释放的判断
启动ADC转换
stylus_action()完成的功能:
ADC转换程序
上报事件
touch_timer_fire()完成的功能:
ADC的子功能
1.4 代码分析
代码如下:
-
-
-
-
-
-
- #include<linux/kernel.h> /* 提供prink等内核特有属性 */
- #include<linux/module.h> /* 提供模块及符号接口*/
- #include<linux/init.h> /* 设置段,如_init、_exit,设置初始化优先级,如__initcall */
- #include<linux/wait.h> /* 等待队列wait_queue */
- #include<linux/interrupt.h> /* 中断方式,如IRQF_SHARED */
- #include<linux/fs.h> /* file_operations操作接口等 */
- #include<linux/clk.h> /* 时钟控制接口,如struct clk */
- #include<linux/miscdevice.h> /* 杂项设备 */
- #include<asm/io.h> /* 提供readl、writel */
- #include<linux/irq.h> /* 提供中断相关宏 */
- #include<asm/irq.h> /* 提供中断号,中断类型等,如IRQ_ADC中断号 */
- #include<asm/arch/regs-adc.h> /* 提供控制器的寄存器操作,如S3C2410_ADCCON */
- #include<asm/uaccess.h> /* 提供copy_to_user等存储接口 */
- #include<linux/input.h> /* 内核输入子系统操作接口 */
- #include<linux/slab.h> /* kzalloc内存分配函数 */
- #include<linux/time.h> /* do_gettimeofday时间函数 */
- #include<linux/timer.h> /* timer定时器 */
-
-
- #define CONFIG_S3C2440_TOUCHSCREEN__DEBUG 1
-
-
- #define BITS_PER_LONG 32
- #define BIT_MASK(nr) (1UL << ((nr) %BITS_PER_LONG))
- #define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
-
-
-
-
- #define WAIT4INT(x) (((x)<<8) |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))
-
-
- struct ts_event {
- short pressure;
- short xp;
- short yp;
- };
-
-
- struct s3c2440_ts {
- struct input_dev *input;
- struct timer_list timer;
- struct ts_event tc;
- int pendown;
- int count;
- int shift;
- };
-
-
- static struct s3c2440_ts *s3c2440_ts;
-
-
- static void __iomem *base_addr;
-
-
- static struct clk *adc_clock;
-
-
- extern struct semaphore adc_lock;
-
-
-
- static void touch_timer_fire(unsigned long data)
- {
-
- unsigned long data0;
- unsigned long data1;
-
-
- set_irq_type(IRQ_TC, IRQT_NOEDGE);
- set_irq_type(IRQ_ADC, IRQT_NOEDGE);
-
-
- data0 = readl(base_addr + S3C2410_ADCDAT0);
- data1 = readl(base_addr + S3C2410_ADCDAT1);
-
-
- s3c2440_ts->pendown = (!(data0 &S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
-
-
- if (s3c2440_ts->pendown) {
-
-
-
-
-
- if(s3c2440_ts->count != 0) {
- s3c2440_ts->tc.xp>>= s3c2440_ts->shift;
- s3c2440_ts->tc.yp>>= s3c2440_ts->shift;
-
-
- #ifdef CONFIG_S3C2440_TOUCHSCREEN__DEBUG
- {
- struct timeval tv;
- do_gettimeofday(&tv);
- printk(KERN_INFO"T: %06d, X: %03x, Y: %03x\n", (int)tv.tv_usec, s3c2440_ts->tc.xp,s3c2440_ts->tc.yp);
- }
- #endif
-
- input_report_abs(s3c2440_ts->input,ABS_X, s3c2440_ts->tc.xp);
- input_report_abs(s3c2440_ts->input,ABS_Y, s3c2440_ts->tc.yp);
-
-
- input_report_abs(s3c2440_ts->input,ABS_PRESSURE, s3c2440_ts->tc.pressure);
-
-
- input_report_key(s3c2440_ts->input,BTN_TOUCH, s3c2440_ts->pendown);
-
-
- input_sync(s3c2440_ts->input);
- }
-
-
- s3c2440_ts->tc.xp = 0;
- s3c2440_ts->tc.yp = 0;
- s3c2440_ts->count = 0;
- s3c2440_ts->tc.pressure = 0;
-
-
-
-
- writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST, base_addr + S3C2410_ADCTSC);
-
-
- writel(readl(base_addr +S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr + S3C2410_ADCCON);
- } else {
-
- s3c2440_ts->count = 0;
-
-
- input_report_key(s3c2440_ts->input,BTN_TOUCH, 0);
-
-
- input_report_abs(s3c2440_ts->input,ABS_PRESSURE, 0);
-
-
- input_sync(s3c2440_ts->input);
-
-
- writel(WAIT4INT(0), base_addr +S3C2410_ADCTSC);
-
-
- up(&adc_lock);
- }
-
-
- set_irq_type(IRQ_TC, IRQT_BOTHEDGE);
- set_irq_type(IRQ_ADC, IRQT_BOTHEDGE);
- }
-
-
- static irqreturn_t stylus_updown(int irq, void *dev_id)
- {
-
- unsigned long data0;
- unsigned long data1;
-
-
- if (down_trylock(&adc_lock) == 0)
- {
-
- data0 = readl(base_addr +S3C2410_ADCDAT0);
- data1 = readl(base_addr +S3C2410_ADCDAT1);
-
-
- s3c2440_ts->pendown = (!(data0 &S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
-
-
- if (s3c2440_ts->pendown)
-
- touch_timer_fire(0);
- }
-
- return IRQ_RETVAL(IRQ_HANDLED);
- }
-
-
- static irqreturn_t stylus_action(int irq, void *dev_id)
- {
-
- unsigned long data0;
- unsigned long data1;
-
- #ifdef CONFIG_S3C2440_TOUCHSCREEN__DEBUG
- printk(KERN_ERR "%s() No.%dline:\n\r",__FUNCTION__,__LINE__);
- #endif
-
-
- data0 = readl(base_addr + S3C2410_ADCDAT0);
- data1 = readl(base_addr + S3C2410_ADCDAT1);
-
-
-
-
- s3c2440_ts->tc.xp += data0 &S3C2410_ADCDAT0_XPDATA_MASK;
- s3c2440_ts->tc.yp += data1 &S3C2410_ADCDAT1_YPDATA_MASK;
- s3c2440_ts->count++;
- s3c2440_ts->tc.pressure = 1;
-
-
- if (s3c2440_ts->count <(1<<s3c2440_ts->shift)) {
- writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST, base_addr + S3C2410_ADCTSC);
- writel(readl(base_addr+ S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr + S3C2410_ADCCON);
- } else {
-
- mod_timer(&s3c2440_ts->timer,jiffies + HZ / 100);
-
- writel(WAIT4INT(1),base_addr + S3C2410_ADCTSC);
- }
-
- return IRQ_HANDLED;
- }
-
-
- static void adc_init(void)
- {
-
-
-
-
- writel(S3C2410_ADCCON_PRSCEN |S3C2410_ADCCON_PRSCVL(49), base_addr + S3C2410_ADCCON);
-
-
- writel(0xffff, base_addr + S3C2410_ADCDLY);
-
-
-
-
-
-
-
-
- writel(WAIT4INT(0), base_addr +S3C2410_ADCTSC);
- }
-
-
-
-
-
- static int __init s3c2440ts_init(void)
- {
- struct input_dev *input_dev;
- int err = -ENOMEM;
-
- s3c2440_ts = kzalloc(sizeof(structs3c2440_ts), GFP_KERNEL);
-
- input_dev = input_allocate_device();
- if (!s3c2440_ts || !input_dev)
- gotofail1;
-
-
- adc_clock = clk_get(NULL, "adc");
- if (!adc_clock)
- {
- printk(KERN_ERR "failed to get adcclock source\n");
- return -ENOENT;
- }
-
-
- clk_enable(adc_clock);
-
-
-
-
- base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
- if (base_addr == NULL)
- {
- printk(KERN_ERR "Failed to remapregister block\n");
- return -ENOMEM;
- goto fail1;
- }
-
-
- adc_init();
-
-
- init_timer(&s3c2440_ts->timer);
- s3c2440_ts->timer.data = 1;
- s3c2440_ts->timer.function =touch_timer_fire;
-
-
- input_dev->name = "s3c2410Touchscreen";
- input_dev->phys ="s3c2440ts/input0";
- input_dev->id.bustype = BUS_HOST;
- input_dev->id.vendor = 0x1;
- input_dev->id.product = 0x2;
- input_dev->id.version = 0x0100;
-
- s3c2440_ts->shift = 2;
-
-
-
-
- input_dev->evbit[0] = BIT_MASK(EV_SYN) |BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
-
-
- input_dev->keybit[BIT_WORD(BTN_TOUCH)] =BIT_MASK(BTN_TOUCH);
-
-
-
- input_set_abs_params(input_dev, ABS_X, 0,0x3FF, 0, 0);
- input_set_abs_params(input_dev, ABS_Y, 0,0x3FF, 0, 0);
- input_set_abs_params(input_dev,ABS_PRESSURE, 0, 1, 0, 0);
-
-
-
- if(request_irq(IRQ_ADC, stylus_action,IRQF_SHARED | IRQF_SAMPLE_RANDOM, input_dev->name, s3c2440_ts))
- {
- printk(KERN_ERR "s3c2440_ts.c:Could not allocate ts IRQ_ADC !\n");
- err = -EBUSY;
- goto fail2;
- }
-
-
- if(request_irq(IRQ_TC, stylus_updown,IRQF_SAMPLE_RANDOM, input_dev->name, s3c2440_ts))
- {
- printk(KERN_ERR "s3c2440_ts.c:Could not allocate ts IRQ_TC !\n");
- err = -EBUSY;
- goto fail2;
- }
-
-
- s3c2440_ts->input = input_dev;
- err =input_register_device(s3c2440_ts->input);
- if(err)
- gotofail3;
-
-
- set_irq_type(IRQ_TC, IRQT_BOTHEDGE);
- set_irq_type(IRQ_ADC, IRQT_BOTHEDGE);
-
- return 0;
-
- fail3:
- free_irq(IRQ_TC, (void *)s3c2440_ts);
- free_irq(IRQ_ADC, (void *)s3c2440_ts);
- fail2:
- iounmap(base_addr);
- fail1:
- input_free_device(input_dev);
- kfree(s3c2440_ts);
-
- return err;
- }
-
- static void __exit s3c2440ts_exit(void)
- {
-
- disable_irq(IRQ_TC);
- disable_irq(IRQ_ADC);
- free_irq(IRQ_TC, (void *)s3c2440_ts);
- free_irq(IRQ_ADC, (void *)s3c2440_ts);
-
-
- del_timer_sync(&s3c2440_ts->timer);
-
-
- if(adc_clock)
- {
- clk_disable(adc_clock);
- clk_put(adc_clock);
- adc_clock = NULL;
- }
-
-
- input_unregister_device(s3c2440_ts->input);
-
-
- iounmap(base_addr);
-
-
- kfree(s3c2440_ts);
- }
-
- module_init(s3c2440ts_init);
- module_exit(s3c2440ts_exit);
-
- MODULE_AUTHOR("KevinLee <www.ielife.cn>");
- MODULE_DESCRIPTION("S3c2440TouchScreen Device Driver");
- MODULE_VERSION("S3C2440TOUCHSCREEN 1.0");
- MODULE_LICENSE("GPL");
1.5 添加Makefile及编译模块
Mkaefile脚本如下:
- MODULENAME:= s3c2440_ts.o
-
- ifneq($(KERNELRELEASE),)
- #call from kernel build system
- obj-m := $(MODULENAME)
-
- else
- #KERNELDIR?= /lib/modules/$(shell uname -r)/build
- KERNELDIR?= /work/system/linux-2.6.22.6
- PWD := $(shell pwd)
- default:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
-
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko*.mod.c .tmp_versions module* Module* $(APPNAME)
-
- depend.depend dep:
- $(CC) $(CFLAGS) -M *.c > .depend
-
- ifeq(.depend,$(wildcard .depend))
- include.depend
- endif
直接执行make,获得s3c2440_ts.ko文件,insmod进入内核,点击触摸屏可以看到驱动中打印的信息。
insmod驱动模块s3c2440_ts.ko之后,还可以通过cat /proc/bus/input/devices来查看输入设备在输入子系统中的信息:
I:Bus=0019 Vendor=0001 Product=0002 Version=0100
N:Name="s3c2410 Touchscreen"
P:Phys=s3c2440ts/input0
S:Sysfs=/class/input/input0
U:Uniq=
H:Handlers=mouse0 event0 evbug
B:EV=b
B:KEY=400 0 0 0 0 0 0 0 0 0 0
B:ABS=1000003