diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/qtest/meson.build | 4 | ||||
-rw-r--r-- | tests/qtest/npcm7xx_adc-test.c | 377 | ||||
-rw-r--r-- | tests/qtest/npcm7xx_pwm-test.c | 490 |
3 files changed, 870 insertions, 1 deletions
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 6ec09821d7..16d04625b8 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -134,7 +134,9 @@ qtests_sparc64 = \ ['prom-env-test', 'boot-serial-test'] qtests_npcm7xx = \ - ['npcm7xx_gpio-test', + ['npcm7xx_adc-test', + 'npcm7xx_gpio-test', + 'npcm7xx_pwm-test', 'npcm7xx_rng-test', 'npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test'] diff --git a/tests/qtest/npcm7xx_adc-test.c b/tests/qtest/npcm7xx_adc-test.c new file mode 100644 index 0000000000..f029706945 --- /dev/null +++ b/tests/qtest/npcm7xx_adc-test.c @@ -0,0 +1,377 @@ +/* + * QTests for Nuvoton NPCM7xx ADCModules. + * + * Copyright 2020 Google LLC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/timer.h" +#include "libqos/libqtest.h" +#include "qapi/qmp/qdict.h" + +#define REF_HZ (25000000) + +#define CON_OFFSET 0x0 +#define DATA_OFFSET 0x4 + +#define NUM_INPUTS 8 +#define DEFAULT_IREF 2000000 +#define CONV_CYCLES 20 +#define RESET_CYCLES 10 +#define R0_INPUT 500000 +#define R1_INPUT 1500000 +#define MAX_RESULT 1023 + +#define DEFAULT_CLKDIV 5 + +#define FUSE_ARRAY_BA 0xf018a000 +#define FCTL_OFFSET 0x14 +#define FST_OFFSET 0x0 +#define FADDR_OFFSET 0x4 +#define FDATA_OFFSET 0x8 +#define ADC_CALIB_ADDR 24 +#define FUSE_READ 0x2 + +/* Register field definitions. */ +#define CON_MUX(rv) ((rv) << 24) +#define CON_INT_EN BIT(21) +#define CON_REFSEL BIT(19) +#define CON_INT BIT(18) +#define CON_EN BIT(17) +#define CON_RST BIT(16) +#define CON_CONV BIT(14) +#define CON_DIV(rv) extract32(rv, 1, 8) + +#define FST_RDST BIT(1) +#define FDATA_MASK 0xff + +#define MAX_ERROR 10000 +#define MIN_CALIB_INPUT 100000 +#define MAX_CALIB_INPUT 1800000 + +static const uint32_t input_list[] = { + 100000, + 500000, + 1000000, + 1500000, + 1800000, + 2000000, +}; + +static const uint32_t vref_list[] = { + 2000000, + 2200000, + 2500000, +}; + +static const uint32_t iref_list[] = { + 1800000, + 1900000, + 2000000, + 2100000, + 2200000, +}; + +static const uint32_t div_list[] = {0, 1, 3, 7, 15}; + +typedef struct ADC { + int irq; + uint64_t base_addr; +} ADC; + +ADC adc = { + .irq = 0, + .base_addr = 0xf000c000 +}; + +static uint32_t adc_read_con(QTestState *qts, const ADC *adc) +{ + return qtest_readl(qts, adc->base_addr + CON_OFFSET); +} + +static void adc_write_con(QTestState *qts, const ADC *adc, uint32_t value) +{ + qtest_writel(qts, adc->base_addr + CON_OFFSET, value); +} + +static uint32_t adc_read_data(QTestState *qts, const ADC *adc) +{ + return qtest_readl(qts, adc->base_addr + DATA_OFFSET); +} + +static uint32_t adc_calibrate(uint32_t measured, uint32_t *rv) +{ + return R0_INPUT + (R1_INPUT - R0_INPUT) * (int32_t)(measured - rv[0]) + / (int32_t)(rv[1] - rv[0]); +} + +static void adc_qom_set(QTestState *qts, const ADC *adc, + const char *name, uint32_t value) +{ + QDict *response; + const char *path = "/machine/soc/adc"; + + g_test_message("Setting properties %s of %s with value %u", + name, path, value); + response = qtest_qmp(qts, "{ 'execute': 'qom-set'," + " 'arguments': { 'path': %s, 'property': %s, 'value': %u}}", + path, name, value); + /* The qom set message returns successfully. */ + g_assert_true(qdict_haskey(response, "return")); +} + +static void adc_write_input(QTestState *qts, const ADC *adc, + uint32_t index, uint32_t value) +{ + char name[100]; + + sprintf(name, "adci[%u]", index); + adc_qom_set(qts, adc, name, value); +} + +static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value) +{ + adc_qom_set(qts, adc, "vref", value); +} + +static uint32_t adc_calculate_output(uint32_t input, uint32_t ref) +{ + uint32_t output; + + g_assert_cmpuint(input, <=, ref); + output = (input * (MAX_RESULT + 1)) / ref; + if (output > MAX_RESULT) { + output = MAX_RESULT; + } + + return output; +} + +static uint32_t adc_prescaler(QTestState *qts, const ADC *adc) +{ + uint32_t div = extract32(adc_read_con(qts, adc), 1, 8); + + return 2 * (div + 1); +} + +static int64_t adc_calculate_steps(uint32_t cycles, uint32_t prescale, + uint32_t clkdiv) +{ + return (NANOSECONDS_PER_SECOND / (REF_HZ >> clkdiv)) * cycles * prescale; +} + +static void adc_wait_conv_finished(QTestState *qts, const ADC *adc, + uint32_t clkdiv) +{ + uint32_t prescaler = adc_prescaler(qts, adc); + + /* + * ADC should takes roughly 20 cycles to convert one sample. So we assert it + * should take 10~30 cycles here. + */ + qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES / 2, prescaler, + clkdiv)); + /* ADC is still converting. */ + g_assert_true(adc_read_con(qts, adc) & CON_CONV); + qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES, prescaler, clkdiv)); + /* ADC has finished conversion. */ + g_assert_false(adc_read_con(qts, adc) & CON_CONV); +} + +/* Check ADC can be reset to default value. */ +static void test_init(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + + QTestState *qts = qtest_init("-machine quanta-gsj"); + adc_write_con(qts, adc, CON_REFSEL | CON_INT); + g_assert_cmphex(adc_read_con(qts, adc), ==, CON_REFSEL); + qtest_quit(qts); +} + +/* Check ADC can convert from an internal reference. */ +static void test_convert_internal(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + uint32_t index, input, output, expected_output; + QTestState *qts = qtest_init("-machine quanta-gsj"); + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + + for (index = 0; index < NUM_INPUTS; ++index) { + for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) { + input = input_list[i]; + expected_output = adc_calculate_output(input, DEFAULT_IREF); + + adc_write_input(qts, adc, index, input); + adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT | + CON_EN | CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | + CON_REFSEL | CON_EN); + g_assert_false(qtest_get_irq(qts, adc->irq)); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + } + } + + qtest_quit(qts); +} + +/* Check ADC can convert from an external reference. */ +static void test_convert_external(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + uint32_t index, input, vref, output, expected_output; + QTestState *qts = qtest_init("-machine quanta-gsj"); + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + + for (index = 0; index < NUM_INPUTS; ++index) { + for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) { + for (size_t j = 0; j < ARRAY_SIZE(vref_list); ++j) { + input = input_list[i]; + vref = vref_list[j]; + expected_output = adc_calculate_output(input, vref); + + adc_write_input(qts, adc, index, input); + adc_write_vref(qts, adc, vref); + adc_write_con(qts, adc, CON_MUX(index) | CON_INT | CON_EN | + CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, + CON_MUX(index) | CON_EN); + g_assert_false(qtest_get_irq(qts, adc->irq)); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + } + } + } + + qtest_quit(qts); +} + +/* Check ADC interrupt files if and only if CON_INT_EN is set. */ +static void test_interrupt(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + uint32_t index, input, output, expected_output; + QTestState *qts = qtest_init("-machine quanta-gsj"); + + index = 1; + input = input_list[1]; + expected_output = adc_calculate_output(input, DEFAULT_IREF); + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + adc_write_input(qts, adc, index, input); + g_assert_false(qtest_get_irq(qts, adc->irq)); + adc_write_con(qts, adc, CON_MUX(index) | CON_INT_EN | CON_REFSEL | CON_INT + | CON_EN | CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | CON_INT_EN + | CON_REFSEL | CON_INT | CON_EN); + g_assert_true(qtest_get_irq(qts, adc->irq)); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + + qtest_quit(qts); +} + +/* Check ADC is reset after setting ADC_RST for 10 ADC cycles. */ +static void test_reset(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + QTestState *qts = qtest_init("-machine quanta-gsj"); + + for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) { + uint32_t div = div_list[i]; + + adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div)); + qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES, + adc_prescaler(qts, adc), DEFAULT_CLKDIV)); + g_assert_false(adc_read_con(qts, adc) & CON_EN); + } + qtest_quit(qts); +} + +/* Check ADC Calibration works as desired. */ +static void test_calibrate(gconstpointer adc_p) +{ + int i, j; + const ADC *adc = adc_p; + + for (j = 0; j < ARRAY_SIZE(iref_list); ++j) { + uint32_t iref = iref_list[j]; + uint32_t expected_rv[] = { + adc_calculate_output(R0_INPUT, iref), + adc_calculate_output(R1_INPUT, iref), + }; + char buf[100]; + QTestState *qts; + + sprintf(buf, "-machine quanta-gsj -global npcm7xx-adc.iref=%u", iref); + qts = qtest_init(buf); + + /* Check the converted value is correct using the calibration value. */ + for (i = 0; i < ARRAY_SIZE(input_list); ++i) { + uint32_t input; + uint32_t output; + uint32_t expected_output; + uint32_t calibrated_voltage; + uint32_t index = 0; + + input = input_list[i]; + /* Calibration only works for input range 0.1V ~ 1.8V. */ + if (input < MIN_CALIB_INPUT || input > MAX_CALIB_INPUT) { + continue; + } + expected_output = adc_calculate_output(input, iref); + + adc_write_input(qts, adc, index, input); + adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT | + CON_EN | CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, + CON_REFSEL | CON_MUX(index) | CON_EN); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + + calibrated_voltage = adc_calibrate(output, expected_rv); + g_assert_cmpuint(calibrated_voltage, >, input - MAX_ERROR); + g_assert_cmpuint(calibrated_voltage, <, input + MAX_ERROR); + } + + qtest_quit(qts); + } +} + +static void adc_add_test(const char *name, const ADC* wd, + GTestDataFunc fn) +{ + g_autofree char *full_name = g_strdup_printf("npcm7xx_adc/%s", name); + qtest_add_data_func(full_name, wd, fn); +} +#define add_test(name, td) adc_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + add_test(init, &adc); + add_test(convert_internal, &adc); + add_test(convert_external, &adc); + add_test(interrupt, &adc); + add_test(reset, &adc); + add_test(calibrate, &adc); + + return g_test_run(); +} diff --git a/tests/qtest/npcm7xx_pwm-test.c b/tests/qtest/npcm7xx_pwm-test.c new file mode 100644 index 0000000000..33fbdf5f54 --- /dev/null +++ b/tests/qtest/npcm7xx_pwm-test.c @@ -0,0 +1,490 @@ +/* + * QTests for Nuvoton NPCM7xx PWM Modules. + * + * Copyright 2020 Google LLC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "libqos/libqtest.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qnum.h" + +#define REF_HZ 25000000 + +/* Register field definitions. */ +#define CH_EN BIT(0) +#define CH_INV BIT(2) +#define CH_MOD BIT(3) + +/* Registers shared between all PWMs in a module */ +#define PPR 0x00 +#define CSR 0x04 +#define PCR 0x08 +#define PIER 0x3c +#define PIIR 0x40 + +/* CLK module related */ +#define CLK_BA 0xf0801000 +#define CLKSEL 0x04 +#define CLKDIV1 0x08 +#define CLKDIV2 0x2c +#define PLLCON0 0x0c +#define PLLCON1 0x10 +#define PLL_INDV(rv) extract32((rv), 0, 6) +#define PLL_FBDV(rv) extract32((rv), 16, 12) +#define PLL_OTDV1(rv) extract32((rv), 8, 3) +#define PLL_OTDV2(rv) extract32((rv), 13, 3) +#define APB3CKDIV(rv) extract32((rv), 28, 2) +#define CLK2CKDIV(rv) extract32((rv), 0, 1) +#define CLK4CKDIV(rv) extract32((rv), 26, 2) +#define CPUCKSEL(rv) extract32((rv), 0, 2) + +#define MAX_DUTY 1000000 + +typedef struct PWMModule { + int irq; + uint64_t base_addr; +} PWMModule; + +typedef struct PWM { + uint32_t cnr_offset; + uint32_t cmr_offset; + uint32_t pdr_offset; + uint32_t pwdr_offset; +} PWM; + +typedef struct TestData { + const PWMModule *module; + const PWM *pwm; +} TestData; + +static const PWMModule pwm_module_list[] = { + { + .irq = 93, + .base_addr = 0xf0103000 + }, + { + .irq = 94, + .base_addr = 0xf0104000 + } +}; + +static const PWM pwm_list[] = { + { + .cnr_offset = 0x0c, + .cmr_offset = 0x10, + .pdr_offset = 0x14, + .pwdr_offset = 0x44, + }, + { + .cnr_offset = 0x18, + .cmr_offset = 0x1c, + .pdr_offset = 0x20, + .pwdr_offset = 0x48, + }, + { + .cnr_offset = 0x24, + .cmr_offset = 0x28, + .pdr_offset = 0x2c, + .pwdr_offset = 0x4c, + }, + { + .cnr_offset = 0x30, + .cmr_offset = 0x34, + .pdr_offset = 0x38, + .pwdr_offset = 0x50, + }, +}; + +static const int ppr_base[] = { 0, 0, 8, 8 }; +static const int csr_base[] = { 0, 4, 8, 12 }; +static const int pcr_base[] = { 0, 8, 12, 16 }; + +static const uint32_t ppr_list[] = { + 0, + 1, + 10, + 100, + 255, /* Max possible value. */ +}; + +static const uint32_t csr_list[] = { + 0, + 1, + 2, + 3, + 4, /* Max possible value. */ +}; + +static const uint32_t cnr_list[] = { + 0, + 1, + 50, + 100, + 150, + 200, + 1000, + 10000, + 65535, /* Max possible value. */ +}; + +static const uint32_t cmr_list[] = { + 0, + 1, + 10, + 50, + 100, + 150, + 200, + 1000, + 10000, + 65535, /* Max possible value. */ +}; + +/* Returns the index of the PWM module. */ +static int pwm_module_index(const PWMModule *module) +{ + ptrdiff_t diff = module - pwm_module_list; + + g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_module_list)); + + return diff; +} + +/* Returns the index of the PWM entry. */ +static int pwm_index(const PWM *pwm) +{ + ptrdiff_t diff = pwm - pwm_list; + + g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_list)); + + return diff; +} + +static uint64_t pwm_qom_get(QTestState *qts, const char *path, const char *name) +{ + QDict *response; + + g_test_message("Getting properties %s from %s", name, path); + response = qtest_qmp(qts, "{ 'execute': 'qom-get'," + " 'arguments': { 'path': %s, 'property': %s}}", + path, name); + /* The qom set message returns successfully. */ + g_assert_true(qdict_haskey(response, "return")); + return qnum_get_uint(qobject_to(QNum, qdict_get(response, "return"))); +} + +static uint64_t pwm_get_freq(QTestState *qts, int module_index, int pwm_index) +{ + char path[100]; + char name[100]; + + sprintf(path, "/machine/soc/pwm[%d]", module_index); + sprintf(name, "freq[%d]", pwm_index); + + return pwm_qom_get(qts, path, name); +} + +static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index) +{ + char path[100]; + char name[100]; + + sprintf(path, "/machine/soc/pwm[%d]", module_index); + sprintf(name, "duty[%d]", pwm_index); + + return pwm_qom_get(qts, path, name); +} + +static uint32_t get_pll(uint32_t con) +{ + return REF_HZ * PLL_FBDV(con) / (PLL_INDV(con) * PLL_OTDV1(con) + * PLL_OTDV2(con)); +} + +static uint64_t read_pclk(QTestState *qts) +{ + uint64_t freq = REF_HZ; + uint32_t clksel = qtest_readl(qts, CLK_BA + CLKSEL); + uint32_t pllcon; + uint32_t clkdiv1 = qtest_readl(qts, CLK_BA + CLKDIV1); + uint32_t clkdiv2 = qtest_readl(qts, CLK_BA + CLKDIV2); + + switch (CPUCKSEL(clksel)) { + case 0: + pllcon = qtest_readl(qts, CLK_BA + PLLCON0); + freq = get_pll(pllcon); + break; + case 1: + pllcon = qtest_readl(qts, CLK_BA + PLLCON1); + freq = get_pll(pllcon); + break; + case 2: + break; + case 3: + break; + default: + g_assert_not_reached(); + } + + freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + APB3CKDIV(clkdiv2)); + + return freq; +} + +static uint32_t pwm_selector(uint32_t csr) +{ + switch (csr) { + case 0: + return 2; + case 1: + return 4; + case 2: + return 8; + case 3: + return 16; + case 4: + return 1; + default: + g_assert_not_reached(); + } +} + +static uint64_t pwm_compute_freq(QTestState *qts, uint32_t ppr, uint32_t csr, + uint32_t cnr) +{ + return read_pclk(qts) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1)); +} + +static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted) +{ + uint64_t duty; + + if (cnr == 0) { + /* PWM is stopped. */ + duty = 0; + } else if (cmr >= cnr) { + duty = MAX_DUTY; + } else { + duty = MAX_DUTY * (cmr + 1) / (cnr + 1); + } + + if (inverted) { + duty = MAX_DUTY - duty; + } + + return duty; +} + +static uint32_t pwm_read(QTestState *qts, const TestData *td, unsigned offset) +{ + return qtest_readl(qts, td->module->base_addr + offset); +} + +static void pwm_write(QTestState *qts, const TestData *td, unsigned offset, + uint32_t value) +{ + qtest_writel(qts, td->module->base_addr + offset, value); +} + +static uint32_t pwm_read_ppr(QTestState *qts, const TestData *td) +{ + return extract32(pwm_read(qts, td, PPR), ppr_base[pwm_index(td->pwm)], 8); +} + +static void pwm_write_ppr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, PPR, value << ppr_base[pwm_index(td->pwm)]); +} + +static uint32_t pwm_read_csr(QTestState *qts, const TestData *td) +{ + return extract32(pwm_read(qts, td, CSR), csr_base[pwm_index(td->pwm)], 3); +} + +static void pwm_write_csr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, CSR, value << csr_base[pwm_index(td->pwm)]); +} + +static uint32_t pwm_read_pcr(QTestState *qts, const TestData *td) +{ + return extract32(pwm_read(qts, td, PCR), pcr_base[pwm_index(td->pwm)], 4); +} + +static void pwm_write_pcr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, PCR, value << pcr_base[pwm_index(td->pwm)]); +} + +static uint32_t pwm_read_cnr(QTestState *qts, const TestData *td) +{ + return pwm_read(qts, td, td->pwm->cnr_offset); +} + +static void pwm_write_cnr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, td->pwm->cnr_offset, value); +} + +static uint32_t pwm_read_cmr(QTestState *qts, const TestData *td) +{ + return pwm_read(qts, td, td->pwm->cmr_offset); +} + +static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, td->pwm->cmr_offset, value); +} + +/* Check pwm registers can be reset to default value */ +static void test_init(gconstpointer test_data) +{ + const TestData *td = test_data; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int module = pwm_module_index(td->module); + int pwm = pwm_index(td->pwm); + + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0); + + qtest_quit(qts); +} + +/* One-shot mode should not change frequency and duty cycle. */ +static void test_oneshot(gconstpointer test_data) +{ + const TestData *td = test_data; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int module = pwm_module_index(td->module); + int pwm = pwm_index(td->pwm); + uint32_t ppr, csr, pcr; + int i, j; + + pcr = CH_EN; + for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { + ppr = ppr_list[i]; + pwm_write_ppr(qts, td, ppr); + + for (j = 0; j < ARRAY_SIZE(csr_list); ++j) { + csr = csr_list[j]; + pwm_write_csr(qts, td, csr); + pwm_write_pcr(qts, td, pcr); + + g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr); + g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr); + g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr); + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0); + } + } + + qtest_quit(qts); +} + +/* In toggle mode, the PWM generates correct outputs. */ +static void test_toggle(gconstpointer test_data) +{ + const TestData *td = test_data; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int module = pwm_module_index(td->module); + int pwm = pwm_index(td->pwm); + uint32_t ppr, csr, pcr, cnr, cmr; + int i, j, k, l; + uint64_t expected_freq, expected_duty; + + pcr = CH_EN | CH_MOD; + for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { + ppr = ppr_list[i]; + pwm_write_ppr(qts, td, ppr); + + for (j = 0; j < ARRAY_SIZE(csr_list); ++j) { + csr = csr_list[j]; + pwm_write_csr(qts, td, csr); + + for (k = 0; k < ARRAY_SIZE(cnr_list); ++k) { + cnr = cnr_list[k]; + pwm_write_cnr(qts, td, cnr); + + for (l = 0; l < ARRAY_SIZE(cmr_list); ++l) { + cmr = cmr_list[l]; + pwm_write_cmr(qts, td, cmr); + expected_freq = pwm_compute_freq(qts, ppr, csr, cnr); + expected_duty = pwm_compute_duty(cnr, cmr, false); + + pwm_write_pcr(qts, td, pcr); + g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr); + g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr); + g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr); + g_assert_cmpuint(pwm_read_cnr(qts, td), ==, cnr); + g_assert_cmpuint(pwm_read_cmr(qts, td), ==, cmr); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), + ==, expected_duty); + if (expected_duty != 0 && expected_duty != 100) { + /* Duty cycle with 0 or 100 doesn't need frequency. */ + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), + ==, expected_freq); + } + + /* Test inverted mode */ + expected_duty = pwm_compute_duty(cnr, cmr, true); + pwm_write_pcr(qts, td, pcr | CH_INV); + g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr | CH_INV); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), + ==, expected_duty); + if (expected_duty != 0 && expected_duty != 100) { + /* Duty cycle with 0 or 100 doesn't need frequency. */ + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), + ==, expected_freq); + } + + } + } + } + } + + qtest_quit(qts); +} + +static void pwm_add_test(const char *name, const TestData* td, + GTestDataFunc fn) +{ + g_autofree char *full_name = g_strdup_printf( + "npcm7xx_pwm/module[%d]/pwm[%d]/%s", pwm_module_index(td->module), + pwm_index(td->pwm), name); + qtest_add_data_func(full_name, td, fn); +} +#define add_test(name, td) pwm_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + TestData test_data_list[ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)]; + + g_test_init(&argc, &argv, NULL); + + for (int i = 0; i < ARRAY_SIZE(pwm_module_list); ++i) { + for (int j = 0; j < ARRAY_SIZE(pwm_list); ++j) { + TestData *td = &test_data_list[i * ARRAY_SIZE(pwm_list) + j]; + + td->module = &pwm_module_list[i]; + td->pwm = &pwm_list[j]; + + add_test(init, td); + add_test(oneshot, td); + add_test(toggle, td); + } + } + + return g_test_run(); +} |