/* * 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(); }