xref: /linux/drivers/input/touch-overlay.c (revision 68a052239fc4b351e961f698b824f7654a346091)
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 
16 struct 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 
27 static 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(segment_node, "x-origin",
34 					 &segment->x_origin);
35 	if (error)
36 		return error;
37 
38 	error = fwnode_property_read_u32(segment_node, "y-origin",
39 					 &segment->y_origin);
40 	if (error)
41 		return error;
42 
43 	error = fwnode_property_read_u32(segment_node, "x-size",
44 					 &segment->x_size);
45 	if (error)
46 		return error;
47 
48 	error = fwnode_property_read_u32(segment_node, "y-size",
49 					 &segment->y_size);
50 	if (error)
51 		return error;
52 
53 	error = fwnode_property_read_u32(segment_node, "linux,code",
54 					 &segment->key);
55 	if (!error)
56 		input_set_capability(input, EV_KEY, 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  */
73 int 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, "touch-overlay");
82 	if (!overlay)
83 		return 0;
84 
85 	fwnode_for_each_available_child_node(overlay, fw_segment) {
86 		segment = devm_kzalloc(dev, sizeof(*segment), GFP_KERNEL);
87 		if (!segment) {
88 			fwnode_handle_put(fw_segment);
89 			return -ENOMEM;
90 		}
91 		error = touch_overlay_get_segment(fw_segment, segment, input);
92 		if (error) {
93 			fwnode_handle_put(fw_segment);
94 			return error;
95 		}
96 		list_add_tail(&segment->list, list);
97 	}
98 
99 	return 0;
100 }
101 EXPORT_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  */
109 void 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 }
123 EXPORT_SYMBOL(touch_overlay_get_touchscreen_abs);
124 
125 static 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  */
141 bool 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 }
154 EXPORT_SYMBOL(touch_overlay_mapped_touchscreen);
155 
156 static 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(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 
179 static 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(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(input, segment->key, 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(input, segment->key, 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  */
215 void 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, s) && segment->pressed) {
229 			input_report_key(input, segment->key, false);
230 			segment->pressed = false;
231 		}
232 	}
233 }
234 EXPORT_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  */
251 bool 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 }
275 EXPORT_SYMBOL(touch_overlay_process_contact);
276 
277 MODULE_LICENSE("GPL");
278 MODULE_DESCRIPTION("Helper functions for overlay objects on touch devices");
279