一、GSC3280触摸屏驱动简介 | |||||
ADC与触摸屏控制器通过SPI接口挂在GSC3280的SPI0总线上,支持4线电阻式触摸屏或当ADC输入使用。 控制器可以接四线电阻式触摸屏,最大支持4000像素的屏,市面上绝大部分四线电阻式触摸屏都能支持。所接触摸屏的输出阻抗不要大于2K欧姆,否则会影响测量精度。当控制器四根引脚(XP、YP、XN、YN)接四线电阻式触摸屏时,掉电及屏没有被按下时,控制器的中断信号INT_PEN保持为低电平。当触摸屏被按下时,控制器会置起INT_PEN信号来通知CPU,然后在中断服务程序里,CPU可以通过SPI0接口发出触摸屏相关命令进行操作。接触摸屏时,屏被按下时就有中断,当CPU往SPI_DIN 线上发测量命令时,控制器自动撤掉中断,无需CPU清除。CPU取到有效数据(即在SPI_DOUT线上返回状态0,此时为有效数据)时,如果此时触摸屏还是被按下的(可能是上次按下没来得及撤掉,也可能是再次被按下的),INT_PEN会再次被置起。 | |||||
二、GSC3280四线触摸屏驱动 | |||||
2.1、平台设备注册 | |||||
分析一个驱动,一般都是从模块初始化函数开始,程序如下: 1. #if defined(CONFIG_TOUCHSCREEN_GSC3280) 2. static struct resource gsc3280_ts_resources[] = { 3. { 4. .name = "adc-ts-irq", 5. .start = EXT_GSC3280_ADC_IRQ, 6. .end = EXT_GSC3280_ADC_IRQ, 7. .flags = IORESOURCE_IRQ, 8. }, 9. 10.}; 11.static struct platform_device gsc3280_ts_device = { 12. .name = "adc-ts-irq", 13. .id = -1, 14. .resource = gsc3280_ts_resources, 15. .num_resources = ARRAY_SIZE(gsc3280_ts_resources), 16.}; 17.#endif 18.static struct platform_driver gsc3280_ts_driver = { 19. .driver = { 20. .name = "adc-ts-irq", 21. .owner = THIS_MODULE, 22. }, 23. .probe = gsc3280_ts_probe, 24. .remove = __devexit_p(gsc3280_ts_remove), 25.}; 26.static int __init gsc3280_ts_init(void) 27.{ 28. int ret = 0; 29. 30. ret = platform_driver_register(&gsc3280_ts_driver); 31. if (ret) 32. printk(KERN_ERR "!!!!!!gsc ts init register error!!!!!!\n"); 33. return ret; 34.} 35.static void __exit gsc3280_ts_exit(void) 36.{ 37. platform_driver_unregister(&gsc3280_ts_driver); 38.} 39.subsys_initcall(gsc3280_ts_init); 40.module_exit(gsc3280_ts_exit); 1) 此处模块初始化使用的是subsys_initcall(),优先级高于module_init(),为什么使用subsys_initcall(),将在第三篇中讲述。 | |||||
2.2、探测函数gsc3280_ts_probe() | |||||
程序如下: 1. static int __devinit gsc3280_ts_probe(struct platform_device *pdev) 2. { 3. int ret = 0; 4. struct input_dev *input = NULL; 5. struct gsc3280_ts_s *gsc = NULL; 6. 7. DBG("############\n"); 8. printk(KERN_INFO "gsc3280 touch screen probe start.\n"); 9. gsc = kzalloc(sizeof(struct gsc3280_ts_s), GFP_KERNEL); 10. input = input_allocate_device(); 11. if (!gsc || !input) { 12. ret = -ENOMEM; 13. goto err_free_mem; 14. } 15. gsc->irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 16. if (gsc->irq == NULL) { 17. DBG("!!!!no irq resource specified!\n"); 18. ret = -ENOENT; 19. goto err_free_mem; 20. } 21. ret = request_threaded_irq(gsc->irq->start, NULL, gsc3280_ts_irq, IRQF_ONESHOT, "adcirq", gsc); 22. if (ret) { 23. goto err_free_mem; 24. } 25. writel(0x01, (volatile unsigned int *)0xbc04a0ac); /* enable ts */ 26. 27. spin_lock_init(&gsc->slock); 28. gsc->dev = &pdev->dev; 29. gsc->x = 0; 30. gsc->y = 0; 31. gsc->z = 0; 32. gsc->con_cnt = 0; 33. snprintf(gsc->phys, sizeof(gsc->phys), "%s/input0", dev_name(gsc->dev)); 34. input->name = "h3600_ts"; 35. input->phys = gsc->phys; 36. input->dev.parent = gsc->dev; 37. input->id.vendor = 0x00; 38. input->id.version = 0x00; 39. input->id.product = 0x03; 40. input->id.bustype = BUS_HOST; 41. 42. input->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 43. input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 44. input_set_abs_params(input, ABS_X, 0, 0x3FF, 0, 0); 45. input_set_abs_params(input, ABS_Y, 0, 0x3FF, 0, 0); 46. gsc->input = input; 47. input_set_abs_params(gsc->input, ABS_X, GSC_X_MIN, GSC_X_MAX, GSC_X_FUZZ, 0); 48. input_set_abs_params(gsc->input, ABS_Y, GSC_Y_MIN, GSC_Y_MAX, GSC_Y_FUZZ, 0); 49. input_set_abs_params(gsc->input, ABS_PRESSURE, GSC_PRESSURE_MIN, 50. GSC_PRESSURE_MAX, 0, 0); 51. ret = input_register_device(gsc->input); 52. if (ret) { 53. DBG("!!!!input register device error!\n"); 54. goto err_free_irq; 55. } 56. INIT_LIST_HEAD(&gsc->device_entry); 57. strlcpy(gsc->name, GSC3280_TS_NAME, GSC3280_TS_NAME_SIZE); 58. 59. mutex_lock(&gsc3280_ts_list_lock); 60. list_add(&gsc->device_entry, &gsc3280_ts_list); 61. mutex_unlock(&gsc3280_ts_list_lock); 62. 63. platform_set_drvdata(pdev, gsc); 64. printk(KERN_INFO "gsc3280 touch screen probe success.\n"); 65. DBG("############\n"); 66. return 0; 67. 68.err_free_irq: 69. free_irq(gsc->irq->start, gsc); 70.err_free_mem: 71. input_free_device(input); 72. kfree(gsc); 73. printk(KERN_INFO "!!!!gsc3280 touch screen probe error!\n"); 74. return ret; 75.} 76.static int __devexit gsc3280_ts_remove(struct platform_device *pdev) 77.{ 78. struct gsc3280_ts_s *gsc = platform_get_drvdata(pdev); 79. 80. free_irq(gsc->irq->start, gsc); 81. input_unregister_device(gsc->input); 82. kfree(gsc); 83. platform_set_drvdata(pdev, NULL); 84. printk(KERN_INFO "gsc3280 touch screen remove\n"); 85. return 0; 86.}
1) 首先申请结构体内存gsc3280_ts_s和input设备内存。 2) 申请中断资源和注册中断函数。 3) 初始化成员变量。 4) 注册input_dev。 5) 移除函数主要就是释放探测函数中申请的资源。 | |||||
2.3、中断函数gsc3280_ts_irq() | |||||
程序如下: 1. static int test_ts_state(void) 2. { 3. u32 state = *((volatile unsigned int *)(ICTL_RAW_STATUS_REG)) ; 4. 5. if (state & ICTL_SPI0_BITS) { 6. return TS_PRESS_DOWN; 7. } else 8. return TS_PRESS_UP; 9. } 10. static irqreturn_t gsc3280_ts_irq(int irq, void *dev_id) 11. { 12. u8 flg = 0; 13. u32 x = 0, y = 0, z = 0; 14. struct gsc3280_ts *ts = dev_id; 15. 16.begin: 17. x = adc_cmd(ADC_CMD_MEASUREX); 18. y = adc_cmd(ADC_CMD_MEASUREY); 19. z = adc_cmd(ADC_CMD_MEASUREZ); 20. x = ((x -0x83) * 800) / (0xf5d - 0x83); 21. y = ((0xeea - y) * 480) / (0xeea - 0xcc); 22. 23. if ((z < 700) && (z > 10) && (x > 0) && (x < 800) && (y > 0) && (y < 480)) { 24. if (flg == 0) { 25. flg = 1; 26. gsc3280_report_event(ts, x, y, 0); 27. } 28. gsc3280_report_event(ts, x, y, z); 29. msleep(10); 30. } 31. if (test_ts_state() == TS_PRESS_UP) 32. goto Up; 33. else 34. goto begin; 35.Up: 36. if (flg == 0) 37. return IRQ_HANDLED; 38. if ((x == 0) || (y == 0)) 39. return IRQ_HANDLED; 40. gsc3280_report_event(ts, x, y, 0); 41. return IRQ_HANDLED; 42.}
1) 进入中断后,调用adc_cmd()函数测量触摸点位置和按键力度信息。 2) 根据公式计算绝对坐标,具体公式原理可以查看input子系统(二)--GSC3280一线触摸屏驱动。 3) 判断测量数据是否正确,如果正确,报告相应位置。 4) flg是报告标志,即是否向input子系统报告了位置信息,该变量还用来做是否是第一次触摸判断,如果该变量为0,表示第一次触摸,如果为1,表示不是第一次触摸。 5) mdelay(10);的作用是除抖。 6) 除抖后判断触摸是否按下,如果按下,还需要判断是否是第一次触摸,如果是第一次触摸,而且按键是抬起状态,则表示此次是抖动,直接退出。如果不是第一次触摸,此时触摸是抬起状态,则直接进入抬起流程。 7) 调用gsc_report_data(gsc);报告信息。 8) 再次判断触摸状态,如果是按下,进入开始流程。如果是抬起,进入抬起流程。 9) 抬起流程中,判断是否报告了触摸按下信息,如果报告了,就报告触摸抬起信息。 | |||||
2.4、触摸点信息测量函数 | |||||
程序如下: 1. void spi0_write(unsigned short v) 2. { 3. int cnt=0; 4. DBG("enter adc_write()\n"); 5. do { 6. while (SPI0_STATUS_REG & 0x10) 7. if (cnt++ > 10000) 8. break; 9. } while(0); 10. cnt = 0; 11. /*spi0 fifo can write, transmit fifo empty */ 12. while (SPI0_STATUS_REG & SPI_RX_FULL) 13. if (cnt++ > 1000000) 14. break; 15. SPI0_DATA_REG = v; 16. DBG("leave adc_write()\n"); 17.} 18.unsigned short spi0_read(void) 19.{ 20. int cnt= 0; 21. DBG("enter adc_read()\n"); 22. do { 23. while (SPI0_STATUS_REG & 0x10) 24. if (cnt++ > 10000) 25. break; 26. } while(0); 27. cnt = 0; 28. /*spi0 fifo receive not empty*/ 29. while (!(SPI0_STATUS_REG & SPI_RX_N_EMPTY)) 30. if (cnt++>10000000) 31. break; 32. DBG("leave adc_read()\n"); 33. return (unsigned short)(SPI0_DATA_REG); 34.} 35.unsigned short adc_cmd(unsigned short cmd) 36.{ 37. unsigned short res = 0; 38. DBG("enter adc_cmd\n"); 39. spi0_write( cmd ); 40. while (1) { 41. res = spi0_read(); 42. if ((res == 0xF000) || (res == (0x8000 | (cmd >> 12)))) { 43. spi0_write(0xF000); 44. } 45. else if ( res < 0x1000 ) { //data 46. char buf[20] ; 47. sprintf(buf, "adc_cmd %0x result %0x\n", cmd, res); 48. DBG(buf); 49. return res; 50. } 51. else{ 52. spi0_write( cmd ); 53. } 54. } 55. DBG("leave adc_cmd\n"); 56. return res; 57.} 说明: 1) adc_cmd()函数的形参为具体的命令。 2) adc_cmd()函数首先通过SPI0发送命令,然后根据协议读取数据。 | |||||
2.5、信息报告函数 | |||||
1. static void gsc3280_report_event(struct gsc3280_ts *ts, u32 x, u32 y, u32 z) 2. { 3. #ifdef CONFIG_GSC3280_POS_PRINT 4. printk(KERN_INFO "x = %d\n", x); 5. printk(KERN_INFO "y = %d\n", y); 6. printk(KERN_INFO "z = %d\n", z); 7. #endif 8. 9. input_report_abs(ts->input, ABS_PRESSURE, z); 10. input_report_abs(ts->input, ABS_X, x); 11. input_report_abs(ts->input, ABS_Y, y); 12. if (z > 0) 13. input_report_key(ts->input, BTN_TOUCH, 1); 14. else 15. input_report_key(ts->input, BTN_TOUCH, 0); 16. input_sync(ts->input); 17.} 1) 根据宏标志判断是否打印测量信息,用于测试。 2) 向input核心报告位置信息。 | |||||
三、注册input_handler | |||||
handler中的成员主要作为回调函数,具体如下: | |||||
3.1、模块注册函数 | |||||
程序如下: 1. static struct input_handler tsdev_handler = { 2. .event = tsdev_event, 3. .connect = tsdev_connect, 4. .disconnect = tsdev_disconnect, 5. .fops = &tsdev_fops, 6. .minor = TSDEV_MINOR_BASE, 7. .name = "tsdev", 8. .id_table = tsdev_ids, 9. }; 10.static int __init tsdev_init(void) 11.{ 12. input_register_handler(&tsdev_handler); 13. return 0; 14.} 15.static void __exit tsdev_exit(void) 16.{ 17. input_unregister_handler(&tsdev_handler); 18.} 19.module_init(tsdev_init); 20.module_exit(tsdev_exit);
1) 调用input_register_handler()注册tsdev_handler。input_register_handler()函数将在第三篇文章中讲述。 2) 模块退出函数就是注销tsdev_handler。 | |||||
3.2、连接和释放连接函数 | |||||
程序如下: 1. static int tsdev_connect(struct input_handler *handler, struct input_dev *dev, 2. const struct input_device_id *id) 3. { 4. int minor = 0, ret = 0; 5. struct ts_dev *ts = NULL; 6. for (minor = 0; ((minor < (TSDEV_MINOR_MAX >> 1)) && tsdev_table[minor]); minor++) { 7. ; 8. } 9. if (minor >= (TSDEV_MINOR_MAX >> 1)) { 10. DBG("!!!!You have way too many touchscreens!\n"); 11. return -EBUSY; 12. } 13. if (!(ts = kzalloc(sizeof(struct ts_dev), GFP_KERNEL))) { 14. DBG("!!!!kmalloc error!\n"); 15. return -ENOMEM; 16. } 17. INIT_LIST_HEAD(&ts->list); 18. init_waitqueue_head(&ts->wait); 19. wake_up_interruptible(&ts_wait_queue); 20. dev_set_name(&ts->dev, "ts%d", minor); 21. ts->exist = 1; 22. ts->minor = minor; 23. ts->handle.dev = input_get_device(dev); 24. ts->handle.name = dev_name(&ts->dev);; 25. ts->handle.handler = handler; 26. ts->handle.private = ts; 27. ts->dev.class = &input_class; 28. if (dev) { 29. ts->dev.parent = &dev->dev; 30. } 31. ts->dev.devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor); 32. ts->dev.release = tsdev_free; 33. device_initialize(&ts->dev); 34. ret = input_register_handle(&ts->handle); 35. if (ret) { 36. DBG("!!!!register handler tsdev error!\n"); 37. return -EFAULT; 38. } 39. tsdev_table[minor] = ts; 40. ret = device_add(&ts->dev); 41. if (ret) { 42. DBG("!!!!add tsdev class error!\n"); 43. return -EFAULT; 44. } 45. return 0; 46.} 47.static void tsdev_disconnect(struct input_handle *handle) 48.{ 49. struct ts_dev *ts = handle->private; 50. 51. ts->exist = 0; 52. device_del(&ts->dev); 53. if (ts->minor != (TSDEV_MINOR_MAX >>1)) 54. input_unregister_handle(&ts->handle); 55. put_device(&ts->dev); 56.} 1) 此函数作为handler的回调函数,具体调用地点将在第三篇文章中讲述。 2) 在数组指针tsdev_table中找到还没有使用的数组索引编号。 2) 申请结构体内存,然后对其成员进行初始化。 3) 设置设备名称,此名称就是显示在“/dev”目录下的名称。 4) 计算设备号。 5) 调用input_register_handle()函数注册handle。 6) 将结构体指针放入指针数组tsdev_table中。 7) 释放连接函数主要是注销连接函数中注册的handle。 | |||||
3.3、事件函数 | |||||
程序如下: 1. static void tsdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) 2. { 3. unsigned long flags; 4. struct timeval time; 5. struct tsdev_list *list; 6. struct ts_dev *ts = handle->private; 7. 8. switch (type) { 9. case EV_ABS: 10. switch (code) { 11. case ABS_X: 12. ts->x = value; 13. break; 14. case ABS_Y: 15. ts->y = value; 16. break; 17. case ABS_PRESSURE: 18. ts->pressure = value; 19. break; 20. } 21. break; 22. case EV_REL: 23. switch (code) { 24. case REL_X: 25. ts->x += value; 26. if (ts->x < 0) 27. ts->x = 0; 28. else if (ts->x > gXres) 29. ts->x = gXres; 30. break; 31. case REL_Y: 32. ts->y += value; 33. if (ts->y < 0) 34. ts->y = 0; 35. else if (ts->y > gYres) 36. ts->y = gYres; 37. break; 38. } 39. break; 40. case EV_KEY: 41. if (code == BTN_TOUCH || code == BTN_MOUSE) { 42. switch (value) { 43. case 0: 44. ts->pressure = 0; 45. break; 46. case 1: 47. if (!ts->pressure) 48. ts->pressure = 1; 49. break; 50. } 51. } 52. break; 53. } 54. if (type != EV_SYN) 55. return; 56. list_for_each_entry(list, &ts->list, node) { 57. if (list) { 58. spin_lock_irqsave(&list->lock, flags); 59. do_gettimeofday(&time); 60. list->event[list->head].pressure = ts->pressure; 61. list->event[list->head].x = ts->x; 62. list->event[list->head].y = ts->y; 63. list->head = (list->head + 1) & (TSDEV_BUFFER_SIZE - 1); 64. kill_fasync(&list->fasync, SIGIO, POLL_IN); 65. spin_unlock_irqrestore(&list->lock, flags); 66. } 67. } 68. wake_up_interruptible(&ts_wait_queue); 69.} 1) 事件函数也是一个回调函数,具体调用地点将在第三篇文章中介绍。 2) 首先是一个switch语句,根据不同情况,进入不同的分支。各个分支主要是根据情况的不同,赋值触摸信息。 3) 判断是否是同步事件,如果不是,直接退出。 4) 如果是同步事件,将触摸信息赋值给list结构体成员,list结构体成员值将会被传递到应用层,接下来会讲述。 5) 唤醒等待队列,标志有数据可读 | |||||
3.4、函数操作集tsdev_fops | |||||
操作集具体内容如下: 1. struct file_operations tsdev_fops = { 2. .owner = THIS_MODULE, 3. .open = tsdev_open, 4. .release = tsdev_release, 5. .read = tsdev_read, 6. .poll = tsdev_poll, 7. .fasync = tsdev_fasync, 8. }; 1、open和release函数
1. static int tsdev_open(struct inode *inode, struct file *file) 2. { 3. struct tsdev_list *list; 4. int ret = 0, i = iminor(inode) - TSDEV_MINOR_BASE; 5. 6. if ((i >= TSDEV_MINOR_MAX) || (!tsdev_table[i & TSDEV_MINOR_MASK])) { 7. DBG("!!!!tsdev minor error!\n"); 8. return -ENODEV; 9. } 10. list = kzalloc(sizeof(struct tsdev_list), GFP_KERNEL); 11. if (!list) { 12. DBG("!!!!kzalloc error!\n"); 13. return -ENOMEM; 14. } 15. list->raw = (i >= (TSDEV_MINOR_MAX >> 1)) ? 1 : 0; 16. i &= TSDEV_MINOR_MASK; 17. list->tsdev = tsdev_table[i]; 18. list_add_tail(&list->node, &list->tsdev->list); 19. list->num++; 20. list->head = list->tail = 0; 21. spin_lock_init(&list->lock); 22. file->private_data = list; 23. if (!list->tsdev->open++) 24. if (list->tsdev->exist) 25. ret = input_open_device(&list->tsdev->handle); 26. return ret; 27.} 28.static int tsdev_release(struct inode *inode, struct file *file) 29.{ 30. struct tsdev_list *list = file->private_data; 31. tsdev_fasync(-1, file, 0); 32. list_del(&list->node); 33. if (!--list->tsdev->open) { 34. if (list->tsdev->exist) 35. input_close_device(&list->tsdev->handle); 36. else 37. tsdev_free(&(list->tsdev->dev)); 38. } 39. kfree(list); 40. return 0; 41.}
1) 首先计算设备节点的次设备号,此即接下来申请的结构体内存地址存放索引号。 2) 申请list结构体内存,对其成员变量初始化。 3) 此处list->head和list->tail两个成员变量比较重要,通过这两个变量来标识有多少个数据可读。 4) 释放函数主要就是释放open函数中申请到的资源。 2、读函数 应用层操作“/dev/ts0”设备节点读数据时,就是调用此函数,程序如下: 2. { 3. int ret = 0; 4. unsigned long flags = 0; 5. struct tsdev_list *list = file->private_data; 6. 7. if ((list->head == list->tail) && (file->f_flags & O_NONBLOCK) ) 8. return -EAGAIN; 9. ret = wait_event_interruptible(ts_wait_queue, (list->head != list->tail)); 10. if (ret) 11. return ret; 12. spin_lock_irqsave(&list->lock, flags); 13. while ((list->head != list->tail) && (ret + sizeof (struct ts_event) <= count)) { 14. if (copy_to_user (buffer + ret, list->event + list->tail, sizeof (struct ts_event))) { 15. spin_unlock_irqrestore(&list->lock, flags); 16. return ret; 17. } 18. list->tail = (list->tail + 1) & (TSDEV_BUFFER_SIZE - 1); 19. ret += sizeof (struct ts_event); 20. } 21. spin_unlock_irqrestore(&list->lock, flags); 22. return ret; 23.}
1) 首先判断是否有数据,此时就是通过list->head和list->tail来判断的。 2) 如果没有数据,则表达式(list->head != list->tail)不满足,睡眠等待。 3) 如果有数据,将数据拷贝到应用层,在操作数据过程中,使用自旋锁保护,函数返回值为应用层接收到的数据长度。 3、poll函数 当应用层使用select或者poll函数时,调用的就是底层驱动的poll函数,此处poll函数的程序如下: 2. { 3. unsigned long flags = 0; 4. struct tsdev_list *list = file->private_data; 5. 6. poll_wait(file, &ts_wait_queue, wait); 7. spin_lock_irqsave(&list->lock, flags); 8. if (list->head != list->tail) { 9. spin_unlock_irqrestore(&list->lock, flags); 10. return POLLIN | POLLRDNORM; 11. } 12. spin_unlock_irqrestore(&list->lock, flags); 13. return 0; 14.} 说明: 1) 首先调用poll_wait()函数等待。 2) 当有数据时,唤醒等待队列,从上面发现,唤醒函数在3.3事件函数tsdev_event()中。 3) 如果有数据,则返回值为可读,否则返回0。 4、fasync函数 程序如下: 1. static int tsdev_fasync(int fd, struct file *file, int on) 2. { 3. int ret = 0; 4. struct tsdev_list *list = file->private_data; 5. 6. ret = fasync_helper(fd, file, on, &list->fasync); 7. return ret < 0 ? ret : 0; 8. } 说明: 1) fasync函数供上层调用。 2) 在3.3中调用了kill_fasync(&list->fasync, SIGIO, POLL_IN); 函数,其表示向应用层发送数据,应用层进程中哪个函数调用fasync_helper()函数就向它发送数据,也就是说应用层哪个进程调用tsdev_fasync()函数就向它发送数据。 | |||||
原文参见:http://blog.chinaunix.net/uid-25445243-id-4042727.html | |||||