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