From 999be4a2d3d156ba38d4620e3dbfc8851943874f Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Thu, 11 Mar 2021 10:08:55 -0800 Subject: tests/qtest: Test PWM fan RPM using MFT in PWM test This patch adds testing of PWM fan RPMs in the existing npcm7xx pwm test. It tests whether the MFT module can measure correct fan values for a PWM fan in NPCM7XX boards. Reviewed-by: Doug Evans Reviewed-by: Tyrone Ting Signed-off-by: Hao Wu Reviewed-by: Peter Maydell Message-id: 20210311180855.149764-6-wuhaotsh@google.com [PMM: fixed format strings for printing uint64_t] Signed-off-by: Peter Maydell --- tests/qtest/npcm7xx_pwm-test.c | 205 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 6 deletions(-) diff --git a/tests/qtest/npcm7xx_pwm-test.c b/tests/qtest/npcm7xx_pwm-test.c index 3d82654..bd15a1c 100644 --- a/tests/qtest/npcm7xx_pwm-test.c +++ b/tests/qtest/npcm7xx_pwm-test.c @@ -45,6 +45,7 @@ #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 APB4CKDIV(rv) extract32((rv), 30, 2) #define APB3CKDIV(rv) extract32((rv), 28, 2) #define CLK2CKDIV(rv) extract32((rv), 0, 1) #define CLK4CKDIV(rv) extract32((rv), 26, 2) @@ -52,6 +53,49 @@ #define MAX_DUTY 1000000 +/* MFT (PWM fan) related */ +#define MFT_BA(n) (0xf0180000 + ((n) * 0x1000)) +#define MFT_IRQ(n) (96 + (n)) +#define MFT_CNT1 0x00 +#define MFT_CRA 0x02 +#define MFT_CRB 0x04 +#define MFT_CNT2 0x06 +#define MFT_PRSC 0x08 +#define MFT_CKC 0x0a +#define MFT_MCTRL 0x0c +#define MFT_ICTRL 0x0e +#define MFT_ICLR 0x10 +#define MFT_IEN 0x12 +#define MFT_CPA 0x14 +#define MFT_CPB 0x16 +#define MFT_CPCFG 0x18 +#define MFT_INASEL 0x1a +#define MFT_INBSEL 0x1c + +#define MFT_MCTRL_ALL 0x64 +#define MFT_ICLR_ALL 0x3f +#define MFT_IEN_ALL 0x3f +#define MFT_CPCFG_EQ_MODE 0x44 + +#define MFT_CKC_C2CSEL BIT(3) +#define MFT_CKC_C1CSEL BIT(0) + +#define MFT_ICTRL_TFPND BIT(5) +#define MFT_ICTRL_TEPND BIT(4) +#define MFT_ICTRL_TDPND BIT(3) +#define MFT_ICTRL_TCPND BIT(2) +#define MFT_ICTRL_TBPND BIT(1) +#define MFT_ICTRL_TAPND BIT(0) + +#define MFT_MAX_CNT 0xffff +#define MFT_TIMEOUT 0x5000 + +#define DEFAULT_RPM 19800 +#define DEFAULT_PRSC 255 +#define MFT_PULSE_PER_REVOLUTION 2 + +#define MAX_ERROR 1 + typedef struct PWMModule { int irq; uint64_t base_addr; @@ -210,19 +254,36 @@ static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index) return pwm_qom_get(qts, path, name); } +static void mft_qom_set(QTestState *qts, int index, const char *name, + uint32_t value) +{ + QDict *response; + char *path = g_strdup_printf("/machine/soc/mft[%d]", index); + + g_test_message("Setting properties %s of mft[%d] with value %u", + name, index, 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 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) +static uint64_t read_pclk(QTestState *qts, bool mft) { 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); + uint32_t apbdiv = mft ? APB4CKDIV(clkdiv2) : APB3CKDIV(clkdiv2); switch (CPUCKSEL(clksel)) { case 0: @@ -241,7 +302,7 @@ static uint64_t read_pclk(QTestState *qts) g_assert_not_reached(); } - freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + APB3CKDIV(clkdiv2)); + freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + apbdiv); return freq; } @@ -267,7 +328,7 @@ static uint32_t pwm_selector(uint32_t csr) 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)); + return read_pclk(qts, false) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1)); } static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted) @@ -301,6 +362,28 @@ static void pwm_write(QTestState *qts, const TestData *td, unsigned offset, qtest_writel(qts, td->module->base_addr + offset, value); } +static uint8_t mft_readb(QTestState *qts, int index, unsigned offset) +{ + return qtest_readb(qts, MFT_BA(index) + offset); +} + +static uint16_t mft_readw(QTestState *qts, int index, unsigned offset) +{ + return qtest_readw(qts, MFT_BA(index) + offset); +} + +static void mft_writeb(QTestState *qts, int index, unsigned offset, + uint8_t value) +{ + qtest_writeb(qts, MFT_BA(index) + offset, value); +} + +static void mft_writew(QTestState *qts, int index, unsigned offset, + uint16_t value) +{ + return qtest_writew(qts, MFT_BA(index) + 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); @@ -351,11 +434,116 @@ static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value) pwm_write(qts, td, td->pwm->cmr_offset, value); } +static int mft_compute_index(const TestData *td) +{ + int index = pwm_module_index(td->module) * ARRAY_SIZE(pwm_list) + + pwm_index(td->pwm); + + g_assert_cmpint(index, <, + ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)); + + return index; +} + +static void mft_reset_counters(QTestState *qts, int index) +{ + mft_writew(qts, index, MFT_CNT1, MFT_MAX_CNT); + mft_writew(qts, index, MFT_CNT2, MFT_MAX_CNT); + mft_writew(qts, index, MFT_CRA, MFT_MAX_CNT); + mft_writew(qts, index, MFT_CRB, MFT_MAX_CNT); + mft_writew(qts, index, MFT_CPA, MFT_MAX_CNT - MFT_TIMEOUT); + mft_writew(qts, index, MFT_CPB, MFT_MAX_CNT - MFT_TIMEOUT); +} + +static void mft_init(QTestState *qts, const TestData *td) +{ + int index = mft_compute_index(td); + + /* Enable everything */ + mft_writeb(qts, index, MFT_CKC, 0); + mft_writeb(qts, index, MFT_ICLR, MFT_ICLR_ALL); + mft_writeb(qts, index, MFT_MCTRL, MFT_MCTRL_ALL); + mft_writeb(qts, index, MFT_IEN, MFT_IEN_ALL); + mft_writeb(qts, index, MFT_INASEL, 0); + mft_writeb(qts, index, MFT_INBSEL, 0); + + /* Set cpcfg to use EQ mode, same as kernel driver */ + mft_writeb(qts, index, MFT_CPCFG, MFT_CPCFG_EQ_MODE); + + /* Write default counters, timeout and prescaler */ + mft_reset_counters(qts, index); + mft_writeb(qts, index, MFT_PRSC, DEFAULT_PRSC); + + /* Write default max rpm via QMP */ + mft_qom_set(qts, index, "max_rpm[0]", DEFAULT_RPM); + mft_qom_set(qts, index, "max_rpm[1]", DEFAULT_RPM); +} + +static int32_t mft_compute_cnt(uint32_t rpm, uint64_t clk) +{ + uint64_t cnt; + + if (rpm == 0) { + return -1; + } + + cnt = clk * 60 / ((DEFAULT_PRSC + 1) * rpm * MFT_PULSE_PER_REVOLUTION); + if (cnt >= MFT_TIMEOUT) { + return -1; + } + return MFT_MAX_CNT - cnt; +} + +static void mft_verify_rpm(QTestState *qts, const TestData *td, uint64_t duty) +{ + int index = mft_compute_index(td); + uint16_t cnt, cr; + uint32_t rpm = DEFAULT_RPM * duty / MAX_DUTY; + uint64_t clk = read_pclk(qts, true); + int32_t expected_cnt = mft_compute_cnt(rpm, clk); + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + g_test_message( + "verifying rpm for mft[%d]: clk: %" PRIu64 ", duty: %" PRIu64 ", rpm: %u, cnt: %d", + index, clk, duty, rpm, expected_cnt); + + /* Verify rpm for fan A */ + /* Stop capture */ + mft_writeb(qts, index, MFT_CKC, 0); + mft_writeb(qts, index, MFT_ICLR, MFT_ICLR_ALL); + mft_reset_counters(qts, index); + g_assert_cmphex(mft_readw(qts, index, MFT_CNT1), ==, MFT_MAX_CNT); + g_assert_cmphex(mft_readw(qts, index, MFT_CRA), ==, MFT_MAX_CNT); + g_assert_cmphex(mft_readw(qts, index, MFT_CPA), ==, + MFT_MAX_CNT - MFT_TIMEOUT); + /* Start capture */ + mft_writeb(qts, index, MFT_CKC, MFT_CKC_C1CSEL); + g_assert_true(qtest_get_irq(qts, MFT_IRQ(index))); + if (expected_cnt == -1) { + g_assert_cmphex(mft_readb(qts, index, MFT_ICTRL), ==, MFT_ICTRL_TEPND); + } else { + g_assert_cmphex(mft_readb(qts, index, MFT_ICTRL), ==, MFT_ICTRL_TAPND); + cnt = mft_readw(qts, index, MFT_CNT1); + /* + * Due to error in clock measurement and rounding, we might have a small + * error in measuring RPM. + */ + g_assert_cmphex(cnt + MAX_ERROR, >=, expected_cnt); + g_assert_cmphex(cnt, <=, expected_cnt + MAX_ERROR); + cr = mft_readw(qts, index, MFT_CRA); + g_assert_cmphex(cnt, ==, cr); + } + + /* Verify rpm for fan B */ + + qtest_irq_intercept_out(qts, "/machine/soc/a9mpcore/gic"); +} + /* 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"); + QTestState *qts = qtest_init("-machine npcm750-evb"); int module = pwm_module_index(td->module); int pwm = pwm_index(td->pwm); @@ -369,7 +557,7 @@ static void test_init(gconstpointer test_data) static void test_oneshot(gconstpointer test_data) { const TestData *td = test_data; - QTestState *qts = qtest_init("-machine quanta-gsj"); + QTestState *qts = qtest_init("-machine npcm750-evb"); int module = pwm_module_index(td->module); int pwm = pwm_index(td->pwm); uint32_t ppr, csr, pcr; @@ -400,13 +588,15 @@ static void test_oneshot(gconstpointer test_data) static void test_toggle(gconstpointer test_data) { const TestData *td = test_data; - QTestState *qts = qtest_init("-machine quanta-gsj"); + QTestState *qts = qtest_init("-machine npcm750-evb"); 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; + mft_init(qts, td); + pcr = CH_EN | CH_MOD; for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { ppr = ppr_list[i]; @@ -440,6 +630,9 @@ static void test_toggle(gconstpointer test_data) ==, expected_freq); } + /* Test MFT's RPM is correct. */ + mft_verify_rpm(qts, td, expected_duty); + /* Test inverted mode */ expected_duty = pwm_compute_duty(cnr, cmr, true); pwm_write_pcr(qts, td, pcr | CH_INV); -- cgit v1.1