1+ /*
2+ * Copyright (c) 2023 T-Mobile USA, Inc.
3+ *
4+ * SPDX-License-Identifier: Apache-2.0
5+ */
6+
7+ #include <zephyr/device.h>
8+ #include <zephyr/drivers/gpio.h>
9+ #include <zephyr/kernel.h>
10+ #include <zephyr/sys/printk.h>
11+
12+ #include "button.h"
13+
14+ #define SW0_NODE DT_ALIAS(sw0)
15+
16+ static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR (SW0_NODE , gpios , {0 });
17+ static struct gpio_callback button_cb_data ;
18+ static button_press_callback_t registered_callback ;
19+ static struct gpio_callback button_cb_data ;
20+ button_press_state button_state = BUTTON_STATE_DEINIT ;
21+ struct k_timer debounce_timer ;
22+ struct k_timer timeout_timer ;
23+ static int64_t last_button_press_time ;
24+ static int64_t button_press_duration ;
25+ static int64_t max_button_press_timeout ;
26+
27+ /* button GPIO interrupt handler */
28+ static void button_press_handler (const struct device * port , struct gpio_callback * cb ,
29+ gpio_port_pins_t pins )
30+ {
31+ k_timer_stop (& debounce_timer );
32+ k_timer_start (& debounce_timer , K_MSEC (50 ), K_NO_WAIT );
33+ button_state = BUTTON_STATE_DEBOUNCING ;
34+ }
35+
36+ static void button_timeout_handler (struct k_timer * p_timeout_timer )
37+ {
38+ button_state = BUTTON_STATE_TIMEOUT ;
39+ if (NULL != registered_callback ) {
40+ /* saving the last button press time, since k_uptime_delta updates it with current
41+ * system uptime, which is undesired behavior in the context of measuring the real
42+ * button press duration even if timeout has occurred */
43+ int64_t save_last_button_press = last_button_press_time ;
44+
45+ button_press_duration = k_uptime_delta (& last_button_press_time );
46+ last_button_press_time = save_last_button_press ;
47+ /* Invoke the registered user callback handler */
48+ registered_callback (last_button_press_time , button_press_duration ,
49+ BUTTON_PRESS_TIMEOUT );
50+ }
51+ k_timer_stop (p_timeout_timer );
52+ }
53+
54+ /* debounce timer handler */
55+ static void button_debounce_timer_handler (struct k_timer * p_debounce_timer )
56+ {
57+ /* As soon as a button press action (hold or release) has occurred, we don't need the
58+ * timeout timer enabled anymore, until the next button event */
59+ k_timer_stop (& timeout_timer );
60+
61+ if (BUTTON_STATE_DEINIT == button_state ) {
62+ return ;
63+ }
64+ button_state = BUTTON_STATE_DEBOUNCED ;
65+ /* Each the button is pressed, last_button_press_time will be 0, indicating a new action on
66+ * user button */
67+ if (last_button_press_time == 0 ) {
68+ /* button press detected */
69+ last_button_press_time = k_uptime_get ();
70+ k_timer_start (& timeout_timer , K_MSEC (max_button_press_timeout ), K_NO_WAIT );
71+ } else if (registered_callback != NULL ) {
72+ /* button release detected */
73+
74+ /* This basically compares the timestamps of each time the timer handler is called
75+ * (one on button press, another on button release )*/
76+ button_press_duration = k_uptime_delta (& last_button_press_time );
77+
78+ /* Depending on whether the time delta exceeds the configured max timeout value set
79+ * during init function, the button press type is evaluated */
80+ if (button_state != BUTTON_STATE_TIMEOUT &&
81+ button_press_duration < max_button_press_timeout ) {
82+ /* Invoke the registered user callback handler */
83+ registered_callback (last_button_press_time , button_press_duration ,
84+ BUTTON_PRESS_NORMAL );
85+ }
86+
87+ /* Reset last_button_press_time to capture next button press event */
88+ last_button_press_time = 0 ;
89+
90+ button_state = BUTTON_STATE_INIT ;
91+ }
92+ }
93+
94+ int button_press_module_init (uint32_t max_timeout , button_press_callback_t callback )
95+ {
96+ /* Cannot initialize new paramters of button press without calling deinit first */
97+ if (BUTTON_STATE_DEINIT < button_state ) {
98+ return - EBUSY ;
99+ }
100+ /* Invalid params */
101+ if (0 == max_timeout || NULL == callback ) {
102+ return - EINVAL ;
103+ }
104+
105+ int ret = 0 ;
106+
107+ max_button_press_timeout = max_timeout ;
108+ registered_callback = callback ;
109+
110+ ret = gpio_pin_configure_dt (& button , GPIO_INPUT | GPIO_PULL_UP );
111+ if (ret != 0 ) {
112+ printk ("Error %d: failed to configure %s pin %d\n" , ret , button .port -> name ,
113+ button .pin );
114+ button_state = BUTTON_STATE_ERROR ;
115+ return ret ;
116+ }
117+
118+ /* Configrue interrputs on both edges so we can sample timestamp to caluclate duration of
119+ * button press */
120+ ret = gpio_pin_interrupt_configure_dt (& button , GPIO_INT_EDGE_BOTH );
121+ if (ret != 0 ) {
122+ printk ("Error %d: failed to configure interrupt on %s pin %d\n" , ret ,
123+ button .port -> name , button .pin );
124+ button_state = BUTTON_STATE_ERROR ;
125+ return ret ;
126+ }
127+ gpio_init_callback (& button_cb_data , button_press_handler , BIT (button .pin ));
128+ gpio_add_callback_dt (& button , & button_cb_data );
129+ printk ("Set up button at %s pin %d\n\n" , button .port -> name , button .pin );
130+
131+ k_timer_init (& debounce_timer , button_debounce_timer_handler , NULL );
132+ k_timer_init (& timeout_timer , button_timeout_handler , NULL );
133+
134+ /* Set state of button as configured. Only one configruation can exist at a time */
135+ button_state = BUTTON_STATE_INIT ;
136+
137+ return ret ;
138+ }
139+
140+ int button_press_module_deinit (void )
141+ {
142+ if (BUTTON_STATE_DEINIT == button_state ) {
143+ return 0 ;
144+ }
145+
146+ int ret = 0 ;
147+
148+ max_button_press_timeout = 0 ;
149+ registered_callback = NULL ;
150+
151+ /* Disconnect the GPIO from the MCU */
152+ ret = gpio_pin_configure_dt (& button , GPIO_DISCONNECTED );
153+ if (ret != 0 ) {
154+ printk ("Error %d: failed to disconnect %s pin %d\n" , ret , button .port -> name ,
155+ button .pin );
156+ button_state = BUTTON_STATE_ERROR ;
157+ return ret ;
158+ }
159+
160+ /* Disable all interrupts on it */
161+ ret = gpio_pin_interrupt_configure_dt (& button , GPIO_INT_DISABLE );
162+ if (ret != 0 ) {
163+ printk ("Error %d: failed to disable interrupt on %s pin %d\n" , ret ,
164+ button .port -> name , button .pin );
165+ button_state = BUTTON_STATE_ERROR ;
166+ return ret ;
167+ }
168+
169+ gpio_remove_callback_dt (& button , & button_cb_data );
170+ if (ret != 0 ) {
171+ printk ("Error %d: failed to remove intr callback on %s pin %d\n" , ret ,
172+ button .port -> name , button .pin );
173+ button_state = BUTTON_STATE_ERROR ;
174+ return ret ;
175+ }
176+
177+ k_timer_stop (& debounce_timer );
178+ k_timer_stop (& timeout_timer );
179+
180+ /* Set the button state to undefined so a new configruation can be set by calling the init
181+ * function */
182+ button_state = BUTTON_STATE_DEINIT ;
183+
184+ return ret ;
185+ }
186+
187+ button_press_state button_press_get_state (void )
188+ {
189+ /* Return current state of button */
190+ return button_state ;
191+ }
0 commit comments