/* * Copyright 2023 Google LLC * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT gpio_qdec #include #include #include #include #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 ab_gpio[GPIO_QDEC_GPIO_NUM]; const struct gpio_dt_spec *led_gpio; uint8_t led_gpio_count; uint32_t led_pre_us; uint32_t sample_time_us; uint32_t idle_poll_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; atomic_t polling; #ifdef CONFIG_PM_DEVICE atomic_t suspended; #endif }; /* 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 void gpio_qdec_irq_setup(const struct device *dev, bool enable) { const struct gpio_qdec_config *cfg = dev->config; gpio_flags_t 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->ab_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 bool gpio_qdec_idle_polling_mode(const struct device *dev) { const struct gpio_qdec_config *cfg = dev->config; if (cfg->idle_poll_time_us > 0) { return true; } return false; } static void gpio_qdec_poll_mode(const struct device *dev) { const struct gpio_qdec_config *cfg = dev->config; struct gpio_qdec_data *data = dev->data; if (!gpio_qdec_idle_polling_mode(dev)) { gpio_qdec_irq_setup(dev, false); } k_timer_start(&data->sample_timer, K_NO_WAIT, K_USEC(cfg->sample_time_us)); atomic_set(&data->polling, 1); LOG_DBG("polling start"); } static void gpio_qdec_idle_mode(const struct device *dev) { const struct gpio_qdec_config *cfg = dev->config; struct gpio_qdec_data *data = dev->data; if (gpio_qdec_idle_polling_mode(dev)) { k_timer_start(&data->sample_timer, K_NO_WAIT, K_USEC(cfg->idle_poll_time_us)); } else { k_timer_stop(&data->sample_timer); gpio_qdec_irq_setup(dev, true); } atomic_set(&data->polling, 0); LOG_DBG("polling stop"); } 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_qdec_idle_polling_mode(dev)) { for (int i = 0; i < cfg->led_gpio_count; i++) { gpio_pin_set_dt(&cfg->led_gpio[i], 1); } k_busy_wait(cfg->led_pre_us); } if (gpio_pin_get_dt(&cfg->ab_gpio[0])) { step |= 0x01; } if (gpio_pin_get_dt(&cfg->ab_gpio[1])) { step |= 0x02; } if (gpio_qdec_idle_polling_mode(dev)) { for (int i = 0; i < cfg->led_gpio_count; i++) { gpio_pin_set_dt(&cfg->led_gpio[i], 0); } } 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; #ifdef CONFIG_PM_DEVICE if (atomic_get(&data->suspended) == 1) { return; } #endif step = gpio_qdec_get_step(dev); if (data->prev_step == step) { return; } if (gpio_qdec_idle_polling_mode(dev) && atomic_get(&data->polling) == 0) { gpio_qdec_poll_mode(dev); } 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_idle_worker(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); struct gpio_qdec_data *data = CONTAINER_OF( dwork, struct gpio_qdec_data, idle_work); const struct device *dev = data->dev; gpio_qdec_idle_mode(dev); } 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; gpio_qdec_poll_mode(dev); } 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->ab_gpio[0].pin) | BIT(cfg->ab_gpio[1].pin)); for (int i = 0; i < GPIO_QDEC_GPIO_NUM; i++) { const struct gpio_dt_spec *gpio = &cfg->ab_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; } if (gpio_qdec_idle_polling_mode(dev)) { continue; } ret = gpio_add_callback_dt(gpio, &data->gpio_cb); if (ret < 0) { LOG_ERR("Could not set gpio callback"); return ret; } } for (int i = 0; i < cfg->led_gpio_count; i++) { const struct gpio_dt_spec *gpio = &cfg->led_gpio[i]; gpio_flags_t mode; if (!gpio_is_ready_dt(gpio)) { LOG_ERR("%s is not ready", gpio->port->name); return -ENODEV; } mode = gpio_qdec_idle_polling_mode(dev) ? GPIO_OUTPUT_INACTIVE : GPIO_OUTPUT_ACTIVE; ret = gpio_pin_configure_dt(gpio, mode); if (ret != 0) { LOG_ERR("Pin %d configuration failed: %d", i, ret); return ret; } } data->prev_step = gpio_qdec_get_step(dev); gpio_qdec_idle_mode(dev); ret = pm_device_runtime_enable(dev); if (ret < 0) { LOG_ERR("Failed to enable runtime power management"); return ret; } LOG_DBG("Device %s initialized", dev->name); return 0; } #ifdef CONFIG_PM_DEVICE static void gpio_qdec_pin_suspend(const struct device *dev, bool suspend) { const struct gpio_qdec_config *cfg = dev->config; gpio_flags_t mode = suspend ? GPIO_DISCONNECTED : GPIO_INPUT; int ret; for (int i = 0; i < GPIO_QDEC_GPIO_NUM; i++) { const struct gpio_dt_spec *gpio = &cfg->ab_gpio[i]; ret = gpio_pin_configure_dt(gpio, mode); if (ret != 0) { LOG_ERR("Pin %d configuration failed: %d", i, ret); return; } } for (int i = 0; i < cfg->led_gpio_count; i++) { if (suspend) { gpio_pin_set_dt(&cfg->led_gpio[i], 0); } else if (!gpio_qdec_idle_polling_mode(dev)) { gpio_pin_set_dt(&cfg->led_gpio[i], 1); } } } static int gpio_qdec_pm_action(const struct device *dev, enum pm_device_action action) { struct gpio_qdec_data *data = dev->data; switch (action) { case PM_DEVICE_ACTION_SUSPEND: struct k_work_sync sync; atomic_set(&data->suspended, 1); k_work_cancel_delayable_sync(&data->idle_work, &sync); if (!gpio_qdec_idle_polling_mode(dev)) { gpio_qdec_irq_setup(dev, false); } k_timer_stop(&data->sample_timer); gpio_qdec_pin_suspend(dev, true); break; case PM_DEVICE_ACTION_RESUME: atomic_set(&data->suspended, 0); gpio_qdec_pin_suspend(dev, false); data->prev_step = gpio_qdec_get_step(dev); data->acc = 0; gpio_qdec_idle_mode(dev); break; default: return -ENOTSUP; } return 0; } #endif #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"); \ \ BUILD_ASSERT(!(DT_INST_NODE_HAS_PROP(n, led_gpios) && \ DT_INST_NODE_HAS_PROP(n, idle_poll_time_us)) || \ DT_INST_NODE_HAS_PROP(n, led_pre_us), \ "led-pre-us must be specified when setting led-gpios and " \ "idle-poll-time-us"); \ \ IF_ENABLED(DT_INST_NODE_HAS_PROP(n, led_gpios), ( \ static const struct gpio_dt_spec gpio_qdec_led_gpio_##n[] = { \ DT_INST_FOREACH_PROP_ELEM_SEP(n, led_gpios, \ GPIO_DT_SPEC_GET_BY_IDX, (,)) \ }; \ )) \ \ static const struct gpio_qdec_config gpio_qdec_cfg_##n = { \ .ab_gpio = { \ GPIO_DT_SPEC_INST_GET_BY_IDX(n, gpios, 0), \ GPIO_DT_SPEC_INST_GET_BY_IDX(n, gpios, 1), \ }, \ IF_ENABLED(DT_INST_NODE_HAS_PROP(n, led_gpios), ( \ .led_gpio = gpio_qdec_led_gpio_##n, \ .led_gpio_count = ARRAY_SIZE(gpio_qdec_led_gpio_##n), \ .led_pre_us = DT_INST_PROP_OR(n, led_pre_us, 0), \ )) \ .sample_time_us = DT_INST_PROP(n, sample_time_us), \ .idle_poll_time_us = DT_INST_PROP_OR(n, idle_poll_time_us, 0), \ .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; \ \ PM_DEVICE_DT_INST_DEFINE(n, gpio_qdec_pm_action); \ \ DEVICE_DT_INST_DEFINE(n, gpio_qdec_init, PM_DEVICE_DT_INST_GET(n), \ &gpio_qdec_data_##n, \ &gpio_qdec_cfg_##n, \ POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \ NULL); DT_INST_FOREACH_STATUS_OKAY(QDEC_GPIO_INIT)