From 90825a8812d0f74994ecb1323503efa004f3a916 Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Sat, 19 Dec 2020 16:07:02 +0100 Subject: [PATCH] tests: drivers: pwm: add PWM loopback test Add test cases for the PWM capture API using PWM signal loopback. Signed-off-by: Henrik Brix Andersen --- tests/drivers/pwm/pwm_loopback/CMakeLists.txt | 9 + .../pwm/pwm_loopback/boards/frdm_k64f.overlay | 15 + .../dts/bindings/test,pwm_loopback.yaml | 22 ++ tests/drivers/pwm/pwm_loopback/prj.conf | 5 + tests/drivers/pwm/pwm_loopback/src/main.c | 32 ++ .../pwm/pwm_loopback/src/test_pwm_loopback.c | 344 ++++++++++++++++++ .../pwm/pwm_loopback/src/test_pwm_loopback.h | 66 ++++ tests/drivers/pwm/pwm_loopback/testcase.yaml | 8 + 8 files changed, 501 insertions(+) create mode 100644 tests/drivers/pwm/pwm_loopback/CMakeLists.txt create mode 100644 tests/drivers/pwm/pwm_loopback/boards/frdm_k64f.overlay create mode 100644 tests/drivers/pwm/pwm_loopback/dts/bindings/test,pwm_loopback.yaml create mode 100644 tests/drivers/pwm/pwm_loopback/prj.conf create mode 100644 tests/drivers/pwm/pwm_loopback/src/main.c create mode 100644 tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.c create mode 100644 tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.h create mode 100644 tests/drivers/pwm/pwm_loopback/testcase.yaml diff --git a/tests/drivers/pwm/pwm_loopback/CMakeLists.txt b/tests/drivers/pwm/pwm_loopback/CMakeLists.txt new file mode 100644 index 00000000000000..7b20a1fa828a92 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pwm_loopback) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/pwm/pwm_loopback/boards/frdm_k64f.overlay b/tests/drivers/pwm/pwm_loopback/boards/frdm_k64f.overlay new file mode 100644 index 00000000000000..e091c1303211f5 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/boards/frdm_k64f.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020-2021 Vestas Wind Systems A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + pwm_loopback_0 { + compatible = "test,pwm_loopback"; + pwms = <&ftm0 0 0 PWM_POLARITY_NORMAL>, /* PTC1, J1 pin 5 */ + <&ftm3 4 0 PWM_POLARITY_NORMAL>; /* PTC8, J1 pin 7 */ + }; +}; diff --git a/tests/drivers/pwm/pwm_loopback/dts/bindings/test,pwm_loopback.yaml b/tests/drivers/pwm/pwm_loopback/dts/bindings/test,pwm_loopback.yaml new file mode 100644 index 00000000000000..81fc5855ea23d0 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/dts/bindings/test,pwm_loopback.yaml @@ -0,0 +1,22 @@ +# +# Copyright (c) 2020-2021 Vestas Wind Systems A/S +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: | + This binding provides resources required to build and run the + tests/drivers/pwm/pwm_loopback test in Zephyr. + +compatible: "test,pwm_loopback" + +properties: + pwms: + type: phandle-array + required: true + description: | + PWM pins that will be used for generating and capturing a pulse-width + modulated signal. The pin at the first index will be used for signal + generation while the pin at the second index will be used for capuring + the generated signal. The two pins must be physically connected to + each other. diff --git a/tests/drivers/pwm/pwm_loopback/prj.conf b/tests/drivers/pwm/pwm_loopback/prj.conf new file mode 100644 index 00000000000000..73f7ae3cd455b6 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/prj.conf @@ -0,0 +1,5 @@ +CONFIG_ZTEST=y +CONFIG_TEST_USERSPACE=y + +CONFIG_PWM=y +CONFIG_PWM_CAPTURE=y diff --git a/tests/drivers/pwm/pwm_loopback/src/main.c b/tests/drivers/pwm/pwm_loopback/src/main.c new file mode 100644 index 00000000000000..7fde0472b82c49 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/src/main.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 Vestas Wind Systems A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "test_pwm_loopback.h" + +void test_main(void) +{ + struct test_pwm in; + struct test_pwm out; + + get_test_pwms(&in, &out); + + k_object_access_grant(out.dev, k_current_get()); + k_object_access_grant(in.dev, k_current_get()); + + ztest_test_suite(pwm_loopback_test, + ztest_user_unit_test(test_pulse_capture), + ztest_user_unit_test(test_pulse_capture_inverted), + ztest_user_unit_test(test_period_capture), + ztest_user_unit_test(test_period_capture_inverted), + ztest_user_unit_test(test_pulse_and_period_capture), + ztest_user_unit_test(test_capture_timeout), + ztest_unit_test(test_continuous_capture), + ztest_unit_test(test_capture_busy)); + ztest_run_test_suite(pwm_loopback_test); +} diff --git a/tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.c b/tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.c new file mode 100644 index 00000000000000..4061f48172fbd6 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.c @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2020-2021 Vestas Wind Systems A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "test_pwm_loopback.h" + +#define TEST_PWM_PERIOD_NSEC 100000000 +#define TEST_PWM_PULSE_NSEC 15000000 +#define TEST_PWM_PERIOD_USEC 100000 +#define TEST_PWM_PULSE_USEC 75000 + +enum test_pwm_unit { + TEST_PWM_UNIT_NSEC, + TEST_PWM_UNIT_USEC, +}; + +void get_test_pwms(struct test_pwm *out, struct test_pwm *in) +{ + /* PWM generator device */ + out->dev = device_get_binding(PWM_LOOPBACK_OUT_LABEL); + out->pwm = PWM_LOOPBACK_OUT_CHANNEL; + out->flags = PWM_LOOPBACK_OUT_FLAGS; + zassert_not_null(out->dev, "pwm loopback output device not found"); + + /* PWM capture device */ + in->dev = device_get_binding(PWM_LOOPBACK_IN_LABEL); + in->pwm = PWM_LOOPBACK_IN_CHANNEL; + in->flags = PWM_LOOPBACK_IN_FLAGS; + zassert_not_null(in->dev, "pwm loopback input device not found"); +} + +void test_capture(uint32_t period, uint32_t pulse, enum test_pwm_unit unit, + pwm_flags_t flags) +{ + struct test_pwm in; + struct test_pwm out; + uint64_t period_capture = 0; + uint64_t pulse_capture = 0; + int err = 0; + + get_test_pwms(&out, &in); + + switch (unit) { + case TEST_PWM_UNIT_NSEC: + TC_PRINT("Testing PWM capture @ %u/%u nsec\n", + pulse, period); + err = pwm_pin_set_nsec(out.dev, out.pwm, period, + pulse, out.flags); + break; + + case TEST_PWM_UNIT_USEC: + TC_PRINT("Testing PWM capture @ %u/%u usec\n", + pulse, period); + err = pwm_pin_set_usec(out.dev, out.pwm, period, + pulse, out.flags); + break; + + default: + TC_PRINT("Unsupported test unit"); + ztest_test_fail(); + } + + zassert_equal(err, 0, "failed to set pwm output (err %d)", err); + + switch (unit) { + case TEST_PWM_UNIT_NSEC: + err = pwm_pin_capture_nsec(in.dev, in.pwm, flags, + &period_capture, &pulse_capture, + K_NSEC(period * 10)); + break; + + case TEST_PWM_UNIT_USEC: + err = pwm_pin_capture_usec(in.dev, in.pwm, flags, + &period_capture, &pulse_capture, + K_USEC(period * 10)); + break; + + default: + TC_PRINT("Unsupported test unit"); + ztest_test_fail(); + } + + if (err == -ENOTSUP) { + TC_PRINT("capture type not supported\n"); + ztest_test_skip(); + } + + zassert_equal(err, 0, "failed to capture pwm (err %d)", err); + + if (flags & PWM_CAPTURE_TYPE_PERIOD) { + zassert_within(period_capture, period, period / 100, + "period capture off by more than 1%"); + } + + if (flags & PWM_CAPTURE_TYPE_PULSE) { + if (flags & PWM_POLARITY_INVERTED) { + zassert_within(pulse_capture, period - pulse, + (period - pulse) / 100, + "pulse capture off by more than 1%"); + } else { + zassert_within(pulse_capture, pulse, pulse / 100, + "pulse capture off by more than 1%"); + } + } +} + +void test_pulse_capture(void) +{ + test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC, + TEST_PWM_UNIT_NSEC, + PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_NORMAL); + test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC, + TEST_PWM_UNIT_USEC, + PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_NORMAL); +} + +void test_pulse_capture_inverted(void) +{ + test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC, + TEST_PWM_UNIT_NSEC, + PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_INVERTED); + test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC, + TEST_PWM_UNIT_USEC, + PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_INVERTED); +} + +void test_period_capture(void) +{ + test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC, + TEST_PWM_UNIT_NSEC, + PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_NORMAL); + test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC, + TEST_PWM_UNIT_USEC, + PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_NORMAL); +} + +void test_period_capture_inverted(void) +{ + test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC, + TEST_PWM_UNIT_NSEC, + PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_INVERTED); + test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC, + TEST_PWM_UNIT_USEC, + PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_INVERTED); +} + +void test_pulse_and_period_capture(void) +{ + test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC, + TEST_PWM_UNIT_NSEC, + PWM_CAPTURE_TYPE_BOTH | PWM_POLARITY_NORMAL); + test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC, + TEST_PWM_UNIT_USEC, + PWM_CAPTURE_TYPE_BOTH | PWM_POLARITY_NORMAL); +} + +void test_capture_timeout(void) +{ + struct test_pwm in; + struct test_pwm out; + uint32_t period; + uint32_t pulse; + int err; + + get_test_pwms(&out, &in); + + err = pwm_pin_set_cycles(out.dev, out.pwm, 100, 0, out.flags); + zassert_equal(err, 0, "failed to set pwm output (err %d)", err); + + err = pwm_pin_capture_cycles(in.dev, in.pwm, + PWM_CAPTURE_TYPE_PULSE, + &period, &pulse, K_MSEC(1000)); + if (err == -ENOTSUP) { + TC_PRINT("Pulse capture not supported, " + "trying period capture\n"); + err = pwm_pin_capture_cycles(in.dev, in.pwm, + PWM_CAPTURE_TYPE_PERIOD, + &period, &pulse, K_MSEC(1000)); + } + + zassert_equal(err, -EAGAIN, "pwm capture did not timeout (err %d)", + err); +} + +static void continuous_capture_callback(const struct device *dev, + uint32_t pwm, + uint32_t period_cycles, + uint32_t pulse_cycles, + int status, + void *user_data) +{ + struct test_pwm_callback_data *data = user_data; + + if (data->count > data->buffer_len) { + /* Safe guard in case capture is not disabled */ + return; + } + + if (status != 0) { + /* Error occurred */ + data->status = status; + k_sem_give(&data->sem); + } + + if (data->pulse_capture) { + data->buffer[data->count++] = pulse_cycles; + } else { + data->buffer[data->count++] = period_cycles; + } + + if (data->count > data->buffer_len) { + data->status = 0; + k_sem_give(&data->sem); + } +} + +void test_continuous_capture(void) +{ + static const uint32_t period_usec = 10000; + static const uint32_t pulse_usec = 7500; + struct test_pwm in; + struct test_pwm out; + uint32_t buffer[10]; + struct test_pwm_callback_data data = { + .buffer = buffer, + .buffer_len = ARRAY_SIZE(buffer), + .count = 0, + .pulse_capture = true, + }; + uint64_t usec = 0; + int err; + int i; + + get_test_pwms(&out, &in); + + memset(buffer, 0, sizeof(buffer)); + k_sem_init(&data.sem, 0, 1); + + err = pwm_pin_set_usec(out.dev, out.pwm, period_usec, pulse_usec, + out.flags); + zassert_equal(err, 0, "failed to set pwm output (err %d)", err); + + err = pwm_pin_configure_capture(in.dev, in.pwm, + in.flags | + PWM_CAPTURE_MODE_CONTINUOUS | + PWM_CAPTURE_TYPE_PULSE, + continuous_capture_callback, + &data); + if (err == -ENOTSUP) { + TC_PRINT("Pulse capture not supported, " + "trying period capture\n"); + err = pwm_pin_configure_capture(in.dev, in.pwm, + in.flags | + PWM_CAPTURE_MODE_CONTINUOUS | + PWM_CAPTURE_TYPE_PERIOD, + continuous_capture_callback, + &data); + zassert_equal(err, 0, "failed to configure pwm input (err %d)", + err); + data.pulse_capture = false; + } + + err = pwm_pin_enable_capture(in.dev, in.pwm); + zassert_equal(err, 0, "failed to enable pwm capture (err %d)", err); + + err = k_sem_take(&data.sem, K_USEC(period_usec * data.buffer_len * 10)); + zassert_equal(err, 0, "pwm capture timed out (err %d)", err); + zassert_equal(data.status, 0, "pwm capture failed (err %d)", err); + + err = pwm_pin_disable_capture(in.dev, in.pwm); + zassert_equal(err, 0, "failed to disable pwm capture (err %d)", err); + + for (i = 0; i < data.buffer_len; i++) { + err = pwm_pin_cycles_to_usec(in.dev, in.pwm, buffer[i], &usec); + zassert_equal(err, 0, "failed to calculate usec (err %d)", err); + + if (data.pulse_capture) { + zassert_within(usec, pulse_usec, pulse_usec / 100, + "pulse capture off by more than 1%"); + } else { + zassert_within(usec, period_usec, period_usec / 100, + "period capture off by more than 1%"); + } + } +} + +void test_capture_busy(void) +{ + struct test_pwm in; + struct test_pwm out; + uint32_t buffer[10]; + struct test_pwm_callback_data data = { + .buffer = buffer, + .buffer_len = ARRAY_SIZE(buffer), + .count = 0, + .pulse_capture = true, + }; + pwm_flags_t flags = PWM_CAPTURE_MODE_SINGLE | + PWM_CAPTURE_TYPE_PULSE; + int err; + + get_test_pwms(&out, &in); + + memset(buffer, 0, sizeof(buffer)); + k_sem_init(&data.sem, 0, 1); + + err = pwm_pin_set_cycles(out.dev, out.pwm, 100, 0, out.flags); + zassert_equal(err, 0, "failed to set pwm output (err %d)", err); + + err = pwm_pin_configure_capture(in.dev, in.pwm, + in.flags | flags, + continuous_capture_callback, + &data); + if (err == -ENOTSUP) { + TC_PRINT("Pulse capture not supported, " + "trying period capture\n"); + flags = PWM_CAPTURE_MODE_SINGLE | PWM_CAPTURE_TYPE_PERIOD; + err = pwm_pin_configure_capture(in.dev, in.pwm, + in.flags | flags, + continuous_capture_callback, + &data); + zassert_equal(err, 0, "failed to configure pwm input (err %d)", + err); + data.pulse_capture = false; + } + + err = pwm_pin_enable_capture(in.dev, in.pwm); + zassert_equal(err, 0, "failed to enable pwm capture (err %d)", err); + + err = pwm_pin_configure_capture(in.dev, in.pwm, + in.flags | flags, + continuous_capture_callback, + &data); + zassert_equal(err, -EBUSY, "pwm capture not busy (err %d)", err); + + err = pwm_pin_disable_capture(in.dev, in.pwm); + zassert_equal(err, 0, "failed to disable pwm capture (err %d)", err); +} diff --git a/tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.h b/tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.h new file mode 100644 index 00000000000000..62918529e77590 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/src/test_pwm_loopback.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2021 Vestas Wind Systems A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __TEST_PWM_LOOPBACK_H__ +#define __TEST_PWM_LOOPBACK_H__ + +#include +#include +#include + +#define PWM_LOOPBACK_OUT_IDX 0 +#define PWM_LOOPBACK_IN_IDX 1 + +#define PWM_LOOPBACK_NODE DT_INST(0, test_pwm_loopback) + +#define PWM_LOOPBACK_OUT_LABEL \ + DT_PWMS_LABEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_OUT_IDX) +#define PWM_LOOPBACK_OUT_CHANNEL \ + DT_PWMS_CHANNEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_OUT_IDX) +#define PWM_LOOPBACK_OUT_FLAGS \ + DT_PWMS_FLAGS_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_OUT_IDX) + +#define PWM_LOOPBACK_IN_LABEL \ + DT_PWMS_LABEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_IN_IDX) +#define PWM_LOOPBACK_IN_CHANNEL \ + DT_PWMS_CHANNEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_IN_IDX) +#define PWM_LOOPBACK_IN_FLAGS \ + DT_PWMS_FLAGS_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_IN_IDX) + +struct test_pwm { + const struct device *dev; + uint32_t pwm; + pwm_flags_t flags; +}; + +struct test_pwm_callback_data { + uint32_t *buffer; + size_t buffer_len; + size_t count; + int status; + struct k_sem sem; + bool pulse_capture; +}; + +void get_test_pwms(struct test_pwm *out, struct test_pwm *in); + +void test_pulse_capture(void); + +void test_pulse_capture_inverted(void); + +void test_period_capture(void); + +void test_period_capture_inverted(void); + +void test_pulse_and_period_capture(void); + +void test_capture_timeout(void); + +void test_continuous_capture(void); + +void test_capture_busy(void); + +#endif /* __TEST_PWM_LOOPBACK_H__ */ diff --git a/tests/drivers/pwm/pwm_loopback/testcase.yaml b/tests/drivers/pwm/pwm_loopback/testcase.yaml new file mode 100644 index 00000000000000..4936d1b187cca7 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/testcase.yaml @@ -0,0 +1,8 @@ +tests: + drivers.pwm.loopback: + tags: pwm drivers userspace + depends_on: pwm + filter: dt_compat_enabled("test,pwm_loopback") + harness: ztest + harness_config: + fixture: pwm_loopback