Detailed explanation of mini2440 touch screen driver

Publisher:科技徜徉Latest update time:2016-12-03 Source: eefocusKeywords:mini2440 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

#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;
}


Keywords:mini2440 Reference address:Detailed explanation of mini2440 touch screen driver

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

Summary and analysis of IIC timing problems in mini2440 debugging
Description: mini2440 platform, wince6.0 system, vs2005 After several months of study and pause, I finally added the IIC bus simulated by GPIO under wince and realized the transplantation of IIC. The chip of IIC is SHT21 temperature and humidity chip. The reason why I haven't figured out IIC for so long is
[Microcontroller]
u-boot-2011.03 porting on mini2440/micro2440 runs in RAM
2.1 include/configs/micro2440.h delete #define CONFIG_S3C2410 1 /* specifically a SAMSUNG S3C2410 SoC */ #define CONFIG_SMDK2410 1 /* on a SAMSUNG SMDK2410 Board */ Add #define CONFIG_S3C2440 1 /* specifically a SAMSUNG S3C2440 SoC */ #define CONF
[Microcontroller]
u-boot-2011.03 porting on mini2440/micro2440 runs in RAM
Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号