1. 程式人生 > >mini2440的觸控式螢幕驅動----使用input子系統實現

mini2440的觸控式螢幕驅動----使用input子系統實現

沒說程式之前先上圖一張,這個圖方便理解input子系統下的觸控式螢幕的實現。

關於input子系統的實現在前幾篇部落格中已經實現了input子系統的按鍵驅動。

下面開始說觸控式螢幕的驅動程式。

在說之前先結合上面的圖說一下幾個主要的函式的呼叫情況。

箇中斷函式的呼叫次序:
 先是stylus_updown TC中斷處理函式,
 如果要是壓下則呼叫touch_timer_fire來啟動ADC轉換,
 在ADC轉換結束之後呼叫stylus_action。

程式中前面定義的巨集和變數:

/* For ts.dev.id.version */
#define S3C2410TSVERSION	0x0101

/*定義一個WAIT4INT巨集,該巨集將對ADC觸控式螢幕控制暫存器進行操作 
S3C2410_ADCTSC_YM_SEN這些巨集都定義在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巨集,將ADC觸控式螢幕控制暫存器設定成自動轉換模式*/ 
#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";

/*定義一個輸入裝置來表示我們的觸控式螢幕裝置*/  
static	struct input_dev *dev;
/*用於記錄轉換後的X座標值和Y座標值*/ 
static	long xp;
static	long yp;
/*用於計數對觸控式螢幕壓下或擡起時模擬輸入轉換的次數*/ 
static	int count;

/*定義一個外部的訊號量ADC_LOCK,因為ADC_LOCK在ADC驅動程式中已申明 
這樣就能保證ADC資源在ADC驅動和觸控式螢幕驅動中進行互斥訪問*/  
extern struct semaphore ADC_LOCK;
/*做為一個標籤,只有對觸控式螢幕操作後才對X和Y座標進行轉換*/ 
static int OwnADC = 0;

static void __iomem *base_addr;

老規矩:從init走你

static int __init s3c2410ts_init(void)
{
	struct input_dev *input_dev;

	/*從平臺時鐘佇列中獲取ADC的時鐘,這裡為什麼要取得這個時鐘,
	因為ADC的轉換頻率跟時鐘有關。 系統的一些時鐘定義
	在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;
	}
	 /*時鐘獲取後要使能後才可以使用,clk_enable定義在arch/arm/plat-s3c/clock.c中*/ 
		clk_enable(adc_clock);
	/*
	 將ADC的IO端口占用的這段IO空間對映到記憶體的虛擬地址,ioremap定義在io.h中。 
     注意:IO空間要對映後才能使用,以後對虛擬地址的操作就是對IO空間的操作, 
     S3C2410_PA_ADC是ADC控制器的基地址,定義在mach-s3c2410/include/mach/map.h中,
	 0x20是虛擬地址長度大小
	 */
	base_addr=ioremap(S3C2410_PA_ADC,0x20);
	//普通的錯誤驗證。
	if (base_addr == NULL) {
		printk(KERN_ERR "Failed to remap register block\n");
		return -ENOMEM;
	}

	/* Configure GPIOs */
	/*初始化ADC控制暫存器和ADC觸控式螢幕控制暫存器*/
	s3c2410_ts_connect();
	
	//不懂這是做什麼。
	iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\
		     base_addr+S3C2410_ADCCON);
	iowrite32(0xffff,  base_addr+S3C2410_ADCDLY);
	iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);

	/* Initialise input stuff */
	/*給輸入裝置申請空間,input_allocate_device定義在input.h中*/ 
	input_dev = input_allocate_device();

	if (!input_dev) {
		printk(KERN_ERR "Unable to allocate the input device !!\n");
		return -ENOMEM;
	}
	
