blob: cdb51d0f9870d1cdaa2b1b839346d21ba91ab29e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Included code:
#include <stdint.h>
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_adc_ctrl.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/irq.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"
// Global Variables
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_rv_plic_t rv_plic;
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;
static dif_adc_ctrl_t adc_ctrl;
static volatile const bool kCoreClkOff = false;
static volatile const bool kIoClkOff = false;
static volatile const bool kUsbSlpOff = false;
static volatile const bool kUsbActOff = false;
static volatile const bool kDeepSleep = false;
static const uint32_t kPlicTarget = kTopEarlgreyPlicTargetIbex0;
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));
}
// Specific handlee for the wdog bark NMI IRQ.
static void wdog_irq_handler(void) {
bool is_pending;
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));
CHECK_DIF_OK(
dif_aon_timer_irq_acknowledge(&aon_timer, kDifAonTimerIrqWdogTimerBark));
// Signal a software interrupt
dif_rv_plic_t rv_plic_isr;
mmio_region_t plic_base_addr =
mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR);
CHECK_DIF_OK(dif_rv_plic_init(plic_base_addr, &rv_plic_isr));
CHECK_DIF_OK(dif_rv_plic_software_irq_force(&rv_plic_isr, kPlicTarget));
}
// Functions
void prepare_to_exit(void) {
CHECK_DIF_OK(dif_pwm_phase_cntr_set_enabled(&pwm, kDifToggleDisabled));
CHECK_DIF_OK(dif_gpio_write(&gpio, 2, 0));
LOG_INFO("Prepare to exit");
// 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
pwrmgr_testutils_is_wakeup_reason(&pwrmgr, 0);
aon_timer_testutils_shutdown(&aon_timer);
// Set the test status flag back to "TestStatusInTest"
test_status_set(kTestStatusInTest);
}
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));
CHECK_DIF_OK(dif_adc_ctrl_init(
mmio_region_from_addr(TOP_EARLGREY_ADC_CTRL_AON_BASE_ADDR), &adc_ctrl));
LOG_INFO("Running CHIP Power Sleep Load test");
// Testplan: "The test should set a GPIO (mapped to the IOA2 pin) to high
// while the power state of interest is active"
const uint32_t kGpioMask = 0x00000004;
// PINMUX
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");
// PWRMGR- Read wakeup reason
dif_pwrmgr_wakeup_reason_t wakeup_reason;
CHECK_DIF_OK(dif_pwrmgr_wakeup_reason_get(&pwrmgr, &wakeup_reason));
LOG_INFO("wakeup type:%d. wakeup reason: 0x%02X", wakeup_reason.types,
wakeup_reason.request_sources);
if (wakeup_reason.types == 0) {
// Wakeup due to Power Up - DO NOTHING HERE - go on with the rest of the
// test's flow....
} else if ((wakeup_reason.types == kDifPwrmgrWakeupTypeRequest) &&
(wakeup_reason.request_sources ==
kDifPwrmgrWakeupRequestSourceFive)) {
// Wakeup due to a peripheral request and reason is AON Timer
prepare_to_exit();
return true;
} else {
LOG_ERROR("unexpected wakeup reason! type=0x%x, source=0x%x",
wakeup_reason.types, wakeup_reason.request_sources);
return false;
}
// RV Timer
const uint32_t kHart = (uint32_t)kTopEarlgreyPlicTargetIbex0;
const uint32_t kComparator = 0;
const uint64_t kTickFreqHz = 1000000;
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, &current_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 (size_t 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
// Configuration struct for PWM general
const dif_pwm_config_t config_ = {
.clock_divisor = 0,
.beats_per_pulse_cycle = 32,
};
// Configuration struct for a specific PWM channel
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,
};
const dif_pwm_channel_t kPwmChannel[PWM_PARAM_N_OUTPUTS] = {
kDifPwmChannel0, kDifPwmChannel1, kDifPwmChannel2,
kDifPwmChannel3, kDifPwmChannel4, kDifPwmChannel5,
};
// Duty cycle (arbitrary) values (in the beats)
const uint16_t kPwmDutycycle[PWM_PARAM_N_OUTPUTS] = {
6, 11, 27, 8, 17, 7,
};
const dif_pinmux_index_t kPinmuxMioOut[PWM_PARAM_N_OUTPUTS] = {
kTopEarlgreyPinmuxMioOutIob10, kTopEarlgreyPinmuxMioOutIob11,
kTopEarlgreyPinmuxMioOutIob12, kTopEarlgreyPinmuxMioOutIoc10,
kTopEarlgreyPinmuxMioOutIoc11, kTopEarlgreyPinmuxMioOutIoc12,
};
const dif_pinmux_index_t kPinmuxOutsel[PWM_PARAM_N_OUTPUTS] = {
kTopEarlgreyPinmuxOutselPwmAonPwm0, kTopEarlgreyPinmuxOutselPwmAonPwm1,
kTopEarlgreyPinmuxOutselPwmAonPwm2, kTopEarlgreyPinmuxOutselPwmAonPwm3,
kTopEarlgreyPinmuxOutselPwmAonPwm4, kTopEarlgreyPinmuxOutselPwmAonPwm5,
};
CHECK_DIF_OK(dif_pwm_configure(&pwm, config_));
// Configure each of the PWM channels:
dif_pwm_channel_config_t channel_config_ = default_ch_cfg_;
for (size_t 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 (size_t 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
// Configure OTP Control to do periodic "consistency" & "integrity" checks.
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
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);
if (kDeepSleep) {
// activate in Wakeup mode (no need for IRQ)
aon_timer_testutils_wakeup_config(&aon_timer, kTimeTillBark);
} else {
// Unmask the software interrupt so it can be used to bring the CPU out of
// sleep without having an NMI race WFI.
irq_software_ctrl(/*en=*/true);
// activate in Watchdog mode & IRQ
aon_timer_testutils_watchdog_config(&aon_timer, count_cycles, UINT32_MAX,
false);
}
LOG_INFO("AON Timer active");
// ADC Controller
const uint8_t kNumLowPowerSamples = 2;
const uint8_t kNumNormalPowerSamples = 1;
const uint64_t kPowerUpTime = 30;
const uint64_t kWakeUpTime = 500;
uint32_t power_up_time_aon_cycles =
aon_timer_testutils_get_aon_cycles_from_us(kPowerUpTime);
uint32_t wake_up_time_aon_cycles =
aon_timer_testutils_get_aon_cycles_from_us(kWakeUpTime);
// ADC configuration
CHECK_DIF_OK(dif_adc_ctrl_set_enabled(&adc_ctrl, kDifToggleDisabled));
CHECK_DIF_OK(dif_adc_ctrl_reset(&adc_ctrl));
CHECK_DIF_OK(dif_adc_ctrl_configure(
&adc_ctrl, (dif_adc_ctrl_config_t){
.mode = kDifAdcCtrlLowPowerScanMode,
.num_low_power_samples = kNumLowPowerSamples,
.num_normal_power_samples = kNumNormalPowerSamples,
.power_up_time_aon_cycles = power_up_time_aon_cycles,
.wake_up_time_aon_cycles = wake_up_time_aon_cycles}));
CHECK_DIF_OK(dif_adc_ctrl_set_enabled(&adc_ctrl, kDifToggleEnabled));
LOG_INFO("ADC Controller active");
// Power Manager
dif_pwrmgr_domain_config_t pwrmgr_cfg;
dif_pwrmgr_request_sources_t pwrmgr_wakeups;
// Specify which request sources are enabled for wake-up
pwrmgr_wakeups =
(kDifPwrmgrWakeupRequestSourceOne | kDifPwrmgrWakeupRequestSourceTwo |
kDifPwrmgrWakeupRequestSourceThree | kDifPwrmgrWakeupRequestSourceFour |
kDifPwrmgrWakeupRequestSourceFive | kDifPwrmgrWakeupRequestSourceSix);
// Power manager configuration
pwrmgr_cfg = ((kCoreClkOff ? 0 : kDifPwrmgrDomainOptionCoreClockInLowPower) |
(kIoClkOff ? 0 : kDifPwrmgrDomainOptionIoClockInLowPower) |
(kUsbSlpOff ? 0 : kDifPwrmgrDomainOptionUsbClockInLowPower) |
(kUsbActOff ? 0 : kDifPwrmgrDomainOptionUsbClockInActivePower) |
(kDeepSleep ? 0 : kDifPwrmgrDomainOptionMainPowerInLowPower));
pwrmgr_testutils_enable_low_power(&pwrmgr, pwrmgr_wakeups, pwrmgr_cfg);
LOG_INFO("Power Manage configured");
CHECK_DIF_OK(dif_gpio_write(&gpio, 2, 1));
LOG_INFO("all HW is active");
test_status_set(0xff20);
wait_for_interrupt();
// Check for software interrupt and clear. Re-initialize the struct in case
// the software interrupt did not happen.
mmio_region_t plic_base_addr =
mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR);
CHECK_DIF_OK(dif_rv_plic_init(plic_base_addr, &rv_plic));
bool software_irq_pending;
CHECK_DIF_OK(dif_rv_plic_software_irq_is_pending(&rv_plic, kPlicTarget,
&software_irq_pending));
CHECK(software_irq_pending,
"Software IRQ unexpectedly not pending after WFI");
CHECK_DIF_OK(dif_rv_plic_software_irq_acknowledge(&rv_plic, kPlicTarget));
// 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);
prepare_to_exit();
return true;
}