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 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) 71static 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) 89void 90lws_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) 108static 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) 204static 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 */ 360classify_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 } 392stilldown: 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 417emit: 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 430struct lws_button_state * 431lws_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 467void 468lws_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 481lws_button_idx_t 482lws_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 494void 495lws_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