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

#ifndef _HARDWARE_FLASH_H
#define _HARDWARE_FLASH_H

#include "pico.h"

/** \file flash.h
 *  \defgroup hardware_flash hardware_flash
 *
 * \brief Low level flash programming and erase API
 *
 * Note these functions are *unsafe* if you are using both cores, and the other
 * is executing from flash concurrently with the operation. In this case, you
 * must perform your own synchronisation to make sure that no XIP accesses take
 * place during flash programming. One option is to use the
 * \ref multicore_lockout functions.
 *
 * Likewise they are *unsafe* if you have interrupt handlers or an interrupt
 * vector table in flash, so you must disable interrupts before calling in
 * this case.
 *
 * If PICO_NO_FLASH=1 is not defined (i.e. if the program is built to run from
 * flash) then these functions will make a static copy of the second stage
 * bootloader in SRAM, and use this to reenter execute-in-place mode after
 * programming or erasing flash, so that they can safely be called from
 * flash-resident code.
 *
 * \subsection flash_example Example
 * \include flash_program.c
 */

// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_HARDWARE_FLASH, Enable/disable assertions in the hardware_flash module, type=bool, default=0, group=hardware_flash
#ifndef PARAM_ASSERTIONS_ENABLED_HARDWARE_FLASH
#ifdef PARAM_ASSERTIONS_ENABLED_FLASH // backwards compatibility with SDK < 2.0.0
#define PARAM_ASSERTIONS_ENABLED_HARDWARE_FLASH PARAM_ASSERTIONS_ENABLED_FLASH
#else
#define PARAM_ASSERTIONS_ENABLED_HARDWARE_FLASH 0
#endif
#endif
#define FLASH_PAGE_SIZE (1u << 8)
#define FLASH_SECTOR_SIZE (1u << 12)
#define FLASH_BLOCK_SIZE (1u << 16)

#ifndef FLASH_UNIQUE_ID_SIZE_BYTES
#define FLASH_UNIQUE_ID_SIZE_BYTES 8
#endif

// PICO_CONFIG: PICO_FLASH_SIZE_BYTES, size of primary flash in bytes, type=int, default=Usually provided via board header, group=hardware_flash

