162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Raspberry Pi firmware based touchscreen driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2015, 2017 Raspberry Pi
662306a36Sopenharmony_ci * Copyright (C) 2018 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/bitops.h>
1562306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/input.h>
1862306a36Sopenharmony_ci#include <linux/input/mt.h>
1962306a36Sopenharmony_ci#include <linux/input/touchscreen.h>
2062306a36Sopenharmony_ci#include <soc/bcm2835/raspberrypi-firmware.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define RPI_TS_DEFAULT_WIDTH	800
2362306a36Sopenharmony_ci#define RPI_TS_DEFAULT_HEIGHT	480
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define RPI_TS_MAX_SUPPORTED_POINTS	10
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define RPI_TS_FTS_TOUCH_DOWN		0
2862306a36Sopenharmony_ci#define RPI_TS_FTS_TOUCH_CONTACT	2
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define RPI_TS_POLL_INTERVAL		17	/* 60fps */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define RPI_TS_NPOINTS_REG_INVALIDATE	99
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct rpi_ts {
3562306a36Sopenharmony_ci	struct platform_device *pdev;
3662306a36Sopenharmony_ci	struct input_dev *input;
3762306a36Sopenharmony_ci	struct touchscreen_properties prop;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	void __iomem *fw_regs_va;
4062306a36Sopenharmony_ci	dma_addr_t fw_regs_phys;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	int known_ids;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistruct rpi_ts_regs {
4662306a36Sopenharmony_ci	u8 device_mode;
4762306a36Sopenharmony_ci	u8 gesture_id;
4862306a36Sopenharmony_ci	u8 num_points;
4962306a36Sopenharmony_ci	struct rpi_ts_touch {
5062306a36Sopenharmony_ci		u8 xh;
5162306a36Sopenharmony_ci		u8 xl;
5262306a36Sopenharmony_ci		u8 yh;
5362306a36Sopenharmony_ci		u8 yl;
5462306a36Sopenharmony_ci		u8 pressure; /* Not supported */
5562306a36Sopenharmony_ci		u8 area;     /* Not supported */
5662306a36Sopenharmony_ci	} point[RPI_TS_MAX_SUPPORTED_POINTS];
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic void rpi_ts_poll(struct input_dev *input)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct rpi_ts *ts = input_get_drvdata(input);
6262306a36Sopenharmony_ci	struct rpi_ts_regs regs;
6362306a36Sopenharmony_ci	int modified_ids = 0;
6462306a36Sopenharmony_ci	long released_ids;
6562306a36Sopenharmony_ci	int event_type;
6662306a36Sopenharmony_ci	int touchid;
6762306a36Sopenharmony_ci	int x, y;
6862306a36Sopenharmony_ci	int i;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	memcpy_fromio(&regs, ts->fw_regs_va, sizeof(regs));
7162306a36Sopenharmony_ci	/*
7262306a36Sopenharmony_ci	 * We poll the memory based register copy of the touchscreen chip using
7362306a36Sopenharmony_ci	 * the number of points register to know whether the copy has been
7462306a36Sopenharmony_ci	 * updated (we write 99 to the memory copy, the GPU will write between
7562306a36Sopenharmony_ci	 * 0 - 10 points)
7662306a36Sopenharmony_ci	 */
7762306a36Sopenharmony_ci	iowrite8(RPI_TS_NPOINTS_REG_INVALIDATE,
7862306a36Sopenharmony_ci		 ts->fw_regs_va + offsetof(struct rpi_ts_regs, num_points));
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (regs.num_points == RPI_TS_NPOINTS_REG_INVALIDATE ||
8162306a36Sopenharmony_ci	    (regs.num_points == 0 && ts->known_ids == 0))
8262306a36Sopenharmony_ci		return;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	for (i = 0; i < regs.num_points; i++) {
8562306a36Sopenharmony_ci		x = (((int)regs.point[i].xh & 0xf) << 8) + regs.point[i].xl;
8662306a36Sopenharmony_ci		y = (((int)regs.point[i].yh & 0xf) << 8) + regs.point[i].yl;
8762306a36Sopenharmony_ci		touchid = (regs.point[i].yh >> 4) & 0xf;
8862306a36Sopenharmony_ci		event_type = (regs.point[i].xh >> 6) & 0x03;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		modified_ids |= BIT(touchid);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		if (event_type == RPI_TS_FTS_TOUCH_DOWN ||
9362306a36Sopenharmony_ci		    event_type == RPI_TS_FTS_TOUCH_CONTACT) {
9462306a36Sopenharmony_ci			input_mt_slot(input, touchid);
9562306a36Sopenharmony_ci			input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
9662306a36Sopenharmony_ci			touchscreen_report_pos(input, &ts->prop, x, y, true);
9762306a36Sopenharmony_ci		}
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	released_ids = ts->known_ids & ~modified_ids;
10162306a36Sopenharmony_ci	for_each_set_bit(i, &released_ids, RPI_TS_MAX_SUPPORTED_POINTS) {
10262306a36Sopenharmony_ci		input_mt_slot(input, i);
10362306a36Sopenharmony_ci		input_mt_report_slot_inactive(input);
10462306a36Sopenharmony_ci		modified_ids &= ~(BIT(i));
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci	ts->known_ids = modified_ids;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	input_mt_sync_frame(input);
10962306a36Sopenharmony_ci	input_sync(input);
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void rpi_ts_dma_cleanup(void *data)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct rpi_ts *ts = data;
11562306a36Sopenharmony_ci	struct device *dev = &ts->pdev->dev;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	dma_free_coherent(dev, PAGE_SIZE, ts->fw_regs_va, ts->fw_regs_phys);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic int rpi_ts_probe(struct platform_device *pdev)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
12362306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
12462306a36Sopenharmony_ci	struct input_dev *input;
12562306a36Sopenharmony_ci	struct device_node *fw_node;
12662306a36Sopenharmony_ci	struct rpi_firmware *fw;
12762306a36Sopenharmony_ci	struct rpi_ts *ts;
12862306a36Sopenharmony_ci	u32 touchbuf;
12962306a36Sopenharmony_ci	int error;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	fw_node = of_get_parent(np);
13262306a36Sopenharmony_ci	if (!fw_node) {
13362306a36Sopenharmony_ci		dev_err(dev, "Missing firmware node\n");
13462306a36Sopenharmony_ci		return -ENOENT;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	fw = devm_rpi_firmware_get(&pdev->dev, fw_node);
13862306a36Sopenharmony_ci	of_node_put(fw_node);
13962306a36Sopenharmony_ci	if (!fw)
14062306a36Sopenharmony_ci		return -EPROBE_DEFER;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
14362306a36Sopenharmony_ci	if (!ts)
14462306a36Sopenharmony_ci		return -ENOMEM;
14562306a36Sopenharmony_ci	ts->pdev = pdev;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	ts->fw_regs_va = dma_alloc_coherent(dev, PAGE_SIZE, &ts->fw_regs_phys,
14862306a36Sopenharmony_ci					    GFP_KERNEL);
14962306a36Sopenharmony_ci	if (!ts->fw_regs_va) {
15062306a36Sopenharmony_ci		dev_err(dev, "failed to dma_alloc_coherent\n");
15162306a36Sopenharmony_ci		return -ENOMEM;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	error = devm_add_action_or_reset(dev, rpi_ts_dma_cleanup, ts);
15562306a36Sopenharmony_ci	if (error) {
15662306a36Sopenharmony_ci		dev_err(dev, "failed to devm_add_action_or_reset, %d\n", error);
15762306a36Sopenharmony_ci		return error;
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	touchbuf = (u32)ts->fw_regs_phys;
16162306a36Sopenharmony_ci	error = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF,
16262306a36Sopenharmony_ci				      &touchbuf, sizeof(touchbuf));
16362306a36Sopenharmony_ci	if (error || touchbuf != 0) {
16462306a36Sopenharmony_ci		dev_warn(dev, "Failed to set touchbuf, %d\n", error);
16562306a36Sopenharmony_ci		return error;
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	input = devm_input_allocate_device(dev);
16962306a36Sopenharmony_ci	if (!input) {
17062306a36Sopenharmony_ci		dev_err(dev, "Failed to allocate input device\n");
17162306a36Sopenharmony_ci		return -ENOMEM;
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	ts->input = input;
17562306a36Sopenharmony_ci	input_set_drvdata(input, ts);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	input->name = "raspberrypi-ts";
17862306a36Sopenharmony_ci	input->id.bustype = BUS_HOST;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	input_set_abs_params(input, ABS_MT_POSITION_X, 0,
18162306a36Sopenharmony_ci			     RPI_TS_DEFAULT_WIDTH, 0, 0);
18262306a36Sopenharmony_ci	input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
18362306a36Sopenharmony_ci			     RPI_TS_DEFAULT_HEIGHT, 0, 0);
18462306a36Sopenharmony_ci	touchscreen_parse_properties(input, true, &ts->prop);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	error = input_mt_init_slots(input, RPI_TS_MAX_SUPPORTED_POINTS,
18762306a36Sopenharmony_ci				    INPUT_MT_DIRECT);
18862306a36Sopenharmony_ci	if (error) {
18962306a36Sopenharmony_ci		dev_err(dev, "could not init mt slots, %d\n", error);
19062306a36Sopenharmony_ci		return error;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	error = input_setup_polling(input, rpi_ts_poll);
19462306a36Sopenharmony_ci	if (error) {
19562306a36Sopenharmony_ci		dev_err(dev, "could not set up polling mode, %d\n", error);
19662306a36Sopenharmony_ci		return error;
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	input_set_poll_interval(input, RPI_TS_POLL_INTERVAL);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	error = input_register_device(input);
20262306a36Sopenharmony_ci	if (error) {
20362306a36Sopenharmony_ci		dev_err(dev, "could not register input device, %d\n", error);
20462306a36Sopenharmony_ci		return error;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	return 0;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic const struct of_device_id rpi_ts_match[] = {
21162306a36Sopenharmony_ci	{ .compatible = "raspberrypi,firmware-ts", },
21262306a36Sopenharmony_ci	{},
21362306a36Sopenharmony_ci};
21462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rpi_ts_match);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic struct platform_driver rpi_ts_driver = {
21762306a36Sopenharmony_ci	.driver = {
21862306a36Sopenharmony_ci		.name = "raspberrypi-ts",
21962306a36Sopenharmony_ci		.of_match_table = rpi_ts_match,
22062306a36Sopenharmony_ci	},
22162306a36Sopenharmony_ci	.probe = rpi_ts_probe,
22262306a36Sopenharmony_ci};
22362306a36Sopenharmony_cimodule_platform_driver(rpi_ts_driver);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ciMODULE_AUTHOR("Gordon Hollingworth");
22662306a36Sopenharmony_ciMODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
22762306a36Sopenharmony_ciMODULE_DESCRIPTION("Raspberry Pi firmware based touchscreen driver");
22862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
229