ucb1x00-ts.c
来自「linux 内核源代码」· C语言 代码 · 共 441 行
C
441 行
/* * Touchscreen driver for UCB1x00-based touchscreens * * Copyright (C) 2001 Russell King, All Rights Reserved. * Copyright (C) 2005 Pavel Machek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 21-Jan-2002 <jco@ict.es> : * * Added support for synchronous A/D mode. This mode is useful to * avoid noise induced in the touchpanel by the LCD, provided that * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin. * It is important to note that the signal connected to the ADCSYNC * pin should provide pulses even when the LCD is blanked, otherwise * a pen touch needed to unblank the LCD will never be read. */#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/smp.h>#include <linux/sched.h>#include <linux/completion.h>#include <linux/delay.h>#include <linux/string.h>#include <linux/input.h>#include <linux/device.h>#include <linux/freezer.h>#include <linux/slab.h>#include <linux/kthread.h>#include <asm/dma.h>#include <asm/semaphore.h>#include <asm/arch/collie.h>#include <asm/mach-types.h>#include "ucb1x00.h"struct ucb1x00_ts { struct input_dev *idev; struct ucb1x00 *ucb; wait_queue_head_t irq_wait; struct task_struct *rtask; u16 x_res; u16 y_res; unsigned int restart:1; unsigned int adcsync:1;};static int adcsync;static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y){ struct input_dev *idev = ts->idev; input_report_abs(idev, ABS_X, x); input_report_abs(idev, ABS_Y, y); input_report_abs(idev, ABS_PRESSURE, pressure); input_sync(idev);}static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts){ struct input_dev *idev = ts->idev; input_report_abs(idev, ABS_PRESSURE, 0); input_sync(idev);}/* * Switch to interrupt mode. */static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts){ ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | UCB_TS_CR_MODE_INT);}/* * Switch to pressure mode, and read pressure. We don't need to wait * here, since both plates are being driven. */static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts){ if (machine_is_collie()) { ucb1x00_io_write(ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0); ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); udelay(55); return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync); } else { ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); }}/* * Switch to X position mode and measure Y plate. We switch the plate * configuration in pressure mode, then switch to position mode. This * gives a faster response time. Even so, we need to wait about 55us * for things to stabilise. */static inline unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts){ if (machine_is_collie()) ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); else { ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); } ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); udelay(55); return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync);}/* * Switch to Y position mode and measure X plate. We switch the plate * configuration in pressure mode, then switch to position mode. This * gives a faster response time. Even so, we need to wait about 55us * for things to stabilise. */static inline unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts){ if (machine_is_collie()) ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); else { ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); } ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); udelay(55); return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync);}/* * Switch to X plate resistance mode. Set MX to ground, PX to * supply. Measure current. */static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts){ ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync);}/* * Switch to Y plate resistance mode. Set MY to ground, PY to * supply. Measure current. */static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts){ ucb1x00_reg_write(ts->ucb, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync);}static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts){ unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR); if (machine_is_collie()) return (!(val & (UCB_TS_CR_TSPX_LOW))); else return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW));}/* * This is a RT kernel thread that handles the ADC accesses * (mainly so we can use semaphores in the UCB1200 core code * to serialise accesses to the ADC). */static int ucb1x00_thread(void *_ts){ struct ucb1x00_ts *ts = _ts; struct task_struct *tsk = current; DECLARE_WAITQUEUE(wait, tsk); int valid = 0; set_freezable(); add_wait_queue(&ts->irq_wait, &wait); while (!kthread_should_stop()) { unsigned int x, y, p; signed long timeout; ts->restart = 0; ucb1x00_adc_enable(ts->ucb); x = ucb1x00_ts_read_xpos(ts); y = ucb1x00_ts_read_ypos(ts); p = ucb1x00_ts_read_pressure(ts); /* * Switch back to interrupt mode. */ ucb1x00_ts_mode_int(ts); ucb1x00_adc_disable(ts->ucb); msleep(10); ucb1x00_enable(ts->ucb); if (ucb1x00_ts_pen_down(ts)) { set_task_state(tsk, TASK_INTERRUPTIBLE); ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, machine_is_collie() ? UCB_RISING : UCB_FALLING); ucb1x00_disable(ts->ucb); /* * If we spat out a valid sample set last time, * spit out a "pen off" sample here. */ if (valid) { ucb1x00_ts_event_release(ts); valid = 0; } timeout = MAX_SCHEDULE_TIMEOUT; } else { ucb1x00_disable(ts->ucb); /* * Filtering is policy. Policy belongs in user * space. We therefore leave it to user space * to do any filtering they please. */ if (!ts->restart) { ucb1x00_ts_evt_add(ts, p, x, y); valid = 1; } set_task_state(tsk, TASK_INTERRUPTIBLE); timeout = HZ / 100; } try_to_freeze(); schedule_timeout(timeout); } remove_wait_queue(&ts->irq_wait, &wait); ts->rtask = NULL; return 0;}/* * We only detect touch screen _touches_ with this interrupt * handler, and even then we just schedule our task. */static void ucb1x00_ts_irq(int idx, void *id){ struct ucb1x00_ts *ts = id; ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); wake_up(&ts->irq_wait);}static int ucb1x00_ts_open(struct input_dev *idev){ struct ucb1x00_ts *ts = input_get_drvdata(idev); int ret = 0; BUG_ON(ts->rtask); init_waitqueue_head(&ts->irq_wait); ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_TSPX, ucb1x00_ts_irq, ts); if (ret < 0) goto out; /* * If we do this at all, we should allow the user to * measure and read the X and Y resistance at any time. */ ucb1x00_adc_enable(ts->ucb); ts->x_res = ucb1x00_ts_read_xres(ts); ts->y_res = ucb1x00_ts_read_yres(ts); ucb1x00_adc_disable(ts->ucb); ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd"); if (!IS_ERR(ts->rtask)) { ret = 0; } else { ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); ts->rtask = NULL; ret = -EFAULT; } out: return ret;}/* * Release touchscreen resources. Disable IRQs. */static void ucb1x00_ts_close(struct input_dev *idev){ struct ucb1x00_ts *ts = input_get_drvdata(idev); if (ts->rtask) kthread_stop(ts->rtask); ucb1x00_enable(ts->ucb); ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0); ucb1x00_disable(ts->ucb);}#ifdef CONFIG_PMstatic int ucb1x00_ts_resume(struct ucb1x00_dev *dev){ struct ucb1x00_ts *ts = dev->priv; if (ts->rtask != NULL) { /* * Restart the TS thread to ensure the * TS interrupt mode is set up again * after sleep. */ ts->restart = 1; wake_up(&ts->irq_wait); } return 0;}#else#define ucb1x00_ts_resume NULL#endif/* * Initialisation. */static int ucb1x00_ts_add(struct ucb1x00_dev *dev){ struct ucb1x00_ts *ts; struct input_dev *idev; int err; ts = kzalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL); idev = input_allocate_device(); if (!ts || !idev) { err = -ENOMEM; goto fail; } ts->ucb = dev->ucb; ts->idev = idev; ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC; idev->name = "Touchscreen panel"; idev->id.product = ts->ucb->id; idev->open = ucb1x00_ts_open; idev->close = ucb1x00_ts_close; __set_bit(EV_ABS, idev->evbit); __set_bit(ABS_X, idev->absbit); __set_bit(ABS_Y, idev->absbit); __set_bit(ABS_PRESSURE, idev->absbit); input_set_drvdata(idev, ts); err = input_register_device(idev); if (err) goto fail; dev->priv = ts; return 0; fail: input_free_device(idev); kfree(ts); return err;}static void ucb1x00_ts_remove(struct ucb1x00_dev *dev){ struct ucb1x00_ts *ts = dev->priv; input_unregister_device(ts->idev); kfree(ts);}static struct ucb1x00_driver ucb1x00_ts_driver = { .add = ucb1x00_ts_add, .remove = ucb1x00_ts_remove, .resume = ucb1x00_ts_resume,};static int __init ucb1x00_ts_init(void){ return ucb1x00_register_driver(&ucb1x00_ts_driver);}static void __exit ucb1x00_ts_exit(void){ ucb1x00_unregister_driver(&ucb1x00_ts_driver);}module_param(adcsync, int, 0444);module_init(ucb1x00_ts_init);module_exit(ucb1x00_ts_exit);MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");MODULE_DESCRIPTION("UCB1x00 touchscreen driver");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?