#ifdef __cplusplus
extern "C" {
#endif

/*! \brief  Initialise QSPI interface and external QSPI devices for execute-in-place
 *  \ingroup hardware_flash
 *
 * This function performs the same first-time flash setup that would normally occur over the course
 * of the bootrom locating a flash binary and booting it, and that flash binary executing the SDK
 * crt0. Specifically:
 *
 * * Initialise QSPI pads to their default states, and (non-RP2040) disable pad isolation latches
 * * Issue a hardcoded sequence to attached QSPI devices to return them to a serial command state
 * * Flush the XIP cache
 * * Configure the QSPI interface for low-speed 03h reads
 * * If this is not a PICO_NO_FLASH=1 binary:
 *      * (RP2040) load a boot2 stage from the first 256 bytes of RAM and execute it
 *      * (non-RP2040) execute an XIP setup function stored in boot RAM by either the bootrom or by crt0
 *
 * This is mostly useful for initialising flash on a PICO_NO_FLASH=1 binary. (In spite of the name,
 * this binary type really means "preloaded to RAM" and there may still be a flash device.)
 *
 * This function does not preserve the QSPI interface state or pad state. This is in contrast to
 * most other functions in this library, which preserve at least the QSPI pad state. However, on
 * RP2350 it does preserve the QMI window 1 configuration if you have not opted into bootrom CS1
 * support via FLASH_DEVINFO.
 */
void flash_start_xip(void);


/*! \brief  Erase areas of flash
 *  \ingroup hardware_flash
 *
 * \param flash_offs Offset into flash, in bytes, to start the erase. Must be aligned to a 4096-byte flash sector.
 * \param count Number of bytes to be erased. Must be a multiple of 4096 bytes (one sector).
 *
 * @note Erasing a flash sector sets all the bits in all the pages in that sector to one.
 * You can then "program" flash pages in the sector to turn some of the bits to zero.
 * Once a bit is set to zero it can only be changed back to one by erasing the whole sector again.
 */
void flash_range_erase(uint32_t flash_offs, size_t count);

/*! \brief  Program flash
 *  \ingroup hardware_flash
 *
 * \param flash_offs Flash address of the first byte to be programmed. Must be aligned to a 256-byte flash page.
 * \param data Pointer to the data to program into flash
 * \param count Number of bytes to program. Must be a multiple of 256 bytes (one page).
 *
 * @note: Programming a flash page effectively changes some of the bits from one to zero.
 * The only way to change a zero bit back to one is to "erase" the whole sector that the page resides in.
 * So you may need to make sure you have called flash_range_erase before calling flash_range_program.
 */

void flash_range_program(uint32_t flash_offs, const uint8_t *data, size_t count);

/*! \brief Get flash unique 64 bit identifier
 *  \ingroup hardware_flash
 *
 * Use a standard 4Bh RUID instruction to retrieve the 64 bit unique
 * identifier from a flash device attached to the QSPI interface. Since there
 * is a 1:1 association between the MCU and this flash, this also serves as a
 * unique identifier for the board.
 *
 *  \param id_out Pointer to an 8-byte buffer to which the ID will be written
 */
void flash_get_unique_id(uint8_t *id_out);

/*! \brief Execute bidirectional flash command
 *  \ingroup hardware_flash
 *
 * Low-level function to execute a serial command on a flash device attached
 * to the QSPI interface. Bytes are simultaneously transmitted and received
 * from txbuf and to rxbuf. Therefore, both buffers must be the same length,
 * count, which is the length of the overall transaction. This is useful for
 * reading metadata from the flash chip, such as device ID or SFDP
 * parameters.
 *
 * The XIP cache is flushed following each command, in case flash state
 * has been modified. Like other hardware_flash functions, the flash is not
 * accessible for execute-in-place transfers whilst the command is in
 * progress, so entering a flash-resident interrupt handler or executing flash
 * code on the second core concurrently will be fatal. To avoid these pitfalls
 * it is recommended that this function only be used to extract flash metadata
 * during startup, before the main application begins to run: see the
 * implementation of pico_get_unique_id() for an example of this.
 *
 *  \param txbuf Pointer to a byte buffer which will be transmitted to the flash
 *  \param rxbuf Pointer to a byte buffer where data received from the flash will be written. txbuf and rxbuf may be the same buffer.
 *  \param count Length in bytes of txbuf and of rxbuf
 */
void flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count);

void flash_flush_cache(void);

#if !PICO_RP2040
typedef enum {
    FLASH_DEVINFO_SIZE_NONE = 0x0,
    FLASH_DEVINFO_SIZE_8K = 0x1,
    FLASH_DEVINFO_SIZE_16K = 0x2,
    FLASH_DEVINFO_SIZE_32K = 0x3,
    FLASH_DEVINFO_SIZE_64K = 0x4,
    FLASH_DEVINFO_SIZE_128K = 0x5,
    FLASH_DEVINFO_SIZE_256K = 0x6,
    FLASH_DEVINFO_SIZE_512K = 0x7,
    FLASH_DEVINFO_SIZE_1M = 0x8,
    FLASH_DEVINFO_SIZE_2M = 0x9,
    FLASH_DEVINFO_SIZE_4M = 0xa,
    FLASH_DEVINFO_SIZE_8M = 0xb,
    FLASH_DEVINFO_SIZE_16M = 0xc,
    FLASH_DEVINFO_SIZE_MAX = 0xc
} flash_devinfo_size_t;

/*! \brief Convert a flash/PSRAM size enum to an integer size in bytes
 *  \ingroup hardware_flash
 */
static inline uint32_t flash_devinfo_size_to_bytes(flash_devinfo_size_t size) {
    if (size == FLASH_DEVINFO_SIZE_NONE) {
        return 0;
    } else {
        return 4096u << (uint)size;
    }
}

/*! \brief Convert an integer flash/PSRAM size in bytes to a size enum, as
  !  stored in OTP and used by the ROM.
 *  \ingroup hardware_flash
 */
