• 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 	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)
70 static 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)
88 void
lws_button_irq_cb_t(void * arg)89 lws_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)
LWS_PLAT_TIMER_CB(lws_button_bh,th)105 static 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)
LWS_PLAT_TIMER_CB(lws_button_mon,th)202 static 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) {
342 classify_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 
387 classify:
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 
397 struct lws_button_state *
lws_button_controller_create(struct lws_context * ctx,const lws_button_controller_t * controller)398 lws_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 
432 void
lws_button_controller_destroy(struct lws_button_state * bcs)433 lws_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 
446 lws_button_idx_t
lws_button_get_bit(struct lws_button_state * bcs,const char * name)447 lws_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 
459 void
lws_button_enable(lws_button_state_t * bcs,lws_button_idx_t _reset,lws_button_idx_t _set)460 lws_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