| /* |
| * Copyright (c) 2024, Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/ztest.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/pm/device_runtime.h> |
| |
| static K_SEM_DEFINE(sem, 0, 1); |
| static const struct gpio_dt_spec phase_a = GPIO_DT_SPEC_GET(DT_ALIAS(qenca), gpios); |
| static const struct gpio_dt_spec phase_b = GPIO_DT_SPEC_GET(DT_ALIAS(qencb), gpios); |
| static const struct device *const qdec_dev = DEVICE_DT_GET(DT_ALIAS(qdec0)); |
| static const uint32_t qdec_config_step = DT_PROP(DT_ALIAS(qdec0), steps); |
| static struct sensor_trigger qdec_trigger = {.type = SENSOR_TRIG_DATA_READY, |
| .chan = SENSOR_CHAN_ROTATION}; |
| static bool toggle_a = true; |
| |
| static void qdec_trigger_handler(const struct device *dev, const struct sensor_trigger *trigger) |
| { |
| zassert_not_null(dev); |
| zassert_not_null(trigger); |
| zassert_equal_ptr(dev, qdec_dev); |
| /* trigger should be stored as a pointer in a driver |
| * thus this address should match |
| */ |
| zassert_equal_ptr(trigger, &qdec_trigger); |
| |
| k_sem_give(&sem); |
| } |
| |
| static void qenc_emulate_work_handler(struct k_work *work) |
| { |
| if (toggle_a) { |
| gpio_pin_toggle_dt(&phase_a); |
| } else { |
| gpio_pin_toggle_dt(&phase_b); |
| } |
| toggle_a = !toggle_a; |
| } |
| |
| static K_WORK_DEFINE(qenc_emulate_work, qenc_emulate_work_handler); |
| |
| static void qenc_emulate_timer_handler(struct k_timer *dummy) |
| { |
| k_work_submit(&qenc_emulate_work); |
| } |
| |
| static K_TIMER_DEFINE(qenc_emulate_timer, qenc_emulate_timer_handler, NULL); |
| |
| static void qenc_emulate_reset_pin(const struct gpio_dt_spec *gpio_dt) |
| { |
| int rc; |
| |
| rc = gpio_pin_set_dt(gpio_dt, 0); |
| zassert_ok(rc, "%s: pin set failed: %d", gpio_dt->port->name, rc); |
| } |
| |
| static void qenc_emulate_setup_pin(const struct gpio_dt_spec *gpio_dt) |
| { |
| int rc; |
| |
| rc = gpio_is_ready_dt(gpio_dt); |
| zassert_true(rc, "%s: device not ready: %d", gpio_dt->port->name, rc); |
| |
| rc = gpio_pin_configure_dt(gpio_dt, GPIO_OUTPUT); |
| zassert_true(rc == 0, "%s: pin configure failed: %d", gpio_dt->port->name, rc); |
| } |
| |
| static void qenc_emulate_start(k_timeout_t period, bool forward) |
| { |
| qenc_emulate_reset_pin(&phase_a); |
| qenc_emulate_reset_pin(&phase_b); |
| |
| toggle_a = !forward; |
| |
| k_timer_start(&qenc_emulate_timer, period, period); |
| } |
| |
| static void qenc_emulate_stop(void) |
| { |
| k_timer_stop(&qenc_emulate_timer); |
| |
| qenc_emulate_reset_pin(&phase_a); |
| qenc_emulate_reset_pin(&phase_b); |
| } |
| |
| static void qenc_emulate_verify_reading(int emulator_period_ms, int emulation_duration_ms, |
| bool forward, bool overflow_expected) |
| { |
| int rc; |
| struct sensor_value val = {0}; |
| int32_t expected_steps = emulation_duration_ms / emulator_period_ms; |
| int32_t expected_reading = 360 * expected_steps / qdec_config_step; |
| int32_t delta = expected_reading / 5; |
| |
| if (!forward) { |
| expected_reading *= -1; |
| } |
| |
| qenc_emulate_start(K_MSEC(emulator_period_ms), forward); |
| |
| /* wait for some readings*/ |
| k_msleep(emulation_duration_ms); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| |
| if (!overflow_expected) { |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| } else { |
| zassert_true(rc == -EOVERFLOW, "Failed to detect overflow"); |
| } |
| |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val); |
| zassert_true(rc == 0, "Failed to get sample (%d)", rc); |
| |
| if (!overflow_expected) { |
| zassert_within(val.val1, expected_reading, delta, |
| "Expected reading: %d, but got: %d", expected_reading, val.val1); |
| } |
| |
| qenc_emulate_stop(); |
| |
| /* wait and get readings to clear state */ |
| k_msleep(100); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val); |
| zassert_true(rc == 0, "Failed to get sample (%d)", rc); |
| } |
| |
| /** |
| * @brief sensor_trigger_set test disable trigger |
| * |
| * Confirm trigger happens after set and stops after being disabled |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_trigger_set_and_disable) |
| { |
| int rc; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| qdec_trigger.type = SENSOR_TRIG_DATA_READY; |
| qdec_trigger.chan = SENSOR_CHAN_ALL; |
| rc = sensor_trigger_set(qdec_dev, &qdec_trigger, qdec_trigger_handler); |
| zassume_true(rc != -ENOSYS, "sensor_trigger_set not supported"); |
| |
| zassert_true(rc == 0, "sensor_trigger_set failed: %d", rc); |
| |
| qenc_emulate_start(K_MSEC(10), true); |
| |
| /* emulation working, handler should be called */ |
| rc = k_sem_take(&sem, K_MSEC(200)); |
| zassert_true(rc == 0, "qdec handler should be triggered (%d)", rc); |
| |
| qenc_emulate_stop(); |
| |
| /* emulation not working, but there maybe old trigger, ignore */ |
| rc = k_sem_take(&sem, K_MSEC(200)); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| |
| /* there should be no triggers now*/ |
| rc = k_sem_take(&sem, K_MSEC(200)); |
| zassert_true(rc == -EAGAIN, "qdec handler should not be triggered (%d)", rc); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| /* register empty trigger - disable trigger */ |
| rc = sensor_trigger_set(qdec_dev, &qdec_trigger, NULL); |
| zassert_true(rc == 0, "sensor_trigger_set failed: %d", rc); |
| |
| qenc_emulate_start(K_MSEC(10), true); |
| |
| /* emulation working, but handler not set, thus should not be called */ |
| rc = k_sem_take(&sem, K_MSEC(200)); |
| zassert_true(rc == -EAGAIN, "qdec handler should not be triggered (%d)", rc); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief sensor_trigger_set test |
| * |
| * Confirm trigger happens after set |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_trigger_set) |
| { |
| int rc; |
| struct sensor_value val = {0}; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| qdec_trigger.type = SENSOR_TRIG_DATA_READY; |
| qdec_trigger.chan = SENSOR_CHAN_ROTATION; |
| rc = sensor_trigger_set(qdec_dev, &qdec_trigger, qdec_trigger_handler); |
| zassume_true(rc != -ENOSYS, "sensor_trigger_set not supported"); |
| |
| zassert_true(rc == 0, "sensor_trigger_set failed: %d", rc); |
| |
| qenc_emulate_start(K_MSEC(10), true); |
| |
| /* emulation working now */ |
| rc = k_sem_take(&sem, K_MSEC(200)); |
| zassert_true(rc == 0, "qdec handler should be triggered (%d)", rc); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| TC_PRINT("QDEC reading: %d\n", val.val1); |
| zassert_true(val.val1 != 0, "No readings from QDEC"); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief sensor_trigger_set test negative |
| * |
| * Confirm setting trigger with invalid data does not work |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_trigger_set_negative) |
| { |
| int rc; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| rc = sensor_trigger_set(qdec_dev, &qdec_trigger, qdec_trigger_handler); |
| zassume_true(rc != -ENOSYS, "sensor_trigger_set not supported"); |
| |
| qdec_trigger.type = SENSOR_TRIG_MAX; |
| qdec_trigger.chan = SENSOR_CHAN_ROTATION; |
| |
| rc = sensor_trigger_set(qdec_dev, &qdec_trigger, qdec_trigger_handler); |
| zassume_true(rc < 0, "sensor_trigger_set should fail due to invalid trigger type"); |
| |
| qdec_trigger.type = SENSOR_TRIG_DATA_READY; |
| qdec_trigger.chan = SENSOR_CHAN_MAX; |
| |
| rc = sensor_trigger_set(qdec_dev, &qdec_trigger, qdec_trigger_handler); |
| zassume_true(rc < 0, "sensor_trigger_set should fail due to invalid channel"); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief QDEC readings tests |
| * |
| * Valid reading from QDEC base on simulated signal |
| * |
| */ |
| ZTEST(qdec_sensor, test_qdec_readings) |
| { |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| qenc_emulate_verify_reading(10, 100, true, false); |
| qenc_emulate_verify_reading(2, 500, true, false); |
| qenc_emulate_verify_reading(10, 200, false, false); |
| qenc_emulate_verify_reading(1, 1000, false, true); |
| qenc_emulate_verify_reading(1, 1000, true, true); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief sensor_channel_get test with no emulation |
| * |
| * Confirm getting empty reading from QDEC |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_channel_get_empty) |
| { |
| int rc; |
| struct sensor_value val = {0}; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| /* wait for potential new readings */ |
| k_msleep(100); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| /* get readings but ignore them, as they may include reading from time |
| * when emulation was still working (i.e. during previous test) |
| */ |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val); |
| zassert_true(rc == 0, "Failed to get sample (%d)", rc); |
| |
| /* wait for potential new readings */ |
| k_msleep(100); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| /* emulation was not working, expect no readings */ |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val); |
| zassert_true(rc == 0, "Failed to get sample (%d)", rc); |
| zassert_true(val.val1 == 0, "Expected no readings but got: %d", val.val1); |
| zassert_true(val.val2 == 0, "Expected no readings but got: %d", val.val2); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief sensor_channel_get test with emulation |
| * |
| * Confirm getting readings from QDEC |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_channel_get) |
| { |
| int rc; |
| struct sensor_value val_first = {0}; |
| struct sensor_value val_second = {0}; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| qenc_emulate_start(K_MSEC(10), true); |
| |
| /* wait for some readings*/ |
| k_msleep(100); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val_first); |
| zassert_true(rc == 0, "Failed to get sample (%d)", rc); |
| zassert_true(val_first.val1 != 0, "No readings from QDEC"); |
| |
| /* wait for more readings*/ |
| k_msleep(200); |
| |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &val_second); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| zassert_true(val_second.val1 != 0, "No readings from QDEC"); |
| |
| /* subsequent calls of sensor_channel_get without calling sensor_sample_fetch |
| * should yield the same value |
| */ |
| zassert_true(val_first.val1 == val_second.val1, |
| "Expected the same readings: %d vs %d", |
| val_first.val1, |
| val_second.val1); |
| |
| zassert_true(val_first.val2 == val_second.val2, |
| "Expected the same readings: %d vs %d", |
| val_first.val2, |
| val_second.val2); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief sensor_channel_get test negative |
| * |
| * Confirm getting readings from QDEC with invalid channel |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_channel_get_negative) |
| { |
| int rc; |
| struct sensor_value val = {0}; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| qenc_emulate_start(K_MSEC(10), true); |
| |
| /* wait for some readings*/ |
| k_msleep(100); |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| rc = sensor_channel_get(qdec_dev, SENSOR_CHAN_MAX, &val); |
| zassert_true(rc < 0, "Should failed to get sample (%d)", rc); |
| zassert_true(val.val1 == 0, "Some readings from QDEC: %d", val.val1); |
| zassert_true(val.val2 == 0, "Some readings from QDEC: %d", val.val2); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| /** |
| * @brief sensor_sample_fetch(_chan) test |
| * |
| * Confirm fetching work with QDEC specific channel - rotation |
| * |
| */ |
| ZTEST(qdec_sensor, test_sensor_sample_fetch) |
| { |
| int rc; |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_get(qdec_dev); |
| } |
| |
| rc = sensor_sample_fetch(qdec_dev); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| rc = sensor_sample_fetch_chan(qdec_dev, SENSOR_CHAN_ROTATION); |
| zassert_true(rc == 0, "Failed to fetch sample (%d)", rc); |
| |
| rc = sensor_sample_fetch_chan(qdec_dev, SENSOR_CHAN_MAX); |
| zassert_true(rc < 0, "Should fail to fetch sample from invalid channel (%d)", rc); |
| |
| if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { |
| pm_device_runtime_put(qdec_dev); |
| } |
| } |
| |
| static void *setup(void) |
| { |
| int rc; |
| |
| rc = device_is_ready(qdec_dev); |
| zassert_true(rc, "QDEC device not ready: %d", rc); |
| |
| qenc_emulate_setup_pin(&phase_a); |
| qenc_emulate_setup_pin(&phase_b); |
| |
| return NULL; |
| } |
| |
| static void before(void *fixture) |
| { |
| ARG_UNUSED(fixture); |
| |
| qenc_emulate_stop(); |
| } |
| |
| static void after(void *fixture) |
| { |
| ARG_UNUSED(fixture); |
| |
| qenc_emulate_stop(); |
| } |
| |
| ZTEST_SUITE(qdec_sensor, NULL, setup, before, after, NULL); |