static inline flash_devinfo_size_t flash_devinfo_bytes_to_size(uint32_t bytes) {
    // Must be zero or a power of two
    valid_params_if(HARDWARE_FLASH, (bytes & (bytes - 1)) == 0u);
    uint sectors = bytes / 4096u;
    if (sectors <= 1u) {
        return FLASH_DEVINFO_SIZE_NONE;
    } else {
        return (flash_devinfo_size_t) __builtin_ctz(sectors);
    }
}

/*! \brief Get the size of the QSPI device attached to chip select cs, according to FLASH_DEVINFO
 *  \ingroup hardware_flash
 *
 * \param cs Chip select index: 0 is QMI chip select 0 (QSPI CS pin), 1 is QMI chip select 1.
 *
 * The bootrom reads the FLASH_DEVINFO OTP data entry from OTP into boot RAM during startup. This
 * contains basic information about the flash device which can be queried without communicating
 * with the external device.(There are several methods to determine the size of a QSPI device over
 * QSPI, but none are universally supported.)
 *
 * Since the FLASH_DEVINFO information is stored in boot RAM at runtime, it can be updated. Updates
 * made in this way persist until the next reboot. The ROM uses this device information to control
 * some low-level flash API behaviour, such as issuing an XIP exit sequence to CS 1 if its size is
 * nonzero.
 *
 * If the macro PICO_FLASH_SIZE_BYTES is specified, this overrides the value for chip select 0. This
 * can be specified in a board header if a board is always equipped with the same size of flash.
 */
flash_devinfo_size_t flash_devinfo_get_cs_size(uint cs);

/*! \brief Update the size of the QSPI device attached to chip select cs in the runtime copy
 *         of FLASH_DEVINFO.
 *
 *  \ingroup hardware_flash
 *
 * \param cs Chip select index: 0 is QMI chip select 0 (QSPI CS pin), 1 is QMI chip select 1.
 *
 * \param size The size of the attached device, or FLASH_DEVINFO_SIZE_NONE if there is none on this
 *  chip select.
 *
 * The bootrom maintains a copy in boot RAM of the FLASH_DEVINFO information read from OTP during
 * startup. This function updates that copy to reflect runtime information about the sizes of
 * attached QSPI devices.
 *
 * This controls the behaviour of some ROM flash APIs, such as bounds checking addresses for
 * erase/programming in the checked_flash_op() API, or issuing an XIP exit sequence to CS 1 in
 * flash_exit_xip() if the size is nonzero.
 */
void flash_devinfo_set_cs_size(uint cs, flash_devinfo_size_t size);

/*! \brief Check whether all attached devices support D8h block erase with 64k size, according to
 *         FLASH_DEVINFO.
 *
 *  \ingroup hardware_flash
 *
 * This controls whether checked_flash_op() ROM API uses D8h 64k block erase where possible, for
 * faster erase times. If not, this ROM API always uses 20h 4k sector erase.
 *
 * The bootrom loads this flag from the OTP FLASH_DEVINFO data entry during startup, and stores it
 * in boot RAM. You can update the boot RAM copy based on runtime knowledge of the attached QSPI
 * devices.
 */
bool flash_devinfo_get_d8h_erase_supported(void);

/*! \brief Specify whether all attached devices support D8h block erase with 64k size, in the
 *         runtime copy of FLASH_DEVINFO
 *
 *  \ingroup hardware_flash
 *
 * This function updates the boot RAM copy of OTP FLASH_DEVINFO. The flag passed here is visible to
 * ROM APIs, and is also returned in the next call to flash_devinfo_get_d8h_erase_supported()
 */
void flash_devinfo_set_d8h_erase_supported(bool supported);

/*! \brief Check the GPIO allocated for each chip select, according to FLASH_DEVINFO
 *  \ingroup hardware_flash
 *
 * \param cs Chip select index (only the value 1 is supported on RP2350)
 */
uint flash_devinfo_get_cs_gpio(uint cs);

/*! \brief Update the GPIO allocated for each chip select in the runtime copy of FLASH_DEVINFO
 *  \ingroup hardware_flash
 *
 * \param cs Chip select index (only the value 1 is supported on RP2350)
 *
 * \param gpio GPIO index (must be less than NUM_BANK0_GPIOS)
 */
void flash_devinfo_set_cs_gpio(uint cs, uint gpio);

#endif // !PICO_RP2040

#ifdef __cplusplus
}
#endif

#endif
