diff options
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c')
-rw-r--r-- | firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c new file mode 100644 index 0000000000..db5ece10b0 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c | |||
@@ -0,0 +1,503 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "button.h" | ||
23 | #include "kernel.h" | ||
24 | #include "backlight.h" | ||
25 | #include "panic.h" | ||
26 | #include "lcd.h" | ||
27 | #include "gpio-x1000.h" | ||
28 | #include "i2c-x1000.h" | ||
29 | #include <string.h> | ||
30 | #include <stdbool.h> | ||
31 | |||
32 | #ifndef BOOTLOADER | ||
33 | # include "font.h" | ||
34 | #endif | ||
35 | |||
36 | #define FT_RST_PIN (1 << 15) | ||
37 | #define FT_INT_PIN (1 << 12) | ||
38 | #define ft_interrupt GPIOB12 | ||
39 | |||
40 | /* Touch event types */ | ||
41 | #define EVENT_NONE (-1) | ||
42 | #define EVENT_PRESS 0 | ||
43 | #define EVENT_RELEASE 1 | ||
44 | #define EVENT_CONTACT 2 | ||
45 | |||
46 | /* FSM states */ | ||
47 | #define STATE_IDLE 0 | ||
48 | #define STATE_PRESS 1 | ||
49 | #define STATE_REPORT 2 | ||
50 | #define STATE_SCROLL_PRESS 3 | ||
51 | #define STATE_SCROLLING 4 | ||
52 | |||
53 | /* Assume there's no active touch if no event is reported in this time */ | ||
54 | #define AUTORELEASE_TIME (10000 * OST_TICKS_PER_US) | ||
55 | |||
56 | /* If there's no significant motion on the scrollbar for this time, | ||
57 | * then report it as a button press instead */ | ||
58 | #define SCROLL_PRESS_TIME (100000 * OST_TICKS_PER_US) | ||
59 | |||
60 | /* If a press on the scrollbar moves more than this during SCROLL_PRESS_TIME, | ||
61 | * then we enter scrolling mode. */ | ||
62 | #define MIN_SCROLL_THRESH 15 | ||
63 | |||
64 | /* If OST tick a is after OST tick b, then returns the number of ticks | ||
65 | * in the interval between a and b; otherwise undefined. */ | ||
66 | #define TICKS_SINCE(a, b) ((a) - (b)) | ||
67 | |||
68 | /* Number of touch samples to smooth before reading */ | ||
69 | #define TOUCH_SAMPLES 3 | ||
70 | |||
71 | static struct ft_driver { | ||
72 | int i2c_cookie; | ||
73 | i2c_descriptor i2c_desc; | ||
74 | uint8_t raw_data[6]; | ||
75 | bool active; | ||
76 | |||
77 | /* Number of pixels squared which must be moved before | ||
78 | * a scrollbar pulse is generated */ | ||
79 | int scroll_thresh_sqr; | ||
80 | } ftd; | ||
81 | |||
82 | static struct ft_state_machine { | ||
83 | /* Current button state, used by button_read_device() */ | ||
84 | int buttons; | ||
85 | |||
86 | /* FSM state */ | ||
87 | int state; | ||
88 | |||
89 | /* Time of the last touch event, as 32-bit OST timestamp. The kernel | ||
90 | * tick is simply too low-resolution to work reliably, especially as | ||
91 | * we handle touchpad events asynchronously. */ | ||
92 | uint32_t last_event_t; | ||
93 | |||
94 | /* Time of entering the SCROLL_PRESS state, used to differentiate | ||
95 | * between a press, hold, or scrolling motion */ | ||
96 | uint32_t scroll_press_t; | ||
97 | |||
98 | /* Number of CONTACT events sampled in the PRESS state. | ||
99 | * Must reach TOUCH_SAMPLES before we move forward. */ | ||
100 | int samples; | ||
101 | |||
102 | /* Filter for smoothing touch points */ | ||
103 | int sum_x, sum_y; | ||
104 | |||
105 | /* Position of the original touch */ | ||
106 | int orig_x, orig_y; | ||
107 | |||
108 | /* Current touch position */ | ||
109 | int cur_x, cur_y; | ||
110 | } fsm; | ||
111 | |||
112 | static int touch_to_button(int x, int y) | ||
113 | { | ||
114 | if(x == 900) { | ||
115 | /* Right strip */ | ||
116 | if(y == 80) | ||
117 | return BUTTON_BACK; | ||
118 | else if(y == 240) | ||
119 | return BUTTON_RIGHT; | ||
120 | else | ||
121 | return 0; | ||
122 | } else if(x < 80) { | ||
123 | /* Left strip */ | ||
124 | if(y < 80) | ||
125 | return BUTTON_MENU; | ||
126 | else if(y > 190) | ||
127 | return BUTTON_LEFT; | ||
128 | else | ||
129 | return 0; | ||
130 | } else { | ||
131 | /* Middle strip */ | ||
132 | if(y < 100) | ||
133 | return BUTTON_UP; | ||
134 | else if(y > 220) | ||
135 | return BUTTON_DOWN; | ||
136 | else | ||
137 | return BUTTON_SELECT; | ||
138 | } | ||
139 | } | ||
140 | |||
141 | static bool ft_accum_touch(uint32_t t, int tx, int ty) | ||
142 | { | ||
143 | /* Record event time */ | ||
144 | fsm.last_event_t = t; | ||
145 | |||
146 | if(fsm.samples < TOUCH_SAMPLES) { | ||
147 | /* Continue "priming" the filter */ | ||
148 | fsm.sum_x += tx; | ||
149 | fsm.sum_y += ty; | ||
150 | fsm.samples += 1; | ||
151 | |||
152 | /* Return if filter is not ready */ | ||
153 | if(fsm.samples < TOUCH_SAMPLES) | ||
154 | return false; | ||
155 | } else { | ||
156 | /* Update filter */ | ||
157 | fsm.sum_x += tx - fsm.sum_x / TOUCH_SAMPLES; | ||
158 | fsm.sum_y += ty - fsm.sum_y / TOUCH_SAMPLES; | ||
159 | } | ||
160 | |||
161 | /* Filter is ready, so read the point */ | ||
162 | fsm.cur_x = fsm.sum_x / TOUCH_SAMPLES; | ||
163 | fsm.cur_y = fsm.sum_y / TOUCH_SAMPLES; | ||
164 | return true; | ||
165 | } | ||
166 | |||
167 | static void ft_go_idle(void) | ||
168 | { | ||
169 | /* Null out the touch state */ | ||
170 | fsm.buttons = 0; | ||
171 | fsm.samples = 0; | ||
172 | fsm.sum_x = fsm.sum_y = 0; | ||
173 | fsm.state = STATE_IDLE; | ||
174 | } | ||
175 | |||
176 | static void ft_start_report(void) | ||
177 | { | ||
178 | /* Report the button bit */ | ||
179 | fsm.buttons = touch_to_button(fsm.cur_x, fsm.cur_y); | ||
180 | fsm.orig_x = fsm.cur_x; | ||
181 | fsm.orig_y = fsm.cur_y; | ||
182 | fsm.state = STATE_REPORT; | ||
183 | } | ||
184 | |||
185 | static void ft_start_report_or_scroll(void) | ||
186 | { | ||
187 | ft_start_report(); | ||
188 | |||
189 | /* If the press occurs on the scrollbar, then we need to | ||
190 | * wait an additional delay before reporting it in case | ||
191 | * this is the beginning of a scrolling motion */ | ||
192 | if(fsm.buttons & (BUTTON_UP|BUTTON_DOWN|BUTTON_SELECT)) { | ||
193 | fsm.buttons = 0; | ||
194 | fsm.scroll_press_t = __ost_read32(); | ||
195 | fsm.state = STATE_SCROLL_PRESS; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | static void ft_step_state(uint32_t t, int evt, int tx, int ty) | ||
200 | { | ||
201 | /* Generate a release event automatically in case we missed it */ | ||
202 | if(evt == EVENT_NONE) { | ||
203 | if(TICKS_SINCE(t, fsm.last_event_t) >= AUTORELEASE_TIME) { | ||
204 | evt = EVENT_RELEASE; | ||
205 | tx = fsm.cur_x; | ||
206 | ty = fsm.cur_y; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | switch(fsm.state) { | ||
211 | case STATE_IDLE: { | ||
212 | if(evt == EVENT_PRESS || evt == EVENT_CONTACT) { | ||
213 | /* Move to REPORT or PRESS state */ | ||
214 | if(ft_accum_touch(t, tx, ty)) | ||
215 | ft_start_report_or_scroll(); | ||
216 | else | ||
217 | fsm.state = STATE_PRESS; | ||
218 | } | ||
219 | } break; | ||
220 | |||
221 | case STATE_PRESS: { | ||
222 | if(evt == EVENT_RELEASE) { | ||
223 | /* Ignore if the number of samples is too low */ | ||
224 | ft_go_idle(); | ||
225 | } else if(evt == EVENT_PRESS || evt == EVENT_CONTACT) { | ||
226 | /* Accumulate the touch position in the filter */ | ||
227 | if(ft_accum_touch(t, tx, ty)) | ||
228 | ft_start_report_or_scroll(); | ||
229 | } | ||
230 | } break; | ||
231 | |||
232 | case STATE_REPORT: { | ||
233 | if(evt == EVENT_RELEASE) | ||
234 | ft_go_idle(); | ||
235 | else if(evt == EVENT_PRESS || evt == EVENT_CONTACT) | ||
236 | ft_accum_touch(t, tx, ty); | ||
237 | } break; | ||
238 | |||
239 | case STATE_SCROLL_PRESS: { | ||
240 | if(evt == EVENT_RELEASE) { | ||
241 | /* This _should_ synthesize a button press. | ||
242 | * | ||
243 | * - ft_start_report() will set the button bit based on the | ||
244 | * current touch position and enter the REPORT state, which | ||
245 | * will automatically hold the bit high | ||
246 | * | ||
247 | * - The next button_read_device() will see the button bit | ||
248 | * and report it back to Rockbox, then step the FSM with | ||
249 | * EVENT_NONE. | ||
250 | * | ||
251 | * - The EVENT_NONE stepping will eventually autogenerate a | ||
252 | * RELEASE event and restore the button state back to 0 | ||
253 | * | ||
254 | * - There's a small logic hole in the REPORT state which | ||
255 | * could cause it to miss an immediately repeated PRESS | ||
256 | * that occurs before the autorelease timeout kicks in. | ||
257 | * FIXME: We might want to special-case that. | ||
258 | */ | ||
259 | ft_start_report(); | ||
260 | break; | ||
261 | } | ||
262 | |||
263 | if(evt == EVENT_PRESS || evt == EVENT_CONTACT) | ||
264 | ft_accum_touch(t, tx, ty); | ||
265 | |||
266 | int dx = fsm.cur_x - fsm.orig_x; | ||
267 | int dy = fsm.cur_y - fsm.orig_y; | ||
268 | int dp = (dx*dx) + (dy*dy); | ||
269 | if(dp >= MIN_SCROLL_THRESH*MIN_SCROLL_THRESH) { | ||
270 | /* Significant motion: enter SCROLLING state */ | ||
271 | fsm.state = STATE_SCROLLING; | ||
272 | } else if(TICKS_SINCE(t, fsm.scroll_press_t) >= SCROLL_PRESS_TIME) { | ||
273 | /* No significant motion: report it as a press */ | ||
274 | fsm.cur_x = fsm.orig_x; | ||
275 | fsm.cur_y = fsm.orig_y; | ||
276 | ft_start_report(); | ||
277 | } | ||
278 | } break; | ||
279 | |||
280 | case STATE_SCROLLING: { | ||
281 | if(evt == EVENT_RELEASE) { | ||
282 | ft_go_idle(); | ||
283 | break; | ||
284 | } | ||
285 | |||
286 | if(evt == EVENT_PRESS || evt == EVENT_CONTACT) | ||
287 | ft_accum_touch(t, tx, ty); | ||
288 | |||
289 | int dx = fsm.cur_x - fsm.orig_x; | ||
290 | int dy = fsm.cur_y - fsm.orig_y; | ||
291 | int dp = (dx*dx) + (dy*dy); | ||
292 | if(dp >= ftd.scroll_thresh_sqr) { | ||
293 | if(dy < 0) { | ||
294 | queue_post(&button_queue, BUTTON_SCROLL_BACK, 0); | ||
295 | } else { | ||
296 | queue_post(&button_queue, BUTTON_SCROLL_FWD, 0); | ||
297 | } | ||
298 | |||
299 | /* Poke the backlight */ | ||
300 | backlight_on(); | ||
301 | buttonlight_on(); | ||
302 | |||
303 | fsm.orig_x = fsm.cur_x; | ||
304 | fsm.orig_y = fsm.cur_y; | ||
305 | } | ||
306 | } break; | ||
307 | |||
308 | default: | ||
309 | panicf("ft6x06: unhandled state"); | ||
310 | break; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | static void ft_i2c_callback(int status, i2c_descriptor* desc) | ||
315 | { | ||
316 | (void)desc; | ||
317 | if(status != I2C_STATUS_OK) | ||
318 | return; | ||
319 | |||
320 | /* The panel is oriented such that its X axis is vertical, | ||
321 | * so swap the axes for reporting */ | ||
322 | int evt = ftd.raw_data[1] >> 6; | ||
323 | int ty = ftd.raw_data[2] | ((ftd.raw_data[1] & 0xf) << 8); | ||
324 | int tx = ftd.raw_data[4] | ((ftd.raw_data[3] & 0xf) << 8); | ||
325 | |||
326 | /* TODO: convert the touch positions to linear positions. | ||
327 | * | ||
328 | * Points reported by the touch controller are distorted and non-linear, | ||
329 | * ideally we'd like to correct these values. There's more precision in | ||
330 | * the middle of the touchpad than on the edges, so scrolling feels slow | ||
331 | * in the middle and faster near the edge. | ||
332 | */ | ||
333 | |||
334 | ft_step_state(__ost_read32(), evt, tx, ty); | ||
335 | } | ||
336 | |||
337 | void ft_interrupt(void) | ||
338 | { | ||
339 | /* We don't care if this fails */ | ||
340 | i2c_async_queue(FT6x06_BUS, TIMEOUT_NOBLOCK, I2C_Q_ONCE, | ||
341 | ftd.i2c_cookie, &ftd.i2c_desc); | ||
342 | } | ||
343 | |||
344 | static void ft_init(void) | ||
345 | { | ||
346 | /* Initialize the driver state */ | ||
347 | ftd.i2c_cookie = i2c_async_reserve_cookies(FT6x06_BUS, 1); | ||
348 | ftd.i2c_desc.slave_addr = FT6x06_ADDR; | ||
349 | ftd.i2c_desc.bus_cond = I2C_START | I2C_STOP; | ||
350 | ftd.i2c_desc.tran_mode = I2C_READ; | ||
351 | ftd.i2c_desc.buffer[0] = &ftd.raw_data[5]; | ||
352 | ftd.i2c_desc.count[0] = 1; | ||
353 | ftd.i2c_desc.buffer[1] = &ftd.raw_data[0]; | ||
354 | ftd.i2c_desc.count[1] = 5; | ||
355 | ftd.i2c_desc.callback = ft_i2c_callback; | ||
356 | ftd.i2c_desc.arg = 0; | ||
357 | ftd.i2c_desc.next = NULL; | ||
358 | ftd.raw_data[5] = 0x02; | ||
359 | ftd.active = true; | ||
360 | touchpad_set_sensitivity(DEFAULT_TOUCHPAD_SENSITIVITY_SETTING); | ||
361 | |||
362 | /* Initialize the state machine */ | ||
363 | fsm.buttons = 0; | ||
364 | fsm.state = STATE_IDLE; | ||
365 | fsm.last_event_t = 0; | ||
366 | fsm.scroll_press_t = 0; | ||
367 | fsm.samples = 0; | ||
368 | fsm.sum_x = fsm.sum_y = 0; | ||
369 | fsm.orig_x = fsm.orig_y = 0; | ||
370 | fsm.cur_x = fsm.cur_y = 0; | ||
371 | |||
372 | /* Bring up I2C bus */ | ||
373 | i2c_x1000_set_freq(FT6x06_BUS, I2C_FREQ_400K); | ||
374 | |||
375 | /* Reset chip */ | ||
376 | gpio_config(GPIO_B, FT_RST_PIN|FT_INT_PIN, GPIO_OUTPUT(0)); | ||
377 | mdelay(5); | ||
378 | gpio_out_level(GPIO_B, FT_RST_PIN, 1); | ||
379 | gpio_config(GPIO_B, FT_INT_PIN, GPIO_IRQ_EDGE(0)); | ||
380 | gpio_enable_irq(GPIO_B, FT_INT_PIN); | ||
381 | } | ||
382 | |||
383 | void touchpad_set_sensitivity(int level) | ||
384 | { | ||
385 | int pixels = 40; | ||
386 | pixels -= level; | ||
387 | ftd.scroll_thresh_sqr = pixels * pixels; | ||
388 | } | ||
389 | |||
390 | void touchpad_enable_device(bool en) | ||
391 | { | ||
392 | i2c_reg_write1(FT6x06_BUS, FT6x06_ADDR, 0xa5, en ? 0 : 3); | ||
393 | ftd.active = en; | ||
394 | } | ||
395 | |||
396 | /* Value of headphone detect register */ | ||
397 | static uint8_t hp_detect_reg = 0x00; | ||
398 | |||
399 | /* Interval to poll the register */ | ||
400 | #define HPD_POLL_TIME (HZ/2) | ||
401 | |||
402 | static int hp_detect_tmo_cb(struct timeout* tmo) | ||
403 | { | ||
404 | i2c_descriptor* d = (i2c_descriptor*)tmo->data; | ||
405 | i2c_async_queue(AXP173_BUS, TIMEOUT_NOBLOCK, I2C_Q_ADD, 0, d); | ||
406 | return HPD_POLL_TIME; | ||
407 | } | ||
408 | |||
409 | static void hp_detect_init(void) | ||
410 | { | ||
411 | static struct timeout tmo; | ||
412 | static const uint8_t gpio_reg = 0x94; | ||
413 | static i2c_descriptor desc = { | ||
414 | .slave_addr = AXP173_ADDR, | ||
415 | .bus_cond = I2C_START | I2C_STOP, | ||
416 | .tran_mode = I2C_READ, | ||
417 | .buffer[0] = (void*)&gpio_reg, | ||
418 | .count[0] = 1, | ||
419 | .buffer[1] = &hp_detect_reg, | ||
420 | .count[1] = 1, | ||
421 | .callback = NULL, | ||
422 | .arg = 0, | ||
423 | .next = NULL, | ||
424 | }; | ||
425 | |||
426 | /* Headphone detect is wired to an undocumented GPIO on the AXP173. | ||
427 | * This sets it to input mode so we can see the pin state. */ | ||
428 | i2c_reg_write1(AXP173_BUS, AXP173_ADDR, 0x93, 0x01); | ||
429 | |||
430 | /* Get an initial reading before startup */ | ||
431 | int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, gpio_reg); | ||
432 | if(r >= 0) | ||
433 | hp_detect_reg = r; | ||
434 | |||
435 | /* Poll the register every second */ | ||
436 | timeout_register(&tmo, &hp_detect_tmo_cb, HPD_POLL_TIME, (intptr_t)&desc); | ||
437 | } | ||
438 | |||
439 | /* Rockbox interface */ | ||
440 | void button_init_device(void) | ||
441 | { | ||
442 | /* Configure physical button GPIOs */ | ||
443 | gpio_config(GPIO_A, (1 << 17) | (1 << 19), GPIO_INPUT); | ||
444 | gpio_config(GPIO_B, (1 << 28) | (1 << 31), GPIO_INPUT); | ||
445 | |||
446 | /* Initialize touchpad */ | ||
447 | ft_init(); | ||
448 | |||
449 | /* Set up headphone detect polling */ | ||
450 | hp_detect_init(); | ||
451 | } | ||
452 | |||
453 | int button_read_device(void) | ||
454 | { | ||
455 | int r = fsm.buttons; | ||
456 | ft_step_state(__ost_read32(), EVENT_NONE, 0, 0); | ||
457 | |||
458 | /* Read GPIOs for physical buttons */ | ||
459 | uint32_t a = REG_GPIO_PIN(GPIO_A); | ||
460 | uint32_t b = REG_GPIO_PIN(GPIO_B); | ||
461 | |||
462 | /* All buttons are active low */ | ||
463 | if((a & (1 << 17)) == 0) r |= BUTTON_PLAY; | ||
464 | if((a & (1 << 19)) == 0) r |= BUTTON_VOL_UP; | ||
465 | if((b & (1 << 28)) == 0) r |= BUTTON_VOL_DOWN; | ||
466 | if((b & (1 << 31)) == 0) r |= BUTTON_POWER; | ||
467 | |||
468 | return r; | ||
469 | } | ||
470 | |||
471 | bool headphones_inserted() | ||
472 | { | ||
473 | return hp_detect_reg & 0x40 ? true : false; | ||
474 | } | ||
475 | |||
476 | #ifndef BOOTLOADER | ||
477 | static int getbtn(void) | ||
478 | { | ||
479 | int btn; | ||
480 | do { | ||
481 | btn = button_get_w_tmo(1); | ||
482 | } while(btn & (BUTTON_REL|BUTTON_REPEAT)); | ||
483 | return btn; | ||
484 | } | ||
485 | |||
486 | bool dbg_fiiom3k_touchpad(void) | ||
487 | { | ||
488 | static const char* fsm_statenames[] = { | ||
489 | "IDLE", "PRESS", "REPORT", "SCROLL_PRESS", "SCROLLING" | ||
490 | }; | ||
491 | |||
492 | do { | ||
493 | int line = 0; | ||
494 | lcd_clear_display(); | ||
495 | lcd_putsf(0, line++, "state: %s", fsm_statenames[fsm.state]); | ||
496 | lcd_putsf(0, line++, "button: %08x", fsm.buttons); | ||
497 | lcd_putsf(0, line++, "pos x: %4d orig x: %4d", fsm.cur_x, fsm.orig_x); | ||
498 | lcd_putsf(0, line++, "pos y: %4d orig y: %4d", fsm.cur_y, fsm.orig_y); | ||
499 | lcd_update(); | ||
500 | } while(getbtn() != BUTTON_POWER); | ||
501 | return false; | ||
502 | } | ||
503 | #endif | ||