summaryrefslogtreecommitdiff
path: root/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c')
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c503
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
71static 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
82static 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
112static 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
141static 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
167static 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
176static 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
185static 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
199static 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
314static 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
337void 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
344static 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
383void touchpad_set_sensitivity(int level)
384{
385 int pixels = 40;
386 pixels -= level;
387 ftd.scroll_thresh_sqr = pixels * pixels;
388}
389
390void 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 */
397static uint8_t hp_detect_reg = 0x00;
398
399/* Interval to poll the register */
400#define HPD_POLL_TIME (HZ/2)
401
402static 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
409static 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 */
440void 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
453int 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
471bool headphones_inserted()
472{
473 return hp_detect_reg & 0x40 ? true : false;
474}
475
476#ifndef BOOTLOADER
477static 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
486bool 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