xref: /openbmc/linux/drivers/input/touchscreen/exc3000.c (revision 27aced19e098ba00e87a09d6ba6d4518e89319c3)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27e577a17SAhmet Inan /*
37e577a17SAhmet Inan  * Driver for I2C connected EETI EXC3000 multiple touch controller
47e577a17SAhmet Inan  *
57e577a17SAhmet Inan  * Copyright (C) 2017 Ahmet Inan <inan@distec.de>
67e577a17SAhmet Inan  *
77e577a17SAhmet Inan  * minimal implementation based on egalax_ts.c and egalax_i2c.c
87e577a17SAhmet Inan  */
97e577a17SAhmet Inan 
107e577a17SAhmet Inan #include <linux/bitops.h>
11*27aced19SSebastian Reichel #include <linux/delay.h>
127e577a17SAhmet Inan #include <linux/device.h>
13*27aced19SSebastian Reichel #include <linux/gpio/consumer.h>
147e577a17SAhmet Inan #include <linux/i2c.h>
157e577a17SAhmet Inan #include <linux/input.h>
167e577a17SAhmet Inan #include <linux/input/mt.h>
177e577a17SAhmet Inan #include <linux/input/touchscreen.h>
187e577a17SAhmet Inan #include <linux/interrupt.h>
197e577a17SAhmet Inan #include <linux/module.h>
207e577a17SAhmet Inan #include <linux/of.h>
213bdd21c6SSebastian Reichel #include <linux/sizes.h>
227e577a17SAhmet Inan #include <linux/timer.h>
237e577a17SAhmet Inan #include <asm/unaligned.h>
247e577a17SAhmet Inan 
257e577a17SAhmet Inan #define EXC3000_NUM_SLOTS		10
267e577a17SAhmet Inan #define EXC3000_SLOTS_PER_FRAME		5
277e577a17SAhmet Inan #define EXC3000_LEN_FRAME		66
287e577a17SAhmet Inan #define EXC3000_LEN_POINT		10
293bdd21c6SSebastian Reichel 
303bdd21c6SSebastian Reichel #define EXC3000_MT1_EVENT		0x06
313bdd21c6SSebastian Reichel #define EXC3000_MT2_EVENT		0x18
323bdd21c6SSebastian Reichel 
337e577a17SAhmet Inan #define EXC3000_TIMEOUT_MS		100
347e577a17SAhmet Inan 
35*27aced19SSebastian Reichel #define EXC3000_RESET_MS		10
36*27aced19SSebastian Reichel #define EXC3000_READY_MS		100
37*27aced19SSebastian Reichel 
383bdd21c6SSebastian Reichel static const struct i2c_device_id exc3000_id[];
393bdd21c6SSebastian Reichel 
403bdd21c6SSebastian Reichel struct eeti_dev_info {
413bdd21c6SSebastian Reichel 	const char *name;
423bdd21c6SSebastian Reichel 	int max_xy;
433bdd21c6SSebastian Reichel };
443bdd21c6SSebastian Reichel 
453bdd21c6SSebastian Reichel enum eeti_dev_id {
463bdd21c6SSebastian Reichel 	EETI_EXC3000,
473bdd21c6SSebastian Reichel 	EETI_EXC80H60,
483bdd21c6SSebastian Reichel 	EETI_EXC80H84,
493bdd21c6SSebastian Reichel };
503bdd21c6SSebastian Reichel 
513bdd21c6SSebastian Reichel static struct eeti_dev_info exc3000_info[] = {
523bdd21c6SSebastian Reichel 	[EETI_EXC3000] = {
533bdd21c6SSebastian Reichel 		.name = "EETI EXC3000 Touch Screen",
543bdd21c6SSebastian Reichel 		.max_xy = SZ_4K - 1,
553bdd21c6SSebastian Reichel 	},
563bdd21c6SSebastian Reichel 	[EETI_EXC80H60] = {
573bdd21c6SSebastian Reichel 		.name = "EETI EXC80H60 Touch Screen",
583bdd21c6SSebastian Reichel 		.max_xy = SZ_16K - 1,
593bdd21c6SSebastian Reichel 	},
603bdd21c6SSebastian Reichel 	[EETI_EXC80H84] = {
613bdd21c6SSebastian Reichel 		.name = "EETI EXC80H84 Touch Screen",
623bdd21c6SSebastian Reichel 		.max_xy = SZ_16K - 1,
633bdd21c6SSebastian Reichel 	},
643bdd21c6SSebastian Reichel };
653bdd21c6SSebastian Reichel 
667e577a17SAhmet Inan struct exc3000_data {
677e577a17SAhmet Inan 	struct i2c_client *client;
683bdd21c6SSebastian Reichel 	const struct eeti_dev_info *info;
697e577a17SAhmet Inan 	struct input_dev *input;
707e577a17SAhmet Inan 	struct touchscreen_properties prop;
71*27aced19SSebastian Reichel 	struct gpio_desc *reset;
727e577a17SAhmet Inan 	struct timer_list timer;
737e577a17SAhmet Inan 	u8 buf[2 * EXC3000_LEN_FRAME];
747e577a17SAhmet Inan };
757e577a17SAhmet Inan 
767e577a17SAhmet Inan static void exc3000_report_slots(struct input_dev *input,
777e577a17SAhmet Inan 				 struct touchscreen_properties *prop,
787e577a17SAhmet Inan 				 const u8 *buf, int num)
797e577a17SAhmet Inan {
807e577a17SAhmet Inan 	for (; num--; buf += EXC3000_LEN_POINT) {
817e577a17SAhmet Inan 		if (buf[0] & BIT(0)) {
827e577a17SAhmet Inan 			input_mt_slot(input, buf[1]);
837e577a17SAhmet Inan 			input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
847e577a17SAhmet Inan 			touchscreen_report_pos(input, prop,
857e577a17SAhmet Inan 					       get_unaligned_le16(buf + 2),
867e577a17SAhmet Inan 					       get_unaligned_le16(buf + 4),
877e577a17SAhmet Inan 					       true);
887e577a17SAhmet Inan 		}
897e577a17SAhmet Inan 	}
907e577a17SAhmet Inan }
917e577a17SAhmet Inan 
927e577a17SAhmet Inan static void exc3000_timer(struct timer_list *t)
937e577a17SAhmet Inan {
947e577a17SAhmet Inan 	struct exc3000_data *data = from_timer(data, t, timer);
957e577a17SAhmet Inan 
967e577a17SAhmet Inan 	input_mt_sync_frame(data->input);
977e577a17SAhmet Inan 	input_sync(data->input);
987e577a17SAhmet Inan }
997e577a17SAhmet Inan 
1003bdd21c6SSebastian Reichel static int exc3000_read_frame(struct exc3000_data *data, u8 *buf)
1017e577a17SAhmet Inan {
1023bdd21c6SSebastian Reichel 	struct i2c_client *client = data->client;
1033bdd21c6SSebastian Reichel 	u8 expected_event = EXC3000_MT1_EVENT;
1047e577a17SAhmet Inan 	int ret;
1057e577a17SAhmet Inan 
1063bdd21c6SSebastian Reichel 	if (data->info->max_xy == SZ_16K - 1)
1073bdd21c6SSebastian Reichel 		expected_event = EXC3000_MT2_EVENT;
1083bdd21c6SSebastian Reichel 
1097e577a17SAhmet Inan 	ret = i2c_master_send(client, "'", 2);
1107e577a17SAhmet Inan 	if (ret < 0)
1117e577a17SAhmet Inan 		return ret;
1127e577a17SAhmet Inan 
1137e577a17SAhmet Inan 	if (ret != 2)
1147e577a17SAhmet Inan 		return -EIO;
1157e577a17SAhmet Inan 
1167e577a17SAhmet Inan 	ret = i2c_master_recv(client, buf, EXC3000_LEN_FRAME);
1177e577a17SAhmet Inan 	if (ret < 0)
1187e577a17SAhmet Inan 		return ret;
1197e577a17SAhmet Inan 
1207e577a17SAhmet Inan 	if (ret != EXC3000_LEN_FRAME)
1217e577a17SAhmet Inan 		return -EIO;
1227e577a17SAhmet Inan 
1233bdd21c6SSebastian Reichel 	if (get_unaligned_le16(buf) != EXC3000_LEN_FRAME)
1243bdd21c6SSebastian Reichel 		return -EINVAL;
1253bdd21c6SSebastian Reichel 
1263bdd21c6SSebastian Reichel 	if (buf[2] != expected_event)
1277e577a17SAhmet Inan 		return -EINVAL;
1287e577a17SAhmet Inan 
1297e577a17SAhmet Inan 	return 0;
1307e577a17SAhmet Inan }
1317e577a17SAhmet Inan 
1323bdd21c6SSebastian Reichel static int exc3000_read_data(struct exc3000_data *data,
1337e577a17SAhmet Inan 			     u8 *buf, int *n_slots)
1347e577a17SAhmet Inan {
1357e577a17SAhmet Inan 	int error;
1367e577a17SAhmet Inan 
1373bdd21c6SSebastian Reichel 	error = exc3000_read_frame(data, buf);
1387e577a17SAhmet Inan 	if (error)
1397e577a17SAhmet Inan 		return error;
1407e577a17SAhmet Inan 
1417e577a17SAhmet Inan 	*n_slots = buf[3];
1427e577a17SAhmet Inan 	if (!*n_slots || *n_slots > EXC3000_NUM_SLOTS)
1437e577a17SAhmet Inan 		return -EINVAL;
1447e577a17SAhmet Inan 
1457e577a17SAhmet Inan 	if (*n_slots > EXC3000_SLOTS_PER_FRAME) {
1467e577a17SAhmet Inan 		/* Read 2nd frame to get the rest of the contacts. */
1473bdd21c6SSebastian Reichel 		error = exc3000_read_frame(data, buf + EXC3000_LEN_FRAME);
1487e577a17SAhmet Inan 		if (error)
1497e577a17SAhmet Inan 			return error;
1507e577a17SAhmet Inan 
1517e577a17SAhmet Inan 		/* 2nd chunk must have number of contacts set to 0. */
1527e577a17SAhmet Inan 		if (buf[EXC3000_LEN_FRAME + 3] != 0)
1537e577a17SAhmet Inan 			return -EINVAL;
1547e577a17SAhmet Inan 	}
1557e577a17SAhmet Inan 
1567e577a17SAhmet Inan 	return 0;
1577e577a17SAhmet Inan }
1587e577a17SAhmet Inan 
1597e577a17SAhmet Inan static irqreturn_t exc3000_interrupt(int irq, void *dev_id)
1607e577a17SAhmet Inan {
1617e577a17SAhmet Inan 	struct exc3000_data *data = dev_id;
1627e577a17SAhmet Inan 	struct input_dev *input = data->input;
1637e577a17SAhmet Inan 	u8 *buf = data->buf;
1647e577a17SAhmet Inan 	int slots, total_slots;
1657e577a17SAhmet Inan 	int error;
1667e577a17SAhmet Inan 
1673bdd21c6SSebastian Reichel 	error = exc3000_read_data(data, buf, &total_slots);
1687e577a17SAhmet Inan 	if (error) {
1697e577a17SAhmet Inan 		/* Schedule a timer to release "stuck" contacts */
1707e577a17SAhmet Inan 		mod_timer(&data->timer,
1717e577a17SAhmet Inan 			  jiffies + msecs_to_jiffies(EXC3000_TIMEOUT_MS));
1727e577a17SAhmet Inan 		goto out;
1737e577a17SAhmet Inan 	}
1747e577a17SAhmet Inan 
1757e577a17SAhmet Inan 	/*
1767e577a17SAhmet Inan 	 * We read full state successfully, no contacts will be "stuck".
1777e577a17SAhmet Inan 	 */
1787e577a17SAhmet Inan 	del_timer_sync(&data->timer);
1797e577a17SAhmet Inan 
1807e577a17SAhmet Inan 	while (total_slots > 0) {
1817e577a17SAhmet Inan 		slots = min(total_slots, EXC3000_SLOTS_PER_FRAME);
1827e577a17SAhmet Inan 		exc3000_report_slots(input, &data->prop, buf + 4, slots);
1837e577a17SAhmet Inan 		total_slots -= slots;
1847e577a17SAhmet Inan 		buf += EXC3000_LEN_FRAME;
1857e577a17SAhmet Inan 	}
1867e577a17SAhmet Inan 
1877e577a17SAhmet Inan 	input_mt_sync_frame(input);
1887e577a17SAhmet Inan 	input_sync(input);
1897e577a17SAhmet Inan 
1907e577a17SAhmet Inan out:
1917e577a17SAhmet Inan 	return IRQ_HANDLED;
1927e577a17SAhmet Inan }
1937e577a17SAhmet Inan 
194deae5764SSebastian Reichel static int exc3000_probe(struct i2c_client *client)
1957e577a17SAhmet Inan {
1967e577a17SAhmet Inan 	struct exc3000_data *data;
1977e577a17SAhmet Inan 	struct input_dev *input;
1983bdd21c6SSebastian Reichel 	int error, max_xy;
1997e577a17SAhmet Inan 
2007e577a17SAhmet Inan 	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
2017e577a17SAhmet Inan 	if (!data)
2027e577a17SAhmet Inan 		return -ENOMEM;
2037e577a17SAhmet Inan 
2047e577a17SAhmet Inan 	data->client = client;
2053bdd21c6SSebastian Reichel 	data->info = device_get_match_data(&client->dev);
2063bdd21c6SSebastian Reichel 	if (!data->info) {
2073bdd21c6SSebastian Reichel 		enum eeti_dev_id eeti_dev_id =
2083bdd21c6SSebastian Reichel 			i2c_match_id(exc3000_id, client)->driver_data;
2093bdd21c6SSebastian Reichel 		data->info = &exc3000_info[eeti_dev_id];
2103bdd21c6SSebastian Reichel 	}
2117e577a17SAhmet Inan 	timer_setup(&data->timer, exc3000_timer, 0);
2127e577a17SAhmet Inan 
213*27aced19SSebastian Reichel 	data->reset = devm_gpiod_get_optional(&client->dev, "reset",
214*27aced19SSebastian Reichel 					      GPIOD_OUT_HIGH);
215*27aced19SSebastian Reichel 	if (IS_ERR(data->reset))
216*27aced19SSebastian Reichel 		return PTR_ERR(data->reset);
217*27aced19SSebastian Reichel 
218*27aced19SSebastian Reichel 	if (data->reset) {
219*27aced19SSebastian Reichel 		msleep(EXC3000_RESET_MS);
220*27aced19SSebastian Reichel 		gpiod_set_value_cansleep(data->reset, 0);
221*27aced19SSebastian Reichel 		msleep(EXC3000_READY_MS);
222*27aced19SSebastian Reichel 	}
223*27aced19SSebastian Reichel 
2247e577a17SAhmet Inan 	input = devm_input_allocate_device(&client->dev);
2257e577a17SAhmet Inan 	if (!input)
2267e577a17SAhmet Inan 		return -ENOMEM;
2277e577a17SAhmet Inan 
2287e577a17SAhmet Inan 	data->input = input;
2297e577a17SAhmet Inan 
2303bdd21c6SSebastian Reichel 	input->name = data->info->name;
2317e577a17SAhmet Inan 	input->id.bustype = BUS_I2C;
2327e577a17SAhmet Inan 
2333bdd21c6SSebastian Reichel 	max_xy = data->info->max_xy;
2343bdd21c6SSebastian Reichel 	input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_xy, 0, 0);
2353bdd21c6SSebastian Reichel 	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_xy, 0, 0);
2363bdd21c6SSebastian Reichel 
2377e577a17SAhmet Inan 	touchscreen_parse_properties(input, true, &data->prop);
2387e577a17SAhmet Inan 
2397e577a17SAhmet Inan 	error = input_mt_init_slots(input, EXC3000_NUM_SLOTS,
2407e577a17SAhmet Inan 				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
2417e577a17SAhmet Inan 	if (error)
2427e577a17SAhmet Inan 		return error;
2437e577a17SAhmet Inan 
2447e577a17SAhmet Inan 	error = input_register_device(input);
2457e577a17SAhmet Inan 	if (error)
2467e577a17SAhmet Inan 		return error;
2477e577a17SAhmet Inan 
2487e577a17SAhmet Inan 	error = devm_request_threaded_irq(&client->dev, client->irq,
2497e577a17SAhmet Inan 					  NULL, exc3000_interrupt, IRQF_ONESHOT,
2507e577a17SAhmet Inan 					  client->name, data);
2517e577a17SAhmet Inan 	if (error)
2527e577a17SAhmet Inan 		return error;
2537e577a17SAhmet Inan 
2547e577a17SAhmet Inan 	return 0;
2557e577a17SAhmet Inan }
2567e577a17SAhmet Inan 
2577e577a17SAhmet Inan static const struct i2c_device_id exc3000_id[] = {
2583bdd21c6SSebastian Reichel 	{ "exc3000", EETI_EXC3000 },
2593bdd21c6SSebastian Reichel 	{ "exc80h60", EETI_EXC80H60 },
2603bdd21c6SSebastian Reichel 	{ "exc80h84", EETI_EXC80H84 },
2617e577a17SAhmet Inan 	{ }
2627e577a17SAhmet Inan };
2637e577a17SAhmet Inan MODULE_DEVICE_TABLE(i2c, exc3000_id);
2647e577a17SAhmet Inan 
2657e577a17SAhmet Inan #ifdef CONFIG_OF
2667e577a17SAhmet Inan static const struct of_device_id exc3000_of_match[] = {
2673bdd21c6SSebastian Reichel 	{ .compatible = "eeti,exc3000", .data = &exc3000_info[EETI_EXC3000] },
2683bdd21c6SSebastian Reichel 	{ .compatible = "eeti,exc80h60", .data = &exc3000_info[EETI_EXC80H60] },
2693bdd21c6SSebastian Reichel 	{ .compatible = "eeti,exc80h84", .data = &exc3000_info[EETI_EXC80H84] },
2707e577a17SAhmet Inan 	{ }
2717e577a17SAhmet Inan };
2727e577a17SAhmet Inan MODULE_DEVICE_TABLE(of, exc3000_of_match);
2737e577a17SAhmet Inan #endif
2747e577a17SAhmet Inan 
2757e577a17SAhmet Inan static struct i2c_driver exc3000_driver = {
2767e577a17SAhmet Inan 	.driver = {
2777e577a17SAhmet Inan 		.name	= "exc3000",
2787e577a17SAhmet Inan 		.of_match_table = of_match_ptr(exc3000_of_match),
2797e577a17SAhmet Inan 	},
2807e577a17SAhmet Inan 	.id_table	= exc3000_id,
281deae5764SSebastian Reichel 	.probe_new	= exc3000_probe,
2827e577a17SAhmet Inan };
2837e577a17SAhmet Inan 
2847e577a17SAhmet Inan module_i2c_driver(exc3000_driver);
2857e577a17SAhmet Inan 
2867e577a17SAhmet Inan MODULE_AUTHOR("Ahmet Inan <inan@distec.de>");
2877e577a17SAhmet Inan MODULE_DESCRIPTION("I2C connected EETI EXC3000 multiple touch controller driver");
2887e577a17SAhmet Inan MODULE_LICENSE("GPL v2");
289