10b9f28feSNicolas Saenz Julienne // SPDX-License-Identifier: GPL-2.0 20b9f28feSNicolas Saenz Julienne /* 30b9f28feSNicolas Saenz Julienne * Raspberry Pi firmware based touchscreen driver 40b9f28feSNicolas Saenz Julienne * 50b9f28feSNicolas Saenz Julienne * Copyright (C) 2015, 2017 Raspberry Pi 60b9f28feSNicolas Saenz Julienne * Copyright (C) 2018 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> 70b9f28feSNicolas Saenz Julienne */ 80b9f28feSNicolas Saenz Julienne 90b9f28feSNicolas Saenz Julienne #include <linux/io.h> 100b9f28feSNicolas Saenz Julienne #include <linux/of.h> 110b9f28feSNicolas Saenz Julienne #include <linux/slab.h> 120b9f28feSNicolas Saenz Julienne #include <linux/device.h> 130b9f28feSNicolas Saenz Julienne #include <linux/module.h> 140b9f28feSNicolas Saenz Julienne #include <linux/bitops.h> 150b9f28feSNicolas Saenz Julienne #include <linux/dma-mapping.h> 160b9f28feSNicolas Saenz Julienne #include <linux/platform_device.h> 170b9f28feSNicolas Saenz Julienne #include <linux/input.h> 180b9f28feSNicolas Saenz Julienne #include <linux/input/mt.h> 190b9f28feSNicolas Saenz Julienne #include <linux/input/touchscreen.h> 200b9f28feSNicolas Saenz Julienne #include <soc/bcm2835/raspberrypi-firmware.h> 210b9f28feSNicolas Saenz Julienne 220b9f28feSNicolas Saenz Julienne #define RPI_TS_DEFAULT_WIDTH 800 230b9f28feSNicolas Saenz Julienne #define RPI_TS_DEFAULT_HEIGHT 480 240b9f28feSNicolas Saenz Julienne 250b9f28feSNicolas Saenz Julienne #define RPI_TS_MAX_SUPPORTED_POINTS 10 260b9f28feSNicolas Saenz Julienne 270b9f28feSNicolas Saenz Julienne #define RPI_TS_FTS_TOUCH_DOWN 0 280b9f28feSNicolas Saenz Julienne #define RPI_TS_FTS_TOUCH_CONTACT 2 290b9f28feSNicolas Saenz Julienne 300b9f28feSNicolas Saenz Julienne #define RPI_TS_POLL_INTERVAL 17 /* 60fps */ 310b9f28feSNicolas Saenz Julienne 320b9f28feSNicolas Saenz Julienne #define RPI_TS_NPOINTS_REG_INVALIDATE 99 330b9f28feSNicolas Saenz Julienne 340b9f28feSNicolas Saenz Julienne struct rpi_ts { 350b9f28feSNicolas Saenz Julienne struct platform_device *pdev; 36bd88ce25SDmitry Torokhov struct input_dev *input; 370b9f28feSNicolas Saenz Julienne struct touchscreen_properties prop; 380b9f28feSNicolas Saenz Julienne 390b9f28feSNicolas Saenz Julienne void __iomem *fw_regs_va; 400b9f28feSNicolas Saenz Julienne dma_addr_t fw_regs_phys; 410b9f28feSNicolas Saenz Julienne 420b9f28feSNicolas Saenz Julienne int known_ids; 430b9f28feSNicolas Saenz Julienne }; 440b9f28feSNicolas Saenz Julienne 450b9f28feSNicolas Saenz Julienne struct rpi_ts_regs { 460b9f28feSNicolas Saenz Julienne u8 device_mode; 470b9f28feSNicolas Saenz Julienne u8 gesture_id; 480b9f28feSNicolas Saenz Julienne u8 num_points; 490b9f28feSNicolas Saenz Julienne struct rpi_ts_touch { 500b9f28feSNicolas Saenz Julienne u8 xh; 510b9f28feSNicolas Saenz Julienne u8 xl; 520b9f28feSNicolas Saenz Julienne u8 yh; 530b9f28feSNicolas Saenz Julienne u8 yl; 540b9f28feSNicolas Saenz Julienne u8 pressure; /* Not supported */ 550b9f28feSNicolas Saenz Julienne u8 area; /* Not supported */ 560b9f28feSNicolas Saenz Julienne } point[RPI_TS_MAX_SUPPORTED_POINTS]; 570b9f28feSNicolas Saenz Julienne }; 580b9f28feSNicolas Saenz Julienne 59bd88ce25SDmitry Torokhov static void rpi_ts_poll(struct input_dev *input) 600b9f28feSNicolas Saenz Julienne { 61bd88ce25SDmitry Torokhov struct rpi_ts *ts = input_get_drvdata(input); 620b9f28feSNicolas Saenz Julienne struct rpi_ts_regs regs; 630b9f28feSNicolas Saenz Julienne int modified_ids = 0; 640b9f28feSNicolas Saenz Julienne long released_ids; 650b9f28feSNicolas Saenz Julienne int event_type; 660b9f28feSNicolas Saenz Julienne int touchid; 670b9f28feSNicolas Saenz Julienne int x, y; 680b9f28feSNicolas Saenz Julienne int i; 690b9f28feSNicolas Saenz Julienne 700b9f28feSNicolas Saenz Julienne memcpy_fromio(®s, ts->fw_regs_va, sizeof(regs)); 710b9f28feSNicolas Saenz Julienne /* 720b9f28feSNicolas Saenz Julienne * We poll the memory based register copy of the touchscreen chip using 730b9f28feSNicolas Saenz Julienne * the number of points register to know whether the copy has been 740b9f28feSNicolas Saenz Julienne * updated (we write 99 to the memory copy, the GPU will write between 750b9f28feSNicolas Saenz Julienne * 0 - 10 points) 760b9f28feSNicolas Saenz Julienne */ 770b9f28feSNicolas Saenz Julienne iowrite8(RPI_TS_NPOINTS_REG_INVALIDATE, 780b9f28feSNicolas Saenz Julienne ts->fw_regs_va + offsetof(struct rpi_ts_regs, num_points)); 790b9f28feSNicolas Saenz Julienne 800b9f28feSNicolas Saenz Julienne if (regs.num_points == RPI_TS_NPOINTS_REG_INVALIDATE || 810b9f28feSNicolas Saenz Julienne (regs.num_points == 0 && ts->known_ids == 0)) 820b9f28feSNicolas Saenz Julienne return; 830b9f28feSNicolas Saenz Julienne 840b9f28feSNicolas Saenz Julienne for (i = 0; i < regs.num_points; i++) { 850b9f28feSNicolas Saenz Julienne x = (((int)regs.point[i].xh & 0xf) << 8) + regs.point[i].xl; 860b9f28feSNicolas Saenz Julienne y = (((int)regs.point[i].yh & 0xf) << 8) + regs.point[i].yl; 870b9f28feSNicolas Saenz Julienne touchid = (regs.point[i].yh >> 4) & 0xf; 880b9f28feSNicolas Saenz Julienne event_type = (regs.point[i].xh >> 6) & 0x03; 890b9f28feSNicolas Saenz Julienne 900b9f28feSNicolas Saenz Julienne modified_ids |= BIT(touchid); 910b9f28feSNicolas Saenz Julienne 920b9f28feSNicolas Saenz Julienne if (event_type == RPI_TS_FTS_TOUCH_DOWN || 930b9f28feSNicolas Saenz Julienne event_type == RPI_TS_FTS_TOUCH_CONTACT) { 940b9f28feSNicolas Saenz Julienne input_mt_slot(input, touchid); 950b9f28feSNicolas Saenz Julienne input_mt_report_slot_state(input, MT_TOOL_FINGER, 1); 960b9f28feSNicolas Saenz Julienne touchscreen_report_pos(input, &ts->prop, x, y, true); 970b9f28feSNicolas Saenz Julienne } 980b9f28feSNicolas Saenz Julienne } 990b9f28feSNicolas Saenz Julienne 1000b9f28feSNicolas Saenz Julienne released_ids = ts->known_ids & ~modified_ids; 1010b9f28feSNicolas Saenz Julienne for_each_set_bit(i, &released_ids, RPI_TS_MAX_SUPPORTED_POINTS) { 1020b9f28feSNicolas Saenz Julienne input_mt_slot(input, i); 1035fc70e35SJiada Wang input_mt_report_slot_inactive(input); 1040b9f28feSNicolas Saenz Julienne modified_ids &= ~(BIT(i)); 1050b9f28feSNicolas Saenz Julienne } 1060b9f28feSNicolas Saenz Julienne ts->known_ids = modified_ids; 1070b9f28feSNicolas Saenz Julienne 1080b9f28feSNicolas Saenz Julienne input_mt_sync_frame(input); 1090b9f28feSNicolas Saenz Julienne input_sync(input); 1100b9f28feSNicolas Saenz Julienne } 1110b9f28feSNicolas Saenz Julienne 1120b9f28feSNicolas Saenz Julienne static void rpi_ts_dma_cleanup(void *data) 1130b9f28feSNicolas Saenz Julienne { 1140b9f28feSNicolas Saenz Julienne struct rpi_ts *ts = data; 1150b9f28feSNicolas Saenz Julienne struct device *dev = &ts->pdev->dev; 1160b9f28feSNicolas Saenz Julienne 1170b9f28feSNicolas Saenz Julienne dma_free_coherent(dev, PAGE_SIZE, ts->fw_regs_va, ts->fw_regs_phys); 1180b9f28feSNicolas Saenz Julienne } 1190b9f28feSNicolas Saenz Julienne 1200b9f28feSNicolas Saenz Julienne static int rpi_ts_probe(struct platform_device *pdev) 1210b9f28feSNicolas Saenz Julienne { 1220b9f28feSNicolas Saenz Julienne struct device *dev = &pdev->dev; 1230b9f28feSNicolas Saenz Julienne struct device_node *np = dev->of_node; 124bd88ce25SDmitry Torokhov struct input_dev *input; 1250b9f28feSNicolas Saenz Julienne struct device_node *fw_node; 1260b9f28feSNicolas Saenz Julienne struct rpi_firmware *fw; 1270b9f28feSNicolas Saenz Julienne struct rpi_ts *ts; 1280b9f28feSNicolas Saenz Julienne u32 touchbuf; 1290b9f28feSNicolas Saenz Julienne int error; 1300b9f28feSNicolas Saenz Julienne 1310b9f28feSNicolas Saenz Julienne fw_node = of_get_parent(np); 1320b9f28feSNicolas Saenz Julienne if (!fw_node) { 1330b9f28feSNicolas Saenz Julienne dev_err(dev, "Missing firmware node\n"); 1340b9f28feSNicolas Saenz Julienne return -ENOENT; 1350b9f28feSNicolas Saenz Julienne } 1360b9f28feSNicolas Saenz Julienne 137*5bca3688SMiaoqian Lin fw = devm_rpi_firmware_get(&pdev->dev, fw_node); 1380b9f28feSNicolas Saenz Julienne of_node_put(fw_node); 1390b9f28feSNicolas Saenz Julienne if (!fw) 1400b9f28feSNicolas Saenz Julienne return -EPROBE_DEFER; 1410b9f28feSNicolas Saenz Julienne 1420b9f28feSNicolas Saenz Julienne ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); 1430b9f28feSNicolas Saenz Julienne if (!ts) 1440b9f28feSNicolas Saenz Julienne return -ENOMEM; 1450b9f28feSNicolas Saenz Julienne ts->pdev = pdev; 1460b9f28feSNicolas Saenz Julienne 147750afb08SLuis Chamberlain ts->fw_regs_va = dma_alloc_coherent(dev, PAGE_SIZE, &ts->fw_regs_phys, 1480b9f28feSNicolas Saenz Julienne GFP_KERNEL); 1490b9f28feSNicolas Saenz Julienne if (!ts->fw_regs_va) { 1500b9f28feSNicolas Saenz Julienne dev_err(dev, "failed to dma_alloc_coherent\n"); 1510b9f28feSNicolas Saenz Julienne return -ENOMEM; 1520b9f28feSNicolas Saenz Julienne } 1530b9f28feSNicolas Saenz Julienne 1540b9f28feSNicolas Saenz Julienne error = devm_add_action_or_reset(dev, rpi_ts_dma_cleanup, ts); 1550b9f28feSNicolas Saenz Julienne if (error) { 1560b9f28feSNicolas Saenz Julienne dev_err(dev, "failed to devm_add_action_or_reset, %d\n", error); 1570b9f28feSNicolas Saenz Julienne return error; 1580b9f28feSNicolas Saenz Julienne } 1590b9f28feSNicolas Saenz Julienne 1600b9f28feSNicolas Saenz Julienne touchbuf = (u32)ts->fw_regs_phys; 1610b9f28feSNicolas Saenz Julienne error = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF, 1620b9f28feSNicolas Saenz Julienne &touchbuf, sizeof(touchbuf)); 1630b9f28feSNicolas Saenz Julienne if (error || touchbuf != 0) { 1640b9f28feSNicolas Saenz Julienne dev_warn(dev, "Failed to set touchbuf, %d\n", error); 1650b9f28feSNicolas Saenz Julienne return error; 1660b9f28feSNicolas Saenz Julienne } 1670b9f28feSNicolas Saenz Julienne 168bd88ce25SDmitry Torokhov input = devm_input_allocate_device(dev); 169bd88ce25SDmitry Torokhov if (!input) { 1700b9f28feSNicolas Saenz Julienne dev_err(dev, "Failed to allocate input device\n"); 1710b9f28feSNicolas Saenz Julienne return -ENOMEM; 1720b9f28feSNicolas Saenz Julienne } 173bd88ce25SDmitry Torokhov 174bd88ce25SDmitry Torokhov ts->input = input; 175bd88ce25SDmitry Torokhov input_set_drvdata(input, ts); 1760b9f28feSNicolas Saenz Julienne 1770b9f28feSNicolas Saenz Julienne input->name = "raspberrypi-ts"; 1780b9f28feSNicolas Saenz Julienne input->id.bustype = BUS_HOST; 1790b9f28feSNicolas Saenz Julienne 1800b9f28feSNicolas Saenz Julienne input_set_abs_params(input, ABS_MT_POSITION_X, 0, 1810b9f28feSNicolas Saenz Julienne RPI_TS_DEFAULT_WIDTH, 0, 0); 1820b9f28feSNicolas Saenz Julienne input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 1830b9f28feSNicolas Saenz Julienne RPI_TS_DEFAULT_HEIGHT, 0, 0); 1840b9f28feSNicolas Saenz Julienne touchscreen_parse_properties(input, true, &ts->prop); 1850b9f28feSNicolas Saenz Julienne 1860b9f28feSNicolas Saenz Julienne error = input_mt_init_slots(input, RPI_TS_MAX_SUPPORTED_POINTS, 1870b9f28feSNicolas Saenz Julienne INPUT_MT_DIRECT); 1880b9f28feSNicolas Saenz Julienne if (error) { 1890b9f28feSNicolas Saenz Julienne dev_err(dev, "could not init mt slots, %d\n", error); 1900b9f28feSNicolas Saenz Julienne return error; 1910b9f28feSNicolas Saenz Julienne } 1920b9f28feSNicolas Saenz Julienne 193bd88ce25SDmitry Torokhov error = input_setup_polling(input, rpi_ts_poll); 194bd88ce25SDmitry Torokhov if (error) { 195bd88ce25SDmitry Torokhov dev_err(dev, "could not set up polling mode, %d\n", error); 196bd88ce25SDmitry Torokhov return error; 197bd88ce25SDmitry Torokhov } 198bd88ce25SDmitry Torokhov 199bd88ce25SDmitry Torokhov input_set_poll_interval(input, RPI_TS_POLL_INTERVAL); 200bd88ce25SDmitry Torokhov 201bd88ce25SDmitry Torokhov error = input_register_device(input); 2020b9f28feSNicolas Saenz Julienne if (error) { 2030b9f28feSNicolas Saenz Julienne dev_err(dev, "could not register input device, %d\n", error); 2040b9f28feSNicolas Saenz Julienne return error; 2050b9f28feSNicolas Saenz Julienne } 2060b9f28feSNicolas Saenz Julienne 2070b9f28feSNicolas Saenz Julienne return 0; 2080b9f28feSNicolas Saenz Julienne } 2090b9f28feSNicolas Saenz Julienne 2100b9f28feSNicolas Saenz Julienne static const struct of_device_id rpi_ts_match[] = { 2110b9f28feSNicolas Saenz Julienne { .compatible = "raspberrypi,firmware-ts", }, 2120b9f28feSNicolas Saenz Julienne {}, 2130b9f28feSNicolas Saenz Julienne }; 2140b9f28feSNicolas Saenz Julienne MODULE_DEVICE_TABLE(of, rpi_ts_match); 2150b9f28feSNicolas Saenz Julienne 2160b9f28feSNicolas Saenz Julienne static struct platform_driver rpi_ts_driver = { 2170b9f28feSNicolas Saenz Julienne .driver = { 2180b9f28feSNicolas Saenz Julienne .name = "raspberrypi-ts", 2190b9f28feSNicolas Saenz Julienne .of_match_table = rpi_ts_match, 2200b9f28feSNicolas Saenz Julienne }, 2210b9f28feSNicolas Saenz Julienne .probe = rpi_ts_probe, 2220b9f28feSNicolas Saenz Julienne }; 2230b9f28feSNicolas Saenz Julienne module_platform_driver(rpi_ts_driver); 2240b9f28feSNicolas Saenz Julienne 2250b9f28feSNicolas Saenz Julienne MODULE_AUTHOR("Gordon Hollingworth"); 2260b9f28feSNicolas Saenz Julienne MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>"); 2270b9f28feSNicolas Saenz Julienne MODULE_DESCRIPTION("Raspberry Pi firmware based touchscreen driver"); 2280b9f28feSNicolas Saenz Julienne MODULE_LICENSE("GPL v2"); 229