summaryrefslogtreecommitdiffstats
path: root/drivers/input/touchscreen/stmpe-ts.c
blob: 7d9aadcef154b054f1c607daae245605fd7f8640 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/*
 * STMicroelectronics STMPE811 Touchscreen Driver
 *
 * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
 * All rights reserved.
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/workqueue.h>

#include <linux/mfd/stmpe.h>

/* Register layouts and functionalities are identical on all stmpexxx variants
 * with touchscreen controller
 */
#define STMPE_REG_INT_STA		0x0B
#define STMPE_REG_ADC_CTRL1		0x20
#define STMPE_REG_ADC_CTRL2		0x21
#define STMPE_REG_TSC_CTRL		0x40
#define STMPE_REG_TSC_CFG		0x41
#define STMPE_REG_FIFO_TH		0x4A
#define STMPE_REG_FIFO_STA		0x4B
#define STMPE_REG_FIFO_SIZE		0x4C
#define STMPE_REG_TSC_DATA_XYZ		0x52
#define STMPE_REG_TSC_FRACTION_Z	0x56
#define STMPE_REG_TSC_I_DRIVE		0x58

#define OP_MOD_XYZ			0

#define STMPE_TSC_CTRL_TSC_EN		(1<<0)

#define STMPE_FIFO_STA_RESET		(1<<0)

#define STMPE_IRQ_TOUCH_DET		0

#define SAMPLE_TIME(x)			((x & 0xf) << 4)
#define MOD_12B(x)			((x & 0x1) << 3)
#define REF_SEL(x)			((x & 0x1) << 1)
#define ADC_FREQ(x)			(x & 0x3)
#define AVE_CTRL(x)			((x & 0x3) << 6)
#define DET_DELAY(x)			((x & 0x7) << 3)
#define SETTLING(x)			(x & 0x7)
#define FRACTION_Z(x)			(x & 0x7)
#define I_DRIVE(x)			(x & 0x1)
#define OP_MODE(x)			((x & 0x7) << 1)

#define STMPE_TS_NAME			"stmpe-ts"
#define XY_MASK				0xfff

struct stmpe_touch {
	struct stmpe *stmpe;
	struct input_dev *idev;
	struct delayed_work work;
	struct device *dev;
	u8 sample_time;
	u8 mod_12b;
	u8 ref_sel;
	u8 adc_freq;
	u8 ave_ctrl;
	u8 touch_det_delay;
	u8 settling;
	u8 fraction_z;
	u8 i_drive;
};

static int __stmpe_reset_fifo(struct stmpe *stmpe)
{
	int ret;

	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
	if (ret)
		return ret;

	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
			STMPE_FIFO_STA_RESET, 0);
}

static void stmpe_work(struct work_struct *work)
{
	int int_sta;
	u32 timeout = 40;

	struct stmpe_touch *ts =
	    container_of(work, struct stmpe_touch, work.work);

	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);

	/*
	 * touch_det sometimes get desasserted or just get stuck. This appears
	 * to be a silicon bug, We still have to clearify this with the
	 * manufacture. As a workaround We release the key anyway if the
	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
	 * during the whole time.
	 */
	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
		timeout--;
		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
		udelay(100);
	}

	/* reset the FIFO before we report release event */
	__stmpe_reset_fifo(ts->stmpe);

	input_report_abs(ts->idev, ABS_PRESSURE, 0);
	input_sync(ts->idev);
}

static irqreturn_t stmpe_ts_handler(int irq, void *data)
{
	u8 data_set[4];
	int x, y, z;
	struct stmpe_touch *ts = data;

	/*
	 * Cancel scheduled polling for release if we have new value
	 * available. Wait if the polling is already running.
	 */
	cancel_delayed_work_sync(&ts->work);

	/*
	 * The FIFO sometimes just crashes and stops generating interrupts. This
	 * appears to be a silicon bug. We still have to clearify this with
	 * the manufacture. As a workaround we disable the TSC while we are
	 * collecting data and flush the FIFO after reading
	 */
	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
				STMPE_TSC_CTRL_TSC_EN, 0);

	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);

	x = (data_set[0] << 4) | (data_set[1] >> 4);
	y = ((data_set[1] & 0xf) << 8) | data_set[2];
	z = data_set[3];

	input_report_abs(ts->idev, ABS_X, x);
	input_report_abs(ts->idev, ABS_Y, y);
	input_report_abs(ts->idev, ABS_PRESSURE, z);
	input_sync(ts->idev);

       /* flush the FIFO after we have read out our values. */
	__stmpe_reset_fifo(ts->stmpe);

	/* reenable the tsc */
	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);

	/* start polling for touch_det to detect release */
	schedule_delayed_work(&ts->work, HZ / 50);

	return IRQ_HANDLED;
}

