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