diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index e6a89b4c3ef318..4d4f7cf4f5e5f2 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -2827,6 +2827,46 @@ opentitan_test( ], ) +opentitan_test( + name = "pwm_smoketest", + srcs = ["pwm_smoketest.c"], + exec_env = dicts.add( + EARLGREY_TEST_ENVS, + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw340_rom_with_fake_keys": None, + "//hw/top_earlgrey:silicon_owner_sival_rom_ext": None, + "//hw/top_earlgrey:fpga_cw310_sival": None, + } + ), + fpga = fpga_params( + test_cmd = """ + --bootstrap="{firmware}" + "{firmware:elf}" + """, + test_harness = "//sw/host/tests/chip/pwm_smoketest", + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap="{firmware}" + "{firmware:elf}" + """, + test_harness = "//sw/host/tests/chip/pwm_smoketest", + ), + deps = [ + "//hw/top_earlgrey/sw/autogen:top_earlgrey", + "//sw/device/lib/dif:pinmux", + "//sw/device/lib/dif:pwm", + "//sw/device/lib/dif:gpio", + "//sw/device/lib/runtime:hart", + "//sw/device/lib/runtime:ibex", + "//sw/device/lib/runtime:irq", + "//sw/device/lib/runtime:log", + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/lib/testing/test_framework:ottf_utils", + ], +) + cc_library( name = "pwrmgr_sleep_resets_lib", srcs = ["pwrmgr_sleep_resets_lib.c"], diff --git a/sw/device/tests/pwm_smoketest.c b/sw/device/tests/pwm_smoketest.c new file mode 100644 index 00000000000000..06955570c2ba08 --- /dev/null +++ b/sw/device/tests/pwm_smoketest.c @@ -0,0 +1,112 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_gpio.h" +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/dif/dif_pwm.h" +#include "sw/device/lib/runtime/hart.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/lib/testing/test_framework/ottf_utils.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" + +enum { + kDefaultTimeoutMicros = 50000, +}; + +OTTF_DEFINE_TEST_CONFIG(); + +static volatile const uint8_t kClocksHz[] = {20, 50, 100, 200}; +static volatile const uint8_t kDutyCycles[] = {10, 30, 50, 70, 90}; + +// This function assumes that DEC_RESN = 0. +static dif_pwm_config_t compute_clk_config(uint32_t pwm_clk) { + enum { + // Theres a trade off on the precision of the clock and the precision of the + // dutycycle, the higher the kDutyCycleResulution the more precise is the + // the dutycycle and less precise is the pwm frequency. + // By experimentation kDutyCycleResulution would generate an error of 2.5% + // on the frequency and 6% on the dutycycle. + kDutyCycleResulution = 4, + kBeatsPerCycle = 2 << (kDutyCycleResulution + 1), // 2 ^ (DC_RESN + 1) + }; + return (dif_pwm_config_t){ + .beats_per_pulse_cycle = kBeatsPerCycle, + .clock_divisor = + ((uint32_t)kClockFreqAonHz / (kBeatsPerCycle * pwm_clk)) - 1}; +} + +bool test_main(void) { + dif_pwm_t pwm; + mmio_region_t addr = mmio_region_from_addr(TOP_EARLGREY_PWM_AON_BASE_ADDR); + CHECK_DIF_OK(dif_pwm_init(addr, &pwm)); + + dif_pinmux_t pinmux; + addr = mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR); + CHECK_DIF_OK(dif_pinmux_init(addr, &pinmux)); + CHECK_DIF_OK(dif_pinmux_output_select(&pinmux, kTopEarlgreyPinmuxMioOutIoa8, + kTopEarlgreyPinmuxOutselPwmAonPwm0)); + + dif_gpio_t gpio; + addr = mmio_region_from_addr(TOP_EARLGREY_GPIO_BASE_ADDR); + CHECK_DIF_OK(dif_gpio_init(addr, &gpio)); + CHECK_DIF_OK(dif_gpio_output_set_enabled_all(&gpio, 0x1)); + + LOG_INFO("Use IOA7 to let host know when sleep is active."); + CHECK_DIF_OK(dif_pinmux_input_select(&pinmux, + kTopEarlgreyPinmuxPeripheralInGpioGpio0, + kTopEarlgreyPinmuxInselIoa7)); + + for (size_t i = 0; i < ARRAYSIZE(kClocksHz); ++i) { + dif_pwm_config_t pwm_config = compute_clk_config(kClocksHz[i]); + LOG_INFO("div: %d", pwm_config.clock_divisor); + for (size_t j = 0; j < ARRAYSIZE(kDutyCycles); ++j) { + dif_pwm_channel_config_t channel_config = { + .duty_cycle_a = + pwm_config.beats_per_pulse_cycle * kDutyCycles[j] / 100, + .duty_cycle_b = 0, + .phase_delay = 0, + .mode = kDifPwmModeFirmware, + .polarity = kDifPwmPolarityActiveHigh, + .blink_parameter_x = 0, + .blink_parameter_y = 0, + }; + LOG_INFO("dity_cycle: %d", channel_config.duty_cycle_a); + + CHECK_DIF_OK(dif_pwm_configure(&pwm, pwm_config)); + CHECK_DIF_OK( + dif_pwm_configure_channel(&pwm, kDifPwmChannel0, channel_config)); + + // The goes low when the host is sampling. + bool not_sampling = true; + do { + CHECK_DIF_OK(dif_gpio_read(&gpio, 0, ¬_sampling)); + if (not_sampling) { // Debauce + busy_spin_micros(500); + CHECK_DIF_OK(dif_gpio_read(&gpio, 0, ¬_sampling)); + } + } while (not_sampling); + + CHECK_DIF_OK(dif_pwm_phase_cntr_set_enabled(&pwm, kDifToggleEnabled)); + CHECK_DIF_OK(dif_pwm_channel_set_enabled(&pwm, kDifPwmChannel0, + kDifToggleEnabled)); + + // The goes high when the host stop sampling. + do { + CHECK_DIF_OK(dif_gpio_read(&gpio, 0, ¬_sampling)); + } while (!not_sampling); + + CHECK_DIF_OK(dif_pwm_channel_set_enabled(&pwm, kDifPwmChannel0, + kDifToggleDisabled)); + CHECK_DIF_OK(dif_pwm_phase_cntr_set_enabled(&pwm, kDifToggleDisabled)); + LOG_INFO("Sync: Disabled pwm"); + } + } + return true; +}