/*
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/** \file platform.h
 *  \defgroup pico_platform pico_platform
 *
 * \brief Macros and definitions (and functions when included by non assembly code) for the RP2 family device / architecture
 * to provide a common abstraction over low level compiler / platform specifics
 *
 * This header may be included by assembly code
 */

#ifndef _PICO_PLATFORM_H
#define _PICO_PLATFORM_H

#ifndef _PICO_H
#error pico/platform.h should not be included directly; include pico.h instead
#endif

// this is used by sections.h, so comes before the includes
// PICO_CONFIG: PICO_USE_XIP_CACHE_AS_RAM, Whether to use xip cache as ram, default=0, type=bool, group=pico_platform
#ifndef PICO_USE_XIP_CACHE_AS_RAM
#define PICO_USE_XIP_CACHE_AS_RAM 0
#endif

#include "pico/platform/compiler.h"
#include "pico/platform/sections.h"
#include "pico/platform/panic.h"
#include "pico/platform/common.h"
#include "hardware/regs/addressmap.h"
#include "hardware/regs/sio.h"
#ifdef __riscv
#include "hardware/regs/rvcsr.h"
#endif

// PICO_CONFIG: PICO_RP2350A, Whether the current board has an RP2350 in an A (30 GPIO) package - set to 0 for RP2350 in a B (48 GPIO) package, type=bool, default=Usually provided via board header, group=pico_platform
#if 0 // make tooling checks happy
#define PICO_RP2350A 0
#endif

// PICO_CONFIG: PICO_RP2350_A2_SUPPORTED, Whether to include any specific software support for RP2350 A2 revision, type=bool, default=1, advanced=true, group=pico_platform
#ifndef PICO_RP2350_A2_SUPPORTED
#define PICO_RP2350_A2_SUPPORTED 1
#endif

// PICO_CONFIG: PICO_STACK_SIZE, Minimum amount of stack space reserved in the linker script for each core. See also PICO_CORE1_STACK_SIZE, min=0x100, default=0x800, advanced=true, group=pico_platform
#ifndef PICO_STACK_SIZE
#define PICO_STACK_SIZE _u(0x800)
#endif

// PICO_CONFIG: PICO_HEAP_SIZE, Minimum amount of heap space reserved by the linker script, min=0x100, default=0x800, advanced=true, group=pico_platform
#ifndef PICO_HEAP_SIZE
#define PICO_HEAP_SIZE _u(0x800)
#endif

// PICO_CONFIG: PICO_NO_RAM_VECTOR_TABLE, Enable/disable the RAM vector table, type=bool, default=0, advanced=true, group=pico_platform
#ifndef PICO_NO_RAM_VECTOR_TABLE
#define PICO_NO_RAM_VECTOR_TABLE 0
#endif

// PICO_CONFIG: PICO_USE_STACK_GUARDS, Enable/disable stack guards, type=bool, default=0, advanced=true, group=pico_platform
#ifndef PICO_USE_STACK_GUARDS
#define PICO_USE_STACK_GUARDS 0
#endif

// PICO_CONFIG: PICO_CLKDIV_ROUND_NEAREST, True if floating point clock divisors should be rounded to the nearest possible clock divisor by default rather than rounding down, type=bool, default=1, group=pico_platform
#ifndef PICO_CLKDIV_ROUND_NEAREST
#define PICO_CLKDIV_ROUND_NEAREST 1
#endif

#ifndef __ASSEMBLER__

