Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2019 Piotr Mienkowski
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include <limits.h>
#include <sys/util.h>
#include "test_gpio_api.h"

struct gpio_callback gpio_cb;
static int cb_count;

static void callback_edge(const struct device *port, struct gpio_callback *cb,
			  gpio_port_pins_t pins)
{
	zassert_equal(pins, BIT(TEST_PIN),
		     "Detected interrupt on an invalid pin");
	cb_count++;
}

static void callback_level(const struct device *port,
			   struct gpio_callback *cb,
			   gpio_port_pins_t pins)
{
	int ret;

	zassert_equal(pins, BIT(TEST_PIN),
		     "Detected interrupt on an invalid pin");

	ret = gpio_pin_interrupt_configure(port, TEST_PIN, GPIO_INT_DISABLE);
	zassert_equal(ret, 0,
		      "Failed to disable pin interrupt in the callback");

	cb_count++;
}

static void pin_set_and_verify(const struct device *port, unsigned int pin,
			       int val,
			       int idx)
{
	zassert_equal(gpio_pin_set(port, pin, val), 0,
		      "Test point %d: failed to set logical pin value", idx);
	k_busy_wait(TEST_GPIO_MAX_RISE_FALL_TIME_US);
}

void test_gpio_pin_interrupt_edge(unsigned int cfg_flags,
				  unsigned int int_flags)
{
	const struct device *port;
	int cb_count_expected;
	unsigned int cfg_out_flag;
	int ret;

	port = device_get_binding(TEST_DEV);
	zassert_not_null(port, "device " TEST_DEV " not found");

	TC_PRINT("Running test on port=%s, pin=%d\n", TEST_DEV, TEST_PIN);

	ret = gpio_pin_configure(port, TEST_PIN, GPIO_INPUT | GPIO_OUTPUT);
	if (ret == -ENOTSUP) {
		TC_PRINT("Simultaneous pin in/out mode is not supported.\n");
		ztest_test_skip();
		return;
	}
	zassert_equal(ret, 0, "Failed to configure the pin");

	if ((cfg_flags & GPIO_ACTIVE_LOW) != 0) {
		cfg_out_flag = GPIO_OUTPUT_HIGH;
	} else {
		cfg_out_flag = GPIO_OUTPUT_LOW;
	}
	ret = gpio_pin_configure(port, TEST_PIN, GPIO_INPUT | cfg_out_flag |
				 cfg_flags);
	zassert_equal(ret, 0, "Failed to configure the pin");

	cb_count = 0;
	cb_count_expected = 0;

	gpio_init_callback(&gpio_cb, callback_edge, BIT(TEST_PIN));
	ret = gpio_add_callback(port, &gpio_cb);

	ret = gpio_pin_interrupt_configure(port, TEST_PIN, int_flags);
	if (ret == -ENOTSUP) {
		TC_PRINT("Pin interrupt is not supported.\n");
		ztest_test_skip();
		return;
	}
	zassert_equal(ret, 0, "Failed to configure pin interrupt");

	for (int i = 0; i < 6; i++) {
		pin_set_and_verify(port, TEST_PIN, 1, i);
		if (int_flags & GPIO_INT_HIGH_1) {
			cb_count_expected++;
		}
		zassert_equal(cb_count, cb_count_expected,
			      "Test point %d: Pin interrupt triggered invalid "
			      "number of times on rising/to active edge", i);

		pin_set_and_verify(port, TEST_PIN, 0, i);
		if (int_flags & GPIO_INT_LOW_0) {
			cb_count_expected++;
		}
		zassert_equal(cb_count, cb_count_expected,
			      "Test point %d: Pin interrupt triggered invalid "
			      "number of times on falling/to inactive edge", i);
	}

	ret = gpio_pin_interrupt_configure(port, TEST_PIN, GPIO_INT_DISABLE);
	zassert_equal(ret, 0, "Failed to disable pin interrupt");

	for (int i = 0; i < 6; i++) {
		pin_set_and_verify(port, TEST_PIN, 1, i);
		pin_set_and_verify(port, TEST_PIN, 0, i);
		zassert_equal(cb_count, cb_count_expected,
			      "Pin interrupt triggered when disabled");
	}
}

