gpio: Add driver support for software based gpio debounce

Software based GPIO debounce driver implementation.

Signed-off-by: Al Semjonovs <asemjonovs@google.com>
This commit is contained in:
Al Semjonovs 2022-12-06 15:14:05 -07:00 committed by Carles Cufí
parent 643d877fe4
commit 211e4d276e
5 changed files with 350 additions and 0 deletions

View file

@ -70,3 +70,4 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_NPM6001 gpio_npm6001.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_RT1718S gpio_rt1718s.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_RT1718S gpio_rt1718s_port.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_NUMICRO gpio_numicro.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_KEYS_ZEPHYR gpio_keys_zephyr.c)

View file

@ -162,4 +162,6 @@ source "drivers/gpio/Kconfig.rt1718s"
source "drivers/gpio/Kconfig.numicro"
source "drivers/gpio/Kconfig.zephyr"
endif # GPIO

View file

@ -0,0 +1,9 @@
# Copyright (c) 2022 Google LLC
# SPDX-License-Identifier: Apache-2.0
config GPIO_KEYS_ZEPHYR
bool "Zephyr GPIO Keys"
default y
depends on DT_HAS_ZEPHYR_GPIO_KEYS_ENABLED
help
Enable support for Zephyr GPIO Keys.

View file

@ -0,0 +1,234 @@
/*
* Copyright (c) 2022 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio_keys.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephyr_gpio_keys, CONFIG_GPIO_LOG_LEVEL);
#define DT_DRV_COMPAT zephyr_gpio_keys
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_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 {
const struct device *dev;
struct gpio_keys_callback cb_data;
struct k_work_delayable work;
int8_t pin_state;
};
struct gpio_keys_data {
gpio_keys_callback_handler_t callback;
int num_keys;
struct gpio_keys_pin_data *pin_data;
};
/**
* Handle debounced gpio pin state.
*/
static void gpio_keys_change_deferred(struct k_work *work)
{
struct gpio_keys_pin_data *pin_data = CONTAINER_OF(work, struct gpio_keys_pin_data, work);
const struct device *dev = pin_data->dev;
struct gpio_keys_data *data = dev->data;
int key_index = pin_data - &data->pin_data[0];
const struct gpio_keys_config *cfg = dev->config;
const struct gpio_keys_pin_config *pin_cfg = &cfg->pin_cfg[key_index];
const int new_pressed = gpio_pin_get(pin_cfg->spec.port, pin_cfg->spec.pin);
LOG_DBG("gpio_change_deferred %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, invoke callback */
if (new_pressed != pin_data->cb_data.pin_state) {
pin_data->cb_data.pin_state = new_pressed;
LOG_DBG("Calling callback %s %d, code=%d", dev->name, new_pressed,
pin_cfg->zephyr_code);
data->callback(dev, &pin_data->cb_data, BIT(pin_cfg->spec.pin));
}
}
static void gpio_keys_change_call_deferred(struct gpio_keys_pin_data *data, uint32_t msec)
{
int rv = k_work_reschedule(&data->work, K_MSEC(msec));
__ASSERT(rv >= 0, "Set wake mask work queue error");
}
static void gpio_keys_interrupt(const struct device *dev, struct gpio_callback *cbdata,
uint32_t pins)
{
ARG_UNUSED(dev); /* This is a pointer to GPIO device, use dev pointer in
* cbdata for pointer to gpio_debounce device node
*/
struct gpio_keys_pin_data *pin_data =
CONTAINER_OF(cbdata, struct gpio_keys_pin_data, cb_data);
const struct gpio_keys_config *cfg = pin_data->dev->config;
struct gpio_keys_data *data = pin_data->dev->data;
for (int i = 0; i < data->num_keys; i++) {
if (pins & BIT(cfg->pin_cfg[i].spec.pin)) {
gpio_keys_change_call_deferred(pin_data, 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 retval = -ENODEV;
gpio_flags_t flags;
gpio_init_callback(&cb->gpio_cb, gpio_keys_interrupt, BIT(gpio_spec->pin));
gpio_add_callback(gpio_spec->port, &cb->gpio_cb);
cb->zephyr_code = zephyr_code;
cb->pin_state = -1;
flags = GPIO_INT_EDGE_BOTH & ~GPIO_INT_MODE_DISABLED;
LOG_DBG("%s [0x%p, %d]", __func__, gpio_spec->port, gpio_spec->pin);
retval = z_impl_gpio_pin_interrupt_configure(gpio_spec->port, gpio_spec->pin, flags);
return retval;
}
static int gpio_keys_zephyr_enable_interrupt(const struct device *dev,
gpio_keys_callback_handler_t gpio_keys_cb)
{
int retval = -ENODEV;
const struct gpio_keys_config *cfg = dev->config;
struct gpio_keys_data *data = dev->data;
data->callback = gpio_keys_cb;
for (int i = 0; i < data->num_keys; i++) {
retval = gpio_keys_interrupt_configure(&cfg->pin_cfg[i].spec,
&data->pin_data[i].cb_data,
cfg->pin_cfg[i].zephyr_code);
}
return retval;
}
static int gpio_keys_zephyr_disable_interrupt(const struct device *dev)
{
int retval = -ENODEV;
const struct gpio_keys_config *cfg = dev->config;
struct gpio_keys_data *data = dev->data;
const struct gpio_dt_spec *gpio_spec;
for (int i = 0; i < data->num_keys; i++) {
gpio_spec = &cfg->pin_cfg[i].spec;
retval = z_impl_gpio_pin_interrupt_configure(gpio_spec->port, gpio_spec->pin,
GPIO_INT_MODE_DISABLED);
if (data->pin_data[i].cb_data.gpio_cb.handler) {
retval = gpio_remove_callback(gpio_spec->port,
&data->pin_data[i].cb_data.gpio_cb);
memset(&data->pin_data[i].cb_data, 0, sizeof(struct gpio_keys_callback));
}
LOG_DBG("disable interrupt [0x%p, %d], rv=%d", gpio_spec->port, gpio_spec->pin,
retval);
}
return retval;
}
static int gpio_keys_get_gpio_port_logical(const struct device *gpio_dev, gpio_port_value_t *value)
{
const struct gpio_driver_data *const data = gpio_dev->data;
int ret = z_impl_gpio_port_get_raw(gpio_dev, value);
if (ret == 0) {
*value ^= data->invert;
}
return ret;
}
static int gpio_keys_zephyr_get_pin(const struct device *dev, uint32_t idx)
{
const struct gpio_keys_config *cfg = dev->config;
const struct gpio_dt_spec *gpio_spec = &cfg->pin_cfg[idx].spec;
const struct device *gpio_dev = gpio_spec->port;
const struct gpio_driver_config *gpio_cfg = gpio_dev->config;
int ret;
gpio_port_value_t value;
__ASSERT((gpio_cfg->port_pin_mask & (gpio_port_pins_t)BIT(gpio_spec->pin)) != 0U,
"Unsupported pin");
ret = gpio_keys_get_gpio_port_logical(gpio_dev, &value);
if (ret == 0) {
ret = (value & (gpio_port_pins_t)BIT(gpio_spec->pin)) != 0 ? 1 : 0;
}
if (ret < 0) {
LOG_ERR("Cannot read %s, ret=%d", dev->name, ret);
ret = 0;
}
return ret;
}
static int gpio_keys_init(const struct device *dev)
{
struct gpio_keys_data *data = dev->data;
for (int i = 0; i < data->num_keys; i++) {
data->pin_data[i].dev = dev;
k_work_init_delayable(&data->pin_data[i].work, gpio_keys_change_deferred);
}
return 0;
}
static const struct gpio_keys_api gpio_keys_zephyr_api = {
.enable_interrupt = gpio_keys_zephyr_enable_interrupt,
.disable_interrupt = gpio_keys_zephyr_disable_interrupt,
.get_pin = gpio_keys_zephyr_get_pin,
};
#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) \
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_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, \
}; \
static struct gpio_keys_pin_data \
gpio_keys_pin_data_##i[ARRAY_SIZE(gpio_keys_pin_config_##i)]; \
static struct gpio_keys_data gpio_keys_data_##i = { \
.num_keys = ARRAY_SIZE(gpio_keys_pin_config_##i), \
.pin_data = gpio_keys_pin_data_##i, \
}; \
DEVICE_DT_INST_DEFINE(i, &gpio_keys_init, NULL, &gpio_keys_data_##i, \
&gpio_keys_config_##i, POST_KERNEL, CONFIG_GPIO_INIT_PRIORITY, \
&gpio_keys_zephyr_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_KEYS_INIT)

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2022 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_GPIO_KEYS_H_
#define ZEPHYR_INCLUDE_DRIVERS_GPIO_KEYS_H_
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
#ifdef __cplusplus
extern "C" {
#endif
struct gpio_keys_callback {
struct gpio_callback gpio_cb;
uint32_t zephyr_code;
int8_t pin_state;
};
typedef void (*gpio_keys_callback_handler_t)(const struct device *port,
struct gpio_keys_callback *cb, gpio_port_pins_t pins);
/**
* @brief GPIO Keys Driver APIs
* @defgroup gpio_keys_interface GPIO KeysDriver APIs
* @ingroup io_interfaces
* @{
*/
__subsystem struct gpio_keys_api {
int (*enable_interrupt)(const struct device *dev, gpio_keys_callback_handler_t cb);
int (*disable_interrupt)(const struct device *dev);
int (*get_pin)(const struct device *dev, uint32_t idx);
};
/**
* @brief Enable interrupt
*
* @param dev Pointer to device structure for the driver instance.
* @param cb Function callback to be invoked after GPIO key has been debounced
*
* @return 0 If successful
*/
__syscall int gpio_keys_enable_interrupt(const struct device *dev, gpio_keys_callback_handler_t cb);
static inline int z_impl_gpio_keys_enable_interrupt(const struct device *dev,
gpio_keys_callback_handler_t cb)
{
struct gpio_keys_api *api;
api = (struct gpio_keys_api *)dev->api;
return api->enable_interrupt(dev, cb);
}
/**
* @brief Disable interrupt
*
* @param dev Pointer to device structure for the driver instance.
*
* @return 0 If successful
*/
__syscall int gpio_keys_disable_interrupt(const struct device *dev);
static inline int z_impl_gpio_keys_disable_interrupt(const struct device *dev)
{
struct gpio_keys_api *api;
api = (struct gpio_keys_api *)dev->api;
return api->disable_interrupt(dev);
}
/**
* @brief Get the logical level of GPIO Key
*
* @param dev Pointer to device structure for the driver instance.
* @param idx GPIO Key index in device tree
*
* @retval 0 If successful.
* @retval -EIO I/O error when accessing an external GPIO chip.
* @retval -EWOULDBLOCK if operation would block.
*/
__syscall int gpio_keys_get_pin(const struct device *dev, uint32_t idx);
static inline int z_impl_gpio_keys_get_pin(const struct device *dev, uint32_t idx)
{
struct gpio_keys_api *api;
api = (struct gpio_keys_api *)dev->api;
return api->get_pin(dev, idx);
}
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#include <syscalls/gpio_keys.h>
#endif /* ZEPHYR_INCLUDE_DRIVERS_GPIO_KEYS_H_ */