input子系统八之触摸屏驱动实例2(源码分析)
2014-10-17 11:34
621 查看
1.1 本节阅读前提
本节的说明建立在前两节的基础之上,需要先阅读如下两篇章:
初识input输入子系统
s3c2440的ADC简单驱动实例分析
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中。
分析代码前,有必要了解驱动程序的组成结构。
s3c2440ts_init()完成的功能:
使能adc的PCLK时钟源
映射操作触摸屏寄存器的地址
初始化寄存器
初始化输入设备
填充输入子系统设备结构体input_dev
申请中断IRQ_TS和IRQ_ADC
注册输入设备到输入子系统中
s3c2440ts_exit()完成的功能:
注销使用的系统资源
stylus_updown()完成的功能:
中断处理程序,完成对触摸屏按下和释放的判断
启动ADC转换
stylus_action()完成的功能:
ADC转换程序
上报事件
touch_timer_fire()完成的功能:
ADC的子功能
代码如下:
[cpp] view
plaincopy
/*
* s3c2440 触摸屏驱动程序
*
* Kevin Lee <www.ielife.cn>
*/
#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)
/* 定义一个宏WAIT4INT,用于对ADCTSC触摸屏控制寄存器进行操作,
* S3C2410_ADCTSC_YM_SEN等在内核include/asm/arch/regs-adc.h中被定义
*/
#define WAIT4INT(x) (((x)<<8) |S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | \
S3C2410_ADCTSC_XP_SEN |S3C2410_ADCTSC_XY_PST(3))
/* 定义一个宏AUTOPST,用于设置ADCTSC触摸屏控制寄存器为自动转换模式 */
#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; /*触摸屏x坐标值 */
short yp; /*触摸屏y坐标值 */
};
/* 触摸屏设备结构体 */
struct s3c2440_ts {
struct input_dev *input;/* 输入子系统设备结构体 */
struct timer_list timer;/* 定时器,用于ADC转换操作 */
struct ts_event tc; /* ADC转换的值的保存位置,也用于上报input子系统的数据 */
int pendown; /* 判断是否有按下 */
int count; /* 用于驱动去抖的计数 */
int shift; /* 用于驱动去抖的基数 */
};
/* 定义触摸屏设备结构体 */
static struct s3c2440_ts *s3c2440_ts;
/* 定义虚拟地址访问硬件寄存器,__iomem只是用于表示指针将指向I/O内存 */
static void __iomem *base_addr;
/* 定义adc时钟,通过adc_clock接口获得adc输入时钟,adc转换器需要 */
static struct clk *adc_clock;
/* 申明外部定义的信号量,adc.c中定义,处理IRQ_ADC共享中断引起的资源互斥 */
extern struct semaphore adc_lock;
//DECLARE_MUTEX(adc_lock);
/* 处理触摸屏的数据及事件上报,属于中断调用的函数,不能睡眠 */
static void touch_timer_fire(unsigned long data)
{
/* 保存ADCDAT0及ADCDAT1的x,y坐标值 */
unsigned long data0;
unsigned long data1;
/* 禁止中断,处理完数据再打开中断 */
set_irq_type(IRQ_TC, IRQT_NOEDGE);
set_irq_type(IRQ_ADC, IRQT_NOEDGE);
/* 读取ADCDAT0和ADCDAT1寄存器,提取ADC转换的x,y坐标值 */
data0 = readl(base_addr + S3C2410_ADCDAT0);
data1 = readl(base_addr + S3C2410_ADCDAT1);
/* 判断ADCDAT0和ADCDAT1中的[15]位,[15]位为等待中断模式下用于判断笔尖是否有抬起或落下,0=落下 */
s3c2440_ts->pendown = (!(data0 &S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
/* 当触摸屏处于被按下状态,执行下面代码 */
if (s3c2440_ts->pendown) {
/*count不为0,说明正在转换,xp和yp往右移动shift(2)位是为了去抖
* 需要结合stylus_action中断函数来看,当count=4时,才能认为转换结束,
* 即最后所得的xp和yp是被计算4次的,最终上报时需要除以4,因此这里预先
* 右移2位,相当于乘以4
*/
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
/*报告X、Y的绝对坐标值*/
input_report_abs(s3c2440_ts->input,ABS_X, s3c2440_ts->tc.xp);
input_report_abs(s3c2440_ts->input,ABS_Y, s3c2440_ts->tc.yp);
/*报告触摸屏的状态,1表明触摸屏被按下*/
input_report_abs(s3c2440_ts->input,ABS_PRESSURE, s3c2440_ts->tc.pressure);
/*报告按键事件,键值为1(代表触摸屏对应的按键被按下)*/
input_report_key(s3c2440_ts->input,BTN_TOUCH, s3c2440_ts->pendown);
/*等待接收方受到数据后回复确认,用于同步*/
input_sync(s3c2440_ts->input);
}
/* count=0执行这里面的代码,ADC还没有开始转换 */
s3c2440_ts->tc.xp = 0;
s3c2440_ts->tc.yp = 0;
s3c2440_ts->count = 0;
s3c2440_ts->tc.pressure = 0;
/* 因为触摸屏是按下状态,ADC还没有转换,需要启动ADC开始转换
* 本句代码是设置触摸屏为自动转换模式
*/
writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST, base_addr + S3C2410_ADCTSC);
/* 启动ADC转换 */
writel(readl(base_addr +S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr + S3C2410_ADCCON);
} else {
/* 执行到这里说明按键没有被按下或按键抬起,count清0 */
s3c2440_ts->count = 0;
/* 报告按键事件,给0值说明按键抬起 */
input_report_key(s3c2440_ts->input,BTN_TOUCH, 0);
/* 报告按键事件,给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);
}
/* 使能中断触发条件,IRQT_BOTHEDGE为使能上升沿和下降沿触发 */
set_irq_type(IRQ_TC, IRQT_BOTHEDGE);
set_irq_type(IRQ_ADC, IRQT_BOTHEDGE);
}
/* 触摸屏中断服务程序,触摸屏按下或抬起时触发执行 */
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
/* 用于记录ADC转换后的值 */
unsigned long data0;
unsigned long data1;
/* 由于是中断程序,所以不能使用down和down_interruptible,会导致睡眠 */
if (down_trylock(&adc_lock) == 0)
{
/* 读取ADCDAT0和ADCDAT1,用于判断触摸屏是否被按下 */
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)
/* 启动ADC开始转换数据 */
touch_timer_fire(0);
}
return IRQ_RETVAL(IRQ_HANDLED);
}
/*ADC中断服务程序,ADC启动后被执行 */
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
/* 获取触摸屏的x,y坐标值 */
data0 = readl(base_addr + S3C2410_ADCDAT0);
data1 = readl(base_addr + S3C2410_ADCDAT1);
/* 既然按键按下了,执行到此ADC转换也开始了,应该取得x,y坐标值了
* x=ADCDAT0[9:0],y=ADCDAT1[9:0],count++,presssure设置为1
*/
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;
/* 如果count小于4,需要重启设置自动转换模式,并进行ADC转换,用于去抖 */
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 {
/*到这里说明count=4,启动定时器去执行touch_timer_fire函数上报按键和触摸事件*/
mod_timer(&s3c2440_ts->timer,jiffies + HZ / 100);
/*检测触摸屏抬起的中断信号*/
writel(WAIT4INT(1),base_addr + S3C2410_ADCTSC);
}
return IRQ_HANDLED;
}
/* 初始化ADC控制寄存器和ADC触摸屏控制寄存器 */
static void adc_init(void)
{
/* S3C2410_ADCCON_PRSCEN设置ADCCON的位[14]=1为使能A/D预分频器
* S3C2410_ADCCON_PRSCVL设置ADCCON的位[13:6]=32表示设置的分频值,
* ADC的转换频率需要在2.5MHZ以下,我们使用的ADC输入时钟为PCLK=50MHZ,50MHZ/(49+1)=1MHZ,满足条件
*/
writel(S3C2410_ADCCON_PRSCEN |S3C2410_ADCCON_PRSCVL(49), base_addr + S3C2410_ADCCON);
/* 初始化ADC启动或延时寄存器,ADC转换启动延时值设置为0xffff */
writel(0xffff, base_addr + S3C2410_ADCDLY);
/* 初始化ADC触摸屏控制寄存器ADCTSC,
* WAIT4INT(0)在上面定义,引脚YM、YP、XM、XP的使能位位于ADCTSC的[7:4]位,
* 设置触摸屏的工作状态在[1:0]位,WAIT4INT(0)=11010011,
* 1101代表笔尖按下时发生触摸屏中断信号IRQ_TS给CPU,0011表示XP上拉使能,
* 使用正常ADC转换,转换的方式为等待中断模式
* 原理图见linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析
*/
writel(WAIT4INT(0), base_addr +S3C2410_ADCTSC);
}
/*s3c2440触摸屏驱动模块加载程序,做了以下工作
* 获得时钟源、设置访问触摸屏控制器的虚拟地址并初始化触摸屏控制器、初始化中断定时器、
* 填充input_dev结构体(设备基本信息及事件信息)、注册中断、注册设备到input子系统
*/
static int __init s3c2440ts_init(void)
{
struct input_dev *input_dev;
int err = -ENOMEM;
s3c2440_ts = kzalloc(sizeof(structs3c2440_ts), GFP_KERNEL);
/*给输入设备申请空间,input_allocate_device定义在input.h中*/
input_dev = input_allocate_device();
if (!s3c2440_ts || !input_dev)
gotofail1;
/* 获得adc的时钟源,通过arch/arm/mach-s3c2410/clock.c获得提供的时钟源为PCLK */
adc_clock = clk_get(NULL, "adc");
if (!adc_clock)
{
printk(KERN_ERR "failed to get adcclock source\n");
return -ENOENT;
}
/* 在时钟控制器中给adc提供输入时钟,ADC转换需要输入时钟 */
clk_enable(adc_clock);
/* 使用ioremap获得操作ADC控制器的虚拟地址
* S3C2410_PA_ADC=ADCCON,是ADC控制器的基地址,寄存器组的长度=0x1c
*/
base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
if (base_addr == NULL)
{
printk(KERN_ERR "Failed to remapregister block\n");
return -ENOMEM;
goto fail1;
}
/*初始化ADC控制寄存器和ADC触摸屏控制寄存器*/
adc_init();
/* 初始化定时器 */
init_timer(&s3c2440_ts->timer);
s3c2440_ts->timer.data = 1;
s3c2440_ts->timer.function =touch_timer_fire;
/* 设置触摸屏输入设备的标志,注册输入设备成功进入根文件系统,可以cat /proc/bus/input/devices查看其内容*/
input_dev->name = "s3c2410Touchscreen"; /* 设备名称 */
input_dev->phys ="s3c2440ts/input0"; /* */
input_dev->id.bustype = BUS_HOST; /*总线类型 */
input_dev->id.vendor = 0x1; /*经销商ID */
input_dev->id.product = 0x2; /*产品ID */
input_dev->id.version = 0x0100; /*版本ID */
s3c2440_ts->shift = 2;
/*下面初始化输入设备,即给输入设备结构体input_dev的成员设置值。
evbit字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,
BIT宏实际就是对1进行位操作,定义在linux/bitops.h中*/
input_dev->evbit[0] = BIT_MASK(EV_SYN) |BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
/*keybit字段用于描述按键的类型,在input.h中定义了很多,这里用BTN_TOUCH类型来表示触摸屏的点击*/
input_dev->keybit[BIT_WORD(BTN_TOUCH)] =BIT_MASK(BTN_TOUCH);
/*对于触摸屏来说,使用的是绝对坐标系统。这里设置该坐标系统中X和Y坐标的最小值和最大值(0-1023范围)
ABS_X和ABS_Y就表示X坐标和Y坐标,ABS_PRESSURE就表示触摸屏是按下还是抬起状态*/
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);
/* 申请ADC中断,AD转换完成后触发。这里使用共享中断IRQF_SHARED是因为该中断号在ADC驱动中也使用了,
最后一个参数1是随便给的一个值,因为如果不给值设为NULL的话,中断就申请不成功*/
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;
/* 设置中断触发条件,IRQT_BOTHEDGE为使能上升沿和下降沿触发 */
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);
/* 屏蔽和禁止adc时钟 */
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");
Mkaefile脚本如下:
[cpp] view
plaincopy
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
本节的说明建立在前两节的基础之上,需要先阅读如下两篇章:
初识input输入子系统
s3c2440的ADC简单驱动实例分析
1.2 触摸屏工作原理
S3C2440的触摸屏接口是4线电阻式触摸屏接口,可以控制x、y方向上的引脚(XP、XM、YP、YM)的变换,S3C2440触摸屏的硬件资源包括触摸屏引脚和ADC转换接口,可以使用寄存器组中的ADCTSC寄存器来操作触摸屏引脚资源,ADCCON寄存器来控制AD转换功能,由linuxinput输入子系统分析《二》: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 代码分析
代码如下:[cpp] view
plaincopy
/*
* s3c2440 触摸屏驱动程序
*
* Kevin Lee <www.ielife.cn>
*/
#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)
/* 定义一个宏WAIT4INT,用于对ADCTSC触摸屏控制寄存器进行操作,
* S3C2410_ADCTSC_YM_SEN等在内核include/asm/arch/regs-adc.h中被定义
*/
#define WAIT4INT(x) (((x)<<8) |S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | \
S3C2410_ADCTSC_XP_SEN |S3C2410_ADCTSC_XY_PST(3))
/* 定义一个宏AUTOPST,用于设置ADCTSC触摸屏控制寄存器为自动转换模式 */
#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; /*触摸屏x坐标值 */
short yp; /*触摸屏y坐标值 */
};
/* 触摸屏设备结构体 */
struct s3c2440_ts {
struct input_dev *input;/* 输入子系统设备结构体 */
struct timer_list timer;/* 定时器,用于ADC转换操作 */
struct ts_event tc; /* ADC转换的值的保存位置,也用于上报input子系统的数据 */
int pendown; /* 判断是否有按下 */
int count; /* 用于驱动去抖的计数 */
int shift; /* 用于驱动去抖的基数 */
};
/* 定义触摸屏设备结构体 */
static struct s3c2440_ts *s3c2440_ts;
/* 定义虚拟地址访问硬件寄存器,__iomem只是用于表示指针将指向I/O内存 */
static void __iomem *base_addr;
/* 定义adc时钟,通过adc_clock接口获得adc输入时钟,adc转换器需要 */
static struct clk *adc_clock;
/* 申明外部定义的信号量,adc.c中定义,处理IRQ_ADC共享中断引起的资源互斥 */
extern struct semaphore adc_lock;
//DECLARE_MUTEX(adc_lock);
/* 处理触摸屏的数据及事件上报,属于中断调用的函数,不能睡眠 */
static void touch_timer_fire(unsigned long data)
{
/* 保存ADCDAT0及ADCDAT1的x,y坐标值 */
unsigned long data0;
unsigned long data1;
/* 禁止中断,处理完数据再打开中断 */
set_irq_type(IRQ_TC, IRQT_NOEDGE);
set_irq_type(IRQ_ADC, IRQT_NOEDGE);
/* 读取ADCDAT0和ADCDAT1寄存器,提取ADC转换的x,y坐标值 */
data0 = readl(base_addr + S3C2410_ADCDAT0);
data1 = readl(base_addr + S3C2410_ADCDAT1);
/* 判断ADCDAT0和ADCDAT1中的[15]位,[15]位为等待中断模式下用于判断笔尖是否有抬起或落下,0=落下 */
s3c2440_ts->pendown = (!(data0 &S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
/* 当触摸屏处于被按下状态,执行下面代码 */
if (s3c2440_ts->pendown) {
/*count不为0,说明正在转换,xp和yp往右移动shift(2)位是为了去抖
* 需要结合stylus_action中断函数来看,当count=4时,才能认为转换结束,
* 即最后所得的xp和yp是被计算4次的,最终上报时需要除以4,因此这里预先
* 右移2位,相当于乘以4
*/
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
/*报告X、Y的绝对坐标值*/
input_report_abs(s3c2440_ts->input,ABS_X, s3c2440_ts->tc.xp);
input_report_abs(s3c2440_ts->input,ABS_Y, s3c2440_ts->tc.yp);
/*报告触摸屏的状态,1表明触摸屏被按下*/
input_report_abs(s3c2440_ts->input,ABS_PRESSURE, s3c2440_ts->tc.pressure);
/*报告按键事件,键值为1(代表触摸屏对应的按键被按下)*/
input_report_key(s3c2440_ts->input,BTN_TOUCH, s3c2440_ts->pendown);
/*等待接收方受到数据后回复确认,用于同步*/
input_sync(s3c2440_ts->input);
}
/* count=0执行这里面的代码,ADC还没有开始转换 */
s3c2440_ts->tc.xp = 0;
s3c2440_ts->tc.yp = 0;
s3c2440_ts->count = 0;
s3c2440_ts->tc.pressure = 0;
/* 因为触摸屏是按下状态,ADC还没有转换,需要启动ADC开始转换
* 本句代码是设置触摸屏为自动转换模式
*/
writel(S3C2410_ADCTSC_PULL_UP_DISABLE| AUTOPST, base_addr + S3C2410_ADCTSC);
/* 启动ADC转换 */
writel(readl(base_addr +S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr + S3C2410_ADCCON);
} else {
/* 执行到这里说明按键没有被按下或按键抬起,count清0 */
s3c2440_ts->count = 0;
/* 报告按键事件,给0值说明按键抬起 */
input_report_key(s3c2440_ts->input,BTN_TOUCH, 0);
/* 报告按键事件,给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);
}
/* 使能中断触发条件,IRQT_BOTHEDGE为使能上升沿和下降沿触发 */
set_irq_type(IRQ_TC, IRQT_BOTHEDGE);
set_irq_type(IRQ_ADC, IRQT_BOTHEDGE);
}
/* 触摸屏中断服务程序,触摸屏按下或抬起时触发执行 */
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
/* 用于记录ADC转换后的值 */
unsigned long data0;
unsigned long data1;
/* 由于是中断程序,所以不能使用down和down_interruptible,会导致睡眠 */
if (down_trylock(&adc_lock) == 0)
{
/* 读取ADCDAT0和ADCDAT1,用于判断触摸屏是否被按下 */
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)
/* 启动ADC开始转换数据 */
touch_timer_fire(0);
}
return IRQ_RETVAL(IRQ_HANDLED);
}
/*ADC中断服务程序,ADC启动后被执行 */
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
/* 获取触摸屏的x,y坐标值 */
data0 = readl(base_addr + S3C2410_ADCDAT0);
data1 = readl(base_addr + S3C2410_ADCDAT1);
/* 既然按键按下了,执行到此ADC转换也开始了,应该取得x,y坐标值了
* x=ADCDAT0[9:0],y=ADCDAT1[9:0],count++,presssure设置为1
*/
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;
/* 如果count小于4,需要重启设置自动转换模式,并进行ADC转换,用于去抖 */
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 {
/*到这里说明count=4,启动定时器去执行touch_timer_fire函数上报按键和触摸事件*/
mod_timer(&s3c2440_ts->timer,jiffies + HZ / 100);
/*检测触摸屏抬起的中断信号*/
writel(WAIT4INT(1),base_addr + S3C2410_ADCTSC);
}
return IRQ_HANDLED;
}
/* 初始化ADC控制寄存器和ADC触摸屏控制寄存器 */
static void adc_init(void)
{
/* S3C2410_ADCCON_PRSCEN设置ADCCON的位[14]=1为使能A/D预分频器
* S3C2410_ADCCON_PRSCVL设置ADCCON的位[13:6]=32表示设置的分频值,
* ADC的转换频率需要在2.5MHZ以下,我们使用的ADC输入时钟为PCLK=50MHZ,50MHZ/(49+1)=1MHZ,满足条件
*/
writel(S3C2410_ADCCON_PRSCEN |S3C2410_ADCCON_PRSCVL(49), base_addr + S3C2410_ADCCON);
/* 初始化ADC启动或延时寄存器,ADC转换启动延时值设置为0xffff */
writel(0xffff, base_addr + S3C2410_ADCDLY);
/* 初始化ADC触摸屏控制寄存器ADCTSC,
* WAIT4INT(0)在上面定义,引脚YM、YP、XM、XP的使能位位于ADCTSC的[7:4]位,
* 设置触摸屏的工作状态在[1:0]位,WAIT4INT(0)=11010011,
* 1101代表笔尖按下时发生触摸屏中断信号IRQ_TS给CPU,0011表示XP上拉使能,
* 使用正常ADC转换,转换的方式为等待中断模式
* 原理图见linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析
*/
writel(WAIT4INT(0), base_addr +S3C2410_ADCTSC);
}
/*s3c2440触摸屏驱动模块加载程序,做了以下工作
* 获得时钟源、设置访问触摸屏控制器的虚拟地址并初始化触摸屏控制器、初始化中断定时器、
* 填充input_dev结构体(设备基本信息及事件信息)、注册中断、注册设备到input子系统
*/
static int __init s3c2440ts_init(void)
{
struct input_dev *input_dev;
int err = -ENOMEM;
s3c2440_ts = kzalloc(sizeof(structs3c2440_ts), GFP_KERNEL);
/*给输入设备申请空间,input_allocate_device定义在input.h中*/
input_dev = input_allocate_device();
if (!s3c2440_ts || !input_dev)
gotofail1;
/* 获得adc的时钟源,通过arch/arm/mach-s3c2410/clock.c获得提供的时钟源为PCLK */
adc_clock = clk_get(NULL, "adc");
if (!adc_clock)
{
printk(KERN_ERR "failed to get adcclock source\n");
return -ENOENT;
}
/* 在时钟控制器中给adc提供输入时钟,ADC转换需要输入时钟 */
clk_enable(adc_clock);
/* 使用ioremap获得操作ADC控制器的虚拟地址
* S3C2410_PA_ADC=ADCCON,是ADC控制器的基地址,寄存器组的长度=0x1c
*/
base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
if (base_addr == NULL)
{
printk(KERN_ERR "Failed to remapregister block\n");
return -ENOMEM;
goto fail1;
}
/*初始化ADC控制寄存器和ADC触摸屏控制寄存器*/
adc_init();
/* 初始化定时器 */
init_timer(&s3c2440_ts->timer);
s3c2440_ts->timer.data = 1;
s3c2440_ts->timer.function =touch_timer_fire;
/* 设置触摸屏输入设备的标志,注册输入设备成功进入根文件系统,可以cat /proc/bus/input/devices查看其内容*/
input_dev->name = "s3c2410Touchscreen"; /* 设备名称 */
input_dev->phys ="s3c2440ts/input0"; /* */
input_dev->id.bustype = BUS_HOST; /*总线类型 */
input_dev->id.vendor = 0x1; /*经销商ID */
input_dev->id.product = 0x2; /*产品ID */
input_dev->id.version = 0x0100; /*版本ID */
s3c2440_ts->shift = 2;
/*下面初始化输入设备,即给输入设备结构体input_dev的成员设置值。
evbit字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,
BIT宏实际就是对1进行位操作,定义在linux/bitops.h中*/
input_dev->evbit[0] = BIT_MASK(EV_SYN) |BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
/*keybit字段用于描述按键的类型,在input.h中定义了很多,这里用BTN_TOUCH类型来表示触摸屏的点击*/
input_dev->keybit[BIT_WORD(BTN_TOUCH)] =BIT_MASK(BTN_TOUCH);
/*对于触摸屏来说,使用的是绝对坐标系统。这里设置该坐标系统中X和Y坐标的最小值和最大值(0-1023范围)
ABS_X和ABS_Y就表示X坐标和Y坐标,ABS_PRESSURE就表示触摸屏是按下还是抬起状态*/
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);
/* 申请ADC中断,AD转换完成后触发。这里使用共享中断IRQF_SHARED是因为该中断号在ADC驱动中也使用了,
最后一个参数1是随便给的一个值,因为如果不给值设为NULL的话,中断就申请不成功*/
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;
/* 设置中断触发条件,IRQT_BOTHEDGE为使能上升沿和下降沿触发 */
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);
/* 屏蔽和禁止adc时钟 */
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脚本如下:[cpp] view
plaincopy
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
相关文章推荐
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析
- linux驱动—input输入子系统—The simplest example(一个最简单的实例)分析(2)
- linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析
- input子系统学习笔记五 按键驱动实例分析上
- input子系统学习 按键驱动实例分析上
- 实例:触摸屏驱动-2.用input子系统报告事件
- input子系统学习笔记六 按键驱动实例分析下
- input子系统学习笔记 按键驱动实例分析下
- input子系统整体流程全面分析(触摸屏驱动为例)
- linux驱动—input输入子系统—The simplest example(一个最简单的实例)分析(1)
- input子系统学习笔记五 按键驱动实例分析上
- input子系统学习笔记六 按键驱动实例分析下
- input子系统学习笔记六 按键驱动实例分析下