| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- /*
- * Generic GPIO / irq buttons
- *
- * Copyright (C) 2019 - 2020 Andy Green <[email protected]>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
- #include "private-lib-core.h"
- typedef enum lws_button_classify_states {
- LBCS_IDLE, /* nothing happening */
- LBCS_MIN_DOWN_QUALIFY,
- LBCS_ASSESS_DOWN_HOLD,
- LBCS_UP_SETTLE1,
- LBCS_WAIT_DOUBLECLICK,
- LBCS_MIN_DOWN_QUALIFY2,
- LBCS_WAIT_UP,
- LBCS_UP_SETTLE2,
- } lws_button_classify_states_t;
- /*
- * This is the opaque, allocated, non-const, dynamic footprint of the
- * button controller
- */
- typedef struct lws_button_state {
- #if defined(LWS_PLAT_TIMER_TYPE)
- LWS_PLAT_TIMER_TYPE timer; /* bh timer */
- LWS_PLAT_TIMER_TYPE timer_mon; /* monitor timer */
- #endif
- const lws_button_controller_t *controller;
- struct lws_context *ctx;
- short mon_refcount;
- lws_button_idx_t enable_bitmap;
- lws_button_idx_t state_bitmap;
- uint16_t mon_timer_count;
- /* incremented each time the mon timer cb happens */
- /* lws_button_each_t per button overallocated after this */
- } lws_button_state_t;
- typedef struct lws_button_each {
- lws_button_state_t *bcs;
- uint16_t mon_timer_comp;
- uint16_t mon_timer_repeat;
- uint8_t state;
- /**^ lws_button_classify_states_t */
- uint8_t isr_pending;
- } lws_button_each_t;
- #if defined(LWS_PLAT_TIMER_START)
- static const lws_button_regime_t default_regime = {
- .ms_min_down = 20,
- .ms_min_down_longpress = 300,
- .ms_up_settle = 20,
- .ms_doubleclick_grace = 120,
- .flags = LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK
- };
- #endif
- /*
- * This is happening in interrupt context, we have to schedule a bottom half to
- * do the foreground lws_smd queueing, using, eg, a platform timer.
- *
- * All the buttons point here and use one timer per button controller. An
- * interrupt here means, "something happened to one or more buttons"
- */
- #if defined(LWS_PLAT_TIMER_START)
- void
- lws_button_irq_cb_t(void *arg)
- {
- lws_button_each_t *each = (lws_button_each_t *)arg;
- each->isr_pending = 1;
- LWS_PLAT_TIMER_START(each->bcs->timer);
- }
- #endif
- /*
- * This is the bottom-half scheduled via a timer set in the ISR. From here we
- * are allowed to hold mutexes etc. We are coming here because any button
- * interrupt arrived, we have to run another timer that tries to put whatever is
- * observed on any active button into context and either discard it or arrive at
- * a definitive event classification.
- */
- #if defined(LWS_PLAT_TIMER_CB)
- static LWS_PLAT_TIMER_CB(lws_button_bh, th)
- {
- lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
- lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
- const lws_button_controller_t *bc = bcs->controller;
- size_t n;
- /*
- * The ISR and bottom-half is shared by all the buttons. Each gpio
- * IRQ has an individual opaque ptr pointing to the corresponding
- * button's dynamic lws_button_each_t, the ISR marks the button's
- * each->isr_pending and schedules this bottom half.
- *
- * So now the bh timer has fired and something to do, we need to go
- * through all the buttons that have isr_pending set and service their
- * state. Intermediate states should start / bump the refcount on the
- * mon timer. That's refcounted so it only runs when a button down.
- */
- for (n = 0; n < bc->count_buttons; n++) {
- if (!each[n].isr_pending)
- continue;
- /*
- * Hide what we're about to do from the delicate eyes of the
- * IRQ controller...
- */
- bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
- LWSGGPIO_IRQ_NONE, NULL, NULL);
- each[n].isr_pending = 0;
- /*
- * Force the network around the switch to the
- * active level briefly
- */
- bc->gpio_ops->set(bc->button_map[n].gpio,
- !!(bc->active_state_bitmap & (1 << n)));
- bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_WRITE);
- if (each[n].state == LBCS_IDLE) {
- /*
- * If this is the first sign something happening on this
- * button, make sure the monitor timer is running to
- * classify its response over time
- */
- each[n].state = LBCS_MIN_DOWN_QUALIFY;
- each[n].mon_timer_comp = bcs->mon_timer_count;
- if (!bcs->mon_refcount++) {
- #if defined(LWS_PLAT_TIMER_START)
- LWS_PLAT_TIMER_START(bcs->timer_mon);
- #endif
- }
- }
- /*
- * Just for a us or two inbetween here, we're driving it to the
- * level we were informed by the interrupt it had enetered, to
- * force to charge on the actual and parasitic network around
- * the switch to a deterministic-ish state.
- *
- * If the switch remains in that state, well, it makes no
- * difference; if it was a pre-contact and the charge on the
- * network was left indeterminate, this will dispose it to act
- * consistently in the short term until the pullup / pulldown
- * has time to act on it or the switch comes and forces the
- * network charge state itself.
- */
- bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_READ);
- /*
- * We could do a better job manipulating the irq mode according
- * to the switch state. But if an interrupt comes and we have
- * done that, we can't tell if it's from before or after the
- * mode change... ie, we don't know what the interrupt was
- * telling us. We can't trust the gpio state if we read it now
- * to be related to what the irq from some time before was
- * trying to tell us. So always set it back to the same mode
- * and accept the limitation.
- */
- bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
- bc->active_state_bitmap & (1 << n) ?
- LWSGGPIO_IRQ_RISING :
- LWSGGPIO_IRQ_FALLING,
- lws_button_irq_cb_t, &each[n]);
- }
- }
- #endif
- #if defined(LWS_PLAT_TIMER_CB)
- static LWS_PLAT_TIMER_CB(lws_button_mon, th)
- {
- lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
- lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
- const lws_button_controller_t *bc = bcs->controller;
- const lws_button_regime_t *regime;
- const char *event_name;
- int comp_age_ms;
- char active;
- size_t n;
- bcs->mon_timer_count++;
- for (n = 0; n < bc->count_buttons; n++) {
- if (each->state == LBCS_IDLE) {
- each++;
- continue;
- }
- if (bc->button_map[n].regime)
- regime = bc->button_map[n].regime;
- else
- regime = &default_regime;
- comp_age_ms = (bcs->mon_timer_count - each->mon_timer_comp) *
- LWS_BUTTON_MON_TIMER_MS;
- active = bc->gpio_ops->read(bc->button_map[n].gpio) ^
- (!(bc->active_state_bitmap & (1 << n)));
- // lwsl_notice("%d\n", each->state);
- switch (each->state) {
- case LBCS_MIN_DOWN_QUALIFY:
- /*
- * We're trying to figure out if the initial down event
- * is a glitch, or if it meets the criteria for being
- * treated as the definitive start of some kind of click
- * action. To get past this, he has to be solidly down
- * for the time mentioned in the applied regime (at
- * least when we sample it).
- *
- * Significant bounce at the start will abort this try,
- * but if it's really down there will be a subsequent
- * solid down period... it will simply restart this flow
- * from a new interrupt and pass the filter then.
- *
- * The "brief drive on edge" strategy considerably
- * reduces inconsistencies here. But physical bounce
- * will continue to be observed.
- */
- if (!active) {
- /* We ignore stuff for a bit after discard */
- each->mon_timer_comp = bcs->mon_timer_count;
- each->state = LBCS_UP_SETTLE2;
- break;
- }
- if (comp_age_ms >= regime->ms_min_down) {
- /* We made it through the initial regime filter,
- * the next step is wait and see if this down
- * event evolves into a single/double click or
- * we can call it as a long-click
- */
- each->mon_timer_repeat = bcs->mon_timer_count;
- each->state = LBCS_ASSESS_DOWN_HOLD;
- event_name = "down";
- goto emit;
- }
- break;
- case LBCS_ASSESS_DOWN_HOLD:
- /*
- * How long is he going to hold it? If he holds it
- * past the long-click threshold, we can call it as a
- * long-click and do the up processing afterwards.
- */
- if (comp_age_ms >= regime->ms_min_down_longpress) {
- /* call it as a longclick */
- event_name = "longclick";
- each->state = LBCS_WAIT_UP;
- goto emit;
- }
- if (!active) {
- /*
- * He didn't hold it past the long-click
- * threshold... we could end up classifying it
- * as either a click or a double-click then.
- *
- * If double-clicks are not allowed to be
- * classified, then we can already classify it
- * as a single-click.
- */
- if (!(regime->flags &
- LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK))
- goto classify_single;
- /*
- * Just wait for the up settle time then start
- * looking for a second down.
- */
- each->mon_timer_comp = bcs->mon_timer_count;
- each->state = LBCS_UP_SETTLE1;
- event_name = "up";
- goto emit;
- }
- goto stilldown;
- case LBCS_UP_SETTLE1:
- if (comp_age_ms > regime->ms_up_settle)
- /*
- * Just block anything for the up settle time
- */
- each->state = LBCS_WAIT_DOUBLECLICK;
- break;
- case LBCS_WAIT_DOUBLECLICK:
- if (active) {
- /*
- * He has gone down again inside the regime's
- * doubleclick grace period... he's going down
- * the double-click path
- */
- each->mon_timer_comp = bcs->mon_timer_count;
- each->state = LBCS_MIN_DOWN_QUALIFY2;
- break;
- }
- if (comp_age_ms >= regime->ms_doubleclick_grace) {
- /*
- * The grace period expired, the second click
- * was either not forthcoming at all, or coming
- * quick enough to count: we classify it as a
- * single-click
- */
- goto classify_single;
- }
- break;
- case LBCS_MIN_DOWN_QUALIFY2:
- if (!active) {
- /*
- * He went up again too quickly, classify it
- * as a single-click. It could be bounce in
- * which case you might want to increase the
- * ms_up_settle in the regime
- */
- classify_single:
- event_name = "click";
- each->mon_timer_comp = bcs->mon_timer_count;
- each->state = LBCS_UP_SETTLE2;
- goto emit;
- }
- if (comp_age_ms == regime->ms_min_down) {
- event_name = "down";
- goto emit;
- }
- if (comp_age_ms > regime->ms_min_down) {
- /*
- * It's a double-click
- */
- event_name = "doubleclick";
- each->state = LBCS_WAIT_UP;
- goto emit;
- }
- break;
- case LBCS_WAIT_UP:
- if (!active) {
- /*
- * He has stopped pressing it
- */
- each->mon_timer_comp = bcs->mon_timer_count;
- each->state = LBCS_UP_SETTLE2;
- event_name = "up";
- goto emit;
- }
- stilldown:
- if (regime->ms_repeat_down &&
- (bcs->mon_timer_count - each->mon_timer_repeat) *
- LWS_BUTTON_MON_TIMER_MS > regime->ms_repeat_down) {
- each->mon_timer_repeat = bcs->mon_timer_count;
- event_name = "stilldown";
- goto emit;
- }
- break;
- case LBCS_UP_SETTLE2:
- if (comp_age_ms < regime->ms_up_settle)
- break;
- each->state = LBCS_IDLE;
- if (!(--bcs->mon_refcount)) {
- #if defined(LWS_PLAT_TIMER_STOP)
- LWS_PLAT_TIMER_STOP(bcs->timer_mon);
- #endif
- }
- }
- each++;
- continue;
- emit:
- lws_smd_msg_printf(bcs->ctx, LWSSMDCL_INTERACTION,
- "{\"type\":\"button\","
- "\"src\":\"%s/%s\",\"event\":\"%s\"}",
- bc->smd_bc_name,
- bc->button_map[n].smd_interaction_name,
- event_name);
- each++;
- }
- }
- #endif
- struct lws_button_state *
- lws_button_controller_create(struct lws_context *ctx,
- const lws_button_controller_t *controller)
- {
- lws_button_state_t *bcs = lws_zalloc(sizeof(lws_button_state_t) +
- (controller->count_buttons * sizeof(lws_button_each_t)),
- __func__);
- lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
- size_t n;
- if (!bcs)
- return NULL;
- bcs->controller = controller;
- bcs->ctx = ctx;
- for (n = 0; n < controller->count_buttons; n++)
- each[n].bcs = bcs;
- #if defined(LWS_PLAT_TIMER_CREATE)
- /* this only runs inbetween a gpio ISR and the bottom half */
- bcs->timer = LWS_PLAT_TIMER_CREATE("bcst",
- 1, 0, bcs, (TimerCallbackFunction_t)lws_button_bh);
- if (!bcs->timer)
- return NULL;
- /* this only runs when a button activity is being classified */
- bcs->timer_mon = LWS_PLAT_TIMER_CREATE("bcmon", LWS_BUTTON_MON_TIMER_MS,
- 1, bcs, (TimerCallbackFunction_t)
- lws_button_mon);
- if (!bcs->timer_mon)
- return NULL;
- #endif
- return bcs;
- }
- void
- lws_button_controller_destroy(struct lws_button_state *bcs)
- {
- /* disable them all */
- lws_button_enable(bcs, 0, 0);
- #if defined(LWS_PLAT_TIMER_DELETE)
- LWS_PLAT_TIMER_DELETE(&bcs->timer);
- LWS_PLAT_TIMER_DELETE(&bcs->timer_mon);
- #endif
- lws_free(bcs);
- }
- lws_button_idx_t
- lws_button_get_bit(struct lws_button_state *bcs, const char *name)
- {
- const lws_button_controller_t *bc = bcs->controller;
- int n;
- for (n = 0; n < bc->count_buttons; n++)
- if (!strcmp(name, bc->button_map[n].smd_interaction_name))
- return 1 << n;
- return 0; /* not found */
- }
- void
- lws_button_enable(lws_button_state_t *bcs,
- lws_button_idx_t _reset, lws_button_idx_t _set)
- {
- lws_button_idx_t u = (bcs->enable_bitmap & (~_reset)) | _set;
- const lws_button_controller_t *bc = bcs->controller;
- #if defined(LWS_PLAT_TIMER_START)
- lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
- #endif
- int n;
- for (n = 0; n < bcs->controller->count_buttons; n++) {
- if (!(bcs->enable_bitmap & (1 << n)) && (u & (1 << n))) {
- /* set as input with pullup or pulldown appropriately */
- bc->gpio_ops->mode(bc->button_map[n].gpio,
- LWSGGPIO_FL_READ |
- ((bc->active_state_bitmap & (1 << n)) ?
- LWSGGPIO_FL_PULLDOWN : LWSGGPIO_FL_PULLUP));
- #if defined(LWS_PLAT_TIMER_START)
- /*
- * This one is becoming enabled... the opaque for the
- * ISR is the indvidual lws_button_each_t, they all
- * point to the same ISR
- */
- bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
- bc->active_state_bitmap & (1 << n) ?
- LWSGGPIO_IRQ_RISING :
- LWSGGPIO_IRQ_FALLING,
- lws_button_irq_cb_t, &each[n]);
- #endif
- }
- if ((bcs->enable_bitmap & (1 << n)) && !(u & (1 << n)))
- /* this one is becoming disabled */
- bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
- LWSGGPIO_IRQ_NONE, NULL, NULL);
- }
- bcs->enable_bitmap = u;
- }
|