s3c2410_ts.c是三星公司提供的触摸屏驱动。
入口函数:
static int __init s3c2410ts_init(void)
{
// init_MUTEX(&gADClock);
return platform_driver_register(&s3c2410ts_driver);
}
static struct platform_driver s3c2410ts_driver = {
.driver = {
.name = "s3c2410-ts",//内核中有同名的平台设备时“s3c2410-ts”时,“s3c2410ts_driver”结构中的“.probe = s3c2410ts_probe,”就被调用。
.owner = THIS_MODULE,
},
.probe = s3c2410ts_probe,
.remove = s3c2410ts_remove,
};
/*
* The functions for inserting/removing us as a module.
*/
static int __init s3c2410ts_probe(struct platform_device *pdev)
{
struct s3c2410_ts_mach_info *info;
struct input_dev *input_dev;
info = ( struct s3c2410_ts_mach_info *)pdev->dev.platform_data;
if (!info)
{
printk(KERN_ERR "Hm... too bad : no platform data for ts\n");
return -EINVAL;
}
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
printk(DEBUG_LVL "Entering s3c2410ts_init\n");
#endif
adc_clock = clk_get(NULL, "adc");
if (!adc_clock) {
printk(KERN_ERR "failed to get adc clock source\n");
return -ENOENT;
}
clk_enable(adc_clock);
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
printk(DEBUG_LVL "got and enabled clock\n");
#endif
base_addr=ioremap(S3C2410_PA_ADC,0x20);
if (base_addr == NULL) {
printk(KERN_ERR "Failed to remap register block\n");
return -ENOMEM;
}
/* Configure GPIOs */
s3c2410_ts_connect();
if ((info->presc&0xff) > 0)
iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(info->presc&0xFF),\
base_addr+S3C2410_ADCCON);
else
iowrite32(0,base_addr+S3C2410_ADCCON);
/* Initialise registers */
if ((info->delay&0xffff) > 0)
iowrite32(info->delay & 0xffff, base_addr+S3C2410_ADCDLY);
iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
//以上是根据平台设备“platform_device *pdev”这个形参获得一些信息,配置一些管脚
/* Initialise input stuff */
memset(&ts, 0, sizeof(struct s3c2410ts));
input_dev = input_allocate_device();//分配一个 input_dev 结构体。
if (!input_dev) {
printk(KERN_ERR "Unable to allocate the input device !!\n");
return -ENOMEM;
}
//开始设置这个 input_dev 结构体。
ts.dev = input_dev;
ts.dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);//能产生哪类事件:EV_KEY 按键类、EV_ABS 绝对位移类
ts.dev->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);//能 产 生 按 键 类 事 件 中BTN_TOUCH 触摸屏事件
input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0);//开始设置绝对位移方面的参数。
input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0);
ts.dev->private = &ts;
ts.dev->name = s3c2410ts_name;
ts.dev->id.bustype = BUS_RS232;
ts.dev->id.vendor = 0xDEAD;
ts.dev->id.product = 0xBEEF;
ts.dev->id.version = S3C2410TSVERSION;
ts.shift = info->oversampling_shift;
/* Get irqs */
if (request_irq(IRQ_ADC, stylus_action, IRQF_SAMPLE_RANDOM | SA_SHIRQ,
"s3c2410_action", ts.dev)) {
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n");
iounmap(base_addr);
return -EIO;
}
if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,
"s3c2410_action", ts.dev)) {
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");
iounmap(base_addr);
return -EIO;
}
printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name);
/* All went ok, so register to the input system */
input_register_device(ts.dev);//注册这个设备。
return 0;
}
//当有事件发生时上报事件。
//在“void touch_timer_fire(unsigned long data)”中有很多这类上报事件,如:
input_report_abs(ts.dev, ABS_X, ts.xp);
input_report_abs(ts.dev, ABS_Y, ts.yp);
input_report_key(ts.dev, BTN_TOUCH, 1);
input_report_abs(ts.dev, ABS_PRESSURE, 1);
input_sync(ts.dev);
//从“input_report_abs()”中看到这个函数就是 input_event() 上报事件。
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
触摸屏工作过程:
- 按下(作为一个高效的系统,按下一般产生某个中断,不会是用轮询方式检测按下。)产生中断。
- 在“按下”中断处理程序里面,启动 ADC 转换 X、Y 坐标电压值。
- 启动 ADC 不会瞬间完成。一般来说启动是异步的。ADC启动 结束,产生 ADC中断。
- 在 ADC 中断处理函数里面,用 input_event() 来上报。上报后,启动“定时器”。
- 定时器 时间到,再到 第 2 步,再次启动 ADC 。按下时产生一个“按下”中断,过了一段时间(定时器),就会再启动一次 ADC 中断,这样就能处理“粘点”滑动的过程。
- 松开。
使能触摸屏时钟
在内核代码“clock.c”中:
/* standard clock definitions */
static struct clk init_clocks_disable[] = {
{
.name = "nand",
.id = -1,
.parent = &clk_h,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_NAND,
}, {
.name = "sdi",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_SDI,
}, {
.name = "adc",//adc_clock = clk_get(NULL, "adc");是获得这个“adc”时钟,在 clock.c 中有这个时钟。
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,//clk_enable()最终会调用到“.enable = s3c2410_clkcon_enable,”
.ctrlbit = S3C2410_CLKCON_ADC,//#define S3C2410_CLKCON_ADC (1<<15)
}, {
.name = "i2c",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_IIC,
}, {
.name = "iis",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_IIS,
}, {
.name = "spi",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_SPI,
}
};
int s3c2410_clkcon_enable(struct clk *clk, int enable)
{
unsigned int clocks = clk->ctrlbit;
unsigned long clkcon;
clkcon = __raw_readl(S3C2410_CLKCON);//这个函数就是把那个寄存器“CLKCON”的 bit15,先读出“S3C2410_CLKCON”到 clkcon。
if (enable)
clkcon |= clocks;// 若是开启就或上“1”
else
clkcon &= ~clocks;
/* ensure none of the special function bits set */
clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);//最后写进去
__raw_writel(clkcon, S3C2410_CLKCON);
return 0;
}
ADC模数转换器和触摸屏的接口
ADC & TOUCH SCREEN INTERFACE
The 10-bit CMOS ADC (Analog to Digital Converter) is a recycling type device with 8-channel analog inputs. It
converts the analog input signal into 10-bit binary digital codes at a maximum conversion rate of 500KSPS with
2.5MHz A/D converter clock. A/D converter operates with on-chip sample-and-hold function and power down
mode is supported.
Power Supply Voltage: 3.3V
Analog Input Range: 0 ~ 3.3V