/*初始化input子系統的dev結構體*/
	dev = input_dev;
	
	/*下面初始化輸入裝置,即給輸入裝置結構體input_dev的成員設定值。 
    evbit欄位用於描述支援的事件,這裡支援同步事件、按鍵事件、絕對座標事件, 
    BIT巨集實際就是對1進行位操作,定義在linux/bitops.h中*/ 
	dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
	
	/*keybit欄位用於描述按鍵的型別,在input.h中定義了很多,這裡用BTN_TOUCH型別來表示觸控式螢幕的點選*/  
	dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
	
	/*對於觸控式螢幕來說,使用的是絕對座標系統。這裡設定該座標系統中X和Y座標的最小值和最大值(0-1023範圍) 
    ABS_X和ABS_Y就表示X座標和Y座標,ABS_PRESSURE就表示觸控式螢幕是按下還是擡起狀態*/ 
	input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);
	input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
	
	//ABS_PRESSURE表示觸控式螢幕是按下還是擡起狀態。
	input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);
	
	/*以下是設定觸控式螢幕輸入裝置的身份資訊,直接在這裡寫死。 
    這些資訊可以在驅動掛載後在/proc/bus/input/devices中檢視到*/ 
	dev->name = s3c2410ts_name;
	dev->id.bustype = BUS_RS232;
	dev->id.vendor = 0xDEAD;
	dev->id.product = 0xBEEF;
	dev->id.version = S3C2410TSVERSION;


	/* Get irqs */
	/*申請ADC中斷,AD轉換完成後觸發。這裡使用共享中斷IRQF_SHARED是因為該中斷號在ADC驅動中也使用了, 
    最後一個引數1是隨便給的一個值,因為如果不給值設為NULL的話,中斷就申請不成功*/ 
	if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,
		"s3c2410_action", 1)) {
		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", 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 */
	/*!!!!把觸控式螢幕裝置的描述結構體dev註冊進input子系統中*/
	input_register_device(dev);

	return 0;
}

這段函式的主要功能是:啟用ADC所需要的時鐘、對映IO口、初始化暫存器、申請中斷、初始化輸入裝置、將輸入設備註冊到輸入子系統。同時注意這句話:/*!!!!把觸控式螢幕裝置的描述結構體dev註冊進input子系統中*/input_register_device(dev);很幫助真正的理解。還有別忘了根本:/*初始化input子系統的dev結構體*/。同時,/*從平臺時鐘佇列中獲取ADC的時鐘,這裡為什麼要取得這個時鐘,因為ADC的轉換頻率跟時鐘有關。 系統的一些時鐘定義在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/adc_clock = clk_get(NULL, "adc");不跑裸機好多年,時鐘感覺距離好遠!

exit退出函式:

static void __exit s3c2410ts_exit(void)
{
	/*遮蔽和釋放中斷*/
	disable_irq(IRQ_ADC);
	disable_irq(IRQ_TC);
	free_irq(IRQ_TC,dev);
	free_irq(IRQ_ADC,dev);
	/*遮蔽並銷燬時鐘*/
	if (adc_clock) {
		clk_disable(adc_clock);
		clk_put(adc_clock);
		adc_clock = NULL;
	}
	//釋放input裝置。
	input_unregister_device(dev);
	/*釋放虛擬地址對映空間*/
	iounmap(base_addr);
}

關於先釋放input裝置,還是先釋放地址我覺得還是源程式對。

下面開始說幾個中斷函式:


/*TC中斷的處理函式*/
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
	/*用於記錄這一次AD轉換後的值*/ 
	unsigned long data0;
	unsigned long data1;
	/*用於記錄觸控式螢幕操作狀態是按下還是擡起*/
	int updown;

	if (down_trylock(&ADC_LOCK) == 0) {
		OwnADC = 1;
		/*讀的是狀態*/ 
		data0 = ioread32(base_addr+S3C2410_ADCDAT0);
		data1 = ioread32(base_addr+S3C2410_ADCDAT1);

		/*記錄這一次對觸控式螢幕是壓下還是擡起,該狀態儲存在資料暫存器的第15位,所以與上S3C2410_ADCDAT0_UPDOWN*/  
		updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

		if (updown) {
			//如果要是壓下則呼叫touch_timer_fire來啟動ADC轉換。
			touch_timer_fire(0);
		} else {
		/*如果是擡起狀態,就結束了這一次的操作,所以就釋放ADC資源的佔有*/  
			OwnADC = 0;
			up(&ADC_LOCK);
		}
	}

	return IRQ_HANDLED;
}