void test_gpio_pin_interrupt_level(unsigned int cfg_flags,
				   unsigned int int_flags)
{
	const struct device *port;
	int cb_count_expected;
	unsigned int cfg_out_flag;
	int pin_out_val;
	int ret;

	port = device_get_binding(TEST_DEV);
	zassert_not_null(port, "device " TEST_DEV " not found");

	TC_PRINT("Running test on port=%s, pin=%d\n", TEST_DEV, TEST_PIN);

	ret = gpio_pin_configure(port, TEST_PIN, GPIO_INPUT | GPIO_OUTPUT);
	if (ret == -ENOTSUP) {
		TC_PRINT("Simultaneous pin in/out mode is not supported.\n");
		ztest_test_skip();
		return;
	}
	zassert_equal(ret, 0, "Failed to configure the pin");

	pin_out_val = ((int_flags & GPIO_INT_HIGH_1) != 0) ? 0 : 1;

	if ((cfg_flags & GPIO_ACTIVE_LOW) != 0) {
		cfg_out_flag = (pin_out_val != 0) ? GPIO_OUTPUT_LOW :
						    GPIO_OUTPUT_HIGH;
	} else {
		cfg_out_flag = (pin_out_val != 0) ? GPIO_OUTPUT_HIGH :
						    GPIO_OUTPUT_LOW;
	}

	ret = gpio_pin_configure(port, TEST_PIN, GPIO_INPUT | cfg_out_flag |
				 cfg_flags);
	zassert_equal(ret, 0, "Failed to configure the pin");

	cb_count = 0;
	cb_count_expected = 0;

	gpio_init_callback(&gpio_cb, callback_level, BIT(TEST_PIN));
	ret = gpio_add_callback(port, &gpio_cb);

	ret = gpio_pin_interrupt_configure(port, TEST_PIN, int_flags);
	if (ret == -ENOTSUP) {
		TC_PRINT("Pin interrupt is not supported.\n");
		ztest_test_skip();
		return;
	}
	zassert_equal(ret, 0, "Failed to configure pin interrupt");

	zassert_equal(cb_count, cb_count_expected,
		      "Pin interrupt triggered on level %d", pin_out_val);

	for (int i = 0; i < 6; i++) {
		pin_out_val = (pin_out_val != 0) ? 0 : 1;
		pin_set_and_verify(port, TEST_PIN, pin_out_val, i);
		cb_count_expected++;
		zassert_equal(cb_count, cb_count_expected,
			      "Test point %d: Pin interrupt triggered invalid "
			      "number of times on level %d", i, pin_out_val);

		pin_out_val = (pin_out_val != 0) ? 0 : 1;
		pin_set_and_verify(port, TEST_PIN, pin_out_val, i);
		zassert_equal(cb_count, cb_count_expected,
			      "Test point %d: Pin interrupt triggered invalid "
			      "number of times on level %d", i, pin_out_val);

		/* Re-enable pin level interrupt */
		ret = gpio_pin_interrupt_configure(port, TEST_PIN, int_flags);
		zassert_equal(ret, 0,
			      "Failed to re-enable pin level interrupt");
	}

	ret = gpio_pin_interrupt_configure(port, TEST_PIN, GPIO_INT_DISABLE);
	zassert_equal(ret, 0, "Failed to disable pin interrupt");

	for (int i = 0; i < 6; i++) {
		pin_set_and_verify(port, TEST_PIN, 1, i);
		pin_set_and_verify(port, TEST_PIN, 0, i);
		zassert_equal(cb_count, cb_count_expected,
			      "Pin interrupt triggered when disabled");
	}
}

/** @brief Verify GPIO_INT_EDGE_RISING flag. */
void test_gpio_int_edge_rising(void)
{
	test_gpio_pin_interrupt_edge(0, GPIO_INT_EDGE_RISING);
}

/** @brief Verify GPIO_INT_EDGE_FALLING flag. */
void test_gpio_int_edge_falling(void)
{
	test_gpio_pin_interrupt_edge(0, GPIO_INT_EDGE_FALLING);
}

/** @brief Verify GPIO_INT_EDGE_BOTH flag. */
void test_gpio_int_edge_both(void)
{
	test_gpio_pin_interrupt_edge(0, GPIO_INT_EDGE_BOTH);
}

/** @brief Verify GPIO_INT_EDGE_TO_ACTIVE flag. */
void test_gpio_int_edge_to_active(void)
{
	TC_PRINT("Step 1: Configure pin as active high\n");
	test_gpio_pin_interrupt_edge(GPIO_ACTIVE_HIGH, GPIO_INT_EDGE_TO_ACTIVE);
	TC_PRINT("Step 2: Configure pin as active low\n");
	test_gpio_pin_interrupt_edge(GPIO_ACTIVE_LOW, GPIO_INT_EDGE_TO_ACTIVE);
}

/** @brief Verify GPIO_INT_EDGE_TO_INACTIVE flag. */
void test_gpio_int_edge_to_inactive(void)
{
	TC_PRINT("Step 1: Configure pin as active high\n");
	test_gpio_pin_interrupt_edge(GPIO_ACTIVE_HIGH, GPIO_INT_EDGE_TO_INACTIVE);
	TC_PRINT("Step 2: Configure pin as active low\n");
	test_gpio_pin_interrupt_edge(GPIO_ACTIVE_LOW, GPIO_INT_EDGE_TO_INACTIVE);
}

/** @brief Verify GPIO_INT_LEVEL_HIGH flag. */
void test_gpio_int_level_high(void)
{
	test_gpio_pin_interrupt_level(0, GPIO_INT_LEVEL_HIGH);
}

/** @brief Verify GPIO_INT_LEVEL_LOW flag. */
void test_gpio_int_level_low(void)
{
	test_gpio_pin_interrupt_level(0, GPIO_INT_LEVEL_LOW);
}

/** @brief Verify GPIO_INT_LEVEL_ACTIVE flag. */
void test_gpio_int_level_active(void)
{
	TC_PRINT("Step 1: Configure pin as active high\n");
	test_gpio_pin_interrupt_level(GPIO_ACTIVE_HIGH, GPIO_INT_LEVEL_ACTIVE);
	TC_PRINT("Step 2: Configure pin as active low\n");
	test_gpio_pin_interrupt_level(GPIO_ACTIVE_LOW, GPIO_INT_LEVEL_ACTIVE);
}

/** @brief Verify GPIO_INT_LEVEL_INACTIVE flag. */
void test_gpio_int_level_inactive(void)
{
	TC_PRINT("Step 1: Configure pin as active high\n");
	test_gpio_pin_interrupt_level(GPIO_ACTIVE_HIGH, GPIO_INT_LEVEL_INACTIVE);
	TC_PRINT("Step 2: Configure pin as active low\n");
	test_gpio_pin_interrupt_level(GPIO_ACTIVE_LOW, GPIO_INT_LEVEL_INACTIVE);
}