#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;
//Wait until the stylus is pressed mode
static void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc = 0xd3;
}
//Wait until the stylus is released mode
static void enter_wait_pen_up_mode(void)
{
s3c_ts_regs->adctsc = 0x1d3;
}
//Enter X/Y direction ADC simultaneous conversion mode
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
//Start ADC conversion
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 that_x, that_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))
{
// Already released
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
{
// Measure X/Y coordinates
enter_measure_xy_mode();
start_adc();
}
}
//Stylus press, lift interrupt service function
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;
// Optimization 2: If the stylus is released when ADC is completed, the result will be discarded
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
// Already released
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);
// Optimization measure 3: Calculate the average value by multiple measurements
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
// Optimization measure 4: software filtering
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();
// Start the timer to handle long press/slide
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. Allocate an input_dev structure
s3c_ts_dev = input_allocate_device();
// 2. Setup
// 2.1 What kind of events can be generated
set_bit(EV_KEY, s3c_ts_dev->evbit); //Key events
set_bit(EV_ABS, s3c_ts_dev->evbit); //Absolute displacement events
// 2.2 Which events of this type can be generated
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); //Pressure: 1 means pressed, 0 means released
// 3. Register
input_register_device(s3c_ts_dev);
// 4. Hardware related operations
//4.1 Enable clock (CLKCON[15])
clk = clk_get(NULL, "adc");
clk_enable(clk);
// 4.2 Set the ADC/TS register of S3C2440
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. Set to 0 first
//
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);
//Optimization measure 1:
// Set ADCDLY to the maximum value, which will cause the IRQ_TC interrupt to be issued after the voltage stabilizes
s3c_ts_regs->adcdly = 0xffff;
//Optimization measure 5: Use timer to handle long press and sliding
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_hours(&ts_hours);
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");
================================================================
Analysis:
After loading the driver, run the s3c_ts_init function, and the program enters the wait-for-stylus-down mode enter_wait_pen_down_mode(). When the stylus is pressed, it enters the press interrupt service function, that is, pen_down_up_irq. After entering the interrupt service function, it immediately determines whether the stylus is still pressed. If the stylus is released at this time, the event is reported; if the stylus is still pressed at this time, it enters the ADC conversion mode for both X/Y directions at the same time, and starts ADC conversion. After the ADC conversion is completed, the ADC interrupt service program is entered, and the ADC data in the x and y directions are obtained from adcdat0 and adcdat1, and then it is determined whether the stylus has left. If it has left, the data is reported; otherwise, the ADC conversion data is saved and then it is determined whether the data collected by the ADC has reached 4 times. If it has accumulated to 4 times, the event is reported after software filtering, and the wait for the stylus to leave mode enter_wait_pen_up_mode is entered, and the timer is started to start timing to process continuous pressing events; if it is less than 4 times, the X/Y bidirectional simultaneous ADC conversion mode is entered again, and the ADC conversion is started. When the timing time is up, it enters the timing interrupt service function, determines whether the stylus has left, and reports the event if the stylus is released, and enters the wait for the stylus to press mode, otherwise it enters the X/Y bidirectional simultaneous ADC conversion mode again, and starts the ADC conversion. In order!
Test 2th~7th:
1. Make menuconfig to remove the original touch screen driver
-> Device Drivers
-> Input device support
-> Generic input layer
-> Touchscreens
<> S3C2410/S3C2440 touchscreens
make uImage
Boot with the new kernel
2. insmod s3c_ts.ko
Press/release the stylus
Test 2th~7th:
1. ls /dev/event*
2. insmod s3c_ts.ko
3. ls /dev/event*
4. hexdump /dev/event0
Seconds Microseconds type code value
0000000 29a4 0000 8625 0008 0003 0000 0172 0000
0000010 29a4 0000 8631 0008 0003 0001 027c 0000
0000020 29a4 0000 8634 0008 0003 0018 0001 0000
0000030 29a4 0000 8638 0008 0001 014a 0001 0000
0000040 29a4 0000 863c 0008 0000 0000 0000 0000
0000050 29a4 0000 c85e 0008 0003 0000 0171 0000
0000060 29a4 0000 c874 0008 0003 0001 027d 0000
0000070 29a4 0000 c87b 0008 0000 0000 0000
0000080 29a4 0000 ed37 0008 0003 0018 0000 0000
0000090 29a4 0000 ed48 0008 0001 014a 0000 0000
00000a0 29a4 0000 ed4a 0008 0000 0000 0000 0000
For the combined use of LCD and touch screen, please refer to "Tslib compilation and usage"
// Temporarily ignore the following three lines of commands
//sudo apt-get install autoconf
//sudo apt-get install automake
//sudo apt-get install libtool
Compile:
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 //Install to tmp directory
Install:
cd tmp
Then copy all 4 files in the tmp directory to the root directory of the development board
cp * /home/book/workspace/JZ2440_TestFile/system/first_fs -rfd
(The root directory of the development board when the network file system is used for startup, but it is not copied to the flash of the development board at this time. If you want to copy it to the flash of the development board, you can start it without the network file system. Use manual mounting to mount the file system to the mnt directory of the development board, and then copy the files in tmp from the mnt directory to the root directory of the development board. In this way, it is really on the flash of the development board.)
use:
Install s3c_ts.ko, lcd.ko first
1.
Modify line 1 of /etc/ts.conf (remove the # sign and the first space):
# module_raw input
to:
module_raw input
2. Set environment variables
export TSLIB_TSDEVICE=/dev/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
Use the following two commands to test:
ts_calibrate
ts_test