| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include <stdint.h> |
| |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/dif/dif_alert_handler.h" |
| #include "sw/device/lib/dif/dif_gpio.h" |
| #include "sw/device/lib/dif/dif_otp_ctrl.h" |
| #include "sw/device/lib/dif/dif_pinmux.h" |
| #include "sw/device/lib/dif/dif_pwm.h" |
| #include "sw/device/lib/dif/dif_pwrmgr.h" |
| #include "sw/device/lib/dif/dif_rv_core_ibex.h" |
| #include "sw/device/lib/dif/dif_rv_timer.h" |
| #include "sw/device/lib/runtime/hart.h" |
| #include "sw/device/lib/runtime/ibex.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/alert_handler_testutils.h" |
| #include "sw/device/lib/testing/aon_timer_testutils.h" |
| #include "sw/device/lib/testing/pwrmgr_testutils.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/test_framework/ottf_isrs.h" |
| #include "sw/device/lib/testing/test_framework/ottf_main.h" |
| |
| #include "alert_handler_regs.h" |
| #include "aon_timer_regs.h" |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| #include "pwm_regs.h" |
| |
| typedef void (*isr_handler)(void); |
| static volatile isr_handler expected_isr_handler; |
| static volatile dif_rv_core_ibex_nmi_state_t nmi_state; |
| static volatile bool nmi_fired = false; |
| static volatile bool ext_irq_fired = false; |
| static volatile bool irq_is_pending = false; |
| |
| static dif_aon_timer_t aon_timer; |
| static dif_rv_core_ibex_t rv_core_ibex; |
| static dif_pwrmgr_t pwrmgr; |
| static dif_rv_timer_t rv_timer; |
| static dif_alert_handler_t alert_handler; |
| static dif_pwm_t pwm; |
| static dif_pinmux_t pinmux; |
| static dif_otp_ctrl_t otp_ctrl; |
| static dif_gpio_t gpio; |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| // ISRs |
| |
| void ottf_external_isr(void) { |
| LOG_INFO("got external IRQ"); |
| ext_irq_fired = true; |
| } |
| |
| void ottf_external_nmi_handler(void) { |
| nmi_fired = true; |
| |
| expected_isr_handler(); |
| |
| CHECK_DIF_OK(dif_rv_core_ibex_get_nmi_state( |
| &rv_core_ibex, (dif_rv_core_ibex_nmi_state_t *)&nmi_state)); |
| CHECK_DIF_OK(dif_rv_core_ibex_clear_nmi_state(&rv_core_ibex, |
| kDifRvCoreIbexNmiSourceAll)); |
| } |
| |
| static void wdog_irq_handler(void) { |
| bool is_pending; |
| |
| // The watchdog bark external interrupt is also connected to the NMI input of |
| // rv_core_ibex. |
| CHECK_DIF_OK(dif_aon_timer_irq_is_pending( |
| &aon_timer, kDifAonTimerIrqWdogTimerBark, &is_pending)); |
| irq_is_pending = is_pending; |
| |
| // Stop the watchdog. |
| CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon_timer)); |
| |
| // In order to handle the NMI we need to acknowledge the interrupt status bit |
| // at the peripheral side. |
| CHECK_DIF_OK( |
| dif_aon_timer_irq_acknowledge(&aon_timer, kDifAonTimerIrqWdogTimerBark)); |
| } |
| |
| bool test_main(void) { |
| // Define access to DUT IPs: |
| CHECK_DIF_OK(dif_aon_timer_init( |
| mmio_region_from_addr(TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR), &aon_timer)); |
| CHECK_DIF_OK(dif_rv_core_ibex_init( |
| mmio_region_from_addr(TOP_EARLGREY_RV_CORE_IBEX_CFG_BASE_ADDR), |
| &rv_core_ibex)); |
| CHECK_DIF_OK(dif_pwrmgr_init( |
| mmio_region_from_addr(TOP_EARLGREY_PWRMGR_AON_BASE_ADDR), &pwrmgr)); |
| CHECK_DIF_OK(dif_rv_timer_init( |
| mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR), &rv_timer)); |
| CHECK_DIF_OK(dif_alert_handler_init( |
| mmio_region_from_addr(TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR), |
| &alert_handler)); |
| CHECK_DIF_OK(dif_pwm_init( |
| mmio_region_from_addr(TOP_EARLGREY_PWM_AON_BASE_ADDR), &pwm)); |
| CHECK_DIF_OK(dif_pinmux_init( |
| mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux)); |
| CHECK_DIF_OK(dif_otp_ctrl_init( |
| mmio_region_from_addr(TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR), &otp_ctrl)); |
| CHECK_DIF_OK( |
| dif_gpio_init(mmio_region_from_addr(TOP_EARLGREY_GPIO_BASE_ADDR), &gpio)); |
| |
| LOG_INFO("Running CHIP Power Idle Load test"); |
| |
| static const uint32_t kGpioMask = 0x00000004; |
| |
| CHECK_DIF_OK( |
| dif_pinmux_output_select(&pinmux, (kTopEarlgreyPinmuxMioOutIoa0 + 2), |
| (kTopEarlgreyPinmuxOutselGpioGpio0 + 2))); |
| |
| // Set output modes of all GPIO pins |
| CHECK_DIF_OK(dif_gpio_output_set_enabled_all(&gpio, kGpioMask)); |
| |
| // Write to set IOA2 low at the start of the test: |
| CHECK_DIF_OK(dif_gpio_write(&gpio, 2, 0)); |
| |
| LOG_INFO("GPIO active"); |
| |
| // RV Timer |
| static const uint32_t kHart = (uint32_t)kTopEarlgreyPlicTargetIbex0; |
| static const uint32_t kComparator = 0; |
| static const uint64_t kTickFreqHz = 1000000; |
| static const uint64_t kDeadline = UINT32_MAX; |
| |
| CHECK_DIF_OK(dif_rv_timer_reset(&rv_timer)); |
| |
| dif_rv_timer_tick_params_t rv_timer_tick_params; |
| CHECK_DIF_OK(dif_rv_timer_approximate_tick_params( |
| kClockFreqPeripheralHz, kTickFreqHz, &rv_timer_tick_params)); |
| // Configure the tick params for a particular hart's counter |
| CHECK_DIF_OK( |
| dif_rv_timer_set_tick_params(&rv_timer, kHart, rv_timer_tick_params)); |
| |
| uint64_t current_time; |
| CHECK_DIF_OK(dif_rv_timer_counter_read(&rv_timer, kHart, ¤t_time)); |
| CHECK_DIF_OK(dif_rv_timer_arm(&rv_timer, kHart, kComparator, |
| (current_time + kDeadline))); |
| CHECK_DIF_OK( |
| dif_rv_timer_counter_set_enabled(&rv_timer, kHart, kDifToggleEnabled)); |
| |
| LOG_INFO("RV Timer active"); |
| |
| // Alert Ping |
| |
| // 1. Config structs for all Incoming Alerts: |
| dif_alert_handler_alert_t alerts[ALERT_HANDLER_PARAM_N_ALERTS]; |
| dif_alert_handler_class_t alert_classes[ALERT_HANDLER_PARAM_N_ALERTS]; |
| for (int i = 0; i < ALERT_HANDLER_PARAM_N_ALERTS; ++i) { |
| alerts[i] = i; |
| alert_classes[i] = kDifAlertHandlerClassA; |
| } |
| |
| // 2. Config structs for Local Alerts |
| dif_alert_handler_local_alert_t loc_alerts[] = { |
| kDifAlertHandlerLocalAlertAlertPingFail}; |
| dif_alert_handler_class_t loc_alert_classes[] = {kDifAlertHandlerClassB}; |
| |
| // 3. Config structs for Escalation phase |
| dif_alert_handler_escalation_phase_t esc_phases[] = { |
| {.phase = kDifAlertHandlerClassStatePhase0, |
| .signal = 0, |
| .duration_cycles = 2000}}; |
| // Escalation protocol (struct) for an alert class |
| dif_alert_handler_class_config_t class_config = { |
| .auto_lock_accumulation_counter = kDifToggleDisabled, |
| .accumulator_threshold = UINT16_MAX, |
| .irq_deadline_cycles = UINT32_MAX, |
| .escalation_phases = esc_phases, |
| .escalation_phases_len = ARRAYSIZE(esc_phases), |
| .crashdump_escalation_phase = kDifAlertHandlerClassStatePhase1, |
| }; |
| dif_alert_handler_class_config_t class_configs[] = {class_config, |
| class_config}; |
| dif_alert_handler_class_t classes[] = {kDifAlertHandlerClassA, |
| kDifAlertHandlerClassB}; |
| |
| // 4. Runtime configuration (struct) for the alert handler: |
| dif_alert_handler_config_t config = { |
| .alerts = alerts, |
| .alert_classes = alert_classes, |
| .alerts_len = ARRAYSIZE(alerts), |
| .local_alerts = loc_alerts, |
| .local_alert_classes = loc_alert_classes, |
| .local_alerts_len = ARRAYSIZE(loc_alerts), |
| .classes = classes, |
| .class_configs = class_configs, |
| .classes_len = ARRAYSIZE(class_configs), |
| .ping_timeout = UINT16_MAX, |
| }; |
| |
| // 5. Configure Alerts |
| alert_handler_testutils_configure_all(&alert_handler, config, |
| kDifToggleEnabled); |
| |
| // Checks whether alert handler's ping timer is locked |
| bool is_locked; |
| CHECK_DIF_OK( |
| dif_alert_handler_is_ping_timer_locked(&alert_handler, &is_locked)); |
| CHECK(is_locked, "Expected alerts to be locked"); |
| |
| LOG_INFO("Alert ping is active"); |
| |
| // PWM |
| static const dif_pwm_config_t config_ = { |
| .clock_divisor = 0, |
| .beats_per_pulse_cycle = 32, |
| }; |
| |
| // Configuration struct for a specific PWM channel |
| static const dif_pwm_channel_config_t default_ch_cfg_ = { |
| .duty_cycle_a = 0, |
| .duty_cycle_b = 0, |
| .phase_delay = 0, |
| .mode = kDifPwmModeFirmware, |
| .polarity = kDifPwmPolarityActiveHigh, |
| .blink_parameter_x = 0, |
| .blink_parameter_y = 0, |
| }; |
| static const dif_pwm_channel_t kPwmChannel[PWM_PARAM_N_OUTPUTS] = { |
| kDifPwmChannel0, kDifPwmChannel1, kDifPwmChannel2, |
| kDifPwmChannel3, kDifPwmChannel4, kDifPwmChannel5, |
| }; |
| // Duty cycle (arbitrary) values (in the beats) |
| static volatile const uint16_t kPwmDutycycle[PWM_PARAM_N_OUTPUTS] = { |
| 6, 11, 27, 8, 17, 7, |
| }; |
| |
| static const dif_pinmux_index_t kPinmuxMioOut[PWM_PARAM_N_OUTPUTS] = { |
| kTopEarlgreyPinmuxMioOutIob10, kTopEarlgreyPinmuxMioOutIob11, |
| kTopEarlgreyPinmuxMioOutIob12, kTopEarlgreyPinmuxMioOutIoc10, |
| kTopEarlgreyPinmuxMioOutIoc11, kTopEarlgreyPinmuxMioOutIoc12, |
| }; |
| static const dif_pinmux_index_t kPinmuxOutsel[PWM_PARAM_N_OUTPUTS] = { |
| kTopEarlgreyPinmuxOutselPwmAonPwm0, kTopEarlgreyPinmuxOutselPwmAonPwm1, |
| kTopEarlgreyPinmuxOutselPwmAonPwm2, kTopEarlgreyPinmuxOutselPwmAonPwm3, |
| kTopEarlgreyPinmuxOutselPwmAonPwm4, kTopEarlgreyPinmuxOutselPwmAonPwm5, |
| }; |
| |
| CHECK_DIF_OK(dif_pwm_configure(&pwm, config_)); |
| |
| // Confugure each of the PWM channels: |
| dif_pwm_channel_config_t channel_config_ = default_ch_cfg_; |
| for (int i = 0; i < PWM_PARAM_N_OUTPUTS; ++i) { |
| CHECK_DIF_OK( |
| dif_pwm_channel_set_enabled(&pwm, kPwmChannel[i], kDifToggleDisabled)); |
| channel_config_.duty_cycle_a = kPwmDutycycle[i]; |
| CHECK_DIF_OK( |
| dif_pwm_configure_channel(&pwm, kPwmChannel[i], channel_config_)); |
| CHECK_DIF_OK( |
| dif_pwm_channel_set_enabled(&pwm, kPwmChannel[i], kDifToggleEnabled)); |
| } |
| |
| // Enable all PWM channels |
| CHECK_DIF_OK(dif_pwm_phase_cntr_set_enabled(&pwm, kDifToggleEnabled)); |
| |
| // PINMUX |
| for (int i = 0; i < PWM_PARAM_N_OUTPUTS; ++i) { |
| CHECK_DIF_OK( |
| dif_pinmux_output_select(&pinmux, kPinmuxMioOut[i], kPinmuxOutsel[i])); |
| } |
| |
| LOG_INFO("PWM active"); |
| |
| // OTP |
| |
| static const dif_otp_ctrl_config_t otp_ctrl_config = { |
| .check_timeout = UINT32_MAX, |
| .integrity_period_mask = 0x1, |
| .consistency_period_mask = 0x1, |
| }; |
| |
| CHECK_DIF_OK(dif_otp_ctrl_configure(&otp_ctrl, otp_ctrl_config)); |
| |
| LOG_INFO("OTP periodic checks active"); |
| |
| // AON Timer - activate in Watchdog mode (not wakeup mode) & IRQ |
| static const uint64_t kTimeTillBark = 1000; |
| |
| CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon_timer)); |
| CHECK_DIF_OK(dif_aon_timer_clear_wakeup_cause(&aon_timer)); |
| expected_isr_handler = wdog_irq_handler; |
| nmi_fired = false; |
| nmi_state = (dif_rv_core_ibex_nmi_state_t){0}; |
| CHECK_DIF_OK( |
| dif_rv_core_ibex_enable_nmi(&rv_core_ibex, kDifRvCoreIbexNmiSourceWdog)); |
| uint32_t count_cycles = |
| aon_timer_testutils_get_aon_cycles_from_us(kTimeTillBark); |
| aon_timer_testutils_watchdog_config(&aon_timer, count_cycles, UINT32_MAX, |
| false); |
| |
| LOG_INFO("AON Timer active"); |
| |
| CHECK_DIF_OK(dif_gpio_write(&gpio, 2, 1)); |
| LOG_INFO("all HW is active"); |
| IBEX_SPIN_FOR(nmi_fired, kTimeTillBark * 2); |
| |
| // Note, if running on DV environment: We wait here for the AON Timer to issue |
| // an NMI. This will make the SW exit with "true" and the Verilog test to |
| // finish with "pass". Without this, the CPU will remain idle and the |
| // simulator will end the test (due to simulation timeout) with "fail". |
| |
| CHECK_DIF_OK(dif_pwm_phase_cntr_set_enabled(&pwm, kDifToggleDisabled)); |
| CHECK_DIF_OK(dif_gpio_write(&gpio, 2, 0)); |
| |
| // Prepare to exit |
| LOG_INFO("Prepare to exit"); |
| |
| // We expect the watchdog bark interrupt to be pending on the peripheral side. |
| CHECK(irq_is_pending, "Expected watchdog bark interrupt to be pending"); |
| |
| // Check NMI previous state (before clearing): |
| CHECK(nmi_state.wdog_enabled && nmi_state.wdog_barked, |
| "WDOG NMI state check1 not expected! wdog_enable:%x, wdog_raised:%x", |
| nmi_state.wdog_enabled, nmi_state.wdog_barked); |
| |
| CHECK_DIF_OK(dif_rv_core_ibex_get_nmi_state( |
| &rv_core_ibex, (dif_rv_core_ibex_nmi_state_t *)&nmi_state)); |
| // Check NMI current state (after clearing): |
| CHECK(nmi_state.wdog_enabled && !nmi_state.wdog_barked, |
| "WDOG NMI state check2 not expected! wdog_enable:%x wdog_raised:%x", |
| nmi_state.wdog_enabled, nmi_state.wdog_barked); |
| |
| // Check that no external interrupts have occurred. |
| CHECK(ext_irq_fired == false, "Unexpected external interrupt triggered."); |
| |
| // Check that the system has not been reset due to escalation and that the |
| // reset reason is still POR. |
| pwrmgr_testutils_is_wakeup_reason(&pwrmgr, 0); |
| |
| aon_timer_testutils_shutdown(&aon_timer); |
| |
| test_status_set(kTestStatusInTest); |
| |
| return true; |
| } |