input: add a gpio qdec input driver
Add a GPIO based quadrature decoder driver that reports relative axes movements using the input subsystem. Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
This commit is contained in:
parent
c2a730edc7
commit
2790106c33
|
@ -5,5 +5,6 @@ zephyr_library_property(ALLOW_EMPTY TRUE)
|
|||
|
||||
zephyr_library_sources_ifdef(CONFIG_INPUT_FT5336 input_ft5336.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_KEYS input_gpio_keys.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_QDEC input_gpio_qdec.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_INPUT_NPCX_KBD input_npcx_kbd.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_INPUT_SDL_TOUCH input_sdl_touch.c)
|
||||
|
|
|
@ -7,6 +7,7 @@ menu "Input drivers"
|
|||
|
||||
source "drivers/input/Kconfig.ft5336"
|
||||
source "drivers/input/Kconfig.gpio_keys"
|
||||
source "drivers/input/Kconfig.gpio_qdec"
|
||||
source "drivers/input/Kconfig.npcx"
|
||||
source "drivers/input/Kconfig.sdl"
|
||||
|
||||
|
|
9
drivers/input/Kconfig.gpio_qdec
Normal file
9
drivers/input/Kconfig.gpio_qdec
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright 2023 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config INPUT_GPIO_QDEC
|
||||
bool "GPIO based QDEC input driver"
|
||||
default y
|
||||
depends on DT_HAS_GPIO_QDEC_ENABLED
|
||||
help
|
||||
GPIO Quadrature Decoder input driver.
|
240
drivers/input/input_gpio_qdec.c
Normal file
240
drivers/input/input_gpio_qdec.c
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT gpio_qdec
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/input/input.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(input_gpio_qdec, CONFIG_INPUT_LOG_LEVEL);
|
||||
|
||||
#define GPIO_QDEC_GPIO_NUM 2
|
||||
|
||||
struct gpio_qdec_config {
|
||||
struct gpio_dt_spec gpio[GPIO_QDEC_GPIO_NUM];
|
||||
uint32_t sample_time_us;
|
||||
uint32_t idle_timeout_ms;
|
||||
uint16_t axis;
|
||||
uint8_t steps_per_period;
|
||||
};
|
||||
|
||||
struct gpio_qdec_data {
|
||||
const struct device *dev;
|
||||
struct k_timer sample_timer;
|
||||
uint8_t prev_step;
|
||||
int32_t acc;
|
||||
struct k_work event_work;
|
||||
struct k_work_delayable idle_work;
|
||||
struct gpio_callback gpio_cb;
|
||||
};
|
||||
|
||||
/* Positive transitions */
|
||||
#define QDEC_LL_LH 0x01
|
||||
#define QDEC_LH_HH 0x13
|
||||
#define QDEC_HH_HL 0x32
|
||||
#define QDEC_HL_LL 0x20
|
||||
|
||||
/* Negative transitions */
|
||||
#define QDEC_LL_HL 0x02
|
||||
#define QDEC_LH_LL 0x10
|
||||
#define QDEC_HH_LH 0x31
|
||||
#define QDEC_HL_HH 0x23
|
||||
|
||||
static uint8_t gpio_qdec_get_step(const struct device *dev)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
uint8_t step = 0x00;
|
||||
|
||||
if (gpio_pin_get_dt(&cfg->gpio[0])) {
|
||||
step |= 0x01;
|
||||
}
|
||||
if (gpio_pin_get_dt(&cfg->gpio[1])) {
|
||||
step |= 0x02;
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
static void gpio_qdec_sample_timer_timeout(struct k_timer *timer)
|
||||
{
|
||||
const struct device *dev = k_timer_user_data_get(timer);
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
struct gpio_qdec_data *data = dev->data;
|
||||
int8_t delta = 0;
|
||||
unsigned int key;
|
||||
uint8_t step;
|
||||
|
||||
step = gpio_qdec_get_step(dev);
|
||||
|
||||
if (data->prev_step == step) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ((data->prev_step << 4U) | step) {
|
||||
case QDEC_LL_LH:
|
||||
case QDEC_LH_HH:
|
||||
case QDEC_HH_HL:
|
||||
case QDEC_HL_LL:
|
||||
delta = 1;
|
||||
break;
|
||||
case QDEC_LL_HL:
|
||||
case QDEC_LH_LL:
|
||||
case QDEC_HH_LH:
|
||||
case QDEC_HL_HH:
|
||||
delta = -1;
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("%s: lost steps", dev->name);
|
||||
}
|
||||
|
||||
data->prev_step = step;
|
||||
|
||||
key = irq_lock();
|
||||
data->acc += delta;
|
||||
irq_unlock(key);
|
||||
|
||||
if (abs(data->acc) >= cfg->steps_per_period) {
|
||||
k_work_submit(&data->event_work);
|
||||
}
|
||||
|
||||
k_work_reschedule(&data->idle_work, K_MSEC(cfg->idle_timeout_ms));
|
||||
}
|
||||
|
||||
static void gpio_qdec_event_worker(struct k_work *work)
|
||||
{
|
||||
struct gpio_qdec_data *data = CONTAINER_OF(
|
||||
work, struct gpio_qdec_data, event_work);
|
||||
const struct device *dev = data->dev;
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
unsigned int key;
|
||||
int32_t acc;
|
||||
|
||||
key = irq_lock();
|
||||
acc = data->acc / cfg->steps_per_period;
|
||||
data->acc -= acc * cfg->steps_per_period;
|
||||
irq_unlock(key);
|
||||
|
||||
if (acc != 0) {
|
||||
input_report_rel(data->dev, cfg->axis, acc, true, K_FOREVER);
|
||||
}
|
||||
}
|
||||
|
||||
static void gpio_qdec_irq_setup(const struct device *dev, bool enable)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
unsigned int flags = enable ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE;
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < GPIO_QDEC_GPIO_NUM; i++) {
|
||||
const struct gpio_dt_spec *gpio = &cfg->gpio[i];
|
||||
|
||||
ret = gpio_pin_interrupt_configure_dt(gpio, flags);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Pin %d interrupt configuration failed: %d", i, ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gpio_qdec_idle_worker(struct k_work *work)
|
||||
{
|
||||
struct gpio_qdec_data *data = CONTAINER_OF(
|
||||
work, struct gpio_qdec_data, idle_work);
|
||||
const struct device *dev = data->dev;
|
||||
|
||||
k_timer_stop(&data->sample_timer);
|
||||
|
||||
gpio_qdec_irq_setup(dev, true);
|
||||
|
||||
LOG_DBG("polling stop");
|
||||
}
|
||||
|
||||
static void gpio_qdec_cb(const struct device *gpio_dev, struct gpio_callback *cb,
|
||||
uint32_t pins)
|
||||
{
|
||||
struct gpio_qdec_data *data = CONTAINER_OF(
|
||||
cb, struct gpio_qdec_data, gpio_cb);
|
||||
const struct device *dev = data->dev;
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
|
||||
gpio_qdec_irq_setup(dev, false);
|
||||
|
||||
k_timer_start(&data->sample_timer, K_NO_WAIT,
|
||||
K_USEC(cfg->sample_time_us));
|
||||
|
||||
LOG_DBG("polling start");
|
||||
}
|
||||
|
||||
static int gpio_qdec_init(const struct device *dev)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
struct gpio_qdec_data *data = dev->data;
|
||||
int ret;
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
k_work_init(&data->event_work, gpio_qdec_event_worker);
|
||||
k_work_init_delayable(&data->idle_work, gpio_qdec_idle_worker);
|
||||
|
||||
k_timer_init(&data->sample_timer, gpio_qdec_sample_timer_timeout, NULL);
|
||||
k_timer_user_data_set(&data->sample_timer, (void *)dev);
|
||||
|
||||
gpio_init_callback(&data->gpio_cb, gpio_qdec_cb,
|
||||
BIT(cfg->gpio[0].pin) | BIT(cfg->gpio[1].pin));
|
||||
for (int i = 0; i < GPIO_QDEC_GPIO_NUM; i++) {
|
||||
const struct gpio_dt_spec *gpio = &cfg->gpio[i];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
gpio_add_callback_dt(gpio, &data->gpio_cb);
|
||||
}
|
||||
|
||||
data->prev_step = gpio_qdec_get_step(dev);
|
||||
|
||||
gpio_qdec_irq_setup(dev, true);
|
||||
|
||||
LOG_DBG("Device %s initialized", dev->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define QDEC_GPIO_INIT(n) \
|
||||
BUILD_ASSERT(DT_INST_PROP_LEN(n, gpios) == GPIO_QDEC_GPIO_NUM, \
|
||||
"input_gpio_qdec: gpios must have exactly two entries"); \
|
||||
\
|
||||
static const struct gpio_qdec_config gpio_qdec_cfg_##n = { \
|
||||
.gpio = {GPIO_DT_SPEC_INST_GET_BY_IDX(n, gpios, 0), \
|
||||
GPIO_DT_SPEC_INST_GET_BY_IDX(n, gpios, 1)}, \
|
||||
.sample_time_us = DT_INST_PROP(n, sample_time_us), \
|
||||
.idle_timeout_ms = DT_INST_PROP(n, idle_timeout_ms), \
|
||||
.steps_per_period = DT_INST_PROP(n, steps_per_period), \
|
||||
.axis = DT_INST_PROP(n, zephyr_axis), \
|
||||
}; \
|
||||
\
|
||||
static struct gpio_qdec_data gpio_qdec_data_##n; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, gpio_qdec_init, NULL, \
|
||||
&gpio_qdec_data_##n, \
|
||||
&gpio_qdec_cfg_##n, \
|
||||
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \
|
||||
NULL);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(QDEC_GPIO_INIT)
|
63
dts/bindings/input/gpio-qdec.yaml
Normal file
63
dts/bindings/input/gpio-qdec.yaml
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Copyright 2023 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
GPIO based QDEC input device
|
||||
|
||||
Implement an input device generating relative axis event reports for a rotary
|
||||
encoder connected to two GPIOs. The driver is normally idling until it sees a
|
||||
transition on any of the encoder signal lines, then switches to polling mode
|
||||
and samples the two signal lines periodically to track the encoder position,
|
||||
and goes back to idling after the specified timeout.
|
||||
|
||||
Example configuration:
|
||||
|
||||
#include <dt-bindings/input/input-event-codes.h>
|
||||
|
||||
qdec {
|
||||
compatible = "gpio-qdec";
|
||||
gpios = <&gpio0 14 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>,
|
||||
<&gpio0 13 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>;
|
||||
steps-per-period = <4>;
|
||||
zephyr,axis = <INPUT_REL_Y>;
|
||||
sample-time-us = <2000>;
|
||||
idle-timeout-ms = <200>;
|
||||
};
|
||||
|
||||
compatible: "gpio-qdec"
|
||||
|
||||
include: base.yaml
|
||||
|
||||
properties:
|
||||
gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: |
|
||||
GPIO for the A and B encoder signals.
|
||||
|
||||
steps-per-period:
|
||||
type: int
|
||||
required: true
|
||||
description: |
|
||||
How many steps to count before reporting an input event.
|
||||
|
||||
zephyr,axis:
|
||||
type: int
|
||||
required: true
|
||||
description: |
|
||||
The input code for the axis to report for the device, typically any of
|
||||
INPUT_REL_*.
|
||||
|
||||
sample-time-us:
|
||||
type: int
|
||||
required: true
|
||||
description: |
|
||||
How often to sample the A and B signal lines when tracking the encoder
|
||||
movement.
|
||||
|
||||
idle-timeout-ms:
|
||||
type: int
|
||||
required: true
|
||||
description: |
|
||||
Timeout for the last detected transition before stopping the sampling
|
||||
timer and going back to idle state.
|
Loading…
Reference in a new issue