1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Helper functions for overlay objects on touchscreens
4 *
5 * Copyright (c) 2023 Javier Carrasco <javier.carrasco@wolfvision.net>
6 */
7
8#include <linux/export.h>
9#include <linux/input.h>
10#include <linux/input/mt.h>
11#include <linux/input/touch-overlay.h>
12#include <linux/list.h>
13#include <linux/module.h>
14#include <linux/property.h>
15
16struct touch_overlay_segment {
17 struct list_head list;
18 u32 x_origin;
19 u32 y_origin;
20 u32 x_size;
21 u32 y_size;
22 u32 key;
23 bool pressed;
24 int slot;
25};
26
27static int touch_overlay_get_segment(struct fwnode_handle *segment_node,
28 struct touch_overlay_segment *segment,
29 struct input_dev *input)
30{
31 int error;
32
33 error = fwnode_property_read_u32(fwnode: segment_node, propname: "x-origin",
34 val: &segment->x_origin);
35 if (error)
36 return error;
37
38 error = fwnode_property_read_u32(fwnode: segment_node, propname: "y-origin",
39 val: &segment->y_origin);
40 if (error)
41 return error;
42
43 error = fwnode_property_read_u32(fwnode: segment_node, propname: "x-size",
44 val: &segment->x_size);
45 if (error)
46 return error;
47
48 error = fwnode_property_read_u32(fwnode: segment_node, propname: "y-size",
49 val: &segment->y_size);
50 if (error)
51 return error;
52
53 error = fwnode_property_read_u32(fwnode: segment_node, propname: "linux,code",
54 val: &segment->key);
55 if (!error)
56 input_set_capability(dev: input, EV_KEY, code: segment->key);
57 else if (error != -EINVAL)
58 return error;
59
60 return 0;
61}
62
63/**
64 * touch_overlay_map - map overlay objects from the device tree and set
65 * key capabilities if buttons are defined.
66 * @list: pointer to the list that will hold the segments
67 * @input: pointer to the already allocated input_dev
68 *
69 * Returns 0 on success and error number otherwise.
70 *
71 * If buttons are defined, key capabilities are set accordingly.
72 */
73int touch_overlay_map(struct list_head *list, struct input_dev *input)
74{
75 struct fwnode_handle *fw_segment;
76 struct device *dev = input->dev.parent;
77 struct touch_overlay_segment *segment;
78 int error;
79
80 struct fwnode_handle *overlay __free(fwnode_handle) =
81 device_get_named_child_node(dev, childname: "touch-overlay");
82 if (!overlay)
83 return 0;
84
85 fwnode_for_each_available_child_node(overlay, fw_segment) {
86 segment = devm_kzalloc(dev, size: sizeof(*segment), GFP_KERNEL);
87 if (!segment) {
88 fwnode_handle_put(fwnode: fw_segment);
89 return -ENOMEM;
90 }
91 error = touch_overlay_get_segment(segment_node: fw_segment, segment, input);
92 if (error) {
93 fwnode_handle_put(fwnode: fw_segment);
94 return error;
95 }
96 list_add_tail(new: &segment->list, head: list);
97 }
98
99 return 0;
100}
101EXPORT_SYMBOL(touch_overlay_map);
102
103/**
104 * touch_overlay_get_touchscreen_abs - get abs size from the touchscreen area.
105 * @list: pointer to the list that holds the segments
106 * @x: horizontal abs
107 * @y: vertical abs
108 */
109void touch_overlay_get_touchscreen_abs(struct list_head *list, u16 *x, u16 *y)
110{
111 struct touch_overlay_segment *segment;
112 struct list_head *ptr;
113
114 list_for_each(ptr, list) {
115 segment = list_entry(ptr, struct touch_overlay_segment, list);
116 if (!segment->key) {
117 *x = segment->x_size - 1;
118 *y = segment->y_size - 1;
119 break;
120 }
121 }
122}
123EXPORT_SYMBOL(touch_overlay_get_touchscreen_abs);
124
125static bool touch_overlay_segment_event(struct touch_overlay_segment *seg,
126 struct input_mt_pos *pos)
127{
128 if (pos->x >= seg->x_origin && pos->x < (seg->x_origin + seg->x_size) &&
129 pos->y >= seg->y_origin && pos->y < (seg->y_origin + seg->y_size))
130 return true;
131
132 return false;
133}
134
135/**
136 * touch_overlay_mapped_touchscreen - check if a touchscreen area is mapped
137 * @list: pointer to the list that holds the segments
138 *
139 * Returns true if a touchscreen area is mapped or false otherwise.
140 */
141bool touch_overlay_mapped_touchscreen(struct list_head *list)
142{
143 struct touch_overlay_segment *segment;
144 struct list_head *ptr;
145
146 list_for_each(ptr, list) {
147 segment = list_entry(ptr, struct touch_overlay_segment, list);
148 if (!segment->key)
149 return true;
150 }
151
152 return false;
153}
154EXPORT_SYMBOL(touch_overlay_mapped_touchscreen);
155
156static bool touch_overlay_event_on_ts(struct list_head *list,
157 struct input_mt_pos *pos)
158{
159 struct touch_overlay_segment *segment;
160 struct list_head *ptr;
161
162 list_for_each(ptr, list) {
163 segment = list_entry(ptr, struct touch_overlay_segment, list);
164 if (segment->key)
165 continue;
166
167 if (touch_overlay_segment_event(seg: segment, pos)) {
168 pos->x -= segment->x_origin;
169 pos->y -= segment->y_origin;
170 return true;
171 }
172 /* ignore touch events outside the defined area */
173 return false;
174 }
175
176 return true;
177}
178
179static bool touch_overlay_button_event(struct input_dev *input,
180 struct touch_overlay_segment *segment,
181 struct input_mt_pos *pos, int slot)
182{
183 struct input_mt *mt = input->mt;
184 struct input_mt_slot *s = &mt->slots[slot];
185 bool button_contact = touch_overlay_segment_event(seg: segment, pos);
186
187 if (segment->slot == slot && segment->pressed) {
188 /* sliding out of the button releases it */
189 if (!button_contact) {
190 input_report_key(dev: input, code: segment->key, value: false);
191 segment->pressed = false;
192 /* keep available for a possible touch event */
193 return false;
194 }
195 /* ignore sliding on the button while pressed */
196 s->frame = mt->frame;
197 return true;
198 } else if (button_contact) {
199 input_report_key(dev: input, code: segment->key, value: true);
200 s->frame = mt->frame;
201 segment->slot = slot;
202 segment->pressed = true;
203 return true;
204 }
205
206 return false;
207}
208
209/**
210 * touch_overlay_sync_frame - update the status of the segments and report
211 * buttons whose tracked slot is unused.
212 * @list: pointer to the list that holds the segments
213 * @input: pointer to the input device associated to the contact
214 */
215void touch_overlay_sync_frame(struct list_head *list, struct input_dev *input)
216{
217 struct touch_overlay_segment *segment;
218 struct input_mt *mt = input->mt;
219 struct input_mt_slot *s;
220 struct list_head *ptr;
221
222 list_for_each(ptr, list) {
223 segment = list_entry(ptr, struct touch_overlay_segment, list);
224 if (!segment->key)
225 continue;
226
227 s = &mt->slots[segment->slot];
228 if (!input_mt_is_used(mt, slot: s) && segment->pressed) {
229 input_report_key(dev: input, code: segment->key, value: false);
230 segment->pressed = false;
231 }
232 }
233}
234EXPORT_SYMBOL(touch_overlay_sync_frame);
235
236/**
237 * touch_overlay_process_contact - process contacts according to the overlay
238 * mapping. This function acts as a filter to release the calling driver
239 * from the contacts that are either related to overlay buttons or out of the
240 * overlay touchscreen area, if defined.
241 * @list: pointer to the list that holds the segments
242 * @input: pointer to the input device associated to the contact
243 * @pos: pointer to the contact position
244 * @slot: slot associated to the contact (0 if multitouch is not supported)
245 *
246 * Returns true if the contact was processed (reported for valid key events
247 * and dropped for contacts outside the overlay touchscreen area) or false
248 * if the contact must be processed by the caller. In that case this function
249 * shifts the (x,y) coordinates to the overlay touchscreen axis if required.
250 */
251bool touch_overlay_process_contact(struct list_head *list,
252 struct input_dev *input,
253 struct input_mt_pos *pos, int slot)
254{
255 struct touch_overlay_segment *segment;
256 struct list_head *ptr;
257
258 /*
259 * buttons must be prioritized over overlay touchscreens to account for
260 * overlappings e.g. a button inside the touchscreen area.
261 */
262 list_for_each(ptr, list) {
263 segment = list_entry(ptr, struct touch_overlay_segment, list);
264 if (segment->key &&
265 touch_overlay_button_event(input, segment, pos, slot))
266 return true;
267 }
268
269 /*
270 * valid contacts on the overlay touchscreen are left for the client
271 * to be processed/reported according to its (possibly) unique features.
272 */
273 return !touch_overlay_event_on_ts(list, pos);
274}
275EXPORT_SYMBOL(touch_overlay_process_contact);
276
277MODULE_LICENSE("GPL");
278MODULE_DESCRIPTION("Helper functions for overlay objects on touch devices");
279