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 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) 70static 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) 88void 89lws_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) 105static 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) 202static 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) { 342classify_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 387classify: 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 397struct lws_button_state * 398lws_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 432void 433lws_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 446lws_button_idx_t 447lws_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 459void 460lws_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