diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index a5f007c0b2..df94c1672f 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -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) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 46f5d69015..065a1f4316 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -162,4 +162,6 @@ source "drivers/gpio/Kconfig.rt1718s" source "drivers/gpio/Kconfig.numicro" +source "drivers/gpio/Kconfig.zephyr" + endif # GPIO diff --git a/drivers/gpio/Kconfig.zephyr b/drivers/gpio/Kconfig.zephyr new file mode 100644 index 0000000000..0e584a6d66 --- /dev/null +++ b/drivers/gpio/Kconfig.zephyr @@ -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. diff --git a/drivers/gpio/gpio_keys_zephyr.c b/drivers/gpio/gpio_keys_zephyr.c new file mode 100644 index 0000000000..04bffdc74a --- /dev/null +++ b/drivers/gpio/gpio_keys_zephyr.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2022 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +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) diff --git a/include/zephyr/drivers/gpio_keys.h b/include/zephyr/drivers/gpio_keys.h new file mode 100644 index 0000000000..18a4faf7e0 --- /dev/null +++ b/include/zephyr/drivers/gpio_keys.h @@ -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 +#include + +#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 + +#endif /* ZEPHYR_INCLUDE_DRIVERS_GPIO_KEYS_H_ */