blob: 3d82654b81a221cba021dac24423d67bcca75ac9 [file] [log] [blame]
/*
* 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;
uint64_t val;
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 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)
{
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 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();
}