1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Raspberry Pi firmware based touchscreen driver |
4 | * |
5 | * Copyright (C) 2015, 2017 Raspberry Pi |
6 | * Copyright (C) 2018 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> |
7 | */ |
8 | |
9 | #include <linux/io.h> |
10 | #include <linux/of.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/device.h> |
13 | #include <linux/module.h> |
14 | #include <linux/bitops.h> |
15 | #include <linux/dma-mapping.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/input.h> |
18 | #include <linux/input/mt.h> |
19 | #include <linux/input/touchscreen.h> |
20 | #include <soc/bcm2835/raspberrypi-firmware.h> |
21 | |
22 | #define RPI_TS_DEFAULT_WIDTH 800 |
23 | #define RPI_TS_DEFAULT_HEIGHT 480 |
24 | |
25 | #define RPI_TS_MAX_SUPPORTED_POINTS 10 |
26 | |
27 | #define RPI_TS_FTS_TOUCH_DOWN 0 |
28 | #define RPI_TS_FTS_TOUCH_CONTACT 2 |
29 | |
30 | #define RPI_TS_POLL_INTERVAL 17 /* 60fps */ |
31 | |
32 | #define RPI_TS_NPOINTS_REG_INVALIDATE 99 |
33 | |
34 | struct rpi_ts { |
35 | struct platform_device *pdev; |
36 | struct input_dev *input; |
37 | struct touchscreen_properties prop; |
38 | |
39 | void __iomem *fw_regs_va; |
40 | dma_addr_t fw_regs_phys; |
41 | |
42 | int known_ids; |
43 | }; |
44 | |
45 | struct rpi_ts_regs { |
46 | u8 device_mode; |
47 | u8 gesture_id; |
48 | u8 num_points; |
49 | struct rpi_ts_touch { |
50 | u8 xh; |
51 | u8 xl; |
52 | u8 yh; |
53 | u8 yl; |
54 | u8 pressure; /* Not supported */ |
55 | u8 area; /* Not supported */ |
56 | } point[RPI_TS_MAX_SUPPORTED_POINTS]; |
57 | }; |
58 | |
59 | static void rpi_ts_poll(struct input_dev *input) |
60 | { |
61 | struct rpi_ts *ts = input_get_drvdata(dev: input); |
62 | struct rpi_ts_regs regs; |
63 | int modified_ids = 0; |
64 | long released_ids; |
65 | int event_type; |
66 | int touchid; |
67 | int x, y; |
68 | int i; |
69 | |
70 | memcpy_fromio(®s, ts->fw_regs_va, sizeof(regs)); |
71 | /* |
72 | * We poll the memory based register copy of the touchscreen chip using |
73 | * the number of points register to know whether the copy has been |
74 | * updated (we write 99 to the memory copy, the GPU will write between |
75 | * 0 - 10 points) |
76 | */ |
77 | iowrite8(RPI_TS_NPOINTS_REG_INVALIDATE, |
78 | ts->fw_regs_va + offsetof(struct rpi_ts_regs, num_points)); |
79 | |
80 | if (regs.num_points == RPI_TS_NPOINTS_REG_INVALIDATE || |
81 | (regs.num_points == 0 && ts->known_ids == 0)) |
82 | return; |
83 | |
84 | for (i = 0; i < regs.num_points; i++) { |
85 | x = (((int)regs.point[i].xh & 0xf) << 8) + regs.point[i].xl; |
86 | y = (((int)regs.point[i].yh & 0xf) << 8) + regs.point[i].yl; |
87 | touchid = (regs.point[i].yh >> 4) & 0xf; |
88 | event_type = (regs.point[i].xh >> 6) & 0x03; |
89 | |
90 | modified_ids |= BIT(touchid); |
91 | |
92 | if (event_type == RPI_TS_FTS_TOUCH_DOWN || |
93 | event_type == RPI_TS_FTS_TOUCH_CONTACT) { |
94 | input_mt_slot(dev: input, slot: touchid); |
95 | input_mt_report_slot_state(dev: input, MT_TOOL_FINGER, active: 1); |
96 | touchscreen_report_pos(input, prop: &ts->prop, x, y, multitouch: true); |
97 | } |
98 | } |
99 | |
100 | released_ids = ts->known_ids & ~modified_ids; |
101 | for_each_set_bit(i, &released_ids, RPI_TS_MAX_SUPPORTED_POINTS) { |
102 | input_mt_slot(dev: input, slot: i); |
103 | input_mt_report_slot_inactive(dev: input); |
104 | modified_ids &= ~(BIT(i)); |
105 | } |
106 | ts->known_ids = modified_ids; |
107 | |
108 | input_mt_sync_frame(dev: input); |
109 | input_sync(dev: input); |
110 | } |
111 | |
112 | static void rpi_ts_dma_cleanup(void *data) |
113 | { |
114 | struct rpi_ts *ts = data; |
115 | struct device *dev = &ts->pdev->dev; |
116 | |
117 | dma_free_coherent(dev, PAGE_SIZE, cpu_addr: ts->fw_regs_va, dma_handle: ts->fw_regs_phys); |
118 | } |
119 | |
120 | static int rpi_ts_probe(struct platform_device *pdev) |
121 | { |
122 | struct device *dev = &pdev->dev; |
123 | struct device_node *np = dev->of_node; |
124 | struct input_dev *input; |
125 | struct device_node *fw_node; |
126 | struct rpi_firmware *fw; |
127 | struct rpi_ts *ts; |
128 | u32 touchbuf; |
129 | int error; |
130 | |
131 | fw_node = of_get_parent(node: np); |
132 | if (!fw_node) { |
133 | dev_err(dev, "Missing firmware node\n" ); |
134 | return -ENOENT; |
135 | } |
136 | |
137 | fw = devm_rpi_firmware_get(dev: &pdev->dev, firmware_node: fw_node); |
138 | of_node_put(node: fw_node); |
139 | if (!fw) |
140 | return -EPROBE_DEFER; |
141 | |
142 | ts = devm_kzalloc(dev, size: sizeof(*ts), GFP_KERNEL); |
143 | if (!ts) |
144 | return -ENOMEM; |
145 | ts->pdev = pdev; |
146 | |
147 | ts->fw_regs_va = dma_alloc_coherent(dev, PAGE_SIZE, dma_handle: &ts->fw_regs_phys, |
148 | GFP_KERNEL); |
149 | if (!ts->fw_regs_va) { |
150 | dev_err(dev, "failed to dma_alloc_coherent\n" ); |
151 | return -ENOMEM; |
152 | } |
153 | |
154 | error = devm_add_action_or_reset(dev, rpi_ts_dma_cleanup, ts); |
155 | if (error) { |
156 | dev_err(dev, "failed to devm_add_action_or_reset, %d\n" , error); |
157 | return error; |
158 | } |
159 | |
160 | touchbuf = (u32)ts->fw_regs_phys; |
161 | error = rpi_firmware_property(fw, tag: RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF, |
162 | data: &touchbuf, len: sizeof(touchbuf)); |
163 | if (error || touchbuf != 0) { |
164 | dev_warn(dev, "Failed to set touchbuf, %d\n" , error); |
165 | return error; |
166 | } |
167 | |
168 | input = devm_input_allocate_device(dev); |
169 | if (!input) { |
170 | dev_err(dev, "Failed to allocate input device\n" ); |
171 | return -ENOMEM; |
172 | } |
173 | |
174 | ts->input = input; |
175 | input_set_drvdata(dev: input, data: ts); |
176 | |
177 | input->name = "raspberrypi-ts" ; |
178 | input->id.bustype = BUS_HOST; |
179 | |
180 | input_set_abs_params(dev: input, ABS_MT_POSITION_X, min: 0, |
181 | RPI_TS_DEFAULT_WIDTH, fuzz: 0, flat: 0); |
182 | input_set_abs_params(dev: input, ABS_MT_POSITION_Y, min: 0, |
183 | RPI_TS_DEFAULT_HEIGHT, fuzz: 0, flat: 0); |
184 | touchscreen_parse_properties(input, multitouch: true, prop: &ts->prop); |
185 | |
186 | error = input_mt_init_slots(dev: input, RPI_TS_MAX_SUPPORTED_POINTS, |
187 | INPUT_MT_DIRECT); |
188 | if (error) { |
189 | dev_err(dev, "could not init mt slots, %d\n" , error); |
190 | return error; |
191 | } |
192 | |
193 | error = input_setup_polling(dev: input, poll_fn: rpi_ts_poll); |
194 | if (error) { |
195 | dev_err(dev, "could not set up polling mode, %d\n" , error); |
196 | return error; |
197 | } |
198 | |
199 | input_set_poll_interval(dev: input, RPI_TS_POLL_INTERVAL); |
200 | |
201 | error = input_register_device(input); |
202 | if (error) { |
203 | dev_err(dev, "could not register input device, %d\n" , error); |
204 | return error; |
205 | } |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | static const struct of_device_id rpi_ts_match[] = { |
211 | { .compatible = "raspberrypi,firmware-ts" , }, |
212 | {}, |
213 | }; |
214 | MODULE_DEVICE_TABLE(of, rpi_ts_match); |
215 | |
216 | static struct platform_driver rpi_ts_driver = { |
217 | .driver = { |
218 | .name = "raspberrypi-ts" , |
219 | .of_match_table = rpi_ts_match, |
220 | }, |
221 | .probe = rpi_ts_probe, |
222 | }; |
223 | module_platform_driver(rpi_ts_driver); |
224 | |
225 | MODULE_AUTHOR("Gordon Hollingworth" ); |
226 | MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>" ); |
227 | MODULE_DESCRIPTION("Raspberry Pi firmware based touchscreen driver" ); |
228 | MODULE_LICENSE("GPL v2" ); |
229 | |