1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
//! Driver for the DSI touchscreen's touch input, read from a shared
//! touch buffer the VideoCore firmware keeps updated, rather than over
//! I2C.
//!
//! On the official Raspberry Pi 7" touchscreen (and its many
//! electrically identical clones — see [`crate::mailbox`]'s doc comment
//! on how a framebuffer ends up on this same display), the FT5406
//! capacitive touch controller is wired to a VideoCore-only I2C bus this
//! crate's own `i2c.rs` (BSC1, on the 40-pin header) can't reach at
//! all. Instead, the firmware itself polls the controller and needs a
//! buffer to continuously rewrite with the current touch points. This
//! driver supplies its own (a fixed `static`, handed to the firmware
//! once via
//! [`Mailbox::set_touch_buffer_address`](crate::mailbox::Mailbox::set_touch_buffer_address),
//! "Set Touchbuffer") rather than asking the firmware for one of *its*
//! choosing ("Get Touchbuffer") — see that method's doc comment for
//! why: on real hardware, `GET_TOUCHBUF`'s returned address landed
//! outside RAM entirely and hung the core on the first read.
//!
//! Register layout, the tag ids, and the event-type/id bit-packing
//! below all follow the Linux kernel's `rpi-ft5406` driver
//! (`drivers/input/touchscreen/rpi-ft5406.c`), the authoritative
//! reference for this firmware-mediated buffer's format — including
//! that same driver's own history of moving from `GET_TOUCHBUF` to
//! `SET_TOUCHBUF` as its primary mechanism.
//!
//! ## What this driver deliberately doesn't do
//!
//! The Linux driver overwrites the buffer's `num_points` byte with a
//! sentinel (`99`) after each read, so it can tell "the firmware hasn't
//! refreshed since I last looked" apart from "genuinely no touches" and
//! skip re-reporting an unchanged frame to the input subsystem. This
//! driver never writes to the buffer at all — it stays purely a reader
//! of firmware-owned shared memory — and re-reads the same unchanged
//! frame harmlessly if nothing changed:
//! [`poll`](crate::touch::TouchScreen::poll)'s edge-triggered
//! [`TouchEvent`](crate::touch::TouchEvent)s are computed by diffing
//! this poll's ids against the previous poll's, so an unchanged frame
//! just produces no events, not incorrect ones.
//!
//! ## Torn reads
//!
//! The firmware updates this buffer with no locking or handshake this
//! driver can observe, so a read here can race a firmware write and
//! see a partially-updated frame (as the Linux driver's own comments
//! acknowledge). Not defended against here either — the same
//! inherent limitation, not a bug specific to this implementation.
use crateinvalidate_range;
use crateMailbox;
/// Maximum simultaneous touch points the firmware's buffer tracks —
/// fixed by the FT5406 controller itself.
const MAX_TOUCHES: usize = 10;
/// Byte size of the firmware's touch buffer: a 3-byte header
/// (`device_mode`, `gesture_id`, `num_points`) followed by
/// [`MAX_TOUCHES`] fixed 6-byte point entries.
const BUFFER_SIZE: usize = 3 + MAX_TOUCHES * 6;
/// A point's `event_type` field: this is the first frame this point
/// touched down.
const EVENT_TOUCH_DOWN: u8 = 0;
/// A point's `event_type` field: the point is still down, possibly at
/// a new position. The third value, `1`, is `EVENT_TOUCH_UP` (the last
/// frame before the point is removed from the buffer entirely) — not
/// checked for by name here, since [`TouchScreen::poll`] only branches
/// on "should this point's position be reported" (`DOWN`/`CONTACT`);
/// an id's actual release is detected separately, by the id dropping
/// out of the buffer on a later poll (see [`TouchEvent::Released`]).
const EVENT_TOUCH_CONTACT: u8 = 2;
/// The firmware's "nothing new since your last poll" sentinel value for
/// `num_points` — confirmed on real hardware (see [`TouchScreen::poll`]).
/// Not a real point count, and specifically *not* `0..=MAX_TOUCHES`
/// clamped down to [`MAX_TOUCHES`]: doing that misreads "nothing new"
/// as "ten points," decoding whatever stale bytes happen to sit in
/// those slots (old data from an earlier, genuinely larger touch) as
/// phantom touches.
const NO_NEW_DATA: u8 = 99;
/// One active touch point.
/// The active touch points read by one [`TouchScreen::poll`], indexed
/// by touch id.
/// A touch id's transition between two consecutive polls.
/// A newly-read [`TouchReport`] together with the id transitions since
/// the previous poll, produced by [`TouchScreen::poll`]. Read current
/// positions from [`Self::report`], and iterate [`Self::events`] for
/// press/release transitions.
/// Iterator over the [`TouchEvent`]s in a [`TouchUpdate`] — see
/// [`TouchUpdate::events`].
/// The buffer handed to the firmware via
/// [`Mailbox::set_touch_buffer_address`](crate::mailbox::Mailbox::set_touch_buffer_address).
/// A `static`, not a [`TouchScreen`] field: once the firmware has this
/// address it writes there indefinitely, so the memory must never
/// move — unlike a struct field, which moves whenever its owning value
/// does (e.g. `TouchScreen::new`'s return value moving into the
/// caller's variable). Cache-line aligned so [`invalidate_range`]'s
/// line-granularity invalidation never touches a neighboring, unrelated
/// static.
;
static mut TOUCH_BUFFER: TouchBuffer = TouchBuffer;
/// Reads the DSI touchscreen's current touch points, kept updated by
/// the VideoCore firmware in a buffer this driver owns and supplies to
/// it (see [`Mailbox::set_touch_buffer_address`](crate::mailbox::Mailbox::set_touch_buffer_address)).
///
/// Build one with [`Self::new`], then call [`Self::poll`] repeatedly.
/// Unlike a USB HID device, there's no transfer to pace — this just
/// re-reads memory the firmware writes on its own, so poll as often as
/// a caller finds useful (e.g. once per display frame). Only ever
/// construct one at a time — every instance shares the same underlying
/// `TOUCH_BUFFER` static, so a second, concurrently-used instance
/// would race the first.