02_053ADC和触摸屏的模块图.png
“8:1”是指左边的 8 路 输入,选择一路。
ADC 转换时间:

02_054ADC转换时间.png
触摸屏接口模式
Touch Screen Interface Mode
1. Normal Conversion Mode //正常的转换模式:进行一般的 ADC 操作。就是用于一般的 ADC 模数转换。其他234模式为触摸屏的模式
Single Conversion Mode is the most likely used for General Purpose ADC Conversion. This mode can be initialized by setting the ADCCON (ADC Control Register) and completed with a read and a write to the ADCDAT0 (ADC Data Register 0).
2. Separate X/Y position conversion Mode //分离的 X,Y 坐标转换模式
//分离的 X,Y 坐标转换模式有两种模式--一种是测量 X 坐标, 另一种是测量 Y 坐标。前面我们说触摸屏测量x,y坐标原理时说过,测量 X 坐标:要设置 XP 接 3.3V,XM 接地时,YM,YP 不接,再测量 YP 的电压。测量 Y 坐标:要设置 YP 接 3.3V,YM 接地,XP,XM 不接,再测量 XP 的电压。进入“分离的 X,Y 坐标转换模式”就是帮我们做这些事。它在测量 X 坐标模式时,会把 X 坐标的转换值写到这个“ADCDAT0”寄存器,然后产生一个中断。当进入 Y 坐标转换模式之时,会把 Y 坐标的值写到“ADCDAT1”寄存器中去,然后产生一个中断。
Touch Screen Controller can be operated by one of two Conversion Modes. Separate X/Y Position Conversion Mode is operated as the following way. X-Position Mode writes X-Position Conversion Data to ADCDAT0, so Touch Screen Interface generates the Interrupt source to Interrupt Controller. Y-Position Mode writes Y-Position Conversion Data to ADCDAT1, so Touch Screen Interface generates the Interrupt source to Interrupt Controller.
3. Auto(Sequential) X/Y Position Conversion Mode //自动(连续的)X,Y 坐标转换模式
//当进入这个模式之后,它会自动的帮你即转换 X 坐标,也转换 Y 坐标,X 坐标的值写到“ADCDAT0”,Y 坐标的值写到“ADCDAT1”。然后产生一个中断。第 2 种方式中是分两次操作,这里第 3 种方式中连续的操作。
Auto (Sequential) X/Y Position Conversion Mode is operated as the following. Touch Screen Controller sequentially converts X-Position and Y-Position that is touched. After Touch controller writes X-measurement data to ADCDAT0 and writes Y-measurement data to ADCDAT1, Touch Screen Interface is generating Interrupt source to Interrupt Controller in Auto Position Conversion Mode.
4. Waiting for Interrupt Mode //等待触摸笔按下、松开模式。
//想产生中断,就需要先让 2440 处于这种“等待中断模式”。当触摸笔按下时触摸屏会产生“INT_TC”中断。
//让“rADCTSC=0xd3”这个寄存器等于 0xd3 就可以。
Touch Screen Controller generates interrupt (INT_TC) signal when the Stylus is down. Waiting for Interrupt Mode setting value is rADCTSC=0xd3; // XP_PU, XP_Dis, XM_Dis, YP_Dis, YM_En.
After Touch Screen Controller generates interrupt signal (INT_TC), Waiting for interrupt Mode must be cleared. (XY_PST sets to the No operation Mode)
Standby Mode //省电模式
Standby mode is activated when ADCCON [2] is set to '1'. In this mode, A/D conversion operation is halted and ADCDAT0, ADCDAT1 register contains the previous converted data.
s3c_ts.c
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/plat-s3c24xx/ts.h>
#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
static struct input_dev *s3c_ts_dev;
static volatile struct s3c_ts_regs *s3c_ts_regs;
static struct timer_list ts_timer;
static void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc = 0xd3;
}
static void enter_wait_pen_up_mode(void)
{
s3c_ts_regs->adctsc = 0x1d3;
}
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}
static int s3c_filter_ts(int x[], int y[])
{
#define ERR_LIMIT 10
int avr_x, avr_y;
int det_x, det_y;
avr_x = (x[0] + x[1])/2;
avr_y = (y[0] + y[1])/2;
det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
return 0;
avr_x = (x[1] + x[2])/2;
avr_y = (y[1] + y[2])/2;
det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
return 0;
return 1;
}
static void s3c_ts_timer_function(unsigned long data)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);//表示上报完毕
enter_wait_pen_down_mode();
}
else
{
/* 测量X/Y坐标 */
enter_measure_xy_mode();
start_adc();
}
}
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
//printk("pen up\n");
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);//表示上报完毕
enter_wait_pen_down_mode();
}
else
{
//printk("pen down\n");
//enter_wait_pen_up_mode();
enter_measure_xy_mode();
start_adc();
}
return IRQ_HANDLED;
}
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
cnt = 0;
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);//表示上报完毕
enter_wait_pen_down_mode();
}
else
{
// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
/* 优化措施3: 多次测量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 优化措施4: 软件过滤 */
if (s3c_filter_ts(x, y))
{
//printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
input_sync(s3c_ts_dev);//表示上报完毕
}
cnt = 0;
enter_wait_pen_up_mode();
/* 启动定时器处理长按/滑动的情况 */
mod_timer(&ts_timer, jiffies + HZ/100);
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
static int s3c_ts_init(void)
{
struct clk* clk;
/* 1. 分配一个input_dev结构体 */
s3c_ts_dev = input_allocate_device();
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
/* 2.2 能产生这类事件里的哪些事件 */
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
/* 3. 注册 */
input_register_device(s3c_ts_dev);
/* 4. 硬件相关的操作 */
/* 4.1 使能时钟(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
/* 4.2 设置S3C2440的ADC/TS寄存器 */
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
/* bit[14] : 1-A/D converter prescaler enable
* bit[13:6]: A/D converter prescaler value,
* 49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
* bit[0]: A/D conversion starts by enable. 先设为0
*/
s3c_ts_regs->adccon = (1<<14)|(49<<6);
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
/* 优化措施1:
* 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
*/
s3c_ts_regs->adcdly = 0xffff;
/* 优化措施5: 使用定时器处理长按,滑动的情况
*
*/
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer);
enter_wait_pen_down_mode();
return 0;
}
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
del_timer(&ts_timer);
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");
编译测试
#
# ls /dev/event*
/dev/event0
#
# hexdump /dev/event0
//按下按键
// 秒 微秒 type code value
0000000 0120 0000 69c2 0005 0003 0000 0179 0000
0000010 0120 0000 69d9 0005 0003 0001 02e5 0000
0000020 0120 0000 69dc 0005 0003 0018 0001 0000
0000030 0120 0000 69e0 0005 0001 014a 0001 0000
0000040 0120 0000 69e3 0005 0000 0000 0000 0000
0000050 0120 0000 a7c0 0005 0003 0000 017a 0000
0000060 0120 0000 a7d6 0005 0003 0001 02e3 0000
0000070 0120 0000 a7de 0005 0000 0000 0000 0000
0000080 0120 0000 f5ac 0005 0003 0000 017c 0000
0000090 0120 0000 f5bd 0005 0003 0001 02e2 0000
00000a0 0120 0000 f5c3 0005 0000 0000 0000 0000
00000b0 0120 0000 43cc 0006 0003 0000 017f 0000
00000c0 0120 0000 43dc 0006 0003 0001 02e0 0000
00000d0 0120 0000 43e2 0006 0000 0000 0000 0000
00000e0 0120 0000 91eb 0006 0003 0000 0181 0000
00000f0 0120 0000 91fc 0006 0003 0001 02df 0000
0000100 0120 0000 9202 0006 0000 0000 0000 0000
0000110 0120 0000 f55f 0006 0003 0018 0000 0000
0000120 0120 0000 f570 0006 0001 014a 0000 0000
0000130 0120 0000 f572 0006 0000 0000 0000 0000
0003 -- 绝对位移。 //#define EV_ABS 0x03
Code 0000 表示 X 方向(ABS_X)。 //#define ABS_X 0x00
0001 表示 Y 方向(ABS_Y) , //#define ABS_Y 0x01
0018 表示压力(ABS_PRESSURE)。 //#define ABS_PRESSURE 0x18
Value 0179 0000 就是 X 坐标值。
Code 014a 是指哪个按键“BTN_TOUCH”。 //#define BTN_TOUCH 0x14a
使用:
想使用触摸屏,就是想点击 LCD 屏上显示的内容,并对应起来。要用到库“tslib”。
库“tslib”编译:
tar xzf tslib-1.4.tar.gz
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
安装:
cd tmp
cp * -rf /xxxx
OpenJTAG> nfs 30000000 192.168.2.100:/work/nfs_root/uImage_nots
OpenJTAG> bootm 30000000
#
# insmod cfbcopyarea.ko
# insmod cfbfillrect.ko
# insmod cfbimgblt.ko
#
# ls /dev/fb*
ls: /dev/fb*: No such file or directory
#
# insmod lcd.ko
Console: switching to colour frame buffer device 30x40
#
# ls /dev/fb*
/dev/fb0 //lcd设备
#
# ls /dev/event*
ls: /dev/event*: No such file or directory
#
# insmod s3c_ts.ko
input: Unspecified device as /class/input/input0
#
# ls /dev/event*
/dev/event0 //触摸屏设备
#