mini2440的触摸屏驱动----使用input子系统实现

没说程序之前先上图一张,这个图方便理解input子系统下的触摸屏的实现。

mini2440的触摸屏驱动----使用input子系统实现_第1张图片

关于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子系统实现触摸屏的主体部分也就结束了。还有好多细节部分和要注意的部分,由于一个脑残的同学让我给他百度论文,思路断了,所以不写了。不过在源码里都有体现了。

你可能感兴趣的:(mini2440的触摸屏驱动----使用input子系统实现)