#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* For ts.dev.id.version */
#define S3C2410TSVERSION 0x0101
/*Define a WAIT4INT macro, which will operate the ADC touch screen control register
S3C2410_ADCTSC_YM_SEN These macros are defined in 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))
#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))
static char *s3c2410ts_name = "s3c2410 TouchScreen";
#define DEVICE_NAME "mini2440_TouchScreen" /*设备名称*/
static struct input_dev *ts_dev; /*Define an input device to represent our touch screen device*/
static long xp;
static long yp;
static int count;
/*Define an external semaphore ADC_LOCK, because ADC_LOCK has been declared in the ADC driver,
so that the ADC resources can be mutually exclusive accessed in the ADC driver and the touch screen driver*/
extern struct semaphore ADC_LOCK;
static int OwnADC = 0;
static void __iomem *base_addr; /*Defines a memory address used to store virtual mapping*/
static inline void s3c2410_ts_connect(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON);
s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON);
s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON);
s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON);
}
static void touch_timer_fire(unsigned long data)
{
/*Used to record the value after AD conversion this time*/
unsigned long data0;
unsigned long data1;
int updown; /*Used to record the touch screen operation status whether it is pressed or lifted*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*Record whether the touch screen is pressed down or lifted up this time. The state is saved in the 15th bit of the data register, so it needs logical AND S3C2410_ADCDAT0_UPDOWN*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) /*Judge the operating status of the touch screen*/
{
/*If the status is pressed and ADC has been converted, report the event and data*/
if (count != 0) //Report the event after four conversions
{
long tmp;
tmp = xp;
xp = yp;
yp = tmp;
//The conversion is performed here because our screen uses 240*320, which is equivalent to transforming the X and Y axes of the original screen.
//Personal understanding, not sure if it is correct
//Device X, Y value
xp >>= 2;
yp >>= 2;
#ifdef CONFIG_TOUCHSCREEN_MINI2440_DEBUG
/*Touch screen debugging information. When this option is selected when compiling the kernel, clicking the touch screen will print out the coordinate information on the terminal*/
struct timeval tv;
do_gettimeofday(&tv);
printk(KERN_DEBUG "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, xp, yp);
#endif
input_report_abs(ts_dev, ABS_X, xp);
input_report_abs(ts_dev, ABS_Y, yp);
/*Report key events, the key value is 1 (indicates that the key corresponding to the touch screen is pressed)*/
input_report_key(ts_dev, BTN_TOUCH, 1);
//input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);
/*Report the status of the touch screen, 1 indicates that the touch screen is pressed*/
input_report_abs(ts_dev, ABS_PRESSURE, 1);
/*Wait for the receiver to reply with confirmation after receiving the data, for synchronization*/
input_sync(ts_dev);
//This indicates that we have reported a complete touch screen event, which is used to interval the next report
}
/*If the status is pressed and the ADC has not started converting, start the ADC for conversion*/
xp = 0;
yp = 0;
count = 0;
/*Set the touch screen mode to automatic conversion mode*/
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
/*Start ADC conversion*/
iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
//If ADC is not started or ACD conversion is completed four times, then start ADC
}
else /*Otherwise it is in the up state*/
{
//If it is in the up state, then report and let the touch screen be in the waiting touch stage
count = 0;
// input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
input_report_key(ts_dev, BTN_TOUCH, 0); /*Report key event, the key value is 0 (indicates that the corresponding key of the touch screen is released)*/
input_report_abs(ts_dev, ABS_PRESSURE, 0); /*Report the status of the touch screen, 0 means the touch screen is not pressed*/
input_sync(ts_dev); /*Wait for the receiver to reply to confirm after receiving the data, for synchronization*/
iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
if (OwnADC)
{
OwnADC = 0;
up(&ADC_LOCK);
}
}
}
/*Define and initialize a timer touch_timer, the timer service program is touch_timer_fire*/
static struct timer_list touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0);
/*ADC interrupt service routine, triggered after AD conversion is completed*/
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
int updown;
//Note that in the touch screen driver module, the role of this ADC_LOCK is to ensure that only one driver uses the ADC
//interrupt line at any time, because the ADC is also used in the mini2440adc module, so only with this lock can you enter the ADC startup
if (down_trylock(&ADC_LOCK) == 0)
{
OwnADC = 1;
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*Record whether the touch screen is pressed down or lifted this time. The state is saved in the 15th bit of the data register, so it needs logical AND S3C2410_ADCDAT0_UPDOWN*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown)
{
touch_timer_fire(0); //This is a timer function. Of course, it is called as a normal function here to start ADC
}
//Note from Xiao Lai: To be precise, the else part will not be executed at all.
/*
Analysis: When pressed for the first time, the ADC semaphore is applied for and the press interrupt is entered. At this time, the touch_timer_fire
function is forced to execute. Because the initial count = 0, the ADC conversion will be forced to start and enter the ADC interrupt. Then the stylus_action function is entered. After entering
this function, four ADC conversions are performed continuously. After completing the four conversions, the esle part is executed, that is, the touch_timer_fire function is executed again after 1ms and
the interrupt is executed as a pop-up detection interrupt. Well, here is the key point. In short, mod_timer(&touch_timer, jiffies+1); function
means to execute the touch_timer_fire function mounted on the touch_timer timer after 1ms. Well, that is to say,
touch_timer_fire function is forced to be executed after 1ms. Then, if it is still pressed after 1ms, there is no way to report the coordinates and continue. Count xp yp is cleared
to enter the next four ADC interrupts. As long as it is pressed, the ADC will continue to convert the coordinate value of the pressed position. Why should it be done all the time? Isn't it completed after one conversion?
Why repeat the conversion? Note that there is another situation that is more important here. That is, if you press and slide on the touch screen, if you only convert once, you can only
get the coordinates of the first pressed point. If you sample four times every 1ms, you can get the sliding track. The mystery is here. Looking back, when a certain
bounce occurs, you should enter the stylus_updown interrupt function again. Since applying for the semaphore again will fail, it will return directly, so it
will not be executed, let alone the statements in else. In other words, in a complete press-to-pop-up process, after the first press to apply for the ADC semaphore, it enters the ADC startup process;
the second pop-up enters stylus_updown, which is equivalent to doing nothing and not executing any operation.
Summary:
1. For the first time, press to enter the stylus_updown interrupt, start the touch_timer_fire function, and then start the ADC conversion interrupt
. 2. For ADC conversion, if the conversion does not exceed four times, continue to convert until four times. After completing four times, start the 1ms timer. After 1ms, execute the touch_timer_fire function
and set the interrupt as a pop-up interrupt
. 3. After 1ms, if it is a press, report the coordinate information, and start the next ADC conversion after completion.
4. Continue with step 2. If it is a pop-up interrupt after 1ms, report the detection coordinate completion information and set the press interrupt.
*/
else
{
OwnADC = 0;
up(&ADC_LOCK); //Note that this part will basically not be executed unless you touch it at a very fast speed and there is
no time to start the ADC. Of course, this fast speed is generally unattainable. When I debugged the program, I found that I could not enter here.
}
}
return IRQ_HANDLED;
}
static irqreturn_t stylus_action(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
if (OwnADC) { //Read data
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
count++;
if (count < (1<<2)) { //If it is less than four times, restart the conversion
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else { //If it is more than four times, wait for 1ms before reporting data
mod_timer(&touch_timer, jiffies+1);
iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
}
}
return IRQ_HANDLED;
}
static struct clk *adc_clock; /*Used to save the ADC clock obtained from the platform clock list*/
static int __init s3c2410ts_init(void)
{
struct input_dev *input_dev;
/*Get the ADC clock from the platform clock queue. Why do we need to get this clock here? Because the conversion frequency of ADC is related to the clock.
Some system clocks are defined in arch/arm/plat-s3c24xx/s3c2410-clock.c*/
adc_clock = clk_get(NULL, "adc");
if (!adc_clock) {
printk(KERN_ERR "failed to get adc clock source\n");
return -ENOENT;
}
/*After the clock is obtained, it must be enabled before it can be used. clk_enable is defined in arch/arm/plat-s3c/clock.c*/
clk_enable(adc_clock);
//Get the clock. To mount the peripherals on the APB BUS, clock control is required. ADC is such a device.
/*I/O memory cannot be accessed directly, it must be mapped and virtual addresses must be assigned to I/O memory. These virtual addresses are
described with __iomem, but cannot be accessed directly. Special functions such as iowrite32 are required, such as iowrite32
S3C2410_PA_ADC is the base address of the ADC controller, defined in mach-s3c2410/include/mach/map.h, 0x20 is the length of the virtual address*/
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();
/*The calculation result is (binary): 111111111000000. According to the data sheet, the AD conversion prescaler value is set to 255 and the AD conversion prescaler is enabled. */
iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\
base_addr+S3C2410_ADCCON); //Enable prescaling and set the division coefficient
iowrite32(0xffff, base_addr+S3C2410_ADCDLY); //Set ADC delay. In the wait interrupt mode, the interval delay value for generating INT_TC is 0xffff*/
/*The calculation result of WAIT4INT macro is (binary): 11010011. According to the data sheet, the ADC touch screen control register is set to wait interrupt mode. */
iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); //Set TSC according to the wait interrupt mode
/* Initialise input stuff */
//allocate memory for new input device, used to allocate space for input device and do some initial settings common to input devices
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR "Unable to allocate the input device !!\n");
return -ENOMEM;
}
//Set event type
ts_dev = input_dev;
ts_dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
//ts_dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); //Why doesn't this work?
set_bit(BTN_TOUCH, ts_dev->keybit);
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);
/*The above four sentences are all codes for setting event types. How to understand them? First, explain the event types. Commonly used event types are EV_KEY,
EV_MOSSE, EV_ABS (used to receive absolute values like touch screens) Coordinate events), and each event has a different type of code,
such as ABS_X, ABS_Y, and these codes have corresponding values*/
ts_dev->name = DEVICE_NAME;
ts_dev->id.bustype = BUS_RS232;
ts_dev->id.vendor = 0xDEAD;
ts_dev->id.product = 0xBEEF;
ts_dev->id.version = S3C2410TSVERSION;
//The above is the name and id of the input device. This information is the identity information of the input device. How can we see it in user space?
//You can use cat /proc/bus/input/devices. The following is its output information./*[root@mini2440
/]#cat proc/bus/input/devices
I: Bus=0013 Vendor=dead Product=beef Version=0101
N: Name="s3c2410 TouchScreen"
P: Phys=
S: Sysfs=/devices/virtual/input/input0
U: Uniq=
H: Handlers=event0
B: EV=b
B: KEY=0
B: ABS=1000003
*/
/* Get irqs */
//Interrupt processing
//stylus_action and stylus_updown interrupt processing functions. When the pen tip touches, it will enter stylus_updown
if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,
"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 */
//The basic information and capabilities of the device have been set up earlier. Everything is ready. Now you can register
input_register_device(ts_dev);
return 0;
}
static void __exit s3c2410ts_exit(void)
{
disable_irq(IRQ_ADC);
disable_irq(IRQ_TC);
free_irq(IRQ_TC,ts_dev);
free_irq(IRQ_ADC,ts_dev);
if (adc_clock) {
clk_disable(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
input_unregister_device(ts_dev);
iounmap(base_addr);
}
module_init(s3c2410ts_init);
module_exit(s3c2410ts_exit);
MODULE_LICENSE("GPL");
test program:
#include
#include
#include
#include
#include
int main()
{
int i,fd;
struct input_event ts_data;
if((fd = open("/dev/event0", O_RDONLY)) < 0)
{
printf("Error open \n");
return -1;
}
while(1)
{
read(fd, &ts_data, sizeof(ts_data));
printf("ts_data.type = %d, ts_data.code = %d, ts_data.value = %d\n",ts_data.type,ts_data.code,ts_data.value);
if (ts_data.type == EV_KEY)
{
printf("type: EV_KEY, event = %s, value = %d\n\n",
ts_data.code == BTN_TOUCH ? "BTN_TOUCH" : "Unkown", ts_data.value);
}
else if(ts_data.type == EV_ABS)
{
printf("type: EV_ABS, event = %s, value = %d\n\n",
ts_data.code == ABS_X ? "ABS_X" :
ts_data.code == ABS_Y ? "ABS_Y" :
ts_data.code == ABS_PRESSURE ? "ABS_PRESSURE" :
"Unkown", ts_data.value);
}
else if (ts_data.type == EV_SYN)
{
printf("type: EV_SYN, event = %s, value = %d\n\n",
ts_data.code == SYN_REPORT ? "SYN_REPORT" : "Unkown", ts_data.value);
}
else
{
printf("type: 0x%x, event = 0x%x, value = %d\n\n", ts_data.type, ts_data.code, ts_data.value);
}
}
return 0;
}
Previous article:S3C2410 UART control (RS232)
Next article:Transplantation and Problem Analysis of S3C2410 Network Card CS8900A Driver
Recommended ReadingLatest update time:2024-11-16 07:33
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- What did you learn from the Industrial Expo Electronics Exhibition?
- [FreeRTOS check-in station 3 is open] Task status and switching, closing time is August 20
- New feature! You can add videos directly to your posts~~~~ No worries~~
- Application of Standing Wave Ratio Analysis Module in Leaky Cable Monitoring System
- DSP system design-DSP development dynamics issues
- What is the function of the INCREMENT COMPONENT PART NUMBER button in PROTEL99?
- MicroPython Retro Gaming on Wio
- Why does the actual throughput of zynq not exceed 10Mbps after configuring 100M Ethernet?
- MicroPython simple task scheduler
- [2022 Digi-Key Innovation Design Competition] Distributed Temperature and Humidity Acquisition System - Brief Design Overview