/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2013-2018 Damien P. George
 * Copyright (c) 2022 Renesas Electronics Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "py/runtime.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "hal_data.h"
#include "rtc.h"
#include "powerctrl.h"

#if 0

// Sleep Mode
// At power on, by default sleep is set as the low-power mode. Sleep
// mode is the most convenient low-power mode available, as it does
// not require any special configuration (other than configuring and
// enabling a suitable interrupt or event to wake the MCU from sleep)
// to return to normal program-execution mode.
// The states of the SRAM, the processor registers, and the hardware
// peripherals are all maintained in sleep mode, and the time needed
// to enter and wake from sleep is minimal. Any interrupt causes the
// MCU device to wake from sleep mode, including the Systick interrupt
// used by the RTOS scheduler.

// Software Standby Mode
// In software-standby mode, the CPU, as well as most of the on-chip
// peripheral functions and all of the internal oscillators, are
// stopped.
// The contents of the CPU internal registers and SRAM data, the states
// of on-chip peripheral functions, and I/O Ports are all retained.
// Software-standby mode allows significant reduction in power
// consumption, because most of the oscillators are stopped in this mode.
// Like sleep mode, standby mode requires an interrupt or event be
// configured and enabled to wake up.

// Snooze Mode
// Snooze mode can be used with some MCU peripherals to execute basic
// tasks while keeping the MCU in a low-power state.
// Many core peripherals and all clocks can be selected to run during
// Snooze, allowing for more flexible low-power configuration than
// Software Standby mode. To enable Snooze, select "Software Standby
// mode with Snooze mode enabled" for the "Low Power Mode" configuration
// option.
// Snooze mode settings (including entry/exit sources) are available
// under "Standby Options".

// Deep Software Standby Mode
// Deep Software Standby Mode is only available on some MCU devices.
// The MCU always wakes from Deep Software Standby Mode by going
// through reset, either by the negation of the reset pin or by one of
// the wakeup sources configurable in the "Deep Standby Options"
// configuration group.

#endif

#if defined(USE_FSP_LPM)

// LPM_MODE_SLEEP:              Sleep mode
// LPM_MODE_STANDBY:            Software Standby mode
// LPM_MODE_STANDBY_SNOOZE:     Software Standby mode with Snooze mode enabled
// LPM_MODE_DEEP:               Deep Software Standby mode

lpm_instance_ctrl_t g_lpm_sleep_ctrl;

const lpm_cfg_t g_lpm_sleep_cfg = {
    .low_power_mode = LPM_MODE_SLEEP,
    .snooze_cancel_sources = LPM_SNOOZE_CANCEL_SOURCE_NONE,
    .standby_wake_sources = (lpm_standby_wake_source_t)0,
    .snooze_request_source = LPM_SNOOZE_REQUEST_RTC_PERIOD,
    .snooze_end_sources = (lpm_snooze_end_t)0,
    .dtc_state_in_snooze = LPM_SNOOZE_DTC_DISABLE,
    #if BSP_FEATURE_LPM_HAS_SBYCR_OPE
    .output_port_enable = 0,
    #endif
    #if BSP_FEATURE_LPM_HAS_DEEP_STANDBY
    .io_port_state = 0,
    .power_supply_state = 0,
    .deep_standby_cancel_source = (lpm_deep_standby_cancel_source_t)0,
    .deep_standby_cancel_edge = (lpm_deep_standby_cancel_edge_t)0,
    #endif
    .p_extend = NULL,
};

const lpm_instance_t g_lpm_sleep = {
    .p_api = &g_lpm_on_lpm,
    .p_ctrl = &g_lpm_sleep_ctrl,
    .p_cfg = &g_lpm_sleep_cfg
};

lpm_instance_ctrl_t g_lpm_deep_ctrl;

const lpm_cfg_t g_lpm_deep_cfg = {
    .low_power_mode = LPM_MODE_DEEP,
    .snooze_cancel_sources =
        LPM_SNOOZE_CANCEL_SOURCE_NONE,
    .standby_wake_sources =
        (lpm_standby_wake_source_t)0,
    .snooze_request_source =
        LPM_SNOOZE_REQUEST_RXD0_FALLING,
    .snooze_end_sources =
        (lpm_snooze_end_t)0,
    .dtc_state_in_snooze =
        LPM_SNOOZE_DTC_DISABLE,
    #if BSP_FEATURE_LPM_HAS_SBYCR_OPE
    .output_port_enable = LPM_OUTPUT_PORT_ENABLE_RETAIN,
    #endif
    #if BSP_FEATURE_LPM_HAS_DEEP_STANDBY
    .io_port_state = LPM_IO_PORT_NO_CHANGE,
    .power_supply_state = LPM_POWER_SUPPLY_DEEPCUT0,
    .deep_standby_cancel_source = LPM_DEEP_STANDBY_CANCEL_SOURCE_RTC_INTERVAL | LPM_DEEP_STANDBY_CANCEL_SOURCE_RTC_ALARM | (lpm_deep_standby_cancel_source_t)0,
    .deep_standby_cancel_edge = (lpm_deep_standby_cancel_edge_t)0,
    #endif
    .p_extend = NULL,
};

const lpm_instance_t g_lpm_deep = {
    .p_api = &g_lpm_on_lpm,
    .p_ctrl = &g_lpm_deep_ctrl,
    .p_cfg = &g_lpm_deep_cfg
};

lpm_instance_ctrl_t g_lpm_standby_ctrl;

