| 1 | /* |
| 2 | * QTests for Nuvoton NPCM7xx PWM Modules. |
| 3 | * |
| 4 | * Copyright 2020 Google LLC |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License as published by the |
| 8 | * Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * for more details. |
| 15 | */ |
| 16 | |
| 17 | #include "qemu/osdep.h" |
| 18 | #include "qemu/bitops.h" |
| 19 | #include "libqtest.h" |
| 20 | #include "qobject/qdict.h" |
| 21 | #include "qobject/qnum.h" |
| 22 | |
| 23 | static int verbosity_level; |
| 24 | |
| 25 | #define REF_HZ 25000000 |
| 26 | |
| 27 | /* Register field definitions. */ |
| 28 | #define CH_EN BIT(0) |
| 29 | #define CH_INV BIT(2) |
| 30 | #define CH_MOD BIT(3) |
| 31 | |
| 32 | /* Registers shared between all PWMs in a module */ |
| 33 | #define PPR 0x00 |
| 34 | #define CSR 0x04 |
| 35 | #define PCR 0x08 |
| 36 | #define PIER 0x3c |
| 37 | #define PIIR 0x40 |
| 38 | |
| 39 | /* CLK module related */ |
| 40 | #define CLK_BA 0xf0801000 |
| 41 | #define CLKSEL 0x04 |
| 42 | #define CLKDIV1 0x08 |
| 43 | #define CLKDIV2 0x2c |
| 44 | #define PLLCON0 0x0c |
| 45 | #define PLLCON1 0x10 |
| 46 | #define PLL_INDV(rv) extract32((rv), 0, 6) |
| 47 | #define PLL_FBDV(rv) extract32((rv), 16, 12) |
| 48 | #define PLL_OTDV1(rv) extract32((rv), 8, 3) |
| 49 | #define PLL_OTDV2(rv) extract32((rv), 13, 3) |
| 50 | #define APB4CKDIV(rv) extract32((rv), 30, 2) |
| 51 | #define APB3CKDIV(rv) extract32((rv), 28, 2) |
| 52 | #define CLK2CKDIV(rv) extract32((rv), 0, 1) |
| 53 | #define CLK4CKDIV(rv) extract32((rv), 26, 2) |
| 54 | #define CPUCKSEL(rv) extract32((rv), 0, 2) |
| 55 | |
| 56 | #define MAX_DUTY 1000000 |
| 57 | |
| 58 | /* MFT (PWM fan) related */ |
| 59 | #define MFT_BA(n) (0xf0180000 + ((n) * 0x1000)) |
| 60 | #define MFT_IRQ(n) (96 + (n)) |
| 61 | #define MFT_CNT1 0x00 |
| 62 | #define MFT_CRA 0x02 |
| 63 | #define MFT_CRB 0x04 |
| 64 | #define MFT_CNT2 0x06 |
| 65 | #define MFT_PRSC 0x08 |
| 66 | #define MFT_CKC 0x0a |
| 67 | #define MFT_MCTRL 0x0c |
| 68 | #define MFT_ICTRL 0x0e |
| 69 | #define MFT_ICLR 0x10 |
| 70 | #define MFT_IEN 0x12 |
| 71 | #define MFT_CPA 0x14 |
| 72 | #define MFT_CPB 0x16 |
| 73 | #define MFT_CPCFG 0x18 |
| 74 | #define MFT_INASEL 0x1a |
| 75 | #define MFT_INBSEL 0x1c |
| 76 | |
| 77 | #define MFT_MCTRL_ALL 0x64 |
| 78 | #define MFT_ICLR_ALL 0x3f |
| 79 | #define MFT_IEN_ALL 0x3f |
| 80 | #define MFT_CPCFG_EQ_MODE 0x44 |
| 81 | |
| 82 | #define MFT_CKC_C2CSEL BIT(3) |
| 83 | #define MFT_CKC_C1CSEL BIT(0) |
| 84 | |
| 85 | #define MFT_ICTRL_TFPND BIT(5) |
| 86 | #define MFT_ICTRL_TEPND BIT(4) |
| 87 | #define MFT_ICTRL_TDPND BIT(3) |
| 88 | #define MFT_ICTRL_TCPND BIT(2) |
| 89 | #define MFT_ICTRL_TBPND BIT(1) |
| 90 | #define MFT_ICTRL_TAPND BIT(0) |
| 91 | |
| 92 | #define MFT_MAX_CNT 0xffff |
| 93 | #define MFT_TIMEOUT 0x5000 |
| 94 | |
| 95 | #define DEFAULT_RPM 19800 |
| 96 | #define DEFAULT_PRSC 255 |
| 97 | #define MFT_PULSE_PER_REVOLUTION 2 |
| 98 | |
| 99 | #define MAX_ERROR 1 |
| 100 | |
| 101 | typedef struct PWMModule { |
| 102 | int irq; |
| 103 | uint64_t base_addr; |
| 104 | } PWMModule; |
| 105 | |
| 106 | typedef struct PWM { |
| 107 | uint32_t cnr_offset; |
| 108 | uint32_t cmr_offset; |
| 109 | uint32_t pdr_offset; |
| 110 | uint32_t pwdr_offset; |
| 111 | } PWM; |
| 112 | |
| 113 | typedef struct TestData { |
| 114 | const PWMModule *module; |
| 115 | const PWM *pwm; |
| 116 | } TestData; |
| 117 | |
| 118 | static const PWMModule pwm_module_list[] = { |
| 119 | { |
| 120 | .irq = 93, |
| 121 | .base_addr = 0xf0103000 |
| 122 | }, |
| 123 | { |
| 124 | .irq = 94, |
| 125 | .base_addr = 0xf0104000 |
| 126 | } |
| 127 | }; |
| 128 | |
| 129 | static const PWM pwm_list[] = { |
| 130 | { |
| 131 | .cnr_offset = 0x0c, |
| 132 | .cmr_offset = 0x10, |
| 133 | .pdr_offset = 0x14, |
| 134 | .pwdr_offset = 0x44, |
| 135 | }, |
| 136 | { |
| 137 | .cnr_offset = 0x18, |
| 138 | .cmr_offset = 0x1c, |
| 139 | .pdr_offset = 0x20, |
| 140 | .pwdr_offset = 0x48, |
| 141 | }, |
| 142 | { |
| 143 | .cnr_offset = 0x24, |
| 144 | .cmr_offset = 0x28, |
| 145 | .pdr_offset = 0x2c, |
| 146 | .pwdr_offset = 0x4c, |
| 147 | }, |
| 148 | { |
| 149 | .cnr_offset = 0x30, |
| 150 | .cmr_offset = 0x34, |
| 151 | .pdr_offset = 0x38, |
| 152 | .pwdr_offset = 0x50, |
| 153 | }, |
| 154 | }; |
| 155 | |
| 156 | static const int ppr_base[] = { 0, 0, 8, 8 }; |
| 157 | static const int csr_base[] = { 0, 4, 8, 12 }; |
| 158 | static const int pcr_base[] = { 0, 8, 12, 16 }; |
| 159 | |
| 160 | static const uint32_t ppr_list[] = { |
| 161 | 0, |
| 162 | 1, |
| 163 | 10, |
| 164 | 100, |
| 165 | 255, /* Max possible value. */ |
| 166 | }; |
| 167 | |
| 168 | static const uint32_t csr_list[] = { |
| 169 | 0, |
| 170 | 1, |
| 171 | 2, |
| 172 | 3, |
| 173 | 4, /* Max possible value. */ |
| 174 | }; |
| 175 | |
| 176 | static const uint32_t cnr_list[] = { |
| 177 | 0, |
| 178 | 1, |
| 179 | 50, |
| 180 | 100, |
| 181 | 150, |
| 182 | 200, |
| 183 | 1000, |
| 184 | 10000, |
| 185 | 65535, /* Max possible value. */ |
| 186 | }; |
| 187 | |
| 188 | static const uint32_t cmr_list[] = { |
| 189 | 0, |
| 190 | 1, |
| 191 | 10, |
| 192 | 50, |
| 193 | 100, |
| 194 | 150, |
| 195 | 200, |
| 196 | 1000, |
| 197 | 10000, |
| 198 | 65535, /* Max possible value. */ |
| 199 | }; |
| 200 | |
| 201 | /* Returns the index of the PWM module. */ |
| 202 | static int pwm_module_index(const PWMModule *module) |
| 203 | { |
| 204 | ptrdiff_t diff = module - pwm_module_list; |
| 205 | |
| 206 | g_assert(diff >= 0 && diff < ARRAY_SIZE(pwm_module_list)); |
| 207 | |
| 208 | return diff; |
| 209 | } |
| 210 | |
| 211 | /* Returns the index of the PWM entry. */ |
| 212 | static int pwm_index(const PWM *pwm) |
| 213 | { |
| 214 | ptrdiff_t diff = pwm - pwm_list; |
| 215 | |
| 216 | g_assert(diff >= 0 && diff < ARRAY_SIZE(pwm_list)); |
| 217 | |
| 218 | return diff; |
| 219 | } |
| 220 | |
| 221 | static uint64_t pwm_qom_get(QTestState *qts, const char *path, const char *name) |
| 222 | { |
| 223 | QDict *response; |
| 224 | uint64_t val; |
| 225 | |
| 226 | if (verbosity_level >= 2) { |
| 227 | g_test_message("Getting properties %s from %s", name, path); |
| 228 | } |
| 229 | response = qtest_qmp(qts, "{ 'execute': 'qom-get'," |
| 230 | " 'arguments': { 'path': %s, 'property': %s}}", |
| 231 | path, name); |
| 232 | /* The qom set message returns successfully. */ |
| 233 | g_assert_true(qdict_haskey(response, "return")); |
| 234 | val = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return"))); |
| 235 | qobject_unref(response); |
| 236 | return val; |
| 237 | } |
| 238 | |
| 239 | static uint64_t pwm_get_freq(QTestState *qts, int module_index, int pwm_index) |
| 240 | { |
| 241 | char path[100]; |
| 242 | char name[100]; |
| 243 | |
| 244 | sprintf(path, "/machine/soc/pwm[%d]", module_index); |
| 245 | sprintf(name, "freq[%d]", pwm_index); |
| 246 | |
| 247 | return pwm_qom_get(qts, path, name); |
| 248 | } |
| 249 | |
| 250 | static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index) |
| 251 | { |
| 252 | char path[100]; |
| 253 | char name[100]; |
| 254 | |
| 255 | sprintf(path, "/machine/soc/pwm[%d]", module_index); |
| 256 | sprintf(name, "duty[%d]", pwm_index); |
| 257 | |
| 258 | return pwm_qom_get(qts, path, name); |
| 259 | } |
| 260 | |
| 261 | static void mft_qom_set(QTestState *qts, int index, const char *name, |
| 262 | uint32_t value) |
| 263 | { |
| 264 | QDict *response; |
| 265 | char *path = g_strdup_printf("/machine/soc/mft[%d]", index); |
| 266 | |
| 267 | if (verbosity_level >= 2) { |
| 268 | g_test_message("Setting properties %s of mft[%d] with value %u", |
| 269 | name, index, value); |
| 270 | } |
| 271 | response = qtest_qmp(qts, "{ 'execute': 'qom-set'," |
| 272 | " 'arguments': { 'path': %s, " |
| 273 | " 'property': %s, 'value': %u}}", |
| 274 | path, name, value); |
| 275 | /* The qom set message returns successfully. */ |
| 276 | g_assert_true(qdict_haskey(response, "return")); |
| 277 | |
| 278 | qobject_unref(response); |
| 279 | g_free(path); |
| 280 | } |
| 281 | |
| 282 | static uint32_t get_pll(uint32_t con) |
| 283 | { |
| 284 | return REF_HZ * PLL_FBDV(con) / (PLL_INDV(con) * PLL_OTDV1(con) |
| 285 | * PLL_OTDV2(con)); |
| 286 | } |
| 287 | |
| 288 | static uint64_t read_pclk(QTestState *qts, bool mft) |
| 289 | { |
| 290 | uint64_t freq = REF_HZ; |
| 291 | uint32_t clksel = qtest_readl(qts, CLK_BA + CLKSEL); |
| 292 | uint32_t pllcon; |
| 293 | uint32_t clkdiv1 = qtest_readl(qts, CLK_BA + CLKDIV1); |
| 294 | uint32_t clkdiv2 = qtest_readl(qts, CLK_BA + CLKDIV2); |
| 295 | uint32_t apbdiv = mft ? APB4CKDIV(clkdiv2) : APB3CKDIV(clkdiv2); |
| 296 | |
| 297 | switch (CPUCKSEL(clksel)) { |
| 298 | case 0: |
| 299 | pllcon = qtest_readl(qts, CLK_BA + PLLCON0); |
| 300 | freq = get_pll(pllcon); |
| 301 | break; |
| 302 | case 1: |
| 303 | pllcon = qtest_readl(qts, CLK_BA + PLLCON1); |
| 304 | freq = get_pll(pllcon); |
| 305 | break; |
| 306 | case 2: |
| 307 | break; |
| 308 | case 3: |
| 309 | break; |
| 310 | default: |
| 311 | g_assert_not_reached(); |
| 312 | } |
| 313 | |
| 314 | freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + apbdiv); |
| 315 | |
| 316 | return freq; |
| 317 | } |
| 318 | |
| 319 | static uint32_t pwm_selector(uint32_t csr) |
| 320 | { |
| 321 | switch (csr) { |
| 322 | case 0: |
| 323 | return 2; |
| 324 | case 1: |
| 325 | return 4; |
| 326 | case 2: |
| 327 | return 8; |
| 328 | case 3: |
| 329 | return 16; |
| 330 | case 4: |
| 331 | return 1; |
| 332 | default: |
| 333 | g_assert_not_reached(); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | static uint64_t pwm_compute_freq(QTestState *qts, uint32_t ppr, uint32_t csr, |
| 338 | uint32_t cnr) |
| 339 | { |
| 340 | return read_pclk(qts, false) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1)); |
| 341 | } |
| 342 | |
| 343 | static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted) |
| 344 | { |
| 345 | uint32_t duty; |
| 346 | |
| 347 | if (cnr == 0) { |
| 348 | /* PWM is stopped. */ |
| 349 | duty = 0; |
| 350 | } else if (cmr >= cnr) { |
| 351 | duty = MAX_DUTY; |
| 352 | } else { |
| 353 | duty = (uint64_t)MAX_DUTY * (cmr + 1) / (cnr + 1); |
| 354 | } |
| 355 | |
| 356 | if (inverted) { |
| 357 | duty = MAX_DUTY - duty; |
| 358 | } |
| 359 | |
| 360 | return duty; |
| 361 | } |
| 362 | |
| 363 | static uint32_t pwm_read(QTestState *qts, const TestData *td, unsigned offset) |
| 364 | { |
| 365 | return qtest_readl(qts, td->module->base_addr + offset); |
| 366 | } |
| 367 | |
| 368 | static void pwm_write(QTestState *qts, const TestData *td, unsigned offset, |
| 369 | uint32_t value) |
| 370 | { |
| 371 | qtest_writel(qts, td->module->base_addr + offset, value); |
| 372 | } |
| 373 | |
| 374 | static uint8_t mft_readb(QTestState *qts, int index, unsigned offset) |
| 375 | { |
| 376 | return qtest_readb(qts, MFT_BA(index) + offset); |
| 377 | } |
| 378 | |
| 379 | static uint16_t mft_readw(QTestState *qts, int index, unsigned offset) |
| 380 | { |
| 381 | return qtest_readw(qts, MFT_BA(index) + offset); |
| 382 | } |
| 383 | |
| 384 | static void mft_writeb(QTestState *qts, int index, unsigned offset, |
| 385 | uint8_t value) |
| 386 | { |
| 387 | qtest_writeb(qts, MFT_BA(index) + offset, value); |
| 388 | } |
| 389 | |
| 390 | static void mft_writew(QTestState *qts, int index, unsigned offset, |
| 391 | uint16_t value) |
| 392 | { |
| 393 | return qtest_writew(qts, MFT_BA(index) + offset, value); |
| 394 | } |
| 395 | |
| 396 | static uint32_t pwm_read_ppr(QTestState *qts, const TestData *td) |
| 397 | { |
| 398 | return extract32(pwm_read(qts, td, PPR), ppr_base[pwm_index(td->pwm)], 8); |
| 399 | } |
| 400 | |
| 401 | static void pwm_write_ppr(QTestState *qts, const TestData *td, uint32_t value) |
| 402 | { |
| 403 | pwm_write(qts, td, PPR, value << ppr_base[pwm_index(td->pwm)]); |
| 404 | } |
| 405 | |
| 406 | static uint32_t pwm_read_csr(QTestState *qts, const TestData *td) |
| 407 | { |
| 408 | return extract32(pwm_read(qts, td, CSR), csr_base[pwm_index(td->pwm)], 3); |
| 409 | } |
| 410 | |
| 411 | static void pwm_write_csr(QTestState *qts, const TestData *td, uint32_t value) |
| 412 | { |
| 413 | pwm_write(qts, td, CSR, value << csr_base[pwm_index(td->pwm)]); |
| 414 | } |
| 415 | |
| 416 | static uint32_t pwm_read_pcr(QTestState *qts, const TestData *td) |
| 417 | { |
| 418 | return extract32(pwm_read(qts, td, PCR), pcr_base[pwm_index(td->pwm)], 4); |
| 419 | } |
| 420 | |
| 421 | static void pwm_write_pcr(QTestState *qts, const TestData *td, uint32_t value) |
| 422 | { |
| 423 | pwm_write(qts, td, PCR, value << pcr_base[pwm_index(td->pwm)]); |
| 424 | } |
| 425 | |
| 426 | static uint32_t pwm_read_cnr(QTestState *qts, const TestData *td) |
| 427 | { |
| 428 | return pwm_read(qts, td, td->pwm->cnr_offset); |
| 429 | } |
| 430 | |
| 431 | static void pwm_write_cnr(QTestState *qts, const TestData *td, uint32_t value) |
| 432 | { |
| 433 | pwm_write(qts, td, td->pwm->cnr_offset, value); |
| 434 | } |
| 435 | |
| 436 | static uint32_t pwm_read_cmr(QTestState *qts, const TestData *td) |
| 437 | { |
| 438 | return pwm_read(qts, td, td->pwm->cmr_offset); |
| 439 | } |
| 440 | |
| 441 | static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value) |
| 442 | { |
| 443 | pwm_write(qts, td, td->pwm->cmr_offset, value); |
| 444 | } |
| 445 | |
| 446 | static int mft_compute_index(const TestData *td) |
| 447 | { |
| 448 | int index = pwm_module_index(td->module) * ARRAY_SIZE(pwm_list) + |
| 449 | pwm_index(td->pwm); |
| 450 | |
| 451 | g_assert_cmpint(index, <, |
| 452 | ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)); |
| 453 | |
| 454 | return index; |
| 455 | } |
| 456 | |
| 457 | static void mft_reset_counters(QTestState *qts, int index) |
| 458 | { |
| 459 | mft_writew(qts, index, MFT_CNT1, MFT_MAX_CNT); |
| 460 | mft_writew(qts, index, MFT_CNT2, MFT_MAX_CNT); |
| 461 | mft_writew(qts, index, MFT_CRA, MFT_MAX_CNT); |
| 462 | mft_writew(qts, index, MFT_CRB, MFT_MAX_CNT); |
| 463 | mft_writew(qts, index, MFT_CPA, MFT_MAX_CNT - MFT_TIMEOUT); |
| 464 | mft_writew(qts, index, MFT_CPB, MFT_MAX_CNT - MFT_TIMEOUT); |
| 465 | } |
| 466 | |
| 467 | static void mft_init(QTestState *qts, const TestData *td) |
| 468 | { |
| 469 | int index = mft_compute_index(td); |
| 470 | |
| 471 | /* Enable everything */ |
| 472 | mft_writeb(qts, index, MFT_CKC, 0); |
| 473 | mft_writeb(qts, index, MFT_ICLR, MFT_ICLR_ALL); |
| 474 | mft_writeb(qts, index, MFT_MCTRL, MFT_MCTRL_ALL); |
| 475 | mft_writeb(qts, index, MFT_IEN, MFT_IEN_ALL); |
| 476 | mft_writeb(qts, index, MFT_INASEL, 0); |
| 477 | mft_writeb(qts, index, MFT_INBSEL, 0); |
| 478 | |
| 479 | /* Set cpcfg to use EQ mode, same as kernel driver */ |
| 480 | mft_writeb(qts, index, MFT_CPCFG, MFT_CPCFG_EQ_MODE); |
| 481 | |
| 482 | /* Write default counters, timeout and prescaler */ |
| 483 | mft_reset_counters(qts, index); |
| 484 | mft_writeb(qts, index, MFT_PRSC, DEFAULT_PRSC); |
| 485 | |
| 486 | /* Write default max rpm via QMP */ |
| 487 | mft_qom_set(qts, index, "max_rpm[0]", DEFAULT_RPM); |
| 488 | mft_qom_set(qts, index, "max_rpm[1]", DEFAULT_RPM); |
| 489 | } |
| 490 | |
| 491 | static int32_t mft_compute_cnt(uint32_t rpm, uint64_t clk) |
| 492 | { |
| 493 | uint64_t cnt; |
| 494 | |
| 495 | if (rpm == 0) { |
| 496 | return -1; |
| 497 | } |
| 498 | |
| 499 | cnt = clk * 60 / ((DEFAULT_PRSC + 1) * rpm * MFT_PULSE_PER_REVOLUTION); |
| 500 | if (cnt >= MFT_TIMEOUT) { |
| 501 | return -1; |
| 502 | } |
| 503 | return MFT_MAX_CNT - cnt; |
| 504 | } |
| 505 | |
| 506 | static void mft_verify_rpm(QTestState *qts, const TestData *td, uint64_t duty) |
| 507 | { |
| 508 | int index = mft_compute_index(td); |
| 509 | uint16_t cnt, cr; |
| 510 | uint32_t rpm = DEFAULT_RPM * duty / MAX_DUTY; |
| 511 | uint64_t clk = read_pclk(qts, true); |
| 512 | int32_t expected_cnt = mft_compute_cnt(rpm, clk); |
| 513 | |
| 514 | qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); |
| 515 | if (verbosity_level >= 2) { |
| 516 | g_test_message( |
| 517 | "verifying rpm for mft[%d]: clk: %" PRIu64 ", duty: %" PRIu64 |
| 518 | ", rpm: %u, cnt: %d", |
| 519 | index, clk, duty, rpm, expected_cnt); |
| 520 | } |
| 521 | |
| 522 | /* Verify rpm for fan A */ |
| 523 | /* Stop capture */ |
| 524 | mft_writeb(qts, index, MFT_CKC, 0); |
| 525 | mft_writeb(qts, index, MFT_ICLR, MFT_ICLR_ALL); |
| 526 | mft_reset_counters(qts, index); |
| 527 | g_assert_cmphex(mft_readw(qts, index, MFT_CNT1), ==, MFT_MAX_CNT); |
| 528 | g_assert_cmphex(mft_readw(qts, index, MFT_CRA), ==, MFT_MAX_CNT); |
| 529 | g_assert_cmphex(mft_readw(qts, index, MFT_CPA), ==, |
| 530 | MFT_MAX_CNT - MFT_TIMEOUT); |
| 531 | /* Start capture */ |
| 532 | mft_writeb(qts, index, MFT_CKC, MFT_CKC_C1CSEL); |
| 533 | g_assert_true(qtest_get_irq(qts, MFT_IRQ(index))); |
| 534 | if (expected_cnt == -1) { |
| 535 | g_assert_cmphex(mft_readb(qts, index, MFT_ICTRL), ==, MFT_ICTRL_TEPND); |
| 536 | } else { |
| 537 | g_assert_cmphex(mft_readb(qts, index, MFT_ICTRL), ==, MFT_ICTRL_TAPND); |
| 538 | cnt = mft_readw(qts, index, MFT_CNT1); |
| 539 | /* |
| 540 | * Due to error in clock measurement and rounding, we might have a small |
| 541 | * error in measuring RPM. |
| 542 | */ |
| 543 | g_assert_cmphex(cnt + MAX_ERROR, >=, expected_cnt); |
| 544 | g_assert_cmphex(cnt, <=, expected_cnt + MAX_ERROR); |
| 545 | cr = mft_readw(qts, index, MFT_CRA); |
| 546 | g_assert_cmphex(cnt, ==, cr); |
| 547 | } |
| 548 | |
| 549 | /* Verify rpm for fan B */ |
| 550 | |
| 551 | qtest_irq_intercept_out(qts, "/machine/soc/a9mpcore/gic"); |
| 552 | } |
| 553 | |
| 554 | /* Check pwm registers can be reset to default value */ |
| 555 | static void test_init(gconstpointer test_data) |
| 556 | { |
| 557 | const TestData *td = test_data; |
| 558 | QTestState *qts = qtest_init("-machine npcm750-evb"); |
| 559 | int module = pwm_module_index(td->module); |
| 560 | int pwm = pwm_index(td->pwm); |
| 561 | |
| 562 | g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0); |
| 563 | g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0); |
| 564 | |
| 565 | qtest_quit(qts); |
| 566 | } |
| 567 | |
| 568 | /* One-shot mode should not change frequency and duty cycle. */ |
| 569 | static void test_oneshot(gconstpointer test_data) |
| 570 | { |
| 571 | const TestData *td = test_data; |
| 572 | QTestState *qts = qtest_init("-machine npcm750-evb"); |
| 573 | int module = pwm_module_index(td->module); |
| 574 | int pwm = pwm_index(td->pwm); |
| 575 | uint32_t ppr, csr, pcr; |
| 576 | int i, j; |
| 577 | |
| 578 | pcr = CH_EN; |
| 579 | for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { |
| 580 | ppr = ppr_list[i]; |
| 581 | pwm_write_ppr(qts, td, ppr); |
| 582 | |
| 583 | for (j = 0; j < ARRAY_SIZE(csr_list); ++j) { |
| 584 | csr = csr_list[j]; |
| 585 | pwm_write_csr(qts, td, csr); |
| 586 | pwm_write_pcr(qts, td, pcr); |
| 587 | |
| 588 | g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr); |
| 589 | g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr); |
| 590 | g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr); |
| 591 | g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0); |
| 592 | g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0); |
| 593 | } |
| 594 | } |
| 595 | |
| 596 | qtest_quit(qts); |
| 597 | } |
| 598 | |
| 599 | /* In toggle mode, the PWM generates correct outputs. */ |
| 600 | static void test_toggle(gconstpointer test_data) |
| 601 | { |
| 602 | const TestData *td = test_data; |
| 603 | QTestState *qts = qtest_init("-machine npcm750-evb"); |
| 604 | int module = pwm_module_index(td->module); |
| 605 | int pwm = pwm_index(td->pwm); |
| 606 | uint32_t ppr, csr, pcr, cnr, cmr; |
| 607 | int i, j, k, l; |
| 608 | uint64_t expected_freq, expected_duty; |
| 609 | int cnr_step = g_test_quick() ? 2 : 1; |
| 610 | |
| 611 | mft_init(qts, td); |
| 612 | |
| 613 | pcr = CH_EN | CH_MOD; |
| 614 | for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { |
| 615 | ppr = ppr_list[i]; |
| 616 | pwm_write_ppr(qts, td, ppr); |
| 617 | |
| 618 | for (j = 0; j < ARRAY_SIZE(csr_list); ++j) { |
| 619 | csr = csr_list[j]; |
| 620 | pwm_write_csr(qts, td, csr); |
| 621 | |
| 622 | for (k = 0; k < ARRAY_SIZE(cnr_list); k += cnr_step) { |
| 623 | cnr = cnr_list[k]; |
| 624 | pwm_write_cnr(qts, td, cnr); |
| 625 | |
| 626 | for (l = 0; l < ARRAY_SIZE(cmr_list); ++l) { |
| 627 | cmr = cmr_list[l]; |
| 628 | pwm_write_cmr(qts, td, cmr); |
| 629 | expected_freq = pwm_compute_freq(qts, ppr, csr, cnr); |
| 630 | expected_duty = pwm_compute_duty(cnr, cmr, false); |
| 631 | |
| 632 | pwm_write_pcr(qts, td, pcr); |
| 633 | g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr); |
| 634 | g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr); |
| 635 | g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr); |
| 636 | g_assert_cmpuint(pwm_read_cnr(qts, td), ==, cnr); |
| 637 | g_assert_cmpuint(pwm_read_cmr(qts, td), ==, cmr); |
| 638 | g_assert_cmpuint(pwm_get_duty(qts, module, pwm), |
| 639 | ==, expected_duty); |
| 640 | if (expected_duty != 0 && expected_duty != 100) { |
| 641 | /* Duty cycle with 0 or 100 doesn't need frequency. */ |
| 642 | g_assert_cmpuint(pwm_get_freq(qts, module, pwm), |
| 643 | ==, expected_freq); |
| 644 | } |
| 645 | |
| 646 | /* Test MFT's RPM is correct. */ |
| 647 | mft_verify_rpm(qts, td, expected_duty); |
| 648 | |
| 649 | /* Test inverted mode */ |
| 650 | expected_duty = pwm_compute_duty(cnr, cmr, true); |
| 651 | pwm_write_pcr(qts, td, pcr | CH_INV); |
| 652 | g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr | CH_INV); |
| 653 | g_assert_cmpuint(pwm_get_duty(qts, module, pwm), |
| 654 | ==, expected_duty); |
| 655 | if (expected_duty != 0 && expected_duty != 100) { |
| 656 | /* Duty cycle with 0 or 100 doesn't need frequency. */ |
| 657 | g_assert_cmpuint(pwm_get_freq(qts, module, pwm), |
| 658 | ==, expected_freq); |
| 659 | } |
| 660 | |
| 661 | } |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | qtest_quit(qts); |
| 667 | } |
| 668 | |
| 669 | static void pwm_add_test(const char *name, const TestData* td, |
| 670 | GTestDataFunc fn) |
| 671 | { |
| 672 | g_autofree char *full_name = g_strdup_printf( |
| 673 | "npcm7xx_pwm/module[%d]/pwm[%d]/%s", pwm_module_index(td->module), |
| 674 | pwm_index(td->pwm), name); |
| 675 | qtest_add_data_func(full_name, td, fn); |
| 676 | } |
| 677 | #define add_test(name, td) pwm_add_test(#name, td, test_##name) |
| 678 | |
| 679 | int main(int argc, char **argv) |
| 680 | { |
| 681 | TestData test_data_list[ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)]; |
| 682 | int pwm_module_list_cnt = 1, pwm_list_cnt = 1; |
| 683 | |
| 684 | char *v_env = getenv("V"); |
| 685 | |
| 686 | if (v_env) { |
| 687 | verbosity_level = atoi(v_env); |
| 688 | } |
| 689 | |
| 690 | g_test_init(&argc, &argv, NULL); |
| 691 | |
| 692 | if (!g_test_quick()) { |
| 693 | pwm_module_list_cnt = ARRAY_SIZE(pwm_module_list); |
| 694 | pwm_list_cnt = ARRAY_SIZE(pwm_list); |
| 695 | } |
| 696 | |
| 697 | for (int i = 0; i < pwm_module_list_cnt; ++i) { |
| 698 | for (int j = 0; j < pwm_list_cnt; ++j) { |
| 699 | TestData *td = &test_data_list[i * ARRAY_SIZE(pwm_list) + j]; |
| 700 | |
| 701 | td->module = &pwm_module_list[i]; |
| 702 | td->pwm = &pwm_list[j]; |
| 703 | |
| 704 | add_test(init, td); |
| 705 | add_test(oneshot, td); |
| 706 | add_test(toggle, td); |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | return g_test_run(); |
| 711 | } |