zephyr/drivers/input/input_gpio_keys.c
Marcel Krüger 45f3b71b39 input_gpio: Fix not using latest pin state on pm resume
When the device was suspended and the pin level changed during
that time, the pin level of course isn't updated in the pins cb_data.
Once the device is resumed, this leads to potentially having
a wrong value in the pin state data leading to swallowing the first
event due to comparing the stored level vs. the new level before
reporting.

Also added some `const`s and deleted an unused struct element.

Signed-off-by: Marcel Krüger <marcel@mkgr.dev>
2025-06-06 10:12:11 +02:00

325 lines
9.7 KiB
C

/*
* Copyright (c) 2022 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT gpio_keys
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/input/input.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/sys/atomic.h>
LOG_MODULE_REGISTER(gpio_keys, CONFIG_INPUT_LOG_LEVEL);
struct gpio_keys_callback {
struct gpio_callback gpio_cb;
int8_t pin_state;
};
struct gpio_keys_pin_config {
/** GPIO specification from devicetree */
struct gpio_dt_spec spec;
/** Zephyr code from devicetree */
uint32_t zephyr_code;
};
struct gpio_keys_pin_data {
const struct device *dev;
struct gpio_keys_callback cb_data;
struct k_work_delayable work;
};
struct gpio_keys_config {
/** Debounce interval in milliseconds from devicetree */
uint32_t debounce_interval_ms;
const int num_keys;
const struct gpio_keys_pin_config *pin_cfg;
struct gpio_keys_pin_data *pin_data;
k_work_handler_t handler;
bool polling_mode;
bool no_disconnect;
};
struct gpio_keys_data {
#ifdef CONFIG_PM_DEVICE
atomic_t suspended;
#endif
};
/**
* Handle debounced gpio pin state.
*/
static void gpio_keys_poll_pin(const struct device *dev, int key_index)
{
const struct gpio_keys_config *cfg = dev->config;
const struct gpio_keys_pin_config *pin_cfg = &cfg->pin_cfg[key_index];
struct gpio_keys_pin_data *pin_data = &cfg->pin_data[key_index];
int new_pressed;
int ret;
ret = gpio_pin_get_dt(&pin_cfg->spec);
if (ret < 0) {
LOG_ERR("key_index %d get failed: %d", key_index, ret);
return;
}
new_pressed = ret;
LOG_DBG("%s: pin_state=%d, new_pressed=%d, key_index=%d", dev->name,
pin_data->cb_data.pin_state, new_pressed, key_index);
/* If gpio changed, report the event */
if (new_pressed != pin_data->cb_data.pin_state) {
pin_data->cb_data.pin_state = new_pressed;
LOG_DBG("Report event %s %d, code=%d", dev->name, new_pressed,
pin_cfg->zephyr_code);
input_report_key(dev, pin_cfg->zephyr_code, new_pressed, true, K_FOREVER);
}
}
static __maybe_unused void gpio_keys_poll_pins(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct gpio_keys_pin_data *pin_data = CONTAINER_OF(dwork, struct gpio_keys_pin_data, work);
const struct device *dev = pin_data->dev;
const struct gpio_keys_config *cfg = dev->config;
#ifdef CONFIG_PM_DEVICE
struct gpio_keys_data *data = dev->data;
if (atomic_get(&data->suspended) == 1) {
return;
}
#endif
for (int i = 0; i < cfg->num_keys; i++) {
gpio_keys_poll_pin(dev, i);
}
k_work_reschedule(dwork, K_MSEC(cfg->debounce_interval_ms));
}
static __maybe_unused void gpio_keys_change_deferred(struct k_work *work)
{
const struct k_work_delayable *dwork = k_work_delayable_from_work(work);
const struct gpio_keys_pin_data *pin_data =
CONTAINER_OF(dwork, struct gpio_keys_pin_data, work);
const struct device *dev = pin_data->dev;
const struct gpio_keys_config *cfg = dev->config;
const int key_index = pin_data - (struct gpio_keys_pin_data *)cfg->pin_data;
#ifdef CONFIG_PM_DEVICE
struct gpio_keys_data *data = dev->data;
if (atomic_get(&data->suspended) == 1) {
return;
}
#endif
gpio_keys_poll_pin(dev, key_index);
}
static void gpio_keys_interrupt(const struct device *dev, struct gpio_callback *cbdata,
uint32_t pins)
{
struct gpio_keys_callback *keys_cb = CONTAINER_OF(
cbdata, struct gpio_keys_callback, gpio_cb);
struct gpio_keys_pin_data *pin_data = CONTAINER_OF(
keys_cb, struct gpio_keys_pin_data, cb_data);
const struct gpio_keys_config *cfg = pin_data->dev->config;
ARG_UNUSED(dev); /* GPIO device pointer. */
ARG_UNUSED(pins);
k_work_reschedule(&pin_data->work, K_MSEC(cfg->debounce_interval_ms));
}
static int gpio_keys_interrupt_configure(const struct gpio_dt_spec *gpio_spec,
struct gpio_keys_callback *cb, uint32_t zephyr_code)
{
int ret;
gpio_init_callback(&cb->gpio_cb, gpio_keys_interrupt, BIT(gpio_spec->pin));
ret = gpio_add_callback(gpio_spec->port, &cb->gpio_cb);
if (ret < 0) {
LOG_ERR("Could not set gpio callback");
return ret;
}
cb->pin_state = gpio_pin_get_dt(gpio_spec);
LOG_DBG("port=%s, pin=%d", gpio_spec->port->name, gpio_spec->pin);
ret = gpio_pin_interrupt_configure_dt(gpio_spec, GPIO_INT_EDGE_BOTH);
if (ret < 0) {
LOG_ERR("interrupt configuration failed: %d", ret);
return ret;
}
return 0;
}
static int gpio_keys_init(const struct device *dev)
{
const struct gpio_keys_config *cfg = dev->config;
struct gpio_keys_pin_data *pin_data = cfg->pin_data;
int ret;
for (int i = 0; i < cfg->num_keys; i++) {
const struct gpio_dt_spec *gpio = &cfg->pin_cfg[i].spec;
if (!gpio_is_ready_dt(gpio)) {
LOG_ERR("%s is not ready", gpio->port->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(gpio, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Pin %d configuration failed: %d", i, ret);
return ret;
}
pin_data[i].dev = dev;
k_work_init_delayable(&pin_data[i].work, cfg->handler);
if (cfg->polling_mode) {
continue;
}
ret = gpio_keys_interrupt_configure(&cfg->pin_cfg[i].spec,
&pin_data[i].cb_data,
cfg->pin_cfg[i].zephyr_code);
if (ret != 0) {
LOG_ERR("Pin %d interrupt configuration failed: %d", i, ret);
return ret;
}
}
if (cfg->polling_mode) {
/* use pin 0 work to poll all the pins periodically */
k_work_reschedule(&pin_data[0].work, K_MSEC(cfg->debounce_interval_ms));
}
ret = pm_device_runtime_enable(dev);
if (ret < 0) {
LOG_ERR("Failed to enable runtime power management");
return ret;
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int gpio_keys_pm_action(const struct device *dev,
enum pm_device_action action)
{
const struct gpio_keys_config *cfg = dev->config;
struct gpio_keys_data *data = dev->data;
struct gpio_keys_pin_data *pin_data = cfg->pin_data;
int ret;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
atomic_set(&data->suspended, 1);
for (int i = 0; i < cfg->num_keys; i++) {
const struct gpio_dt_spec *gpio = &cfg->pin_cfg[i].spec;
if (!cfg->polling_mode) {
ret = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_DISABLE);
if (ret < 0) {
LOG_ERR("interrupt configuration failed: %d", ret);
return ret;
}
}
if (!cfg->no_disconnect) {
ret = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED);
if (ret != 0) {
LOG_ERR("Pin %d configuration failed: %d", i, ret);
return ret;
}
}
}
return 0;
case PM_DEVICE_ACTION_RESUME:
atomic_set(&data->suspended, 0);
for (int i = 0; i < cfg->num_keys; i++) {
const struct gpio_dt_spec *gpio = &cfg->pin_cfg[i].spec;
if (!cfg->no_disconnect) {
ret = gpio_pin_configure_dt(gpio, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Pin %d configuration failed: %d", i, ret);
return ret;
}
}
if (cfg->polling_mode) {
k_work_reschedule(&pin_data[0].work,
K_MSEC(cfg->debounce_interval_ms));
} else {
pin_data[i].cb_data.pin_state = gpio_pin_get_dt(gpio);
ret = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_EDGE_BOTH);
if (ret < 0) {
LOG_ERR("interrupt configuration failed: %d", ret);
return ret;
}
}
}
return 0;
default:
return -ENOTSUP;
}
}
#endif
#define GPIO_KEYS_CFG_CHECK(node_id) \
BUILD_ASSERT(DT_NODE_HAS_PROP(node_id, zephyr_code), \
"zephyr-code must be specified to use the input-gpio-keys driver");
#define GPIO_KEYS_CFG_DEF(node_id) \
{ \
.spec = GPIO_DT_SPEC_GET(node_id, gpios), \
.zephyr_code = DT_PROP(node_id, zephyr_code), \
}
#define GPIO_KEYS_INIT(i) \
DT_INST_FOREACH_CHILD_STATUS_OKAY(i, GPIO_KEYS_CFG_CHECK); \
\
static const struct gpio_keys_pin_config gpio_keys_pin_config_##i[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(i, GPIO_KEYS_CFG_DEF, (,))}; \
\
static struct gpio_keys_pin_data \
gpio_keys_pin_data_##i[ARRAY_SIZE(gpio_keys_pin_config_##i)]; \
\
static const struct gpio_keys_config gpio_keys_config_##i = { \
.debounce_interval_ms = DT_INST_PROP(i, debounce_interval_ms), \
.num_keys = ARRAY_SIZE(gpio_keys_pin_config_##i), \
.pin_cfg = gpio_keys_pin_config_##i, \
.pin_data = gpio_keys_pin_data_##i, \
.handler = COND_CODE_1(DT_INST_PROP(i, polling_mode), \
(gpio_keys_poll_pins), (gpio_keys_change_deferred)), \
.polling_mode = DT_INST_PROP(i, polling_mode), \
.no_disconnect = DT_INST_PROP(i, no_disconnect), \
}; \
\
static struct gpio_keys_data gpio_keys_data_##i; \
\
PM_DEVICE_DT_INST_DEFINE(i, gpio_keys_pm_action); \
\
DEVICE_DT_INST_DEFINE(i, &gpio_keys_init, PM_DEVICE_DT_INST_GET(i), \
&gpio_keys_data_##i, &gpio_keys_config_##i, \
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(GPIO_KEYS_INIT)