18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Raspberry Pi firmware based touchscreen driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015, 2017 Raspberry Pi 68c2ecf20Sopenharmony_ci * Copyright (C) 2018 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/io.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/device.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/bitops.h> 158c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/input.h> 188c2ecf20Sopenharmony_ci#include <linux/input/mt.h> 198c2ecf20Sopenharmony_ci#include <linux/input/touchscreen.h> 208c2ecf20Sopenharmony_ci#include <soc/bcm2835/raspberrypi-firmware.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define RPI_TS_DEFAULT_WIDTH 800 238c2ecf20Sopenharmony_ci#define RPI_TS_DEFAULT_HEIGHT 480 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define RPI_TS_MAX_SUPPORTED_POINTS 10 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define RPI_TS_FTS_TOUCH_DOWN 0 288c2ecf20Sopenharmony_ci#define RPI_TS_FTS_TOUCH_CONTACT 2 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define RPI_TS_POLL_INTERVAL 17 /* 60fps */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define RPI_TS_NPOINTS_REG_INVALIDATE 99 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct rpi_ts { 358c2ecf20Sopenharmony_ci struct platform_device *pdev; 368c2ecf20Sopenharmony_ci struct input_dev *input; 378c2ecf20Sopenharmony_ci struct touchscreen_properties prop; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci void __iomem *fw_regs_va; 408c2ecf20Sopenharmony_ci dma_addr_t fw_regs_phys; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci int known_ids; 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistruct rpi_ts_regs { 468c2ecf20Sopenharmony_ci u8 device_mode; 478c2ecf20Sopenharmony_ci u8 gesture_id; 488c2ecf20Sopenharmony_ci u8 num_points; 498c2ecf20Sopenharmony_ci struct rpi_ts_touch { 508c2ecf20Sopenharmony_ci u8 xh; 518c2ecf20Sopenharmony_ci u8 xl; 528c2ecf20Sopenharmony_ci u8 yh; 538c2ecf20Sopenharmony_ci u8 yl; 548c2ecf20Sopenharmony_ci u8 pressure; /* Not supported */ 558c2ecf20Sopenharmony_ci u8 area; /* Not supported */ 568c2ecf20Sopenharmony_ci } point[RPI_TS_MAX_SUPPORTED_POINTS]; 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void rpi_ts_poll(struct input_dev *input) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct rpi_ts *ts = input_get_drvdata(input); 628c2ecf20Sopenharmony_ci struct rpi_ts_regs regs; 638c2ecf20Sopenharmony_ci int modified_ids = 0; 648c2ecf20Sopenharmony_ci long released_ids; 658c2ecf20Sopenharmony_ci int event_type; 668c2ecf20Sopenharmony_ci int touchid; 678c2ecf20Sopenharmony_ci int x, y; 688c2ecf20Sopenharmony_ci int i; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci memcpy_fromio(®s, ts->fw_regs_va, sizeof(regs)); 718c2ecf20Sopenharmony_ci /* 728c2ecf20Sopenharmony_ci * We poll the memory based register copy of the touchscreen chip using 738c2ecf20Sopenharmony_ci * the number of points register to know whether the copy has been 748c2ecf20Sopenharmony_ci * updated (we write 99 to the memory copy, the GPU will write between 758c2ecf20Sopenharmony_ci * 0 - 10 points) 768c2ecf20Sopenharmony_ci */ 778c2ecf20Sopenharmony_ci iowrite8(RPI_TS_NPOINTS_REG_INVALIDATE, 788c2ecf20Sopenharmony_ci ts->fw_regs_va + offsetof(struct rpi_ts_regs, num_points)); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (regs.num_points == RPI_TS_NPOINTS_REG_INVALIDATE || 818c2ecf20Sopenharmony_ci (regs.num_points == 0 && ts->known_ids == 0)) 828c2ecf20Sopenharmony_ci return; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci for (i = 0; i < regs.num_points; i++) { 858c2ecf20Sopenharmony_ci x = (((int)regs.point[i].xh & 0xf) << 8) + regs.point[i].xl; 868c2ecf20Sopenharmony_ci y = (((int)regs.point[i].yh & 0xf) << 8) + regs.point[i].yl; 878c2ecf20Sopenharmony_ci touchid = (regs.point[i].yh >> 4) & 0xf; 888c2ecf20Sopenharmony_ci event_type = (regs.point[i].xh >> 6) & 0x03; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci modified_ids |= BIT(touchid); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci if (event_type == RPI_TS_FTS_TOUCH_DOWN || 938c2ecf20Sopenharmony_ci event_type == RPI_TS_FTS_TOUCH_CONTACT) { 948c2ecf20Sopenharmony_ci input_mt_slot(input, touchid); 958c2ecf20Sopenharmony_ci input_mt_report_slot_state(input, MT_TOOL_FINGER, 1); 968c2ecf20Sopenharmony_ci touchscreen_report_pos(input, &ts->prop, x, y, true); 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci released_ids = ts->known_ids & ~modified_ids; 1018c2ecf20Sopenharmony_ci for_each_set_bit(i, &released_ids, RPI_TS_MAX_SUPPORTED_POINTS) { 1028c2ecf20Sopenharmony_ci input_mt_slot(input, i); 1038c2ecf20Sopenharmony_ci input_mt_report_slot_inactive(input); 1048c2ecf20Sopenharmony_ci modified_ids &= ~(BIT(i)); 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci ts->known_ids = modified_ids; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci input_mt_sync_frame(input); 1098c2ecf20Sopenharmony_ci input_sync(input); 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic void rpi_ts_dma_cleanup(void *data) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci struct rpi_ts *ts = data; 1158c2ecf20Sopenharmony_ci struct device *dev = &ts->pdev->dev; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci dma_free_coherent(dev, PAGE_SIZE, ts->fw_regs_va, ts->fw_regs_phys); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int rpi_ts_probe(struct platform_device *pdev) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1238c2ecf20Sopenharmony_ci struct device_node *np = dev->of_node; 1248c2ecf20Sopenharmony_ci struct input_dev *input; 1258c2ecf20Sopenharmony_ci struct device_node *fw_node; 1268c2ecf20Sopenharmony_ci struct rpi_firmware *fw; 1278c2ecf20Sopenharmony_ci struct rpi_ts *ts; 1288c2ecf20Sopenharmony_ci u32 touchbuf; 1298c2ecf20Sopenharmony_ci int error; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci fw_node = of_get_parent(np); 1328c2ecf20Sopenharmony_ci if (!fw_node) { 1338c2ecf20Sopenharmony_ci dev_err(dev, "Missing firmware node\n"); 1348c2ecf20Sopenharmony_ci return -ENOENT; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci fw = devm_rpi_firmware_get(&pdev->dev, fw_node); 1388c2ecf20Sopenharmony_ci of_node_put(fw_node); 1398c2ecf20Sopenharmony_ci if (!fw) 1408c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); 1438c2ecf20Sopenharmony_ci if (!ts) 1448c2ecf20Sopenharmony_ci return -ENOMEM; 1458c2ecf20Sopenharmony_ci ts->pdev = pdev; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci ts->fw_regs_va = dma_alloc_coherent(dev, PAGE_SIZE, &ts->fw_regs_phys, 1488c2ecf20Sopenharmony_ci GFP_KERNEL); 1498c2ecf20Sopenharmony_ci if (!ts->fw_regs_va) { 1508c2ecf20Sopenharmony_ci dev_err(dev, "failed to dma_alloc_coherent\n"); 1518c2ecf20Sopenharmony_ci return -ENOMEM; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci error = devm_add_action_or_reset(dev, rpi_ts_dma_cleanup, ts); 1558c2ecf20Sopenharmony_ci if (error) { 1568c2ecf20Sopenharmony_ci dev_err(dev, "failed to devm_add_action_or_reset, %d\n", error); 1578c2ecf20Sopenharmony_ci return error; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci touchbuf = (u32)ts->fw_regs_phys; 1618c2ecf20Sopenharmony_ci error = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF, 1628c2ecf20Sopenharmony_ci &touchbuf, sizeof(touchbuf)); 1638c2ecf20Sopenharmony_ci if (error || touchbuf != 0) { 1648c2ecf20Sopenharmony_ci dev_warn(dev, "Failed to set touchbuf, %d\n", error); 1658c2ecf20Sopenharmony_ci return error; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci input = devm_input_allocate_device(dev); 1698c2ecf20Sopenharmony_ci if (!input) { 1708c2ecf20Sopenharmony_ci dev_err(dev, "Failed to allocate input device\n"); 1718c2ecf20Sopenharmony_ci return -ENOMEM; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci ts->input = input; 1758c2ecf20Sopenharmony_ci input_set_drvdata(input, ts); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci input->name = "raspberrypi-ts"; 1788c2ecf20Sopenharmony_ci input->id.bustype = BUS_HOST; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci input_set_abs_params(input, ABS_MT_POSITION_X, 0, 1818c2ecf20Sopenharmony_ci RPI_TS_DEFAULT_WIDTH, 0, 0); 1828c2ecf20Sopenharmony_ci input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 1838c2ecf20Sopenharmony_ci RPI_TS_DEFAULT_HEIGHT, 0, 0); 1848c2ecf20Sopenharmony_ci touchscreen_parse_properties(input, true, &ts->prop); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci error = input_mt_init_slots(input, RPI_TS_MAX_SUPPORTED_POINTS, 1878c2ecf20Sopenharmony_ci INPUT_MT_DIRECT); 1888c2ecf20Sopenharmony_ci if (error) { 1898c2ecf20Sopenharmony_ci dev_err(dev, "could not init mt slots, %d\n", error); 1908c2ecf20Sopenharmony_ci return error; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci error = input_setup_polling(input, rpi_ts_poll); 1948c2ecf20Sopenharmony_ci if (error) { 1958c2ecf20Sopenharmony_ci dev_err(dev, "could not set up polling mode, %d\n", error); 1968c2ecf20Sopenharmony_ci return error; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci input_set_poll_interval(input, RPI_TS_POLL_INTERVAL); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci error = input_register_device(input); 2028c2ecf20Sopenharmony_ci if (error) { 2038c2ecf20Sopenharmony_ci dev_err(dev, "could not register input device, %d\n", error); 2048c2ecf20Sopenharmony_ci return error; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci return 0; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic const struct of_device_id rpi_ts_match[] = { 2118c2ecf20Sopenharmony_ci { .compatible = "raspberrypi,firmware-ts", }, 2128c2ecf20Sopenharmony_ci {}, 2138c2ecf20Sopenharmony_ci}; 2148c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rpi_ts_match); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic struct platform_driver rpi_ts_driver = { 2178c2ecf20Sopenharmony_ci .driver = { 2188c2ecf20Sopenharmony_ci .name = "raspberrypi-ts", 2198c2ecf20Sopenharmony_ci .of_match_table = rpi_ts_match, 2208c2ecf20Sopenharmony_ci }, 2218c2ecf20Sopenharmony_ci .probe = rpi_ts_probe, 2228c2ecf20Sopenharmony_ci}; 2238c2ecf20Sopenharmony_cimodule_platform_driver(rpi_ts_driver); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gordon Hollingworth"); 2268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>"); 2278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Raspberry Pi firmware based touchscreen driver"); 2288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 229