PageRenderTime 44ms CodeModel.GetById 17ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 1ms

/drivers/input/touchscreen/da9034-ts.c

http://github.com/mirrors/linux
C | 365 lines | 267 code | 74 blank | 24 comment | 35 complexity | 72dae29d53ce41b5840e2856a4a724ba MD5 | raw file
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Touchscreen driver for Dialog Semiconductor DA9034
  4 *
  5 * Copyright (C) 2006-2008 Marvell International Ltd.
  6 *	Fengwei Yin <fengwei.yin@marvell.com>
  7 *	Bin Yang  <bin.yang@marvell.com>
  8 *	Eric Miao <eric.miao@marvell.com>
  9 */
 10
 11#include <linux/module.h>
 12#include <linux/kernel.h>
 13#include <linux/delay.h>
 14#include <linux/platform_device.h>
 15#include <linux/input.h>
 16#include <linux/workqueue.h>
 17#include <linux/mfd/da903x.h>
 18#include <linux/slab.h>
 19
 20#define DA9034_MANUAL_CTRL	0x50
 21#define DA9034_LDO_ADC_EN	(1 << 4)
 22
 23#define DA9034_AUTO_CTRL1	0x51
 24
 25#define DA9034_AUTO_CTRL2	0x52
 26#define DA9034_AUTO_TSI_EN	(1 << 3)
 27#define DA9034_PEN_DETECT	(1 << 4)
 28
 29#define DA9034_TSI_CTRL1	0x53
 30#define DA9034_TSI_CTRL2	0x54
 31#define DA9034_TSI_X_MSB	0x6c
 32#define DA9034_TSI_Y_MSB	0x6d
 33#define DA9034_TSI_XY_LSB	0x6e
 34
 35enum {
 36	STATE_IDLE,	/* wait for pendown */
 37	STATE_BUSY,	/* TSI busy sampling */
 38	STATE_STOP,	/* sample available */
 39	STATE_WAIT,	/* Wait to start next sample */
 40};
 41
 42enum {
 43	EVENT_PEN_DOWN,
 44	EVENT_PEN_UP,
 45	EVENT_TSI_READY,
 46	EVENT_TIMEDOUT,
 47};
 48
 49struct da9034_touch {
 50	struct device		*da9034_dev;
 51	struct input_dev	*input_dev;
 52
 53	struct delayed_work	tsi_work;
 54	struct notifier_block	notifier;
 55
 56	int	state;
 57
 58	int	interval_ms;
 59	int	x_inverted;
 60	int	y_inverted;
 61
 62	int	last_x;
 63	int	last_y;
 64};
 65
 66static inline int is_pen_down(struct da9034_touch *touch)
 67{
 68	return da903x_query_status(touch->da9034_dev, DA9034_STATUS_PEN_DOWN);
 69}
 70
 71static inline int detect_pen_down(struct da9034_touch *touch, int on)
 72{
 73	if (on)
 74		return da903x_set_bits(touch->da9034_dev,
 75				DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
 76	else
 77		return da903x_clr_bits(touch->da9034_dev,
 78				DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
 79}
 80
 81static int read_tsi(struct da9034_touch *touch)
 82{
 83	uint8_t _x, _y, _v;
 84	int ret;
 85
 86	ret = da903x_read(touch->da9034_dev, DA9034_TSI_X_MSB, &_x);
 87	if (ret)
 88		return ret;
 89
 90	ret = da903x_read(touch->da9034_dev, DA9034_TSI_Y_MSB, &_y);
 91	if (ret)
 92		return ret;
 93
 94	ret = da903x_read(touch->da9034_dev, DA9034_TSI_XY_LSB, &_v);
 95	if (ret)
 96		return ret;
 97
 98	touch->last_x = ((_x << 2) & 0x3fc) | (_v & 0x3);
 99	touch->last_y = ((_y << 2) & 0x3fc) | ((_v & 0xc) >> 2);
100
101	return 0;
102}
103
104static inline int start_tsi(struct da9034_touch *touch)
105{
106	return da903x_set_bits(touch->da9034_dev,
107			DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
108}
109
110static inline int stop_tsi(struct da9034_touch *touch)
111{
112	return da903x_clr_bits(touch->da9034_dev,
113			DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
114}
115
116static inline void report_pen_down(struct da9034_touch *touch)
117{
118	int x = touch->last_x;
119	int y = touch->last_y;
120
121	x &= 0xfff;
122	if (touch->x_inverted)
123		x = 1024 - x;
124	y &= 0xfff;
125	if (touch->y_inverted)
126		y = 1024 - y;
127
128	input_report_abs(touch->input_dev, ABS_X, x);
129	input_report_abs(touch->input_dev, ABS_Y, y);
130	input_report_key(touch->input_dev, BTN_TOUCH, 1);
131
132	input_sync(touch->input_dev);
133}
134
135static inline void report_pen_up(struct da9034_touch *touch)
136{
137	input_report_key(touch->input_dev, BTN_TOUCH, 0);
138	input_sync(touch->input_dev);
139}
140
141static void da9034_event_handler(struct da9034_touch *touch, int event)
142{
143	int err;
144
145	switch (touch->state) {
146	case STATE_IDLE:
147		if (event != EVENT_PEN_DOWN)
148			break;
149
150		/* Enable auto measurement of the TSI, this will
151		 * automatically disable pen down detection
152		 */
153		err = start_tsi(touch);
154		if (err)
155			goto err_reset;
156
157		touch->state = STATE_BUSY;
158		break;
159
160	case STATE_BUSY:
161		if (event != EVENT_TSI_READY)
162			break;
163
164		err = read_tsi(touch);
165		if (err)
166			goto err_reset;
167
168		/* Disable auto measurement of the TSI, so that
169		 * pen down status will be available
170		 */
171		err = stop_tsi(touch);
172		if (err)
173			goto err_reset;
174
175		touch->state = STATE_STOP;
176
177		/* FIXME: PEN_{UP/DOWN} events are expected to be
178		 * available by stopping TSI, but this is found not
179		 * always true, delay and simulate such an event
180		 * here is more reliable
181		 */
182		mdelay(1);
183		da9034_event_handler(touch,
184				     is_pen_down(touch) ? EVENT_PEN_DOWN :
185							  EVENT_PEN_UP);
186		break;
187
188	case STATE_STOP:
189		if (event == EVENT_PEN_DOWN) {
190			report_pen_down(touch);
191			schedule_delayed_work(&touch->tsi_work,
192				msecs_to_jiffies(touch->interval_ms));
193			touch->state = STATE_WAIT;
194		}
195
196		if (event == EVENT_PEN_UP) {
197			report_pen_up(touch);
198			touch->state = STATE_IDLE;
199		}
200		break;
201
202	case STATE_WAIT:
203		if (event != EVENT_TIMEDOUT)
204			break;
205
206		if (is_pen_down(touch)) {
207			start_tsi(touch);
208			touch->state = STATE_BUSY;
209		} else {
210			report_pen_up(touch);
211			touch->state = STATE_IDLE;
212		}
213		break;
214	}
215	return;
216
217err_reset:
218	touch->state = STATE_IDLE;
219	stop_tsi(touch);
220	detect_pen_down(touch, 1);
221}
222
223static void da9034_tsi_work(struct work_struct *work)
224{
225	struct da9034_touch *touch =
226		container_of(work, struct da9034_touch, tsi_work.work);
227
228	da9034_event_handler(touch, EVENT_TIMEDOUT);
229}
230
231static int da9034_touch_notifier(struct notifier_block *nb,
232				 unsigned long event, void *data)
233{
234	struct da9034_touch *touch =
235		container_of(nb, struct da9034_touch, notifier);
236
237	if (event & DA9034_EVENT_TSI_READY)
238		da9034_event_handler(touch, EVENT_TSI_READY);
239
240	if ((event & DA9034_EVENT_PEN_DOWN) && touch->state == STATE_IDLE)
241		da9034_event_handler(touch, EVENT_PEN_DOWN);
242
243	return 0;
244}
245
246static int da9034_touch_open(struct input_dev *dev)
247{
248	struct da9034_touch *touch = input_get_drvdata(dev);
249	int ret;
250
251	ret = da903x_register_notifier(touch->da9034_dev, &touch->notifier,
252			DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
253	if (ret)
254		return -EBUSY;
255
256	/* Enable ADC LDO */
257	ret = da903x_set_bits(touch->da9034_dev,
258			DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
259	if (ret)
260		return ret;
261
262	/* TSI_DELAY: 3 slots, TSI_SKIP: 3 slots */
263	ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL1, 0x1b);
264	if (ret)
265		return ret;
266
267	ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL2, 0x00);
268	if (ret)
269		return ret;
270
271	touch->state = STATE_IDLE;
272	detect_pen_down(touch, 1);
273
274	return 0;
275}
276
277static void da9034_touch_close(struct input_dev *dev)
278{
279	struct da9034_touch *touch = input_get_drvdata(dev);
280
281	da903x_unregister_notifier(touch->da9034_dev, &touch->notifier,
282			DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
283
284	cancel_delayed_work_sync(&touch->tsi_work);
285
286	touch->state = STATE_IDLE;
287	stop_tsi(touch);
288	detect_pen_down(touch, 0);
289
290	/* Disable ADC LDO */
291	da903x_clr_bits(touch->da9034_dev,
292			DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
293}
294
295
296static int da9034_touch_probe(struct platform_device *pdev)
297{
298	struct da9034_touch_pdata *pdata = dev_get_platdata(&pdev->dev);
299	struct da9034_touch *touch;
300	struct input_dev *input_dev;
301	int error;
302
303	touch = devm_kzalloc(&pdev->dev, sizeof(struct da9034_touch),
304			     GFP_KERNEL);
305	if (!touch) {
306		dev_err(&pdev->dev, "failed to allocate driver data\n");
307		return -ENOMEM;
308	}
309
310	touch->da9034_dev = pdev->dev.parent;
311
312	if (pdata) {
313		touch->interval_ms	= pdata->interval_ms;
314		touch->x_inverted	= pdata->x_inverted;
315		touch->y_inverted	= pdata->y_inverted;
316	} else {
317		/* fallback into default */
318		touch->interval_ms	= 10;
319	}
320
321	INIT_DELAYED_WORK(&touch->tsi_work, da9034_tsi_work);
322	touch->notifier.notifier_call = da9034_touch_notifier;
323
324	input_dev = devm_input_allocate_device(&pdev->dev);
325	if (!input_dev) {
326		dev_err(&pdev->dev, "failed to allocate input device\n");
327		return -ENOMEM;
328	}
329
330	input_dev->name		= pdev->name;
331	input_dev->open		= da9034_touch_open;
332	input_dev->close	= da9034_touch_close;
333	input_dev->dev.parent	= &pdev->dev;
334
335	__set_bit(EV_ABS, input_dev->evbit);
336	__set_bit(ABS_X, input_dev->absbit);
337	__set_bit(ABS_Y, input_dev->absbit);
338	input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
339	input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
340
341	__set_bit(EV_KEY, input_dev->evbit);
342	__set_bit(BTN_TOUCH, input_dev->keybit);
343
344	touch->input_dev = input_dev;
345	input_set_drvdata(input_dev, touch);
346
347	error = input_register_device(input_dev);
348	if (error)
349		return error;
350
351	return 0;
352}
353
354static struct platform_driver da9034_touch_driver = {
355	.driver	= {
356		.name	= "da9034-touch",
357	},
358	.probe		= da9034_touch_probe,
359};
360module_platform_driver(da9034_touch_driver);
361
362MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9034");
363MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>, Bin Yang <bin.yang@marvell.com>");
364MODULE_LICENSE("GPL");
365MODULE_ALIAS("platform:da9034-touch");