/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "system/status_codes.h"

//! Flash Low-Level API
//!
//! Unless otherwise specified, this API is non-reentrant. It is unsafe to
//! call a function in one thread while another function is being executed in a
//! second thread, and it is unsafe to call these functions from within a
//! flash_impl callback.

typedef uint32_t FlashAddress;

//! Initialize the low-level flash implementation and hardware into a known
//! state where it is ready to accept commands.
//!
//! This function configures microcontroller peripherals. It should be guarded
//! with periph_config_acquire_lock/periph_config_release_lock.
//!
//! @param coredump_mode True if we need this flash driver to not rely on any other system
//!                      services such as FreeRTOS being available because we're in the middle
//!                      of a core dump. This may result in slower operations.
status_t flash_impl_init(bool coredump_mode);

//! Enable or disable synchronous burst mode, if supported.
//!
//! Burst mode is disabled whenever \ref flash_impl_init is called.
//!
//! The result is undefined if this function is called while any other flash
//! operation is in progress.
status_t flash_impl_set_burst_mode(bool enable);

//! Return the base address of the sector overlapping the given address.
//!
//! This function is reentrant.
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr);

//! Return the base address of the subsector overlapping the given address.
//!
//! This function is reentrant.
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr);

//! Query the flash hardware for its capacity in bytes.
size_t flash_impl_get_capacity(void);

//! Enter a low-power state.
//!
//! Once in a low-power mode, all operations may fail until
//! \ref flash_impl_exit_low_power_mode is called. This function is idempotent.
status_t flash_impl_enter_low_power_mode(void);

//! Exit a low-power state.
//!
//! Return the flash to a fully operational mode. This may be a time-intensive
//! operation. This function is idempotent.
status_t flash_impl_exit_low_power_mode(void);

//! Read data into a buffer.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_read_sync(void *buffer, FlashAddress addr, size_t len);

//! Initiate a DMA-accelerated flash read.
//!
//! The caller must ensure that the DMA transfer will not be interfered with
//! by any clock changes or stoppages externally. (read: inhibit stop mode)
//!
//! This function will return immediately once the transfer has begun.
//! \ref flash_impl_on_read_dma_complete_from_isr will be called from an
//! interrupt context to signal that the transfer has completed. The effect of
//! calling flash_impl_read_dma_begin a second time while another DMA transfer
//! is currently in progress is undefined.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_read_dma_begin(void *buffer, FlashAddress addr,
                                   size_t len);

//! Called from an interrupt context when the DMA read has completed. It is
//! guaranteed that the call is made from an interrupt of low enough priority
//! that RTOS API calls are safe to use, and that it is a tail-call from the end
//! of the implementation's ISR (read: portEND_SWITCHING_ISR is permissible).
//!
//! @param result S_SUCCESS iff the read completed successfully.
extern void flash_impl_on_read_dma_complete_from_isr(status_t result);

//! If the flash part requires write protection to be explicitly enabled, enable it.
void flash_impl_enable_write_protection(void);

//! Write protect a region of flash. Only one region may be protected at any
//! given time.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_write_protect(FlashAddress start_sector,
                                  FlashAddress end_sector);

//! Remove write protection.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_unprotect(void);

//! Write a page of bytes to flash.
//!
//! @param buffer The source buffer.
//! @param addr Destination flash address.
//! @param len Length to write.
//! @return The number of bytes that will be written to flash, assuming that the
//!         write completes successfully, or a StatusCode error if there was an
//!         error starting the write operation.
//!
//! Each call to flash_impl_write_page_begin begins a single flash write
//! operation, writing the maximum amount of data supported by the hardware in
//! a single operation. Multiple page writes may be required to write a complete
//! buffer to flash.
//!
//! Example usage:
//! \code
//!   while (len) {
//!     int written = flash_impl_write_page_begin(buffer, addr, len));
//!     if (written < 0) {
//!       // Handle error
//!     }
//!     status_t status;
//!     while ((status = flash_impl_get_write_status()) == E_AGAIN) {
//!       continue;
//!     }
//!     if (status != S_SUCCESS) {
//!       // Handle error
//!     }
//!     buffer += written;
//!     addr += written;
//!     len -= written;
//!   }
//! \endcode
//!
//! The result is undefined if this function is called while a read or erase is
//! in progress. It is an error to call this function while a write is
//! in progress or suspended.
int flash_impl_write_page_begin(const void *buffer, FlashAddress addr,
                                size_t len);

//! Poll the status of a flash page write.
//!
//! @return S_SUCCESS if the write has succeeded, E_ERROR if the write has
//!         failed, E_BUSY if the write is still in progress or E_AGAIN if the
//!         write is suspended.
status_t flash_impl_get_write_status(void);

//! Suspend an in-progress write so that reads and erases are permitted.
//!
//! @param addr The address passed to the \ref flash_impl_write_page_begin
//!        call which initiated the write being suspended.
status_t flash_impl_write_suspend(FlashAddress addr);

//! Resume a previously-suspended write.
//!
//! @param addr The address passed to \ref flash_impl_write_suspend.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress.
status_t flash_impl_write_resume(FlashAddress addr);