const lpm_cfg_t g_lpm_standby_cfg = {
    .low_power_mode = LPM_MODE_STANDBY,
    .snooze_cancel_sources =
        LPM_SNOOZE_CANCEL_SOURCE_NONE,
    .standby_wake_sources =
        LPM_STANDBY_WAKE_SOURCE_RTCALM | LPM_STANDBY_WAKE_SOURCE_RTCPRD | (lpm_standby_wake_source_t)0,
    .snooze_request_source = LPM_SNOOZE_REQUEST_RXD0_FALLING,
    .snooze_end_sources = (lpm_snooze_end_t)0,
    .dtc_state_in_snooze = LPM_SNOOZE_DTC_DISABLE,
    #if BSP_FEATURE_LPM_HAS_SBYCR_OPE
    .output_port_enable = LPM_OUTPUT_PORT_ENABLE_RETAIN,
    #endif
    #if BSP_FEATURE_LPM_HAS_DEEP_STANDBY
    .io_port_state = LPM_IO_PORT_NO_CHANGE,
    .power_supply_state = LPM_POWER_SUPPLY_DEEPCUT0,
    .deep_standby_cancel_source = LPM_DEEP_STANDBY_CANCEL_SOURCE_RTC_INTERVAL | (lpm_deep_standby_cancel_source_t)0,
    .deep_standby_cancel_edge = (lpm_deep_standby_cancel_edge_t)0,
    #endif
    .p_extend = NULL,
};

const lpm_instance_t g_lpm_standby = {
    .p_api = &g_lpm_on_lpm,
    .p_ctrl = &g_lpm_standby_ctrl,
    .p_cfg = &g_lpm_standby_cfg
};

#endif

NORETURN void powerctrl_mcu_reset(void) {
    #if BSP_TZ_SECURE_BUILD
    R_BSP_NonSecureEnter();
    #else
    NVIC_SystemReset();
    #endif
    while (1) {
        ;
    }
}

NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) {
    while (1) {
        ;
    }
}

// static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, uint32_t
// bl_addr) {
// }

void powerctrl_check_enter_bootloader(void) {
}

void powerctrl_enter_sleep_mode(void) {
    // start trandition to RA MCU sleep mode
    #if defined(USE_FSP_LPM)
    fsp_err_t err;
    err = R_LPM_Open(&g_lpm_sleep_ctrl, &g_lpm_sleep_cfg);
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter sleep mode"));
    }
    err = R_LPM_LowPowerModeEnter(&g_lpm_sleep_ctrl);
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter sleep mode"));
    }
    #endif
}

void powerctrl_enter_stop_mode(void) {
    // Disable IRQs so that the IRQ that wakes the device from stop mode is not
    // executed until after the clocks are reconfigured
    uint32_t irq_state = disable_irq();

    #if defined(MICROPY_BOARD_ENTER_STOP)
    MICROPY_BOARD_ENTER_STOP
    #endif

    // start trandition to RA MCU sleep mode
    #if defined(USE_FSP_LPM)
    fsp_err_t err;
    err = R_LPM_Open(&g_lpm_standby_ctrl, &g_lpm_standby_cfg);
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter stop mode"));
    }
    err = R_LPM_LowPowerModeEnter(&g_lpm_standby_ctrl);
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter stop mode"));
    }
    #endif

    // reconfigure the system clock after waking up

    #if defined(MICROPY_BOARD_LEAVE_STOP)
    MICROPY_BOARD_LEAVE_STOP
    #endif

    // Enable IRQs now that all clocks are reconfigured
    enable_irq(irq_state);
}

NORETURN void powerctrl_enter_standby_mode(void) {
    rtc_init_finalise();

    #if defined(MICROPY_BOARD_ENTER_STANDBY)
    MICROPY_BOARD_ENTER_STANDBY
    #endif

    // start trandition to RA MCU deep software standby mode via software standby mode
    #if defined(USE_FSP_LPM)

    fsp_err_t err;
    err = R_LPM_Open(&g_lpm_deep_ctrl, &g_lpm_deep_cfg);
    /* Handle any errors. This function should be defined by the user. */
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter stop mode"));
    }
    /* Check the Deep Software Standby Reset Flag. */
    if (1U == R_SYSTEM->RSTSR0_b.DPSRSTF) {
        /* Clear the IOKEEP bit to allow I/O Port use. */
        err = R_LPM_IoKeepClear(&g_lpm_deep_ctrl);
        if (err != FSP_SUCCESS) {
            mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter stop mode"));
        }
    }
    /* Add user code here. */
    /* Reconfigure the module to set the IOKEEP bit before entering deep software standby. */
    err = R_LPM_LowPowerReconfigure(&g_lpm_deep_ctrl, &g_lpm_deep_cfg);
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter stop mode"));
    }
    err = R_LPM_LowPowerModeEnter(&g_lpm_deep_ctrl);
    /* Code after R_LPM_LowPowerModeEnter when using Deep Software Standby never be executed.
     * Deep software standby exits by resetting the MCU. */
    if (err != FSP_SUCCESS) {
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Can't enter stop mode"));
    }

    #endif

    // We need to clear the PWR wake-up-flag before entering standby, since
    // the flag may have been set by a previous wake-up event.  Furthermore,
    // we need to disable the wake-up sources while clearing this flag, so
    // that if a source is active it does actually wake the device.
    // See section 5.3.7 of RM0090.

    // save RTC interrupts
    // disable register write protection
    // disable RTC interrupts
    // clear RTC wake-up flags

    // enable previously-enabled RTC interrupts
    // enable register write protection

    // enter standby mode

    // we never return; MCU is reset on exit from standby

    powerctrl_mcu_reset();
}
