/* * Copyright 2023 Google LLC * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT gpio_qdec #include #include #include #include #include #include #include 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)