brintos

brintos / linux-shallow public Read only

0
0
Text · 10.7 KiB · 6e1b38f Raw
446 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 *  Touchscreen driver for UCB1x00-based touchscreens4 *5 *  Copyright (C) 2001 Russell King, All Rights Reserved.6 *  Copyright (C) 2005 Pavel Machek7 *8 * 21-Jan-2002 <jco@ict.es> :9 *10 * Added support for synchronous A/D mode. This mode is useful to11 * avoid noise induced in the touchpanel by the LCD, provided that12 * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin.13 * It is important to note that the signal connected to the ADCSYNC14 * pin should provide pulses even when the LCD is blanked, otherwise15 * a pen touch needed to unblank the LCD will never be read.16 */17#include <linux/module.h>18#include <linux/moduleparam.h>19#include <linux/init.h>20#include <linux/interrupt.h>21#include <linux/sched.h>22#include <linux/spinlock.h>23#include <linux/completion.h>24#include <linux/delay.h>25#include <linux/string.h>26#include <linux/input.h>27#include <linux/device.h>28#include <linux/freezer.h>29#include <linux/slab.h>30#include <linux/kthread.h>31#include <linux/mfd/ucb1x00.h>32 33#include <mach/collie.h>34#include <asm/mach-types.h>35 36 37 38struct ucb1x00_ts {39	struct input_dev	*idev;40	struct ucb1x00		*ucb;41 42	spinlock_t		irq_lock;43	unsigned		irq_disabled;44	wait_queue_head_t	irq_wait;45	struct task_struct	*rtask;46	u16			x_res;47	u16			y_res;48 49	unsigned int		adcsync:1;50};51 52static int adcsync;53 54static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y)55{56	struct input_dev *idev = ts->idev;57 58	input_report_abs(idev, ABS_X, x);59	input_report_abs(idev, ABS_Y, y);60	input_report_abs(idev, ABS_PRESSURE, pressure);61	input_report_key(idev, BTN_TOUCH, 1);62	input_sync(idev);63}64 65static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts)66{67	struct input_dev *idev = ts->idev;68 69	input_report_abs(idev, ABS_PRESSURE, 0);70	input_report_key(idev, BTN_TOUCH, 0);71	input_sync(idev);72}73 74/*75 * Switch to interrupt mode.76 */77static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts)78{79	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,80			UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW |81			UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND |82			UCB_TS_CR_MODE_INT);83}84 85/*86 * Switch to pressure mode, and read pressure.  We don't need to wait87 * here, since both plates are being driven.88 */89static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts)90{91	if (machine_is_collie()) {92		ucb1x00_io_write(ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0);93		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,94				  UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW |95				  UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);96 97		udelay(55);98 99		return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync);100	} else {101		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,102				  UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW |103				  UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND |104				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);105 106		return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync);107	}108}109 110/*111 * Switch to X position mode and measure Y plate.  We switch the plate112 * configuration in pressure mode, then switch to position mode.  This113 * gives a faster response time.  Even so, we need to wait about 55us114 * for things to stabilise.115 */116static inline unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts)117{118	if (machine_is_collie())119		ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK);120	else {121		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,122				  UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |123				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);124		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,125				  UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |126				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);127	}128	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,129			UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |130			UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);131 132	udelay(55);133 134	return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync);135}136 137/*138 * Switch to Y position mode and measure X plate.  We switch the plate139 * configuration in pressure mode, then switch to position mode.  This140 * gives a faster response time.  Even so, we need to wait about 55us141 * for things to stabilise.142 */143static inline unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts)144{145	if (machine_is_collie())146		ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK);147	else {148		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,149				  UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |150				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);151		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,152				  UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |153				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);154	}155 156	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,157			UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |158			UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);159 160	udelay(55);161 162	return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync);163}164 165/*166 * Switch to X plate resistance mode.  Set MX to ground, PX to167 * supply.  Measure current.168 */169static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts)170{171	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,172			UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |173			UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);174	return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync);175}176 177/*178 * Switch to Y plate resistance mode.  Set MY to ground, PY to179 * supply.  Measure current.180 */181static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts)182{183	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,184			UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |185			UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);186	return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync);187}188 189static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts)190{191	unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR);192 193	if (machine_is_collie())194		return (!(val & (UCB_TS_CR_TSPX_LOW)));195	else196		return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW));197}198 199/*200 * This is a RT kernel thread that handles the ADC accesses201 * (mainly so we can use semaphores in the UCB1200 core code202 * to serialise accesses to the ADC).203 */204static int ucb1x00_thread(void *_ts)205{206	struct ucb1x00_ts *ts = _ts;207	DECLARE_WAITQUEUE(wait, current);208	bool frozen, ignore = false;209	int valid = 0;210 211	set_freezable();212	add_wait_queue(&ts->irq_wait, &wait);213	while (!kthread_freezable_should_stop(&frozen)) {214		unsigned int x, y, p;215		signed long timeout;216 217		if (frozen)218			ignore = true;219 220		ucb1x00_adc_enable(ts->ucb);221 222		x = ucb1x00_ts_read_xpos(ts);223		y = ucb1x00_ts_read_ypos(ts);224		p = ucb1x00_ts_read_pressure(ts);225 226		/*227		 * Switch back to interrupt mode.228		 */229		ucb1x00_ts_mode_int(ts);230		ucb1x00_adc_disable(ts->ucb);231 232		msleep(10);233 234		ucb1x00_enable(ts->ucb);235 236 237		if (ucb1x00_ts_pen_down(ts)) {238			set_current_state(TASK_INTERRUPTIBLE);239 240			spin_lock_irq(&ts->irq_lock);241			if (ts->irq_disabled) {242				ts->irq_disabled = 0;243				enable_irq(ts->ucb->irq_base + UCB_IRQ_TSPX);244			}245			spin_unlock_irq(&ts->irq_lock);246			ucb1x00_disable(ts->ucb);247 248			/*249			 * If we spat out a valid sample set last time,250			 * spit out a "pen off" sample here.251			 */252			if (valid) {253				ucb1x00_ts_event_release(ts);254				valid = 0;255			}256 257			timeout = MAX_SCHEDULE_TIMEOUT;258		} else {259			ucb1x00_disable(ts->ucb);260 261			/*262			 * Filtering is policy.  Policy belongs in user263			 * space.  We therefore leave it to user space264			 * to do any filtering they please.265			 */266			if (!ignore) {267				ucb1x00_ts_evt_add(ts, p, x, y);268				valid = 1;269			}270 271			set_current_state(TASK_INTERRUPTIBLE);272			timeout = HZ / 100;273		}274 275		schedule_timeout(timeout);276	}277 278	remove_wait_queue(&ts->irq_wait, &wait);279 280	ts->rtask = NULL;281	return 0;282}283 284/*285 * We only detect touch screen _touches_ with this interrupt286 * handler, and even then we just schedule our task.287 */288static irqreturn_t ucb1x00_ts_irq(int irq, void *id)289{290	struct ucb1x00_ts *ts = id;291 292	spin_lock(&ts->irq_lock);293	ts->irq_disabled = 1;294	disable_irq_nosync(ts->ucb->irq_base + UCB_IRQ_TSPX);295	spin_unlock(&ts->irq_lock);296	wake_up(&ts->irq_wait);297 298	return IRQ_HANDLED;299}300 301static int ucb1x00_ts_open(struct input_dev *idev)302{303	struct ucb1x00_ts *ts = input_get_drvdata(idev);304	unsigned long flags = 0;305	int ret = 0;306 307	BUG_ON(ts->rtask);308 309	if (machine_is_collie())310		flags = IRQF_TRIGGER_RISING;311	else312		flags = IRQF_TRIGGER_FALLING;313 314	ts->irq_disabled = 0;315 316	init_waitqueue_head(&ts->irq_wait);317	ret = request_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ucb1x00_ts_irq,318			  flags, "ucb1x00-ts", ts);319	if (ret < 0)320		goto out;321 322	/*323	 * If we do this at all, we should allow the user to324	 * measure and read the X and Y resistance at any time.325	 */326	ucb1x00_adc_enable(ts->ucb);327	ts->x_res = ucb1x00_ts_read_xres(ts);328	ts->y_res = ucb1x00_ts_read_yres(ts);329	ucb1x00_adc_disable(ts->ucb);330 331	ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd");332	if (!IS_ERR(ts->rtask)) {333		ret = 0;334	} else {335		free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts);336		ts->rtask = NULL;337		ret = -EFAULT;338	}339 340 out:341	return ret;342}343 344/*345 * Release touchscreen resources.  Disable IRQs.346 */347static void ucb1x00_ts_close(struct input_dev *idev)348{349	struct ucb1x00_ts *ts = input_get_drvdata(idev);350 351	if (ts->rtask)352		kthread_stop(ts->rtask);353 354	ucb1x00_enable(ts->ucb);355	free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts);356	ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0);357	ucb1x00_disable(ts->ucb);358}359 360 361/*362 * Initialisation.363 */364static int ucb1x00_ts_add(struct ucb1x00_dev *dev)365{366	struct ucb1x00_ts *ts;367	struct input_dev *idev;368	int err;369 370	ts = kzalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL);371	idev = input_allocate_device();372	if (!ts || !idev) {373		err = -ENOMEM;374		goto fail;375	}376 377	ts->ucb = dev->ucb;378	ts->idev = idev;379	ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC;380	spin_lock_init(&ts->irq_lock);381 382	idev->name       = "Touchscreen panel";383	idev->id.product = ts->ucb->id;384	idev->open       = ucb1x00_ts_open;385	idev->close      = ucb1x00_ts_close;386	idev->dev.parent = &ts->ucb->dev;387 388	idev->evbit[0]   = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY);389	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);390 391	input_set_drvdata(idev, ts);392 393	ucb1x00_adc_enable(ts->ucb);394	ts->x_res = ucb1x00_ts_read_xres(ts);395	ts->y_res = ucb1x00_ts_read_yres(ts);396	ucb1x00_adc_disable(ts->ucb);397 398	input_set_abs_params(idev, ABS_X, 0, ts->x_res, 0, 0);399	input_set_abs_params(idev, ABS_Y, 0, ts->y_res, 0, 0);400	input_set_abs_params(idev, ABS_PRESSURE, 0, 0, 0, 0);401 402	err = input_register_device(idev);403	if (err)404		goto fail;405 406	dev->priv = ts;407 408	return 0;409 410 fail:411	input_free_device(idev);412	kfree(ts);413	return err;414}415 416static void ucb1x00_ts_remove(struct ucb1x00_dev *dev)417{418	struct ucb1x00_ts *ts = dev->priv;419 420	input_unregister_device(ts->idev);421	kfree(ts);422}423 424static struct ucb1x00_driver ucb1x00_ts_driver = {425	.add		= ucb1x00_ts_add,426	.remove		= ucb1x00_ts_remove,427};428 429static int __init ucb1x00_ts_init(void)430{431	return ucb1x00_register_driver(&ucb1x00_ts_driver);432}433 434static void __exit ucb1x00_ts_exit(void)435{436	ucb1x00_unregister_driver(&ucb1x00_ts_driver);437}438 439module_param(adcsync, int, 0444);440module_init(ucb1x00_ts_init);441module_exit(ucb1x00_ts_exit);442 443MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");444MODULE_DESCRIPTION("UCB1x00 touchscreen driver");445MODULE_LICENSE("GPL");446