關鍵點在這裡:if (updown) {//如果要是壓下則呼叫touch_timer_fire來啟動ADC轉換。touch_timer_fire(0);

touch_timer_fire函式:


//AD轉換函式
static void touch_timer_fire(unsigned long data)
{
	 /*用於記錄這一次AD轉換後的值*/
  	unsigned long data0;
  	unsigned long data1;
	/*用於記錄觸控式螢幕操作狀態是按下還是擡起*/
	int updown;
	
	//在未進行判定之前先讀取一次狀態。
  	data0 = ioread32(base_addr+S3C2410_ADCDAT0);
  	data1 = ioread32(base_addr+S3C2410_ADCDAT1);
	
	/*判斷這一次對觸控式螢幕是壓下還是擡起,該狀態儲存在資料暫存器的第15位,所以與上S3C2410_ADCDAT0_UPDOWN*/  
 	updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
	
	//開始判斷觸控式螢幕的狀態。
 	if (updown) {
	/*如果狀態是按下,並且ADC已經轉換了就報告事件和資料*/  
 		if (count != 0) {
			long tmp;
                                                                                                 
			tmp = xp;
			xp = yp;
			yp = tmp;

	/*取4次平均值*/																							
                        xp >>= 2;
                        yp >>= 2;

			/*
				上報座標
				報告X,Y的絕對座標值
			*/
 			input_report_abs(dev, ABS_X, xp);
 			input_report_abs(dev, ABS_Y, yp);
			
			/*報告觸控式螢幕的狀態,1表明觸控式螢幕被按下*/  
 			input_report_key(dev, BTN_TOUCH, 1);
			/*報告按鍵事件,鍵值為1(代表觸控式螢幕對應的按鍵被按下)*/  
 			input_report_abs(dev, ABS_PRESSURE, 1);
			/*結束報告*/
 			input_sync(dev);
 		}
		//歸零
 		xp = 0;
 		yp = 0;
 		count = 0;

		/* 啟動AD 轉換*/
		//設定為自動轉換模式
 		iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
		//啟動AD轉換
 		iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
 	} else {
 		count = 0;
		
		/*報告按鍵事件,鍵值為0(代表觸控式螢幕對應的按鍵被釋放)*/
 		input_report_key(dev, BTN_TOUCH, 0);
		/*報告觸控式螢幕的狀態,0表明觸控式螢幕沒被按下*/  
 		input_report_abs(dev, ABS_PRESSURE, 0);
		//結束報告
 		input_sync(dev);
		/*將觸控式螢幕重新設定為等待中斷狀態*/
 		iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
		/*如果觸控式螢幕擡起,就意味著這一次的操作結束,所以就釋放ADC資源的佔有*/  
		if (OwnADC) {
			OwnADC = 0;
			up(&ADC_LOCK);
		}
 	}
}

/* 啟動AD 轉換*///設定為自動轉換模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);//啟動AD轉換 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);

這裡先留一個疑問。我們來看最後一箇中斷函式:

/*ADC中斷服務程式,AD轉換完成後觸發執行*/


static irqreturn_t stylus_action(int irq, void *dev_id)
{
 unsigned long data0;
 unsigned long data1;

 if (OwnADC) {
   /*讀取這一次AD轉換後的值,注意這次主要讀的是座標*/  
  data0 = ioread32(base_addr+S3C2410_ADCDAT0);
  data1 = ioread32(base_addr+S3C2410_ADCDAT1);
   /*記錄這一次通過AD轉換後的X座標值和Y座標值,根據資料手冊可知,X和Y座標轉換數值 
        分別儲存在資料暫存器0和1的第0-9位,所以這裡與上S3C2410_ADCDAT0_XPDATA_MASK就是取0-9位的值*/  
  xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
  yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
  /*計數這一次AD轉換的次數*/
  count++;
  //這裡說如果轉換次數小於4,則重新啟動轉換。為什麼?
     if (count < (1<<2)) {
   /* 再一次啟動AD轉換*/
   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 {
  /*這裡不懂*/
   mod_timer(&touch_timer, jiffies+1);
   iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
  }
 }

 return IRQ_HANDLED;
}


到這裡整個的input子系統實現觸控式螢幕的主體部分也就結束了。還有好多細節部分和要注意的部分,由於一個腦殘的同學讓我給他百度論文,思路斷了,所以不寫了。不過在原始碼裡都有體現了。