mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
712 lines
20 KiB
C
712 lines
20 KiB
C
/*
|
|
* 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 "libqtest.h"
|
|
#include "qapi/qmp/qdict.h"
|
|
#include "qapi/qmp/qnum.h"
|
|
|
|
static int verbosity_level;
|
|
|
|
#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 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)
|
|
#define CPUCKSEL(rv) extract32((rv), 0, 2)
|
|
|
|
#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;
|
|
} 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(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(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;
|
|
uint64_t val;
|
|
|
|
if (verbosity_level >= 2) {
|
|
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"));
|
|
val = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
|
|
qobject_unref(response);
|
|
return val;
|
|
}
|
|
|
|
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 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);
|
|
|
|
if (verbosity_level >= 2) {
|
|
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"));
|
|
|
|
qobject_unref(response);
|
|
g_free(path);
|
|
}
|
|
|
|
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, 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:
|
|
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) + apbdiv);
|
|
|
|
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, false) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1));
|
|
}
|
|
|
|
static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted)
|
|
{
|
|
uint32_t duty;
|
|
|
|
if (cnr == 0) {
|
|
/* PWM is stopped. */
|
|
duty = 0;
|
|
} else if (cmr >= cnr) {
|
|
duty = MAX_DUTY;
|
|
} else {
|
|
duty = (uint64_t)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 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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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");
|
|
if (verbosity_level >= 2) {
|
|
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 npcm750-evb");
|
|
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 npcm750-evb");
|
|
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 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;
|
|
int cnr_step = g_test_quick() ? 2 : 1;
|
|
|
|
mft_init(qts, td);
|
|
|
|
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_step) {
|
|
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 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);
|
|
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)];
|
|
int pwm_module_list_cnt = 1, pwm_list_cnt = 1;
|
|
|
|
char *v_env = getenv("V");
|
|
|
|
if (v_env) {
|
|
verbosity_level = atoi(v_env);
|
|
}
|
|
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
if (!g_test_quick()) {
|
|
pwm_module_list_cnt = ARRAY_SIZE(pwm_module_list);
|
|
pwm_list_cnt = ARRAY_SIZE(pwm_list);
|
|
}
|
|
|
|
for (int i = 0; i < pwm_module_list_cnt; ++i) {
|
|
for (int j = 0; j < pwm_list_cnt; ++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();
|
|
}
|