[PATCH 3/3] tests/qtest: Allwinner GPIO tests
Strahinja Jankovic <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
Add Allwinner GPIO set of qtests to validate functionality. Tests cover input and output port setting, as well as interrupt functionality. Signed-off-by: Strahinja Jankovic <[email protected]> --- MAINTAINERS | 1 + tests/qtest/allwinner-gpio-test.c | 474 ++++++++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 3 +- 3 files changed, 477 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6cefdde70b..2691796557 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -746,6 +746,7 @@ F: hw/misc/axp2xx.c F: hw/gpio/allwinner-gpio.c F: include/hw/gpio/allwinner-gpio* F: tests/functional/arm/test_cubieboard.py +F: tests/qtest/allwinner-gpio-test.c Allwinner-h3 M: Niek Linnenbank <[email protected]> diff --git a/tests/qtest/allwinner-gpio-test.c b/tests/qtest/allwinner-gpio-test.c new file mode 100644 index 0000000000..e1ad2289e9 --- /dev/null +++ b/tests/qtest/allwinner-gpio-test.c @@ -0,0 +1,474 @@ +/* + * Allwinner GPIO Tests + * + * Copyright (C) 2026 Strahinja Jankovic <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "libqtest-single.h" + +#include "include/hw/gpio/allwinner-gpio-regs.h" + +#define AW_A10_GPIO_BASE 0x01c20800 +#define AW_A10_IRQ_LINE 28 + +/* Port n configure register 0 */ +#define GPIO_Pn_CFG0_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG0(n)) +/* Port n configure register 1 */ +#define GPIO_Pn_CFG1_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG1(n)) +/* Port n configure register 2 */ +#define GPIO_Pn_CFG2_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG2(n)) +/* Port n configure register 3 */ +#define GPIO_Pn_CFG3_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG3(n)) +/* Port n data register */ +#define GPIO_Pn_DAT_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_DAT(n)) +/* Port n Multi-driving register 0 */ +#define GPIO_Pn_DRV0_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_DRV0(n)) +/* Port n Multi-driving register 1 */ +#define GPIO_Pn_DRV1_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_DRV1(n)) +/* Port n Pull register 0 */ +#define GPIO_Pn_PUL0_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_PUL0(n)) +/* Port n Pull register 1 */ +#define GPIO_Pn_PUL1_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_PUL1(n)) +/* PIO interrupt configure register 0 */ +#define GPIO_INT_CFG0_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG0) +/* PIO interrupt configure register 1 */ +#define GPIO_INT_CFG1_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG1) +/* PIO interrupt configure register 2 */ +#define GPIO_INT_CFG2_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG2) +/* PIO interrupt configure register 3 */ +#define GPIO_INT_CFG3_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG3) +/* PIO interrupt control register */ +#define GPIO_INT_CTL_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CTL) +/* PIO interrupt status register */ +#define GPIO_INT_STA_ADDR (AW_A10_GPIO_BASE + GPIO_INT_STA) +/* PIO interrupt debounce register */ +#define GPIO_INT_DEB_ADDR (AW_A10_GPIO_BASE + GPIO_INT_DEB) +/* SDRAM Pad Multi-driving register */ +#define SDR_PAD_DRV_ADDR (AW_A10_GPIO_BASE + SDR_PAD_DRV) +/* SDRAM Pad Pull register */ +#define SDR_PAD_PUL_ADDR (AW_A10_GPIO_BASE + SDR_PAD_PUL) + +static inline uint32_t read_cfg_addr(QTestState *s, int port, int pin) +{ + uint32_t addr = 0; + + if (pin >= 24) { + addr = GPIO_Pn_CFG3_ADDR(port); + } else if (pin >= 16) { + addr = GPIO_Pn_CFG2_ADDR(port); + } else if (pin >= 8) { + addr = GPIO_Pn_CFG1_ADDR(port); + } else { + addr = GPIO_Pn_CFG0_ADDR(port); + } + return addr; +} + +static inline uint32_t read_int_cfg_addr(QTestState *s, int irq) +{ + uint32_t addr = 0; + + if (irq >= 24) { + addr = GPIO_INT_CFG3_ADDR; + } else if (irq >= 16) { + addr = GPIO_INT_CFG2_ADDR; + } else if (irq >= 8) { + addr = GPIO_INT_CFG1_ADDR; + } else { + addr = GPIO_INT_CFG0_ADDR; + } + return addr; +} + +static void update_cfg_reg(QTestState *s, int port, int pin, AWGPIOCfg val) +{ + uint32_t addr = read_cfg_addr(s, port, pin); + uint32_t cfg = qtest_readl(s, addr); + uint32_t offset = (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE; + + cfg = deposit32(cfg, offset, CFG_PIN_STRIDE - 1, val); + qtest_writel(s, addr, cfg); +} + +static void assert_cfg_regval(QTestState *s, int port, int pin, + AWGPIOCfg pin_choice) +{ + uint32_t addr = read_cfg_addr(s, port, pin); + uint32_t cfg = qtest_readl(s, addr); + uint32_t offset = (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE; + + g_assert_cmphex( + extract32(cfg, offset, CFG_PIN_STRIDE - 1), ==, pin_choice); +} + +static void update_data_reg(QTestState *s, int port, int pin, int val) +{ + uint32_t addr = GPIO_Pn_DAT_ADDR(port); + uint32_t data = qtest_readl(s, addr); + + data = deposit32(data, pin, 1, val); + qtest_writel(s, addr, data); +} + +static void assert_data_reg(QTestState *s, int port, int pin, int val) +{ + uint32_t addr = GPIO_Pn_DAT_ADDR(port); + uint32_t data = qtest_readl(s, addr); + + g_assert_cmphex(extract32(data, pin, 1), ==, val); +} + +static void update_int_cfg_reg(QTestState *s, int irq, AWGPIOIrqCfg val) +{ + uint32_t addr = read_int_cfg_addr(s, irq); + uint32_t cfg = qtest_readl(s, addr); + uint32_t offset = (irq % INT_CFG_IRQ_PER_REG) * INT_CFG_IRQ_STRIDE; + + cfg = deposit32(cfg, offset, INT_CFG_IRQ_STRIDE - 1, val); + qtest_writel(s, addr, cfg); +} + +static void assert_int_cfg_regval(QTestState *s, int irq, AWGPIOIrqCfg irq_cfg) +{ + uint32_t addr = read_int_cfg_addr(s, irq); + uint32_t cfg = qtest_readl(s, addr); + uint32_t offset = (irq % INT_CFG_IRQ_PER_REG) * INT_CFG_IRQ_STRIDE; + + g_assert_cmphex( + extract32(cfg, offset, INT_CFG_IRQ_STRIDE - 1), ==, irq_cfg); +} + +static void test_reset_values(void) +{ + QTestState *s = qtest_init("-machine cubieboard"); + + for (int port = 0; port < AW_GPIO_PORTS_NUM; port++) { + g_assert_cmphex( + qtest_readl(s, GPIO_Pn_DRV0_ADDR(port)), ==, + aw_gpio_port_reset[port].drv[0]); + g_assert_cmphex( + qtest_readl(s, GPIO_Pn_DRV1_ADDR(port)), ==, + aw_gpio_port_reset[port].drv[1]); + g_assert_cmphex( + qtest_readl(s, GPIO_Pn_PUL0_ADDR(port)), ==, + aw_gpio_port_reset[port].pul[0]); + g_assert_cmphex( + qtest_readl(s, GPIO_Pn_PUL1_ADDR(port)), ==, + aw_gpio_port_reset[port].pul[1]); + } + + qtest_quit(s); +} + +typedef struct { + int port; + int pin; +} PortPinTestParam; + +const PortPinTestParam in_out_test_parameters[] = { + { GPIO_PA, 0 }, + { GPIO_PA, 17 }, + { GPIO_PB, 19 }, + { GPIO_PC, 23 }, + { GPIO_PD, 26 }, + { GPIO_PE, 11 }, + { GPIO_PF, 5 }, + { GPIO_PG, 10 }, + { GPIO_PH, 16 }, + { GPIO_PI, 21 }, +}; + +const PortPinTestParam irq_test_parameters[] = { + { GPIO_PH, 1 }, + { GPIO_PH, 10 }, + { GPIO_PH, 21 }, + { GPIO_PI, 10 }, + { GPIO_PI, 19 }, +}; + +/* Set pin to output, check that it propagates */ +static void test_set_output_pins(const void *data) +{ + PortPinTestParam *param = (PortPinTestParam *)data; + g_autofree char *out_name = portname_out(param->port); + + QTestState *s = qtest_init("-machine cubieboard"); + qtest_irq_intercept_out_named(s, "/machine/soc/gpio", out_name); + + /* Configure pin as output */ + update_cfg_reg(s, param->port, param->pin, AW_GPIO_CFG_OUT); + /* Set value in register */ + update_data_reg(s, param->port, param->pin, AW_GPIO_LEVEL_HIGH); + + /* Check that the output pin is high */ + g_assert_true(qtest_get_irq(s, param->pin)); + + /* Cleanup */ + /* Set value in register */ + update_data_reg(s, param->port, param->pin, AW_GPIO_LEVEL_LOW); + /* Configure pin as input */ + update_cfg_reg(s, param->port, param->pin, AW_GPIO_CFG_IN); + + qtest_quit(s); +} + +/* + * Set pin to input, check that the value is written but it does not propagate + */ +static void test_set_input_pins(const void *data) +{ + PortPinTestParam *param = (PortPinTestParam *)data; + g_autofree char *out_name = portname_out(param->port); + + QTestState *s = qtest_init("-machine cubieboard"); + + qtest_irq_intercept_out_named(s, "/machine/soc/gpio", out_name); + + /* Configure pin as input */ + update_cfg_reg(s, param->port, param->pin, AW_GPIO_CFG_IN); + /* Set value in register */ + update_data_reg(s, param->port, param->pin, AW_GPIO_LEVEL_HIGH); + /* Check that the output pin is still low */ + g_assert_false(qtest_get_irq(s, param->pin)); + + qtest_quit(s); +} + +/* Test rising edge interrupt */ +static void test_irq_pin_rising(const void *data) +{ + PortPinTestParam *param = (PortPinTestParam *)data; + QTestState *s = qtest_init("-machine cubieboard"); + const int gpio_port = param->port; + const int port_pin = param->pin; + g_autofree char *in_name = portname_in(gpio_port); + int irq_pin = irq_nr(gpio_port, port_pin); + unsigned int irq_mask = 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin low */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_LOW); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_RISING_EDGE); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_RISING_EDGE); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), ==, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Clear interrupt */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, ==, 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test falling edge interrupt 2 */ +static void test_irq_pin_falling(const void *data) +{ + PortPinTestParam *param = (PortPinTestParam *)data; + QTestState *s = qtest_init("-machine cubieboard"); + const int gpio_port = param->port; + const int port_pin = param->pin; + g_autofree char *in_name = portname_in(gpio_port); + int irq_pin = irq_nr(gpio_port, port_pin); + unsigned int irq_mask = 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin high */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_HIGH); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_FALLING_EDGE); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_FALLING_EDGE); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_LOW); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_LOW); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), ==, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Clear interrupt */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, ==, 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test high level interrupt */ +static void test_irq_pin_high_level(const void *data) +{ + PortPinTestParam *param = (PortPinTestParam *)data; + QTestState *s = qtest_init("-machine cubieboard"); + const int gpio_port = param->port; + const int port_pin = param->pin; + g_autofree char *in_name = portname_in(gpio_port); + int irq_pin = irq_nr(gpio_port, port_pin); + unsigned int irq_mask = 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin low */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_LOW); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_HIGH_LEVEL); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_HIGH_LEVEL); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), ==, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Try clearing interrupt, it should still be asserted */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), ==, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Set line low and clear interrupt, it should be cleared now */ + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_LOW); + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, ==, 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test low level interrupt */ +static void test_irq_pin_low_level(const void *data) +{ + PortPinTestParam *param = (PortPinTestParam *)data; + QTestState *s = qtest_init("-machine cubieboard"); + const int gpio_port = param->port; + const int port_pin = param->pin; + g_autofree char *in_name = portname_in(gpio_port); + int irq_pin = irq_nr(gpio_port, port_pin); + unsigned int irq_mask = 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin high */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_HIGH); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_LOW_LEVEL); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_LOW_LEVEL); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_LOW); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_LOW); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), ==, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Try clearing interrupt, it should still be asserted */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), ==, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Set line high and clear interrupt, it should be cleared now */ + qtest_set_irq_in( + s, "/machine/soc/gpio", in_name, port_pin, AW_GPIO_LEVEL_HIGH); + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, ==, 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + + +int main(int argc, char **argv) +{ + int r; + + g_test_init(&argc, &argv, NULL); + + for (int i = 0; i < G_N_ELEMENTS(in_out_test_parameters); i++) { + g_autofree char *out_name = g_strdup_printf( + "/allwinner-cubieboard/gpio/set_output_pins/%s%d", + portname(in_out_test_parameters[i].port), in_out_test_parameters[i].pin); + g_autofree char *in_name = g_strdup_printf( + "/allwinner-cubieboard/gpio/set_input_pins/%s%d", + portname(in_out_test_parameters[i].port), in_out_test_parameters[i].pin); + qtest_add_data_func(out_name, &in_out_test_parameters[i], + test_set_output_pins); + qtest_add_data_func(in_name, &in_out_test_parameters[i], + test_set_input_pins); + } + qtest_add_func("/allwinner-cubieboard/gpio/reset_values", + test_reset_values); + for (int i = 0; i < G_N_ELEMENTS(irq_test_parameters); i++) { + g_autofree char *rising_edge_name = g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_rising/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + rising_edge_name, &irq_test_parameters[i], test_irq_pin_rising); + g_autofree char *falling_edge_name = g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_falling/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + falling_edge_name, &irq_test_parameters[i], test_irq_pin_falling); + g_autofree char *high_level_name = g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_high_level/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + high_level_name, &irq_test_parameters[i], test_irq_pin_high_level); + g_autofree char *low_level_name = g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_low_level/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + low_level_name, &irq_test_parameters[i], test_irq_pin_low_level); + + } + r = g_test_run(); + + return r; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index f7c7d06620..853f7a9c4c 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -257,7 +257,8 @@ qtests_arm = \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \ ['arm-cpu-features', - 'boot-serial-test'] + 'boot-serial-test'] + \ + (config_all_devices.has_key('CONFIG_ALLWINNER_A10')? ['allwinner-gpio-test'] : []) # TODO: once aarch64 TCG is fixed on ARM 32 bit host, make bios-tables-test unconditional qtests_aarch64 = \ -- 2.55.0