1/*
2 * Generic GPIO / irq buttons
3 *
4 * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24#include "private-lib-core.h"
25
26typedef enum lws_button_classify_states {
27	LBCS_IDLE,		/* nothing happening */
28	LBCS_MIN_DOWN_QUALIFY,
29
30	LBCS_ASSESS_DOWN_HOLD,
31	LBCS_UP_SETTLE1,
32	LBCS_WAIT_DOUBLECLICK,
33	LBCS_MIN_DOWN_QUALIFY2,
34
35	LBCS_WAIT_UP,
36	LBCS_UP_SETTLE2,
37} lws_button_classify_states_t;
38
39/*
40 * This is the opaque, allocated, non-const, dynamic footprint of the
41 * button controller
42 */
43
44typedef struct lws_button_state {
45#if defined(LWS_PLAT_TIMER_TYPE)
46	LWS_PLAT_TIMER_TYPE			timer;	   /* bh timer */
47	LWS_PLAT_TIMER_TYPE			timer_mon; /* monitor timer */
48#endif
49	const lws_button_controller_t		*controller;
50	struct lws_context			*ctx;
51	short					mon_refcount;
52	lws_button_idx_t			enable_bitmap;
53	lws_button_idx_t			state_bitmap;
54
55	uint16_t				mon_timer_count;
56	/* incremented each time the mon timer cb happens */
57
58	/* lws_button_each_t per button overallocated after this */
59} lws_button_state_t;
60
61typedef struct lws_button_each {
62	lws_button_state_t			*bcs;
63	uint16_t				mon_timer_comp;
64	uint16_t				mon_timer_repeat;
65	uint8_t					state;
66	/**^ lws_button_classify_states_t */
67	uint8_t					isr_pending;
68} lws_button_each_t;
69
70#if defined(LWS_PLAT_TIMER_START)
71static const lws_button_regime_t default_regime = {
72	.ms_min_down			= 20,
73	.ms_min_down_longpress		= 300,
74	.ms_up_settle			= 20,
75	.ms_doubleclick_grace		= 120,
76	.flags				= LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK
77};
78#endif
79
80
81/*
82 * This is happening in interrupt context, we have to schedule a bottom half to
83 * do the foreground lws_smd queueing, using, eg, a platform timer.
84 *
85 * All the buttons point here and use one timer per button controller.  An
86 * interrupt here means, "something happened to one or more buttons"
87 */
88#if defined(LWS_PLAT_TIMER_START)
89void
90lws_button_irq_cb_t(void *arg)
91{
92	lws_button_each_t *each = (lws_button_each_t *)arg;
93
94	each->isr_pending = 1;
95	LWS_PLAT_TIMER_START(each->bcs->timer);
96}
97#endif
98
99/*
100 * This is the bottom-half scheduled via a timer set in the ISR.  From here we
101 * are allowed to hold mutexes etc.  We are coming here because any button
102 * interrupt arrived, we have to run another timer that tries to put whatever is
103 * observed on any active button into context and either discard it or arrive at
104 * a definitive event classification.
105 */
106
107#if defined(LWS_PLAT_TIMER_CB)
108static LWS_PLAT_TIMER_CB(lws_button_bh, th)
109{
110	lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
111	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
112	const lws_button_controller_t *bc = bcs->controller;
113	size_t n;
114
115	/*
116	 * The ISR and bottom-half is shared by all the buttons.  Each gpio
117	 * IRQ has an individual opaque ptr pointing to the corresponding
118	 * button's dynamic lws_button_each_t, the ISR marks the button's
119	 * each->isr_pending and schedules this bottom half.
120	 *
121	 * So now the bh timer has fired and something to do, we need to go
122	 * through all the buttons that have isr_pending set and service their
123	 * state.  Intermediate states should start / bump the refcount on the
124	 * mon timer.  That's refcounted so it only runs when a button down.
125	 */
126
127	for (n = 0; n < bc->count_buttons; n++) {
128
129		if (!each[n].isr_pending)
130			continue;
131
132		/*
133		 * Hide what we're about to do from the delicate eyes of the
134		 * IRQ controller...
135		 */
136
137		bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
138				       LWSGGPIO_IRQ_NONE, NULL, NULL);
139
140		each[n].isr_pending = 0;
141
142		/*
143		 * Force the network around the switch to the
144		 * active level briefly
145		 */
146
147		bc->gpio_ops->set(bc->button_map[n].gpio,
148				  !!(bc->active_state_bitmap & (1 << n)));
149		bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_WRITE);
150
151		if (each[n].state == LBCS_IDLE) {
152			/*
153			 * If this is the first sign something happening on this
154			 * button, make sure the monitor timer is running to
155			 * classify its response over time
156			 */
157
158			each[n].state = LBCS_MIN_DOWN_QUALIFY;
159			each[n].mon_timer_comp = bcs->mon_timer_count;
160
161			if (!bcs->mon_refcount++) {
162#if defined(LWS_PLAT_TIMER_START)
163				LWS_PLAT_TIMER_START(bcs->timer_mon);
164#endif
165			}
166		}
167
168		/*
169		 * Just for a us or two inbetween here, we're driving it to the
170		 * level we were informed by the interrupt it had enetered, to
171		 * force to charge on the actual and parasitic network around
172		 * the switch to a deterministic-ish state.
173		 *
174		 * If the switch remains in that state, well, it makes no
175		 * difference; if it was a pre-contact and the charge on the
176		 * network was left indeterminate, this will dispose it to act
177		 * consistently in the short term until the pullup / pulldown
178		 * has time to act on it or the switch comes and forces the
179		 * network charge state itself.
180		 */
181		bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_READ);
182
183		/*
184		 * We could do a better job manipulating the irq mode according
185		 * to the switch state.  But if an interrupt comes and we have
186		 * done that, we can't tell if it's from before or after the
187		 * mode change... ie, we don't know what the interrupt was
188		 * telling us.  We can't trust the gpio state if we read it now
189		 * to be related to what the irq from some time before was
190		 * trying to tell us.  So always set it back to the same mode
191		 * and accept the limitation.
192		 */
193
194		bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
195				       bc->active_state_bitmap & (1 << n) ?
196					   LWSGGPIO_IRQ_RISING :
197					   LWSGGPIO_IRQ_FALLING,
198					      lws_button_irq_cb_t, &each[n]);
199	}
200}
201#endif
202
203#if defined(LWS_PLAT_TIMER_CB)
204static LWS_PLAT_TIMER_CB(lws_button_mon, th)
205{
206	lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
207	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
208	const lws_button_controller_t *bc = bcs->controller;
209	const lws_button_regime_t *regime;
210	const char *event_name;
211	int comp_age_ms;
212	char active;
213	size_t n;
214
215	bcs->mon_timer_count++;
216
217	for (n = 0; n < bc->count_buttons; n++) {
218
219		if (each->state == LBCS_IDLE) {
220			each++;
221			continue;
222		}
223
224		if (bc->button_map[n].regime)
225			regime = bc->button_map[n].regime;
226		else
227			regime = &default_regime;
228
229		comp_age_ms = (bcs->mon_timer_count - each->mon_timer_comp) *
230				LWS_BUTTON_MON_TIMER_MS;
231
232		active = bc->gpio_ops->read(bc->button_map[n].gpio) ^
233			       (!(bc->active_state_bitmap & (1 << n)));
234
235		// lwsl_notice("%d\n", each->state);
236
237		switch (each->state) {
238		case LBCS_MIN_DOWN_QUALIFY:
239			/*
240			 * We're trying to figure out if the initial down event
241			 * is a glitch, or if it meets the criteria for being
242			 * treated as the definitive start of some kind of click
243			 * action.  To get past this, he has to be solidly down
244			 * for the time mentioned in the applied regime (at
245			 * least when we sample it).
246			 *
247			 * Significant bounce at the start will abort this try,
248			 * but if it's really down there will be a subsequent
249			 * solid down period... it will simply restart this flow
250			 * from a new interrupt and pass the filter then.
251			 *
252			 * The "brief drive on edge" strategy considerably
253			 * reduces inconsistencies here.  But physical bounce
254			 * will continue to be observed.
255			 */
256
257			if (!active) {
258				/* We ignore stuff for a bit after discard */
259				each->mon_timer_comp = bcs->mon_timer_count;
260				each->state = LBCS_UP_SETTLE2;
261				break;
262			}
263
264			if (comp_age_ms >= regime->ms_min_down) {
265
266				/* We made it through the initial regime filter,
267				 * the next step is wait and see if this down
268				 * event evolves into a single/double click or
269				 * we can call it as a long-click
270				 */
271
272				each->mon_timer_repeat = bcs->mon_timer_count;
273				each->state = LBCS_ASSESS_DOWN_HOLD;
274				event_name = "down";
275				goto emit;
276			}
277			break;
278
279		case LBCS_ASSESS_DOWN_HOLD:
280
281			/*
282			 * How long is he going to hold it?  If he holds it
283			 * past the long-click threshold, we can call it as a
284			 * long-click and do the up processing afterwards.
285			 */
286			if (comp_age_ms >= regime->ms_min_down_longpress) {
287				/* call it as a longclick */
288				event_name = "longclick";
289				each->state = LBCS_WAIT_UP;
290				goto emit;
291			}
292
293			if (!active) {
294				/*
295				 * He didn't hold it past the long-click
296				 * threshold... we could end up classifying it
297				 * as either a click or a double-click then.
298				 *
299				 * If double-clicks are not allowed to be
300				 * classified, then we can already classify it
301				 * as a single-click.
302				 */
303				if (!(regime->flags &
304					    LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK))
305					goto classify_single;
306
307				/*
308				 * Just wait for the up settle time then start
309				 * looking for a second down.
310				 */
311				each->mon_timer_comp = bcs->mon_timer_count;
312				each->state = LBCS_UP_SETTLE1;
313				event_name = "up";
314				goto emit;
315			}
316
317			goto stilldown;
318
319		case LBCS_UP_SETTLE1:
320			if (comp_age_ms > regime->ms_up_settle)
321				/*
322				 * Just block anything for the up settle time
323				 */
324				each->state = LBCS_WAIT_DOUBLECLICK;
325			break;
326
327		case LBCS_WAIT_DOUBLECLICK:
328			if (active) {
329				/*
330				 * He has gone down again inside the regime's
331				 * doubleclick grace period... he's going down
332				 * the double-click path
333				 */
334				each->mon_timer_comp = bcs->mon_timer_count;
335				each->state = LBCS_MIN_DOWN_QUALIFY2;
336				break;
337			}
338
339			if (comp_age_ms >= regime->ms_doubleclick_grace) {
340				/*
341				 * The grace period expired, the second click
342				 * was either not forthcoming at all, or coming
343				 * quick enough to count: we classify it as a
344				 * single-click
345				 */
346
347				goto classify_single;
348			}
349			break;
350
351		case LBCS_MIN_DOWN_QUALIFY2:
352			if (!active) {
353
354				/*
355				 * He went up again too quickly, classify it
356				 * as a single-click.  It could be bounce in
357				 * which case you might want to increase the
358				 * ms_up_settle in the regime
359				 */
360classify_single:
361				event_name = "click";
362				each->mon_timer_comp = bcs->mon_timer_count;
363				each->state = LBCS_UP_SETTLE2;
364				goto emit;
365			}
366
367			if (comp_age_ms == regime->ms_min_down) {
368				event_name = "down";
369				goto emit;
370			}
371
372			if (comp_age_ms > regime->ms_min_down) {
373				/*
374				 * It's a double-click
375				 */
376				event_name = "doubleclick";
377				each->state = LBCS_WAIT_UP;
378				goto emit;
379			}
380			break;
381
382		case LBCS_WAIT_UP:
383			if (!active) {
384				/*
385				 * He has stopped pressing it
386				 */
387				each->mon_timer_comp = bcs->mon_timer_count;
388				each->state = LBCS_UP_SETTLE2;
389				event_name = "up";
390				goto emit;
391			}
392stilldown:
393			if (regime->ms_repeat_down &&
394			    (bcs->mon_timer_count - each->mon_timer_repeat) *
395			     LWS_BUTTON_MON_TIMER_MS > regime->ms_repeat_down) {
396				each->mon_timer_repeat = bcs->mon_timer_count;
397				event_name = "stilldown";
398				goto emit;
399			}
400			break;
401
402		case LBCS_UP_SETTLE2:
403			if (comp_age_ms < regime->ms_up_settle)
404				break;
405
406			each->state = LBCS_IDLE;
407			if (!(--bcs->mon_refcount)) {
408#if defined(LWS_PLAT_TIMER_STOP)
409				LWS_PLAT_TIMER_STOP(bcs->timer_mon);
410#endif
411			}
412		}
413
414		each++;
415		continue;
416
417emit:
418		lws_smd_msg_printf(bcs->ctx, LWSSMDCL_INTERACTION,
419				   "{\"type\":\"button\","
420				   "\"src\":\"%s/%s\",\"event\":\"%s\"}",
421				   bc->smd_bc_name,
422				   bc->button_map[n].smd_interaction_name,
423				   event_name);
424
425		each++;
426	}
427}
428#endif
429
430struct lws_button_state *
431lws_button_controller_create(struct lws_context *ctx,
432			     const lws_button_controller_t *controller)
433{
434	lws_button_state_t *bcs = lws_zalloc(sizeof(lws_button_state_t) +
435			(controller->count_buttons * sizeof(lws_button_each_t)),
436			__func__);
437	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
438	size_t n;
439
440	if (!bcs)
441		return NULL;
442
443	bcs->controller = controller;
444	bcs->ctx = ctx;
445
446	for (n = 0; n < controller->count_buttons; n++)
447		each[n].bcs = bcs;
448
449#if defined(LWS_PLAT_TIMER_CREATE)
450	/* this only runs inbetween a gpio ISR and the bottom half */
451	bcs->timer = LWS_PLAT_TIMER_CREATE("bcst",
452			1, 0, bcs, (TimerCallbackFunction_t)lws_button_bh);
453	if (!bcs->timer)
454		return NULL;
455
456	/* this only runs when a button activity is being classified */
457	bcs->timer_mon = LWS_PLAT_TIMER_CREATE("bcmon", LWS_BUTTON_MON_TIMER_MS,
458					       1, bcs, (TimerCallbackFunction_t)
459								lws_button_mon);
460	if (!bcs->timer_mon)
461		return NULL;
462#endif
463
464	return bcs;
465}
466
467void
468lws_button_controller_destroy(struct lws_button_state *bcs)
469{
470	/* disable them all */
471	lws_button_enable(bcs, 0, 0);
472
473#if defined(LWS_PLAT_TIMER_DELETE)
474	LWS_PLAT_TIMER_DELETE(bcs->timer);
475	LWS_PLAT_TIMER_DELETE(bcs->timer_mon);
476#endif
477
478	lws_free(bcs);
479}
480
481lws_button_idx_t
482lws_button_get_bit(struct lws_button_state *bcs, const char *name)
483{
484	const lws_button_controller_t *bc = bcs->controller;
485	int n;
486
487	for (n = 0; n < bc->count_buttons; n++)
488		if (!strcmp(name, bc->button_map[n].smd_interaction_name))
489			return 1 << n;
490
491	return 0; /* not found */
492}
493
494void
495lws_button_enable(lws_button_state_t *bcs,
496		  lws_button_idx_t _reset, lws_button_idx_t _set)
497{
498	lws_button_idx_t u = (bcs->enable_bitmap & (~_reset)) | _set;
499	const lws_button_controller_t *bc = bcs->controller;
500#if defined(LWS_PLAT_TIMER_START)
501	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
502#endif
503	int n;
504
505	for (n = 0; n < bcs->controller->count_buttons; n++) {
506		if (!(bcs->enable_bitmap & (1 << n)) && (u & (1 << n))) {
507			/* set as input with pullup or pulldown appropriately */
508			bc->gpio_ops->mode(bc->button_map[n].gpio,
509				LWSGGPIO_FL_READ |
510				((bc->active_state_bitmap & (1 << n)) ?
511				LWSGGPIO_FL_PULLDOWN : LWSGGPIO_FL_PULLUP));
512#if defined(LWS_PLAT_TIMER_START)
513			/*
514			 * This one is becoming enabled... the opaque for the
515			 * ISR is the indvidual lws_button_each_t, they all
516			 * point to the same ISR
517			 */
518			bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
519					bc->active_state_bitmap & (1 << n) ?
520						LWSGGPIO_IRQ_RISING :
521							LWSGGPIO_IRQ_FALLING,
522						lws_button_irq_cb_t, &each[n]);
523#endif
524		}
525		if ((bcs->enable_bitmap & (1 << n)) && !(u & (1 << n)))
526			/* this one is becoming disabled */
527			bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
528						LWSGGPIO_IRQ_NONE, NULL, NULL);
529	}
530
531	bcs->enable_bitmap = u;
532}
533