//! Erase the subsector which overlaps the given address.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress. It is an error to call this function while an erase is
//! suspended.
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr);

//! Erase the sector which overlaps the given address.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress. It is an error to call this function while an erase is
//! suspended.
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr);

//! Erase the entire flash.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress. It is an error to call this function while an erase is
//! suspended.
status_t flash_impl_erase_bulk_begin(void);

//! Poll the status of a flash erase.
//!
//! @return S_SUCCESS if the erase has succeeded, E_ERROR if the erase has
//!         failed, E_BUSY if the erase is still in progress or E_AGAIN if the
//!         erase is suspended.
status_t flash_impl_get_erase_status(void);

//! Returns the typical duration of a subsector erase, in milliseconds.
//!
//! This function is reentrant.
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void);

//! Returns the typical duration of a sector erase, in milliseconds.
//!
//! This function is reentrant.
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void);

//! Suspend an in-progress erase so that reads and writes are permitted.
//!
//! @param addr The sector address passed to the
//!   \ref flash_impl_erase_subsector_begin or
//!   \ref flash_impl_erase_sector_begin call which initiated the erase being
//!   suspended.
//!
//! @return S_SUCCESS if the erase has been suspended, S_NO_ACTION_REQUIRED if
//!         there was no erase in progress at the time, or an error code.
status_t flash_impl_erase_suspend(FlashAddress addr);

//! Resume a previously-suspended erase.
//!
//! @param addr The address passed to \ref flash_impl_erase_suspend.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress.
status_t flash_impl_erase_resume(FlashAddress addr);

//! Check whether the subsector overlapping the specified address is blank
//! (reads as all 1's).
//!
//! @param addr An address within the subsector being checked.
//!
//! @return S_TRUE if blank, S_FALSE if any bit in the sector has been
//!         programmed, or E_BUSY if another flash operation is in progress.
//!
//! This operation is hardware-accelerated if possible. This operation may not
//! be performed if any reads, writes, or erases are in progress or suspended,
//! and this operation cannot be suspended once initiated. The result is
//! undefined if any other flash operation is initiated or in progress while a
//! blank check operation is in progress.
//!
//! @warning This function may return S_TRUE on a subsector where an erase
//!          operation was terminated prematurely. While such a subsector may
//!          read back as blank, data loss may occur and writes may fail if the
//!          subsector is not erased fully before it is written to.
status_t flash_impl_blank_check_subsector(FlashAddress addr);

//! Check whether the sector overlapping the specified address is blank (reads
//! as all 1's).
//!
//! @param addr An address within the sector being checked.
//!
//! @return S_TRUE if blank, S_FALSE if any bit in the sector has been
//!         programmed, or E_BUSY if another flash operation is in progress.
//!
//! This operation is hardware-accelerated if possible. This operation may not
//! be performed if any reads, writes, or erases are in progress or suspended,
//! and this operation cannot be suspended once initiated. The result is
//! undefined if any other flash operation is initiated or in progress while a
//! blank check operation is in progress.
//!
//! @warning This function may return S_TRUE on a sector where an erase
//!          operation was terminated prematurely. While such a sector may read
//!          back as blank, data loss may occur and writes may fail if the
//!          sector is not erased fully before it is written to.
status_t flash_impl_blank_check_sector(FlashAddress addr);

//! Save the address of an erase in progress to a nonvolatile location. The
//! erase address, along with the fact that an erase is in progress, must be
//! able to survive a system crash and reboot.
//!
//! @note Writing this data to the same flash array that is being erased is
//!       almost certainly a bad idea.
//!
//! @param is_subsector True if the erase is a subsector.
//!
//! @param addr The address being erased.
//!
//! @return S_SUCCESS if the data was successfully stored.
status_t flash_impl_set_nvram_erase_status(bool is_subsector,
                                           FlashAddress addr);

//! Save to a nonvolatile location the fact that no erase is in progress.
//!
//! @return S_SUCCESS if the status was successfully stored.
status_t flash_impl_clear_nvram_erase_status(void);

//! Retrieve the erase status previously set by
//! flash_impl_set_nvram_erase_status or flash_impl_clear_nvram_erase_status.
//!
//! @param [out] is_subsector The value of is_subsector passed to the most
//!   most recent call to flash_impl_set_nvram_erase_status if the status was
//!   not subsequently cleared by flash_impl_clear_nvram_erase_status. The
//!   pointer should not be written to if the erase status was cleared.
//!
//! @param [out] addr The address passed to the most recent call to
//!   flash_impl_set_nvram_erase_status if the status was not subsequently
//!   cleared by flash_impl_clear_nvram_erase_status. The address should not be
//!   written to if the erase status was cleared.
//!
//! @return S_TRUE if an erase was in progress; S_FALSE otherwise.
status_t flash_impl_get_nvram_erase_status(bool *is_subsector,
                                           FlashAddress *addr);

void flash_impl_use(void);
void flash_impl_release(void);
void flash_impl_release_many(uint32_t num_locks);