static int __devinit stmpe_init_hw(struct stmpe_touch *ts)
{
	int ret;
	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
	struct stmpe *stmpe = ts->stmpe;
	struct device *dev = ts->dev;

	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
	if (ret) {
		dev_err(dev, "Could not enable clock for ADC and TS\n");
		return ret;
	}

	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
		REF_SEL(ts->ref_sel);
	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);

	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
			adc_ctrl1_mask, adc_ctrl1);
	if (ret) {
		dev_err(dev, "Could not setup ADC\n");
		return ret;
	}

	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
	if (ret) {
		dev_err(dev, "Could not setup ADC\n");
		return ret;
	}

	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
			SETTLING(ts->settling);
	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);

	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
	if (ret) {
		dev_err(dev, "Could not config touch\n");
		return ret;
	}

	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
	if (ret) {
		dev_err(dev, "Could not config touch\n");
		return ret;
	}

	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
	if (ret) {
		dev_err(dev, "Could not config touch\n");
		return ret;
	}

	/* set FIFO to 1 for single point reading */
	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
	if (ret) {
		dev_err(dev, "Could not set FIFO\n");
		return ret;
	}

	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
	if (ret) {
		dev_err(dev, "Could not set mode\n");
		return ret;
	}

	return 0;
}

static int stmpe_ts_open(struct input_dev *dev)
{
	struct stmpe_touch *ts = input_get_drvdata(dev);
	int ret = 0;

	ret = __stmpe_reset_fifo(ts->stmpe);
	if (ret)
		return ret;

	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
}

static void stmpe_ts_close(struct input_dev *dev)
{
	struct stmpe_touch *ts = input_get_drvdata(dev);

	cancel_delayed_work_sync(&ts->work);

	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
			STMPE_TSC_CTRL_TSC_EN, 0);
}

static int __devinit stmpe_input_probe(struct platform_device *pdev)
{
	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
	const struct stmpe_platform_data *pdata = stmpe->pdata;
	const struct stmpe_ts_platform_data *ts_pdata = NULL;
	struct stmpe_touch *ts;
	struct input_dev *idev;
	int error;
	int ts_irq;

	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
	if (ts_irq < 0)
		return ts_irq;

	ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;

	idev = devm_input_allocate_device(&pdev->dev);
	if (!idev)
		return -ENOMEM;

	platform_set_drvdata(pdev, ts);
	ts->stmpe = stmpe;
	ts->idev = idev;
	ts->dev = &pdev->dev;

	if (pdata)
		ts_pdata = pdata->ts;

	if (ts_pdata) {
		ts->sample_time = ts_pdata->sample_time;
		ts->mod_12b = ts_pdata->mod_12b;
		ts->ref_sel = ts_pdata->ref_sel;
		ts->adc_freq = ts_pdata->adc_freq;
		ts->ave_ctrl = ts_pdata->ave_ctrl;
		ts->touch_det_delay = ts_pdata->touch_det_delay;
		ts->settling = ts_pdata->settling;
		ts->fraction_z = ts_pdata->fraction_z;
		ts->i_drive = ts_pdata->i_drive;
	}

	INIT_DELAYED_WORK(&ts->work, stmpe_work);

	error = devm_request_threaded_irq(&pdev->dev, ts_irq,
					  NULL, stmpe_ts_handler,
					  IRQF_ONESHOT, STMPE_TS_NAME, ts);
	if (error) {
		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
		return error;
	}

	error = stmpe_init_hw(ts);
	if (error)
		return error;

	idev->name = STMPE_TS_NAME;
	idev->id.bustype = BUS_I2C;
	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

	idev->open = stmpe_ts_open;
	idev->close = stmpe_ts_close;

	input_set_drvdata(idev, ts);

	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);

	error = input_register_device(idev);
	if (error) {
		dev_err(&pdev->dev, "Could not register input device\n");
		return error;
	}

	return 0;
}

static int __devexit stmpe_ts_remove(struct platform_device *pdev)
{
	struct stmpe_touch *ts = platform_get_drvdata(pdev);

	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);

	return 0;
}

static struct platform_driver stmpe_ts_driver = {
	.driver = {
		   .name = STMPE_TS_NAME,
		   .owner = THIS_MODULE,
		   },
	.probe = stmpe_input_probe,
	.remove = __devexit_p(stmpe_ts_remove),
};
module_platform_driver(stmpe_ts_driver);

MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" STMPE_TS_NAME);
OpenPOWER on IntegriCloud