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	uint8_t					state;
65	/**^ lws_button_classify_states_t */
66	uint8_t					isr_pending;
67} lws_button_each_t;
68
69#if defined(LWS_PLAT_TIMER_START)
70static const lws_button_regime_t default_regime = {
71	.ms_min_down			= 20,
72	.ms_min_down_longpress		= 300,
73	.ms_up_settle			= 20,
74	.ms_doubleclick_grace		= 120,
75	.flags				= LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK
76};
77#endif
78
79
80/*
81 * This is happening in interrupt context, we have to schedule a bottom half to
82 * do the foreground lws_smd queueing, using, eg, a platform timer.
83 *
84 * All the buttons point here and use one timer per button controller.  An
85 * interrupt here means, "something happened to one or more buttons"
86 */
87#if defined(LWS_PLAT_TIMER_START)
88void
89lws_button_irq_cb_t(void *arg)
90{
91	lws_button_each_t *each = (lws_button_each_t *)arg;
92
93	each->isr_pending = 1;
94	LWS_PLAT_TIMER_START(each->bcs->timer);
95}
96#endif
97
98/*
99 * This is the bottom-half scheduled via a timer set in the ISR.  From here
100 * we are allowed to hold mutexes etc.  We are coming here because any button
101 * interrupt arrived, we have to try to figure out which events have happened.
102 */
103
104#if defined(LWS_PLAT_TIMER_CB)
105static LWS_PLAT_TIMER_CB(lws_button_bh, th)
106{
107	lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
108	const lws_button_controller_t *bc = bcs->controller;
109	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
110	size_t n;
111
112	/*
113	 * The ISR and bottom-half is shared by all the buttons.  Each gpio
114	 * IRQ has an individual opaque ptr pointing to the corresponding
115	 * button's dynamic lws_button_each_t, the ISR marks the button's
116	 * each->isr_pending and schedules this bottom half.
117	 *
118	 * So now the bh timer has fired and something to do, we need to go
119	 * through all the buttons that have isr_pending set and service their
120	 * state.  Intermediate states should start / bump the refcount on the
121	 * mon timer.  That's refcounted so it only runs when a button down.
122	 */
123
124	for (n = 0; n < bc->count_buttons; n++) {
125
126		if (!each[n].isr_pending)
127			continue;
128
129		/*
130		 * Hide what we're about to do from the delicate eyes of the
131		 * IRQ controller...
132		 */
133
134		bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
135				       LWSGGPIO_IRQ_NONE, NULL, NULL);
136
137		each[n].isr_pending = 0;
138
139		/*
140		 * Force the network around the switch to the
141		 * active level briefly
142		 */
143
144		bc->gpio_ops->set(bc->button_map[n].gpio,
145				  !!(bc->active_state_bitmap & (1 << n)));
146		bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_WRITE);
147
148		if (each[n].state == LBCS_IDLE) {
149			/*
150			 * If this is the first sign something happening on this
151			 * button, make sure the monitor timer is running to
152			 * classify it over time
153			 */
154
155			each[n].state = LBCS_MIN_DOWN_QUALIFY;
156			each[n].mon_timer_comp = bcs->mon_timer_count;
157
158			if (!bcs->mon_refcount++) {
159#if defined(LWS_PLAT_TIMER_START)
160				// lwsl_notice("%s: starting mon timer\n", __func__);
161				LWS_PLAT_TIMER_START(bcs->timer_mon);
162#endif
163			}
164		}
165
166		/*
167		 * Just for a us or two inbetween here, we're driving it to the
168		 * level we were informed by the interrupt it had enetered, to
169		 * force to charge on the actual and parasitic network around
170		 * the switch to a deterministic-ish state.
171		 *
172		 * If the switch remains in that state, well, it makes no
173		 * difference; if it was a pre-contact and the charge on the
174		 * network was left indeterminate, this will dispose it to act
175		 * consistently in the short term until the pullup / pulldown
176		 * has time to act on it or the switch comes and forces the
177		 * network charge state itself.
178		 */
179		bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_READ);
180
181		/*
182		 * We could do a better job manipulating the irq mode according
183		 * to the switch state.  But if an interrupt comes and we have
184		 * done that, we can't tell if it's from before or after the
185		 * mode change... ie, we don't know what the interrupt was
186		 * telling us.  We can't trust the gpio state if we read it now
187		 * to be related to what the irq from some time before was
188		 * trying to tell us.  So always set it back to the same mode
189		 * and accept the limitation.
190		 */
191
192		bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
193				       bc->active_state_bitmap & (1 << n) ?
194					   LWSGGPIO_IRQ_RISING :
195					   LWSGGPIO_IRQ_FALLING,
196					      lws_button_irq_cb_t, &each[n]);
197	}
198}
199#endif
200
201#if defined(LWS_PLAT_TIMER_CB)
202static LWS_PLAT_TIMER_CB(lws_button_mon, th)
203{
204	lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
205	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
206	const lws_button_controller_t *bc = bcs->controller;
207	const lws_button_regime_t *regime;
208	const char *event_name;
209	int comp_age_ms;
210	char active;
211	size_t n;
212
213	bcs->mon_timer_count++;
214
215	for (n = 0; n < bc->count_buttons; n++) {
216
217		if (each[n].state == LBCS_IDLE)
218			continue;
219
220		if (bc->button_map[n].regime)
221			regime = bc->button_map[n].regime;
222		else
223			regime = &default_regime;
224
225		comp_age_ms = (bcs->mon_timer_count - each[n].mon_timer_comp) *
226				LWS_BUTTON_MON_TIMER_MS;
227
228		active = bc->gpio_ops->read(bc->button_map[n].gpio) ^
229			       (!(bc->active_state_bitmap & (1 << n)));
230
231		// lwsl_notice("%d\n", each[n].state);
232
233		switch (each[n].state) {
234		case LBCS_MIN_DOWN_QUALIFY:
235			/*
236			 * We're trying to figure out if the initial down event
237			 * is a glitch, or if it meets the criteria for being
238			 * treated as the definitive start of some kind of click
239			 * action.  To get past this, he has to be solidly down
240			 * for the time mentioned in the applied regime (at
241			 * least when we sample it).
242			 *
243			 * Significant bounce at the start will abort this try,
244			 * but if it's really down there will be a subsequent
245			 * solid down period... it will simply restart this flow
246			 * from a new interrupt and pass the filter then.
247			 *
248			 * The "brief drive on edge" strategy considerably
249			 * reduces inconsistencies here.  But physical bounce
250			 * will continue to be observed.
251			 */
252
253			if (!active) {
254				/* We ignore stuff for a bit after discard */
255				each[n].mon_timer_comp = bcs->mon_timer_count;
256				each[n].state = LBCS_UP_SETTLE2;
257				continue;
258			}
259
260			if (comp_age_ms >= regime->ms_min_down) {
261
262				/* We made it through the initial regime filter,
263				 * the next step is wait and see if this down
264				 * event evolves into a single/double click or
265				 * we can call it as a long-click
266				 */
267
268				each[n].state = LBCS_ASSESS_DOWN_HOLD;
269				break;
270			}
271			break;
272
273		case LBCS_ASSESS_DOWN_HOLD:
274			/*
275			 * How long is he going to hold it?  If he holds it
276			 * past the long-click threshold, we can call it as a
277			 * long-click and do the up processing afterwards.
278			 */
279			if (comp_age_ms >= regime->ms_min_down_longpress) {
280				/* call it as a longclick */
281				event_name = "longclick";
282				each[n].state = LBCS_WAIT_UP;
283				goto classify;
284			}
285
286			if (!active) {
287				/*
288				 * He didn't hold it past the long-click
289				 * threshold... we could end up classifying it
290				 * as either a click or a double-click then.
291				 *
292				 * If double-clicks are not allowed to be
293				 * classified, then we can already classify it
294				 * as a single-click.
295				 */
296				if (!(regime->flags & LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK))
297					goto classify_single;
298
299				/*
300				 * Just wait for the up settle time then start
301				 * looking for a second down.
302				 */
303				each[n].mon_timer_comp = bcs->mon_timer_count;
304				each[n].state = LBCS_UP_SETTLE1;
305			}
306			break;
307
308		case LBCS_UP_SETTLE1:
309			if (comp_age_ms > regime->ms_up_settle)
310				/*
311				 * Just block anything for the up settle time
312				 */
313				each[n].state = LBCS_WAIT_DOUBLECLICK;
314			break;
315
316		case LBCS_WAIT_DOUBLECLICK:
317			if (active) {
318				/*
319				 * He has gone down again inside the regime's
320				 * doubleclick grace period... he's going down
321				 * the double-click path
322				 */
323				each[n].mon_timer_comp = bcs->mon_timer_count;
324				each[n].state = LBCS_MIN_DOWN_QUALIFY2;
325				break;
326			}
327
328			if (comp_age_ms >= regime->ms_doubleclick_grace) {
329				/*
330				 * The grace period expired, the second click
331				 * was either not forthcoming at all, or coming
332				 * quick enough to count: we classify it as a
333				 * single-click
334				 */
335
336				goto classify_single;
337			}
338			break;
339
340		case LBCS_MIN_DOWN_QUALIFY2:
341			if (!active) {
342classify_single:
343				/*
344				 * He went up again too quickly, classify it
345				 * as a single-click.  It could be bounce in
346				 * which case you might want to increase
347				 * the ms_up_settle in the regime
348				 */
349				event_name = "click";
350				each[n].mon_timer_comp = bcs->mon_timer_count;
351				each[n].state = LBCS_UP_SETTLE2;
352				goto classify;
353			}
354
355			if (comp_age_ms >= regime->ms_min_down) {
356				/*
357				 * It's a double-click
358				 */
359				event_name = "doubleclick";
360				each[n].state = LBCS_WAIT_UP;
361				goto classify;
362			}
363			break;
364
365		case LBCS_WAIT_UP:
366			if (!active) {
367				each[n].mon_timer_comp = bcs->mon_timer_count;
368				each[n].state = LBCS_UP_SETTLE2;
369			}
370			break;
371
372		case LBCS_UP_SETTLE2:
373			if (comp_age_ms < regime->ms_up_settle)
374				break;
375
376			each[n].state = LBCS_IDLE;
377			if (!(--bcs->mon_refcount)) {
378#if defined(LWS_PLAT_TIMER_STOP)
379				LWS_PLAT_TIMER_STOP(bcs->timer_mon);
380#endif
381			}
382			break;
383		}
384
385		continue;
386
387classify:
388		lws_smd_msg_printf(bcs->ctx, LWSSMDCL_INTERACTION,
389		   "{\"btn\":\"%s/%s\", \"s\":\"%s\"}",
390		   bc->smd_bc_name,
391		   bc->button_map[n].smd_interaction_name,
392		   event_name);
393	}
394}
395#endif
396
397struct lws_button_state *
398lws_button_controller_create(struct lws_context *ctx,
399			     const lws_button_controller_t *controller)
400{
401	lws_button_state_t *bcs = lws_zalloc(sizeof(lws_button_state_t) +
402			(controller->count_buttons * sizeof(lws_button_each_t)),
403			__func__);
404	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
405	size_t n;
406
407	if (!bcs)
408		return NULL;
409
410	bcs->controller = controller;
411	bcs->ctx = ctx;
412
413	for (n = 0; n < controller->count_buttons; n++)
414		each[n].bcs = bcs;
415
416#if defined(LWS_PLAT_TIMER_CREATE)
417	/* this only runs inbetween a gpio ISR and the bottom half */
418	bcs->timer = LWS_PLAT_TIMER_CREATE("bcst",
419			1, 0, bcs, (TimerCallbackFunction_t)lws_button_bh);
420	if (!bcs->timer)
421		return NULL;
422	/* this only runs when a button activity is being classified */
423	bcs->timer_mon = LWS_PLAT_TIMER_CREATE("bcmon", LWS_BUTTON_MON_TIMER_MS, 1, bcs,
424			(TimerCallbackFunction_t)lws_button_mon);
425	if (!bcs->timer_mon)
426		return NULL;
427#endif
428
429	return bcs;
430}
431
432void
433lws_button_controller_destroy(struct lws_button_state *bcs)
434{
435	/* disable them all */
436	lws_button_enable(bcs, 0, 0);
437
438#if defined(LWS_PLAT_TIMER_DELETE)
439	LWS_PLAT_TIMER_DELETE(&bcs->timer);
440	LWS_PLAT_TIMER_DELETE(&bcs->timer_mon);
441#endif
442
443	lws_free(bcs);
444}
445
446lws_button_idx_t
447lws_button_get_bit(struct lws_button_state *bcs, const char *name)
448{
449	const lws_button_controller_t *bc = bcs->controller;
450	int n;
451
452	for (n = 0; n < bc->count_buttons; n++)
453		if (!strcmp(name, bc->button_map[n].smd_interaction_name))
454			return 1 << n;
455
456	return 0; /* not found */
457}
458
459void
460lws_button_enable(lws_button_state_t *bcs,
461		  lws_button_idx_t _reset, lws_button_idx_t _set)
462{
463	lws_button_idx_t u = (bcs->enable_bitmap & (~_reset)) | _set;
464	const lws_button_controller_t *bc = bcs->controller;
465#if defined(LWS_PLAT_TIMER_START)
466	lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
467#endif
468	int n;
469
470	for (n = 0; n < bcs->controller->count_buttons; n++) {
471		if (!(bcs->enable_bitmap & (1 << n)) && (u & (1 << n))) {
472			/* set as input with pullup or pulldown appropriately */
473			bc->gpio_ops->mode(bc->button_map[n].gpio,
474				LWSGGPIO_FL_READ |
475				((bc->active_state_bitmap & (1 << n)) ?
476				LWSGGPIO_FL_PULLDOWN : LWSGGPIO_FL_PULLUP));
477#if defined(LWS_PLAT_TIMER_START)
478			/*
479			 * This one is becoming enabled... the opaque for the
480			 * ISR is the indvidual lws_button_each_t, they all
481			 * point to the same ISR
482			 */
483			bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
484					bc->active_state_bitmap & (1 << n) ?
485						LWSGGPIO_IRQ_RISING :
486							LWSGGPIO_IRQ_FALLING,
487						lws_button_irq_cb_t, &each[n]);
488#endif
489		}
490		if ((bcs->enable_bitmap & (1 << n)) && !(u & (1 << n)))
491			/* this one is becoming disabled */
492			bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
493						LWSGGPIO_IRQ_NONE, NULL, NULL);
494	}
495
496	bcs->enable_bitmap = u;
497}
498