本文主要是介绍S3C2440驱动篇—触摸屏驱动分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
S3C2440触摸屏驱动
一.硬件简介
S3C2440触摸屏接口与ADC接口集成在一起,触摸屏X、Y坐标所产生的模拟信号通过通道7、5输入,2440提供触摸屏接口有4种处理模式:普通转换模式、分离的X/Y轴坐标转换模式、自动X/Y轴坐标转换模式、等待中断模式。具体参考2440硬件手册16章。
二.驱动实现
下面是触摸屏驱动源码,其中使用了linux输入子系统input。暂时还没研究这一块,想深入了解可参考相关资料。
1. #include <linux/errno.h> 2. #include <linux/kernel.h> 3. #include <linux/module.h> 4. #include <linux/slab.h> 5. #include <linux/input.h> 6. #include <linux/init.h> 7. #include <linux/serio.h> 8. #include <linux/delay.h> 9. #include <linux/platform_device.h> 10. #include <linux/clk.h> 11. #include <linux/gpio.h> 12. #include <asm/io.h> 13. #include <asm/irq.h> 14. #include <plat/regs-adc.h> 15. #include <mach/regs-gpio.h> 16. 17. #define S3C2410TSVERSION 0x0101 18. #define DEVICE_NAME "YC2440_TS" 19. 20. //定义一个WAIT4INT宏,该宏将对ADC触摸屏控制寄存器进行操作S3C2410_ADCTSC_YM_SEN这些宏都定义在regs-adc.h中 21. #define WAIT4INT(x) (((x)>>8)|S3C2410_ADCTSC_YM_SEN|S3C2410_ADCTSC_YP_SEN|S3C2410_ADCTSC_XP_SEN|S3C2410_ADCTSC_XY_PST(3)) 22. #define AUTOPST (S3C2410_ADCTSC_YM_SEN|S3C2410_ADCTSC_YP_SEN|S3C2410_ADCTSC_XP_SEN|\ 23. S3C2410_ADCTSC_AUTO_PST|S3C2410_ADCTSC_XY_PST(0)) 24. 25. static struct input_dev *dev; //输入设备结构体 26. static long xp; //记录转换后的X、Y坐标值 27. static long yp; 28. static int count; 29. 30. extern struct semaphore ADC_LOCK; //在ADC驱动中定义的信号量 31. static int ownADC = 0; 32. 33. static struct clk *adc_clk; 34. 35. static void __iomem *base_addr; 36. //touch_timer_fire函数分三块执行,下面1、2、3分别实现不同功能 37. static void touch_timer_fire(unsigned long data) 38. { 39. unsigned long data0; 40. unsigned long data1; 41. int updown; 42. 43. data0 = ioread32(base_addr+S3C2410_ADCDAT0); 44. data1 = ioread32(base_addr+S3C2410_ADCDAT1); 45. //判断触摸屏是按下、抬起状态 46. updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); 47. 48. if(updown) 49. { 50. if(count != 0) //1.如果触摸屏按下,并且ADC已转换,则报告事件、数据 51. { 52. long tmp; 53. 54. tmp = xp; 55. xp = yp; 56. yp = tmp; 57. 58. xp >>= 2; 59. yp >>= 2; 60. 61. #ifdef CONFIG_TOUCHSCREEN_DEBUG 62. struct timeval tv; 63. do_gettimeofday(&tv); 64. printk(KERN_DEBUG "T:%06d, X:%03d, Y:%03d\n",(int)tv.tv_usec,xp,yp); 65. #endif 66. 67. input_report_abs(dev,ABS_X,xp); 68. input_report_abs(dev,ABS_Y,yp); 69. 70. input_report_key(dev,BTN_TOUCH,1); 71. input_report_abs(dev,ABS_PRESSURE,1); 72. input_sync(dev); //等待接收方受到数据后回复确认,用于同步 73. } 74. //2.如果触摸屏按下,并且没有ADC转换,则启动ADC转换 75. xp = 0; 76. yp = 0; 77. count = 0; 78. 79. iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE|AUTOPST,base_addr+S3C2410_ADCTSC); 80. iowrite32(ioread32(base_addr+S3C2410_ADCCON)|S3C2410_ADCCON_ENABLE_START,base_addr+S3C2410_ADCCON); 81. } 82. else //3.如果触摸屏抬起状态,则报告事件、数据,重置等待按下状态 83. { 84. count = 0; 85. 86. input_report_key(dev,BTN_TOUCH,0); 87. input_report_abs(dev,ABS_PRESSURE,0); 88. input_sync(dev); 89. 90. iowrite32(WAIT4INT(0),base_addr+S3C2410_ADCTSC); //将触摸屏重新设置为等待中断状态 91. 92. if(ownADC) //如果触摸屏抬起,就意味着这一次的操作结束,所以就释放ADC资源的占有 93. { 94. printk(KERN_INFO "up\n"); 95. ownADC = 0; 96. up(&ADC_LOCK); 97. } 98. } 99. } 100. //定义并初始化了一个定时器touch_timer,定时器服务程序为touch_timer_fire 101. static struct timer_list touch_timer=TIMER_INITIALIZER(touch_timer_fire,0,0); 102. 103. static irqreturn_t stylus_updown(int irq,void *dev_id) //触摸屏中断服务程序,触摸屏按下、抬起执行 104. { 105. unsigned long data0; 106. unsigned long data1; 107. int updown; 108. 109. if(down_trylock(&ADC_LOCK)==0) 110. { 111. ownADC = 1; 112. data0 = ioread32(base_addr+S3C2410_ADCDAT0); 113. data1 = ioread32(base_addr+S3C2410_ADCDAT1); 114. 115. updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); 116. 117. if(updown) 118. { //如果触摸屏按下,则执行touch_timer_fire的功能2 119. printk(KERN_INFO "down\n"); 120. touch_timer_fire(0); 121. } 122. else 123. { //如果抬起,结束一次操作,释放相应资源 124. printk(KERN_INFO "up-irq\n"); 125. ownADC = 0; 126. up(&ADC_LOCK); 127. } 128. } 129. return IRQ_HANDLED; 130. } 131. 132. static irqreturn_t stylus_action(int irq,void *dev_id) //ADC中断服务程序 133. { 134. unsigned long data0; 135. unsigned long data1; 136. 137. if(ownADC) 138. { //读取一次转换值 139. data0 = ioread32(base_addr+S3C2410_ADCDAT0); 140. data1 = ioread32(base_addr+S3C2410_ADCDAT1); 141. 142. xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; 143. yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; 144. count++; 145. 146. if(count<(1<<2)) //如果转换次数小于4次,重启AD转换 147. { 148. iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE|AUTOPST,base_addr+S3C2410_ADCTSC); 149. iowrite32(ioread32(base_addr+S3C2410_ADCCON)|S3C2410_ADCCON_ENABLE_START,base_addr+S3C2410_ADCCON); 150. } 151. else //如果转换4次,启动1个时间滴答的定时器,最终调用touch_timer_fire功能1或功能3 152. { 153. mod_timer(&touch_timer,jiffies+1); 154. iowrite32(WAIT4INT(1),base_addr+S3C2410_ADCTSC); //置触摸屏等待抬起中断 155. } 156. } 157. 158. return IRQ_HANDLED; 159. } 160. 161. static int __init s3c2440ts_init(void) 162. { 163. int ret; 164. 165. adc_clk = clk_get(NULL,"adc"); 166. if(!adc_clk) 167. { 168. printk(KERN_ERR "Failed to get adc clock\n"); 169. return -ENOENT; 170. } 171. 172. clk_enable(adc_clk); 173. 174. base_addr = ioremap(S3C2410_PA_ADC,0x20); 175. if(base_addr==NULL) 176. { 177. printk(KERN_ERR "Failed to remap register\n"); 178. ret = -EINVAL; 179. goto err_remap; 180. } 181. //初始化AD转换参数,置触摸屏等待按下中断 182. iowrite32(S3C2410_ADCCON_PRSCEN|S3C2410_ADCCON_PRSCVL(0xff),base_addr+S3C2410_ADCCON); 183. iowrite32(0xffff,base_addr+S3C2410_ADCDLY); 184. iowrite32(WAIT4INT(0),base_addr+S3C2410_ADCTSC); 185. //输入设备申请空间,位于include/linux/input.h 186. dev = input_allocate_device(); 187. if(!dev) 188. { 189. printk(KERN_ERR "unable to allocate input_dev\n"); 190. ret = -ENOMEM; 191. goto err_alloc; 192. } 193. 194. /*下面初始化输入设备,即给输入设备结构体input_dev的成员设置值。evbit字段用于描述支持的事件, 195. 这里支持同步事件、按键事件、绝对坐标事件,BIT宏实际就是对1进行位操作,定义在linux/bitops.h中*/ 196. dev->evbit[0] = BIT(EV_SYN)|BIT(EV_KEY)|BIT(EV_ABS); 197. /*keybit字段用于描述按键的类型,在input.h中定义了很多,这里用BTN_TOUCH类型来表示触摸屏的点击*/ 198. dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); 199. /*对于触摸屏来说,使用的是绝对坐标系统。这里设置该坐标系统中X和Y坐标的最小值和最大值(0-1023范围) 200. ABS_X和ABS_Y就表示X坐标和Y坐标,ABS_PRESSURE就表示触摸屏是按下还是抬起状态*/ 201. input_set_abs_params(dev,ABS_X,0,0x3ff,0,0); 202. input_set_abs_params(dev,ABS_Y,0,0x3ff,0,0); 203. input_set_abs_params(dev,ABS_PRESSURE,0,1,0,0); 204. 205. dev->name = DEVICE_NAME; /*设备名称*/ 206. dev->id.bustype = BUS_RS232; /*总线类型*/ 207. dev->id.vendor = 0xDEAD; /*经销商ID号*/ 208. dev->id.product = 0xBEEF; /*产品ID号*/ 209. dev->id.version = S3C2410TSVERSION; /*版本ID号*/ 210. 211. //申请中断,IRQ_ADC为AD、触摸屏共享 212. ret = request_irq(IRQ_ADC,stylus_action,IRQF_SHARED|IRQF_SAMPLE_RANDOM,DEVICE_NAME,dev); 213. if(ret) 214. { 215. printk(KERN_ERR "request IRQ_ADC fail\n"); 216. ret = -EINVAL; 217. goto err_alloc; 218. } 219. 220. ret = request_irq(IRQ_TC,stylus_updown,IRQF_SAMPLE_RANDOM,DEVICE_NAME,dev); 221. if(ret) 222. { 223. printk(KERN_ERR "requers IRQ_TS fail\n"); 224. ret = -EINVAL; 225. goto err_adcirq; 226. } 227. 228. printk(KERN_INFO "%s successfully load\n",DEVICE_NAME); 229. 230. /*把dev触摸屏设备注册到输入子系统中*/ 231. input_register_device(dev); 232. return 0; 233. 234. 235. 236. err_adcirq: 237. free_irq(IRQ_ADC,dev); 238. 239. err_alloc: 240. iounmap(base_addr); 241. 242. err_remap: 243. clk_disable(adc_clk); 244. clk_put(adc_clk); 245. 246. return ret; 247. } 248. 249. static void __exit s3c2440ts_exit(void) 250. { 251. disable_irq(IRQ_ADC); 252. disable_irq(IRQ_TC); 253. free_irq(IRQ_ADC,dev); 254. free_irq(IRQ_TC,dev); 255. 256. if(adc_clk); 257. { 258. clk_disable(adc_clk); 259. clk_put(adc_clk); 260. adc_clk = NULL; 261. } 262. 263. input_unregister_device(dev); 264. iounmap(base_addr); 265. } 266. 267. module_init(s3c2440ts_init); 268. module_exit(s3c2440ts_exit); 269. 270. MODULE_LICENSE("GPL"); 271. MODULE_AUTHOR("Zechin Liao");
(1)如果触摸屏感觉到触摸,则触发触摸屏中断即进入stylus_updown,获取ADC_LOCK后判断触摸屏状态为按下,则调用touch_timer_fire启动ADC转换;
(2)当ADC转换启动后,触发ADC中断即进入stylus_action,如果这一次转换的次数小于4,则重新启动ADC进行转换,如果4次完毕后,启动1个时间滴答的定时器,停止ADC转换,也就是说在这个时间滴答内,ADC转换是停止的;
(3)这里为什么要在1个时间滴答到来之前停止ADC的转换呢?这是为了防止屏幕抖动。
(4)如果1个时间滴答到来则进入定时器服务程序touch_timer_fire,判断触摸屏仍然处于按下状态则上报事件和转换的数据,并重启ADC转换,重复第(2)步;
(5)如果触摸抬起了,则上报释放事件,并将触摸屏重新设置为等待中断状态。
这里采用动态编译,如果把触摸屏驱动编译进内核,参考ADC驱动。
编辑Makefile:
1. ifneq ($(KERNELRELEASE),) 2. obj-m:=s3c2440_ts.o 3. else 4. KERNELDIR:=/home/liao/image/linux-2.6.32.2 5. PWD:=$(shell pwd) 6. all: 7. make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux- 8. clean: 9. rm -rf *.ko *.o *.mod.c *.mod.o *.symvers 10. endif
# make
# insmod s3c2440_ts.ko
# cat /proc/devices 查看input设备号
# cat /bus/input/devices 查看触摸屏设备信息
# mkdir /dev/input
# mknod /dev/input/event0 c 13 64 添加设备文件
三.测试
编写、编译下面测试程序,运行,点击触摸屏会打印采集的AD转换值。这只是一个简单应用,真正触摸屏应用还需要坐标转换、校正,可参考tslib应用。
1. #include <stdio.h> 2. #include <stdlib.h> 3. #include <fcntl.h> 4. #include <linux/input.h> 5. 6. struct input_event buff; 7. 8. int main(void) 9. { 10. int fd; 11. int ret; 12. int count=0; 13. 14. fd = open("/dev/input/event0",O_RDWR); 15. if(fd<0) 16. { 17. perror("can not open event0\n"); 18. return -1; 19. } 20. 21. while(1) 22. { 23. ret = read(fd,&buff,sizeof(struct input_event)); 24. //if(ret == sizeof(struct input_event)) 25. printf("type:%d\n code:%d\n,value:%d\n",buff.type,buff.code,buff.value); 26. count++; 27. printf("count=%d\n",count); 28. } 29. close(fd); 30. }
这篇关于S3C2440驱动篇—触摸屏驱动分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!