162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Touchscreen driver for UCB1x00-based touchscreens
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2001 Russell King, All Rights Reserved.
662306a36Sopenharmony_ci *  Copyright (C) 2005 Pavel Machek
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * 21-Jan-2002 <jco@ict.es> :
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Added support for synchronous A/D mode. This mode is useful to
1162306a36Sopenharmony_ci * avoid noise induced in the touchpanel by the LCD, provided that
1262306a36Sopenharmony_ci * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin.
1362306a36Sopenharmony_ci * It is important to note that the signal connected to the ADCSYNC
1462306a36Sopenharmony_ci * pin should provide pulses even when the LCD is blanked, otherwise
1562306a36Sopenharmony_ci * a pen touch needed to unblank the LCD will never be read.
1662306a36Sopenharmony_ci */
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/moduleparam.h>
1962306a36Sopenharmony_ci#include <linux/init.h>
2062306a36Sopenharmony_ci#include <linux/interrupt.h>
2162306a36Sopenharmony_ci#include <linux/sched.h>
2262306a36Sopenharmony_ci#include <linux/spinlock.h>
2362306a36Sopenharmony_ci#include <linux/completion.h>
2462306a36Sopenharmony_ci#include <linux/delay.h>
2562306a36Sopenharmony_ci#include <linux/string.h>
2662306a36Sopenharmony_ci#include <linux/input.h>
2762306a36Sopenharmony_ci#include <linux/device.h>
2862306a36Sopenharmony_ci#include <linux/freezer.h>
2962306a36Sopenharmony_ci#include <linux/slab.h>
3062306a36Sopenharmony_ci#include <linux/kthread.h>
3162306a36Sopenharmony_ci#include <linux/mfd/ucb1x00.h>
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#include <mach/collie.h>
3462306a36Sopenharmony_ci#include <asm/mach-types.h>
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct ucb1x00_ts {
3962306a36Sopenharmony_ci	struct input_dev	*idev;
4062306a36Sopenharmony_ci	struct ucb1x00		*ucb;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	spinlock_t		irq_lock;
4362306a36Sopenharmony_ci	unsigned		irq_disabled;
4462306a36Sopenharmony_ci	wait_queue_head_t	irq_wait;
4562306a36Sopenharmony_ci	struct task_struct	*rtask;
4662306a36Sopenharmony_ci	u16			x_res;
4762306a36Sopenharmony_ci	u16			y_res;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	unsigned int		adcsync:1;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int adcsync;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct input_dev *idev = ts->idev;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	input_report_abs(idev, ABS_X, x);
5962306a36Sopenharmony_ci	input_report_abs(idev, ABS_Y, y);
6062306a36Sopenharmony_ci	input_report_abs(idev, ABS_PRESSURE, pressure);
6162306a36Sopenharmony_ci	input_report_key(idev, BTN_TOUCH, 1);
6262306a36Sopenharmony_ci	input_sync(idev);
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct input_dev *idev = ts->idev;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	input_report_abs(idev, ABS_PRESSURE, 0);
7062306a36Sopenharmony_ci	input_report_key(idev, BTN_TOUCH, 0);
7162306a36Sopenharmony_ci	input_sync(idev);
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/*
7562306a36Sopenharmony_ci * Switch to interrupt mode.
7662306a36Sopenharmony_ci */
7762306a36Sopenharmony_cistatic inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
8062306a36Sopenharmony_ci			UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW |
8162306a36Sopenharmony_ci			UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND |
8262306a36Sopenharmony_ci			UCB_TS_CR_MODE_INT);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci/*
8662306a36Sopenharmony_ci * Switch to pressure mode, and read pressure.  We don't need to wait
8762306a36Sopenharmony_ci * here, since both plates are being driven.
8862306a36Sopenharmony_ci */
8962306a36Sopenharmony_cistatic inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	if (machine_is_collie()) {
9262306a36Sopenharmony_ci		ucb1x00_io_write(ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0);
9362306a36Sopenharmony_ci		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
9462306a36Sopenharmony_ci				  UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW |
9562306a36Sopenharmony_ci				  UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci		udelay(55);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync);
10062306a36Sopenharmony_ci	} else {
10162306a36Sopenharmony_ci		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
10262306a36Sopenharmony_ci				  UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW |
10362306a36Sopenharmony_ci				  UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND |
10462306a36Sopenharmony_ci				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync);
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci/*
11162306a36Sopenharmony_ci * Switch to X position mode and measure Y plate.  We switch the plate
11262306a36Sopenharmony_ci * configuration in pressure mode, then switch to position mode.  This
11362306a36Sopenharmony_ci * gives a faster response time.  Even so, we need to wait about 55us
11462306a36Sopenharmony_ci * for things to stabilise.
11562306a36Sopenharmony_ci */
11662306a36Sopenharmony_cistatic inline unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	if (machine_is_collie())
11962306a36Sopenharmony_ci		ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK);
12062306a36Sopenharmony_ci	else {
12162306a36Sopenharmony_ci		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
12262306a36Sopenharmony_ci				  UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
12362306a36Sopenharmony_ci				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
12462306a36Sopenharmony_ci		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
12562306a36Sopenharmony_ci				  UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
12662306a36Sopenharmony_ci				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
12962306a36Sopenharmony_ci			UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
13062306a36Sopenharmony_ci			UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	udelay(55);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync);
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/*
13862306a36Sopenharmony_ci * Switch to Y position mode and measure X plate.  We switch the plate
13962306a36Sopenharmony_ci * configuration in pressure mode, then switch to position mode.  This
14062306a36Sopenharmony_ci * gives a faster response time.  Even so, we need to wait about 55us
14162306a36Sopenharmony_ci * for things to stabilise.
14262306a36Sopenharmony_ci */
14362306a36Sopenharmony_cistatic inline unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	if (machine_is_collie())
14662306a36Sopenharmony_ci		ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK);
14762306a36Sopenharmony_ci	else {
14862306a36Sopenharmony_ci		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
14962306a36Sopenharmony_ci				  UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
15062306a36Sopenharmony_ci				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
15162306a36Sopenharmony_ci		ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
15262306a36Sopenharmony_ci				  UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
15362306a36Sopenharmony_ci				  UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
15762306a36Sopenharmony_ci			UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
15862306a36Sopenharmony_ci			UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	udelay(55);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci/*
16662306a36Sopenharmony_ci * Switch to X plate resistance mode.  Set MX to ground, PX to
16762306a36Sopenharmony_ci * supply.  Measure current.
16862306a36Sopenharmony_ci */
16962306a36Sopenharmony_cistatic inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
17262306a36Sopenharmony_ci			UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
17362306a36Sopenharmony_ci			UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
17462306a36Sopenharmony_ci	return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/*
17862306a36Sopenharmony_ci * Switch to Y plate resistance mode.  Set MY to ground, PY to
17962306a36Sopenharmony_ci * supply.  Measure current.
18062306a36Sopenharmony_ci */
18162306a36Sopenharmony_cistatic inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	ucb1x00_reg_write(ts->ucb, UCB_TS_CR,
18462306a36Sopenharmony_ci			UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
18562306a36Sopenharmony_ci			UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
18662306a36Sopenharmony_ci	return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync);
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (machine_is_collie())
19462306a36Sopenharmony_ci		return (!(val & (UCB_TS_CR_TSPX_LOW)));
19562306a36Sopenharmony_ci	else
19662306a36Sopenharmony_ci		return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW));
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci/*
20062306a36Sopenharmony_ci * This is a RT kernel thread that handles the ADC accesses
20162306a36Sopenharmony_ci * (mainly so we can use semaphores in the UCB1200 core code
20262306a36Sopenharmony_ci * to serialise accesses to the ADC).
20362306a36Sopenharmony_ci */
20462306a36Sopenharmony_cistatic int ucb1x00_thread(void *_ts)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	struct ucb1x00_ts *ts = _ts;
20762306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
20862306a36Sopenharmony_ci	bool frozen, ignore = false;
20962306a36Sopenharmony_ci	int valid = 0;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	set_freezable();
21262306a36Sopenharmony_ci	add_wait_queue(&ts->irq_wait, &wait);
21362306a36Sopenharmony_ci	while (!kthread_freezable_should_stop(&frozen)) {
21462306a36Sopenharmony_ci		unsigned int x, y, p;
21562306a36Sopenharmony_ci		signed long timeout;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci		if (frozen)
21862306a36Sopenharmony_ci			ignore = true;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci		ucb1x00_adc_enable(ts->ucb);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		x = ucb1x00_ts_read_xpos(ts);
22362306a36Sopenharmony_ci		y = ucb1x00_ts_read_ypos(ts);
22462306a36Sopenharmony_ci		p = ucb1x00_ts_read_pressure(ts);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		/*
22762306a36Sopenharmony_ci		 * Switch back to interrupt mode.
22862306a36Sopenharmony_ci		 */
22962306a36Sopenharmony_ci		ucb1x00_ts_mode_int(ts);
23062306a36Sopenharmony_ci		ucb1x00_adc_disable(ts->ucb);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci		msleep(10);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci		ucb1x00_enable(ts->ucb);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci		if (ucb1x00_ts_pen_down(ts)) {
23862306a36Sopenharmony_ci			set_current_state(TASK_INTERRUPTIBLE);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci			spin_lock_irq(&ts->irq_lock);
24162306a36Sopenharmony_ci			if (ts->irq_disabled) {
24262306a36Sopenharmony_ci				ts->irq_disabled = 0;
24362306a36Sopenharmony_ci				enable_irq(ts->ucb->irq_base + UCB_IRQ_TSPX);
24462306a36Sopenharmony_ci			}
24562306a36Sopenharmony_ci			spin_unlock_irq(&ts->irq_lock);
24662306a36Sopenharmony_ci			ucb1x00_disable(ts->ucb);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci			/*
24962306a36Sopenharmony_ci			 * If we spat out a valid sample set last time,
25062306a36Sopenharmony_ci			 * spit out a "pen off" sample here.
25162306a36Sopenharmony_ci			 */
25262306a36Sopenharmony_ci			if (valid) {
25362306a36Sopenharmony_ci				ucb1x00_ts_event_release(ts);
25462306a36Sopenharmony_ci				valid = 0;
25562306a36Sopenharmony_ci			}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci			timeout = MAX_SCHEDULE_TIMEOUT;
25862306a36Sopenharmony_ci		} else {
25962306a36Sopenharmony_ci			ucb1x00_disable(ts->ucb);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci			/*
26262306a36Sopenharmony_ci			 * Filtering is policy.  Policy belongs in user
26362306a36Sopenharmony_ci			 * space.  We therefore leave it to user space
26462306a36Sopenharmony_ci			 * to do any filtering they please.
26562306a36Sopenharmony_ci			 */
26662306a36Sopenharmony_ci			if (!ignore) {
26762306a36Sopenharmony_ci				ucb1x00_ts_evt_add(ts, p, x, y);
26862306a36Sopenharmony_ci				valid = 1;
26962306a36Sopenharmony_ci			}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci			set_current_state(TASK_INTERRUPTIBLE);
27262306a36Sopenharmony_ci			timeout = HZ / 100;
27362306a36Sopenharmony_ci		}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci		schedule_timeout(timeout);
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	remove_wait_queue(&ts->irq_wait, &wait);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	ts->rtask = NULL;
28162306a36Sopenharmony_ci	return 0;
28262306a36Sopenharmony_ci}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci/*
28562306a36Sopenharmony_ci * We only detect touch screen _touches_ with this interrupt
28662306a36Sopenharmony_ci * handler, and even then we just schedule our task.
28762306a36Sopenharmony_ci */
28862306a36Sopenharmony_cistatic irqreturn_t ucb1x00_ts_irq(int irq, void *id)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	struct ucb1x00_ts *ts = id;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	spin_lock(&ts->irq_lock);
29362306a36Sopenharmony_ci	ts->irq_disabled = 1;
29462306a36Sopenharmony_ci	disable_irq_nosync(ts->ucb->irq_base + UCB_IRQ_TSPX);
29562306a36Sopenharmony_ci	spin_unlock(&ts->irq_lock);
29662306a36Sopenharmony_ci	wake_up(&ts->irq_wait);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	return IRQ_HANDLED;
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic int ucb1x00_ts_open(struct input_dev *idev)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	struct ucb1x00_ts *ts = input_get_drvdata(idev);
30462306a36Sopenharmony_ci	unsigned long flags = 0;
30562306a36Sopenharmony_ci	int ret = 0;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	BUG_ON(ts->rtask);
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	if (machine_is_collie())
31062306a36Sopenharmony_ci		flags = IRQF_TRIGGER_RISING;
31162306a36Sopenharmony_ci	else
31262306a36Sopenharmony_ci		flags = IRQF_TRIGGER_FALLING;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	ts->irq_disabled = 0;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	init_waitqueue_head(&ts->irq_wait);
31762306a36Sopenharmony_ci	ret = request_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ucb1x00_ts_irq,
31862306a36Sopenharmony_ci			  flags, "ucb1x00-ts", ts);
31962306a36Sopenharmony_ci	if (ret < 0)
32062306a36Sopenharmony_ci		goto out;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	/*
32362306a36Sopenharmony_ci	 * If we do this at all, we should allow the user to
32462306a36Sopenharmony_ci	 * measure and read the X and Y resistance at any time.
32562306a36Sopenharmony_ci	 */
32662306a36Sopenharmony_ci	ucb1x00_adc_enable(ts->ucb);
32762306a36Sopenharmony_ci	ts->x_res = ucb1x00_ts_read_xres(ts);
32862306a36Sopenharmony_ci	ts->y_res = ucb1x00_ts_read_yres(ts);
32962306a36Sopenharmony_ci	ucb1x00_adc_disable(ts->ucb);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd");
33262306a36Sopenharmony_ci	if (!IS_ERR(ts->rtask)) {
33362306a36Sopenharmony_ci		ret = 0;
33462306a36Sopenharmony_ci	} else {
33562306a36Sopenharmony_ci		free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts);
33662306a36Sopenharmony_ci		ts->rtask = NULL;
33762306a36Sopenharmony_ci		ret = -EFAULT;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci out:
34162306a36Sopenharmony_ci	return ret;
34262306a36Sopenharmony_ci}
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci/*
34562306a36Sopenharmony_ci * Release touchscreen resources.  Disable IRQs.
34662306a36Sopenharmony_ci */
34762306a36Sopenharmony_cistatic void ucb1x00_ts_close(struct input_dev *idev)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct ucb1x00_ts *ts = input_get_drvdata(idev);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	if (ts->rtask)
35262306a36Sopenharmony_ci		kthread_stop(ts->rtask);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	ucb1x00_enable(ts->ucb);
35562306a36Sopenharmony_ci	free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts);
35662306a36Sopenharmony_ci	ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0);
35762306a36Sopenharmony_ci	ucb1x00_disable(ts->ucb);
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci/*
36262306a36Sopenharmony_ci * Initialisation.
36362306a36Sopenharmony_ci */
36462306a36Sopenharmony_cistatic int ucb1x00_ts_add(struct ucb1x00_dev *dev)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	struct ucb1x00_ts *ts;
36762306a36Sopenharmony_ci	struct input_dev *idev;
36862306a36Sopenharmony_ci	int err;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	ts = kzalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL);
37162306a36Sopenharmony_ci	idev = input_allocate_device();
37262306a36Sopenharmony_ci	if (!ts || !idev) {
37362306a36Sopenharmony_ci		err = -ENOMEM;
37462306a36Sopenharmony_ci		goto fail;
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	ts->ucb = dev->ucb;
37862306a36Sopenharmony_ci	ts->idev = idev;
37962306a36Sopenharmony_ci	ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC;
38062306a36Sopenharmony_ci	spin_lock_init(&ts->irq_lock);
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	idev->name       = "Touchscreen panel";
38362306a36Sopenharmony_ci	idev->id.product = ts->ucb->id;
38462306a36Sopenharmony_ci	idev->open       = ucb1x00_ts_open;
38562306a36Sopenharmony_ci	idev->close      = ucb1x00_ts_close;
38662306a36Sopenharmony_ci	idev->dev.parent = &ts->ucb->dev;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	idev->evbit[0]   = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY);
38962306a36Sopenharmony_ci	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	input_set_drvdata(idev, ts);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	ucb1x00_adc_enable(ts->ucb);
39462306a36Sopenharmony_ci	ts->x_res = ucb1x00_ts_read_xres(ts);
39562306a36Sopenharmony_ci	ts->y_res = ucb1x00_ts_read_yres(ts);
39662306a36Sopenharmony_ci	ucb1x00_adc_disable(ts->ucb);
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	input_set_abs_params(idev, ABS_X, 0, ts->x_res, 0, 0);
39962306a36Sopenharmony_ci	input_set_abs_params(idev, ABS_Y, 0, ts->y_res, 0, 0);
40062306a36Sopenharmony_ci	input_set_abs_params(idev, ABS_PRESSURE, 0, 0, 0, 0);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	err = input_register_device(idev);
40362306a36Sopenharmony_ci	if (err)
40462306a36Sopenharmony_ci		goto fail;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	dev->priv = ts;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	return 0;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci fail:
41162306a36Sopenharmony_ci	input_free_device(idev);
41262306a36Sopenharmony_ci	kfree(ts);
41362306a36Sopenharmony_ci	return err;
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic void ucb1x00_ts_remove(struct ucb1x00_dev *dev)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	struct ucb1x00_ts *ts = dev->priv;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	input_unregister_device(ts->idev);
42162306a36Sopenharmony_ci	kfree(ts);
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic struct ucb1x00_driver ucb1x00_ts_driver = {
42562306a36Sopenharmony_ci	.add		= ucb1x00_ts_add,
42662306a36Sopenharmony_ci	.remove		= ucb1x00_ts_remove,
42762306a36Sopenharmony_ci};
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic int __init ucb1x00_ts_init(void)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	return ucb1x00_register_driver(&ucb1x00_ts_driver);
43262306a36Sopenharmony_ci}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_cistatic void __exit ucb1x00_ts_exit(void)
43562306a36Sopenharmony_ci{
43662306a36Sopenharmony_ci	ucb1x00_unregister_driver(&ucb1x00_ts_driver);
43762306a36Sopenharmony_ci}
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_cimodule_param(adcsync, int, 0444);
44062306a36Sopenharmony_cimodule_init(ucb1x00_ts_init);
44162306a36Sopenharmony_cimodule_exit(ucb1x00_ts_exit);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ciMODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
44462306a36Sopenharmony_ciMODULE_DESCRIPTION("UCB1x00 touchscreen driver");
44562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
446