#ifdef __cplusplus
extern "C" {
#endif
/*! \brief Helper method to busy-wait for at least the given number of cycles
 *  \ingroup pico_platform
 *
 * This method is useful for introducing very short delays.
 *
 * This method busy-waits in a tight loop for the given number of system clock cycles. The total wait time is only accurate to within 2 cycles,
 * and this method uses a loop counter rather than a hardware timer, so the method will always take longer than expected if an
 * interrupt is handled on the calling core during the busy-wait; you can of course disable interrupts to prevent this.
 *
 * You can use \ref clock_get_hz(clk_sys) to determine the number of clock cycles per second if you want to convert an actual
 * time duration to a number of cycles.
 *
 * \param minimum_cycles the minimum number of system clock cycles to delay for
 */
static inline void busy_wait_at_least_cycles(uint32_t minimum_cycles) {
    pico_default_asm_volatile (
#ifdef __riscv
            // Note the range is halved on RISC-V due to signed comparison (no carry flag)
            ".option push\n"
            ".option norvc\n" // force 32 bit addi, so branch prediction guaranteed
            ".p2align 2\n"
            "1: \n"
            "addi %0, %0, -2 \n"
            "bgez %0, 1b\n"
            ".option pop"
#else
    "1: subs %0, #3\n"
    "bcs 1b\n"
#endif
    : "+r" (minimum_cycles) : : "cc", "memory"
    );
}

/*! \brief Execute a breakpoint instruction
 *  \ingroup pico_platform
 */
static __force_inline void __breakpoint(void) {
#ifdef __riscv
    __asm ("ebreak");
#else
    pico_default_asm_volatile ("bkpt #0" : : : "memory");
#endif
}

/*! \brief Get the current core number
 *  \ingroup pico_platform
 *
 * \return The core number the call was made from
 */
__force_inline static uint get_core_num(void) {
    return (*(uint32_t *) (SIO_BASE + SIO_CPUID_OFFSET));
}

/*! \brief Get the current exception level on this core
 *  \ingroup pico_platform
 *
 * On Cortex-M this is the exception number defined in the architecture
 * reference, which is equal to VTABLE_FIRST_IRQ + irq num if inside an
 * interrupt handler. (VTABLE_FIRST_IRQ is defined in platform_defs.h).
 *
 * On Hazard3, this function returns VTABLE_FIRST_IRQ + irq num if inside of
 * an external IRQ handler (or a fault from such a handler), and 0 otherwise,
 * generally aligning with the Cortex-M values.
 *
 * \return the exception number if the CPU is handling an exception, or 0 otherwise
 */
static __force_inline uint __get_current_exception(void) {
#ifdef __riscv
    uint32_t meicontext;
    pico_default_asm_volatile (
            "csrr %0, %1\n"
    : "=r" (meicontext) : "i" (RVCSR_MEICONTEXT_OFFSET)
    );
    if (meicontext & RVCSR_MEICONTEXT_NOIRQ_BITS) {
        return 0;
    } else {
        return VTABLE_FIRST_IRQ + (
                (meicontext & RVCSR_MEICONTEXT_IRQ_BITS) >> RVCSR_MEICONTEXT_IRQ_LSB
        );
    }
#else
    uint exception;
    pico_default_asm_volatile (
        "mrs %0, ipsr\n"
        "uxtb %0, %0\n"
        : "=l" (exception)
    );
    return exception;
#endif
}

/*! \brief Return true if executing in the NonSecure state (Arm-only)
 *  \ingroup pico_platform
 *
 * \return True if currently executing in the NonSecure state on an Arm processor
 */
__force_inline static bool pico_processor_state_is_nonsecure(void) {
#ifndef __riscv
    // todo add a define to disable NS checking at all?
    // IDAU-Exempt addresses return S=1 when tested in the Secure state,
    // whereas executing a tt in the NonSecure state will always return S=0.
    uint32_t tt;
    pico_default_asm_volatile (
        "movs %0, #0\n"
        "tt %0, %0\n"
        : "=r" (tt) : : "cc"
    );
    return !(tt & (1u << 22));
#else
    // NonSecure is an Arm concept, there is nothing meaningful to return
    // here. Note it's not possible in general to detect whether you are
    // executing in U-mode as, for example, M-mode is classically
    // virtualisable in U-mode.
    return false;
#endif
}

/*! \brief Returns the RP2350 chip revision number
 *  \ingroup pico_platform
 * @return the RP2350 chip revision number (2 for A2, 3 for A3/A4)
 */
uint8_t rp2350_chip_version(void);

/*! \brief Returns the RP2040 chip revision number for compatibility
 *  \ingroup pico_platform
 * @return 2 RP2040 errata fixed in B2 are fixed in RP2350
 */
static inline uint8_t rp2040_chip_version(void) {
    return 2;
}

/*! \brief Returns the RP2350 rom version number
 *  \ingroup pico_platform
 * @return the RP2350 rom version number (2 for RP2350-A2, 3 for RP2350-A3, 4 for RP2350-A4)
 */
static inline uint8_t rp2350_rom_version(void) {
    GCC_Pragma("GCC diagnostic push")
    GCC_Pragma("GCC diagnostic ignored \"-Warray-bounds\"")
    return *(uint8_t*)0x13;
    GCC_Pragma("GCC diagnostic pop")
}

/*! \brief Multiply two integers using an assembly `MUL` instruction
 *  \ingroup pico_platform
 *
 * This multiplies a by b using multiply instruction using the ARM mul instruction regardless of values (the compiler
 * might otherwise choose to perform shifts/adds), i.e. this is a 1 cycle operation.
 *
 * \param a the first operand
 * \param b the second operand
 * \return a * b
 */
__force_inline static int32_t __mul_instruction(int32_t a, int32_t b) {
#ifdef __riscv
    __asm ("mul %0, %0, %1" : "+r" (a) : "r" (b) : );
#else
    pico_default_asm ("muls %0, %1"     : "+l" (a) : "l" (b) : "cc");
#endif
    return a;
}

/*! \brief multiply two integer values using the fastest method possible
 *  \ingroup pico_platform
 *
 * Efficiently multiplies value a by possibly constant value b.
 *
 * If b is known to be constant and not zero or a power of 2, then a mul instruction is used rather than gcc's default
 * which is often a slow combination of shifts and adds. If b is a power of 2 then a single shift is of course preferable
 * and will be used
 *
 * \param a the first operand
 * \param b the second operand
 * \return a * b
 */
#define __fast_mul(a, b) (__builtin_constant_p(b) && !__builtin_constant_p(a) && __builtin_popcount(b) >= 2 ? __mul_instruction(a,b) : (a)*(b))

#ifdef __cplusplus
}
#endif

#endif // __ASSEMBLER__

#endif

