• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
26 typedef 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 
44 typedef 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 
61 typedef 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)
71 static 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)
89 void
lws_button_irq_cb_t(void * arg)90 lws_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)
LWS_PLAT_TIMER_CB(lws_button_bh,th)108 static 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)
LWS_PLAT_TIMER_CB(lws_button_mon,th)204 static 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 				 */
360 classify_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 			}
392 stilldown:
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 
417 emit:
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 
430 struct lws_button_state *
lws_button_controller_create(struct lws_context * ctx,const lws_button_controller_t * controller)431 lws_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 
467 void
lws_button_controller_destroy(struct lws_button_state * bcs)468 lws_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 
481 lws_button_idx_t
lws_button_get_bit(struct lws_button_state * bcs,const char * name)482 lws_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 
494 void
lws_button_enable(lws_button_state_t * bcs,lws_button_idx_t _reset,lws_button_idx_t _set)495 lws_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