/**
 * \file
 *
 * \brief Non Volatile Memory controller driver
 *
 * Copyright (c) 2010-2013 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */
#ifndef NVM_H
#define NVM_H

#include <compiler.h>
#include <ccp.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * \defgroup nvm_group NVM driver
 *
 * See \ref xmega_nvm_quickstart
 *
 * \brief Low-level driver implementation for the AVR XMEGA Non Volatile
 *        Memory Controller (NVM).
 *
 * The XMEGA NVM controller interfaces the internal non-volatile memories
 * in the XMEGA devices. Program memory, EEPROM and signature row is can be
 * interfaced by the module. See the documentation of each sub-module for
 * more information.
 *
 * \note If using GCC and the flash sub-module, remember to configure
 *       the boot section in the make file. More information in the sub-module
 *       documentation.
 *
 * \section xmega_nvm_quickstart_section Quick Start Guide
 * See \ref xmega_nvm_quickstart
 */

/**
 * \defgroup nvm_generic_group NVM driver generic module handling
 * \ingroup nvm_group
 * \brief Support functions for the NVM driver.
 *
 * These functions are helper functions for the functions of the
 * \ref nvm_group "NVM driver".
 *
 * @{
 */

/**
 * \brief Wait for any NVM access to finish.
 *
 * This function is blocking and waits for any NVM access to finish.
 * Use this function before any NVM accesses, if you are not certain that
 * any previous operations are finished yet.
 */
static inline void nvm_wait_until_ready( void )
{
	do {
		// Block execution while waiting for the NVM to be ready
	} while ((NVM.STATUS & NVM_NVMBUSY_bm) == NVM_NVMBUSY_bm);
}

/**
 * \brief Non-Volatile Memory Execute Command
 *
 * This function sets the CCP register before setting the CMDEX bit in the
 * NVM.CTRLA register.
 *
 * \note The correct NVM command must be set in the NVM.CMD register before
 *       calling this function.
 */
static inline void nvm_exec(void)
{
	ccp_write_io((uint8_t *)&NVM.CTRLA, NVM_CMDEX_bm);
}

/**
 * \brief Non-Volatile Memory Execute Specific Command
 *
 * This function sets a command in the NVM.CMD register, then performs an
 * execute command by writing the CMDEX bit to the NVM.CTRLA register.
 *
 * \note The function saves and restores the NVM.CMD register, but if this
 *       function is called from an interrupt, interrupts must be disabled
 *       before this function is called.
 *
 * \param nvm_command NVM Command to execute.
 */
static inline void nvm_issue_command(NVM_CMD_t nvm_command)
{
	uint8_t old_cmd;

	old_cmd = NVM.CMD;
	NVM.CMD = nvm_command;
	ccp_write_io((uint8_t *)&NVM.CTRLA, NVM_CMDEX_bm);
	NVM.CMD = old_cmd;
}

/**
 * \brief Read one byte using the LDI instruction
 * \internal
 *
 * This function sets the specified NVM_CMD, reads one byte using at the
 * specified byte address with the LPM instruction. NVM_CMD is restored after
 * use.
 *
 * \note Interrupts should be disabled before running this function
 *       if program memory/NVM controller is accessed from ISRs.
 *
 * \param nvm_cmd NVM command to load before running LPM
 * \param address Byte offset into the signature row
 */
uint8_t nvm_read_byte(uint8_t nvm_cmd, uint16_t address);


/**
 * \brief Perform SPM write
 * \internal
 *
 * This function sets the specified NVM_CMD, sets CCP and then runs the SPM
 * instruction to write to flash.
 *
 * \note Interrupts should be disabled before running this function
 *       if program memory/NVM controller is accessed from ISRs.
 *
 * \param addr Address to perform the SPM on.
 * \param nvm_cmd NVM command to use in the NVM_CMD register
 */
void nvm_common_spm(uint32_t addr, uint8_t nvm_cmd);

//! @}

/**
 * \defgroup nvm_signature_group NVM driver signature handling
 * \ingroup nvm_group
 * \brief Handling of signature rows
 *
 * Functions for handling signature rows. The following is supported:
 * - Reading values from production and user signature row
 * - Reading device id
 * - Reading device revision
 * - Reading device serial
 *
 * \note Some of these functions are modifying the NVM.CMD register.
 *       If the application are using program space access in interrupts
 *       (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts
 *       needs to be disabled when running EEPROM access functions. If not,
 *       the program space reads will be corrupted. See documentation for
 *       each individual function.
 * \note Do not use the functions of this module in an interrupt service
 *       routine (ISR), since the functions can take several milliseconds to
 *       complete and hence block the interrupt for several milliseconds.
 *       In addition the functions of this module are modifying the page buffer
 *       which will corrupt any ongoing EEPROM handing used outside an ISR.
 * @{
 */

/**
 * \brief Structure containing the device ID
 *
 * This structure can be used to store the device ID of a device.
 */
struct nvm_device_id {
	union {
		struct {
			uint8_t devid0;
			uint8_t devid1;
			uint8_t devid2;
		};
		uint8_t byte[3];
	};
};

/**
 * \brief Structure containing the device serial
 *
 * This structure can be used to store the serial number of a device.
 */
struct nvm_device_serial {
	union {
		struct {
			uint8_t lotnum0;
			uint8_t lotnum1;
			uint8_t lotnum2;
			uint8_t lotnum3;
			uint8_t lotnum4;
			uint8_t lotnum5;
			uint8_t wafnum;
			uint8_t coordx0;
			uint8_t coordx1;
			uint8_t coordy0;
			uint8_t coordy1;
		};
		uint8_t byte[11];
	};
};

/**
 * \brief Get offset of calibration bytes in the production signature row
 *
 * \param regname Name of register within the production signature row
 * \retval Offset of register into the production signature row
 */
#if defined(__GNUC__)
# define nvm_get_production_signature_row_offset(regname) \
		offsetof(NVM_PROD_SIGNATURES_t, regname)
#elif defined(__ICCAVR__)
# define nvm_get_production_signature_row_offset(regname) (regname##_offset)
#else
#  error Unknown compiler
#endif


/**
 * \brief Read one byte from the production signature row
 *
 * This function reads one byte from the production signature row of the device
 * at the given address.
 *
 * \note This function is modifying the NVM.CMD register.
 *       If the application are using program space access in interrupts
 *       (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts
 *       needs to be disabled when running EEPROM access functions. If not
 *       the program space reads will be corrupted.
 *
 * \param address Byte offset into the signature row
 */
static inline uint8_t nvm_read_production_signature_row(uint8_t address)
{
	return nvm_read_byte(NVM_CMD_READ_CALIB_ROW_gc, address);
}

/**
 * \brief Read one byte from the user signature row
 *
 * This function reads one byte from the user signature row of the device
 * at the given address.
 *
 * \note This function is modifying the NVM.CMD register.
 *       If the application are using program space access in interrupts
 *       (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts
 *       needs to be disabled when running EEPROM access functions. If not
 *       the program space reads will be corrupted.
 *
 * \param address Byte offset into the signature row
 */
static inline uint8_t nvm_read_user_signature_row(uint16_t address)
{
	return nvm_read_byte(NVM_CMD_READ_USER_SIG_ROW_gc, address);
}

/**
 * \brief Read the device id
 *
 * This function returns the device ID stored in the device.
 *
 * \retval storage Pointer to the structure where to store the device id
 */
static inline void nvm_read_device_id(struct nvm_device_id *storage)
{
	storage->devid0 = MCU.DEVID0;
	storage->devid1 = MCU.DEVID1;
	storage->devid2 = MCU.DEVID2;
}

/**
 * \brief Read the device revision
 *
 * This function returns the device revision stored in the device.
 *
 * \retval unsigned 8 bit value with the current device revision.
 */
static inline uint8_t nvm_read_device_rev(void)
{
	return MCU.REVID;
}

void nvm_read_device_serial(struct nvm_device_serial *storage);

//! @}


/**
 * \defgroup nvm_eeprom_group NVM driver EEPROM handling
 * \ingroup nvm_group
 * \brief Functions for handling internal EEPROM memory.
 *
 * The internal EEPROM can be used to store data that will persist after
 * power is removed. This can typically be used to store calibration data,
 * application state, encryption keys or other data that need to be preserved
 * when power is removed.
 *
 * The functions in this module uses IO register access to manipulate the
 * EEPROM.
 *
 * \note The functions in this module are modifying the NVM.CMD register.
 *       If the application are using program space access in interrupts
 *       (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts
 *       needs to be disabled when running EEPROM access functions. If not
 *       the program space reads will be corrupted.
 * @{
 */

#ifndef EEPROM_PAGE_SIZE
#  if XMEGA_A || XMEGA_AU || XMEGA_B || XMEGA_C || XMEGA_D || XMEGA_E
#    define EEPROM_PAGE_SIZE 32
#  else
#    error Unknown EEPROM page size
#  endif
#endif

#ifndef CONFIG_NVM_IGNORE_XMEGA_A3_D3_REVB_ERRATA
#  if XMEGA_A3 || XMEGA_D3
#    error This NVM driver does not support rev B of XMEGA A3/D3 devices. \
     Set CONFIG_NVM_IGNORE_XMEGA_A3_D3_REVB_ERRATA to disable this message
#  endif
#endif

/**
 * Data type for holding eeprom memory addresses.
 */
typedef uint16_t eeprom_addr_t;


/*! \brief Enable EEPROM mapping into data space.
 *
 *  This macro enables mapping of EEPROM into data space.
 *  EEPROM starts at EEPROM_START in data memory. Read access
 *  can be done similar to ordinary SRAM access.
 *
 *  \note This disables IO-mapped access to EEPROM, although page erase and
 *        write operations still needs to be done through IO register.
 */
static inline void eeprom_enable_mapping(void)
{
#if !XMEGA_E
	NVM_CTRLB = NVM_CTRLB | NVM_EEMAPEN_bm;
#endif
}


/*! \brief Disable EEPROM mapping into data space.
 *
 *  This macro disables mapping of EEPROM into data space.
 *  IO mapped access is now enabled.
 */
static inline void eeprom_disable_mapping(void)
{
#if !XMEGA_E
	NVM_CTRLB = NVM_CTRLB & ~NVM_EEMAPEN_bm;
#endif
}


uint8_t nvm_eeprom_read_byte(eeprom_addr_t addr);
void nvm_eeprom_write_byte(eeprom_addr_t address, uint8_t value);
void nvm_eeprom_read_buffer(eeprom_addr_t address, void *buf, uint16_t len);
void nvm_eeprom_erase_and_write_buffer(eeprom_addr_t address, const void *buf, uint16_t len);

void nvm_eeprom_flush_buffer(void);
void nvm_eeprom_load_byte_to_buffer(uint8_t byte_addr, uint8_t value);
void nvm_eeprom_load_page_to_buffer(const uint8_t *values);
void nvm_eeprom_atomic_write_page(uint8_t page_addr);
void nvm_eeprom_split_write_page(uint8_t page_addr);
void nvm_eeprom_fill_buffer_with_value(uint8_t value);
void nvm_eeprom_erase_bytes_in_page(uint8_t page_addr);
void nvm_eeprom_erase_page(uint8_t page_addr);
void nvm_eeprom_erase_bytes_in_all_pages(void);
void nvm_eeprom_erase_all(void);

//! @}

/**
 * \defgroup nvm_flash_group NVM driver flash handling
 * \ingroup nvm_group
 * \brief Functions for handling internal flash memory.
 *
 * The internal flash memory on the XMEGA devices consists of the application
 * section, the application table section and the bootloader section.
 * All these sections can store program code for the MCU, but if there is
 * available space, they can be used for storing other persistent data.
 *
 * Writing the flash memory can only be done one page at a time. It consists
 * of loading the data to the internal page buffer and then running one of
 * the write commands. If the page has not been erased before writing, the
 * data will not be written correctly.
 *
 * In order to be able to write to flash memory the programming commands need
 * to be run from the boot section.
 * - When using IAR this is handled automatically by the linker script.
 * - When using GCC this needs to be specified manually in the make files. For
 *   example the ATxmega128A1 has the boot section at the word address 0x10000
 *   the corresponding byte address of 0x20000 needs to be added to the
 *   config.mk makefile:
 *   LDFLAGS += -Wl,--section-start=.BOOT=0x20000
 *   See the device datasheet for the correct address for other devices.
 *
 * \note If using GCC and the flash sub-module, remember to configure
 *       the boot section in the make file.
 *
 * \note The functions in this module are modifying the NVM.CMD register.
 *       If the application are using program space access in interrupts
 *       (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts
 *       needs to be disabled when running EEPROM access functions. If not
 *       the program space reads will be corrupted.
 * @{
 */

/**
 * \brief Size of a flash page in bytes
 *
 * The page size in bytes taken from the toolchain header files.
 *
 * \note Page size is currently missing from the IAR header files, so it needs
 *       to be defined in the driver until it is fixed.
 */
#ifdef __DOXYGEN__
#  define FLASH_SIZE
#  define FLASH_PAGE_SIZE
#else

// 8K devices
#  if AVR8_PART_IS_DEFINED(ATxmega8E5)
#    define FLASH_SIZE      (8*1024L)
#    define FLASH_PAGE_SIZE (128)

// 16K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega16A4)            | \
		AVR8_PART_IS_DEFINED(ATxmega16A4U) | \
		AVR8_PART_IS_DEFINED(ATxmega16D4)  | \
		AVR8_PART_IS_DEFINED(ATxmega16C4)
#    define FLASH_SIZE      (16*1024L)
#    define FLASH_PAGE_SIZE (256)

#  elif AVR8_PART_IS_DEFINED(ATxmega16E5)
#    define FLASH_SIZE      (16*1024L)
#    define FLASH_PAGE_SIZE (128)

// 32K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega32A4)          | \
		AVR8_PART_IS_DEFINED(ATxmega32A4U) | \
		AVR8_PART_IS_DEFINED(ATxmega32D4)  | \
		AVR8_PART_IS_DEFINED(ATxmega32C4)
#    define FLASH_SIZE      (32*1024L)
#    define FLASH_PAGE_SIZE (256)

#  elif AVR8_PART_IS_DEFINED(ATxmega32E5)
#    define FLASH_SIZE      (32*1024L)
#    define FLASH_PAGE_SIZE (128)

// 64K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega64A1)          | \
		AVR8_PART_IS_DEFINED(ATxmega64A1U) | \
		AVR8_PART_IS_DEFINED(ATxmega64A3)  | \
		AVR8_PART_IS_DEFINED(ATxmega64A3U) | \
		AVR8_PART_IS_DEFINED(ATxmega64A4U) | \
		AVR8_PART_IS_DEFINED(ATxmega64B1)  | \
		AVR8_PART_IS_DEFINED(ATxmega64B3)  | \
		AVR8_PART_IS_DEFINED(ATxmega64C3)  | \
		AVR8_PART_IS_DEFINED(ATxmega64D3)  | \
		AVR8_PART_IS_DEFINED(ATxmega64D4)
#    define FLASH_SIZE      (64*1024L)
#    define FLASH_PAGE_SIZE (256)

// 128K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega128A1)          | \
		AVR8_PART_IS_DEFINED(ATxmega128A1U) | \
		AVR8_PART_IS_DEFINED(ATxmega128A3)  | \
		AVR8_PART_IS_DEFINED(ATxmega128A3U) | \
		AVR8_PART_IS_DEFINED(ATxmega128C3) | \
		AVR8_PART_IS_DEFINED(ATxmega128D3)  | \
		AVR8_PART_IS_DEFINED(ATxmega128D4)
#    define FLASH_SIZE      (128*1024L)
#    define FLASH_PAGE_SIZE (512)

#  elif AVR8_PART_IS_DEFINED(ATxmega128A4U)         | \
		AVR8_PART_IS_DEFINED(ATxmega128B1)  | \
		AVR8_PART_IS_DEFINED(ATxmega128B3)
#    define FLASH_SIZE      (128*1024L)
#    define FLASH_PAGE_SIZE (256)

// 192K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega192A3U) | \
		AVR8_PART_IS_DEFINED(ATxmega192C3)  | \
		AVR8_PART_IS_DEFINED(ATxmega192D3)
#    define FLASH_SIZE      (192*1024L)
#    define FLASH_PAGE_SIZE (512)

// 256K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega256A3)           | \
		AVR8_PART_IS_DEFINED(ATxmega256A3U)  | \
		AVR8_PART_IS_DEFINED(ATxmega256A3B)  | \
		AVR8_PART_IS_DEFINED(ATxmega256A3BU) | \
		AVR8_PART_IS_DEFINED(ATxmega256C3)   | \
		AVR8_PART_IS_DEFINED(ATxmega256D3)
#    define FLASH_SIZE      (256*1024L)
#    define FLASH_PAGE_SIZE (512)

// 384K devices
#  elif AVR8_PART_IS_DEFINED(ATxmega384C3)
#    define FLASH_SIZE      (384*1024L)
#    define FLASH_PAGE_SIZE (512)
#  elif AVR8_PART_IS_DEFINED(ATxmega384D3)
#    define FLASH_SIZE      (384*1024L)
#    define FLASH_PAGE_SIZE (512)
#  else
#    error Flash page size needs to be defined.
#  endif
#endif

/**
 * Data type for holding flash memory addresses.
 *
 */
#if (FLASH_SIZE >= 0x10000UL) // Note: Xmega with 64KB of flash have 4KB boot flash
typedef uint32_t flash_addr_t;
#else
typedef uint16_t flash_addr_t;
#endif

/**
 * Flash pointer type to use for accessing flash memory with IAR
 */
#if (FLASH_SIZE >= 0x10000UL) // Note: Xmega with 64KB of flash have 4KB boot flash
#  define IAR_FLASH_PTR __farflash
#else
#  define IAR_FLASH_PTR __flash
#endif

/**
 * \brief Load byte from flash memory
 *
 * Load one word of flash using byte addressing. IAR has __flash pointers
 * and GCC have pgm_read_byte_xx functions which load data from flash memory.
 * This function used for compatibility between the compilers.
 *
 * \param addr Byte address to load
 * \return Byte from program memory
 */
static inline uint8_t nvm_flash_read_byte(flash_addr_t addr)
{
#if defined(__GNUC__)
	return pgm_read_byte_far(addr);
#elif defined(__ICCAVR__)
	uint8_t IAR_FLASH_PTR *flashptr = (uint8_t IAR_FLASH_PTR *)addr;
	return *flashptr;
#else
#  error Unknown compiler
#endif
}

/**
 * \brief Load word from flash memory
 *
 * Load one word of flash using byte addressing. IAR has __flash pointers
 * and GCC have pgm_read_byte_xx functions which load data from flash memory.
 * This function used for compatibility between the compilers.
 *
 * \param addr Byte address to load (last bit is ignored)
 * \return Word from program memory
 */
static inline uint16_t nvm_flash_read_word(flash_addr_t addr)
{
#if defined(__GNUC__)
	return pgm_read_word_far(addr);
#elif defined(__ICCAVR__)
	uint16_t IAR_FLASH_PTR *flashptr = (uint16_t IAR_FLASH_PTR *)addr;
	return *flashptr;
#endif
}


/**
 * \brief Flush flash page buffer
 *
 * Clear the NVM controller page buffer for flash. This needs to be called
 * before using \ref nvm_flash_load_word_to_buffer if it has not already been
 * cleared.
 *
 */
static inline void nvm_flash_flush_buffer(void)
{
	nvm_wait_until_ready();
	nvm_common_spm(0, NVM_CMD_ERASE_FLASH_BUFFER_gc);
}


/**
 * \brief Load word into flash page buffer
 *
 * Clear the NVM controller page buffer for flash. This needs to be called
 * before using \ref nvm_flash_load_word_to_buffer if it has not already been
 * cleared.
 *
 * \param word_addr Address to store data. The upper bits beyond the page size
 *                  is ignored. \ref FLASH_PAGE_SIZE
 * \param data Data word to load into the page buffer
 */
void nvm_flash_load_word_to_buffer(uint32_t word_addr, uint16_t data);


/**
 * \brief Erase entire application section
 *
 * Erase all of the application section.
 */
static inline void nvm_flash_erase_app(void)
{
	nvm_wait_until_ready();
	nvm_common_spm(0, NVM_CMD_ERASE_APP_gc);
}

/**
 * \brief Erase a page within the application section
 *
 * Erase one page within the application section
 *
 * \param page_addr Byte address to the page to delete
 */
static inline void nvm_flash_erase_app_page(flash_addr_t page_addr)
{
	nvm_wait_until_ready();
	nvm_common_spm(page_addr, NVM_CMD_ERASE_APP_PAGE_gc);
}

/**
 * \brief Write a page within the application section
 *
 * Write a page within the application section with the data stored in the
 * page buffer. The page needs to be erased before the write to avoid
 * corruption of the data written.
 *
 * \param page_addr Byte address to the page to delete
 */
static inline void nvm_flash_split_write_app_page(flash_addr_t page_addr)
{
	nvm_wait_until_ready();
	nvm_common_spm(page_addr, NVM_CMD_WRITE_APP_PAGE_gc);
}

/**
 * \brief Erase and write a page within the application section
 *
 * Erase and the write a page within the application section with the data
 * stored in the page buffer. Erase and write is done in an atomic operation.
 *
 * \param page_addr Byte address to the page to delete
 */
static inline void nvm_flash_atomic_write_app_page(flash_addr_t page_addr)
{
	nvm_wait_until_ready();
	nvm_common_spm(page_addr, NVM_CMD_ERASE_WRITE_APP_PAGE_gc);
}

void nvm_issue_flash_range_crc(flash_addr_t start_addr, flash_addr_t end_addr);

void nvm_flash_read_buffer(flash_addr_t address, void *buf, uint16_t len);

void nvm_flash_erase_and_write_buffer(flash_addr_t address, const void *buf,
	uint16_t len, bool b_blank_check);

/**
 * \brief Erase a page within the boot section
 *
 * Erase one page within the boot section
 *
 * \param page_addr Byte address to the page to delete
 */
static inline void nvm_flash_erase_boot_page(flash_addr_t page_addr)
{
	nvm_wait_until_ready();
	nvm_common_spm(page_addr, NVM_CMD_ERASE_BOOT_PAGE_gc);
}

/**
 * \brief Write a page within the boot section
 *
 * Write a page within the boot section with the data stored in the
 * page buffer. The page needs to be erased before the write to avoid
 * corruption of the data written.
 *
 * \param page_addr Byte address to the page to delete
 */
static inline void nvm_flash_split_write_boot_page(flash_addr_t page_addr)
{
	nvm_wait_until_ready();
	nvm_common_spm(page_addr, NVM_CMD_WRITE_BOOT_PAGE_gc);
}

/**
 * \brief Erase and write a page within the boot section
 *
 * Erase and the write a page within the boot section with the data
 * stored in the page buffer. Erase and write is done in an atomic operation.
 *
 * \param page_addr Byte address to the page to delete
 */
static inline void nvm_flash_atomic_write_boot_page(flash_addr_t page_addr)
{
	nvm_wait_until_ready();
	nvm_common_spm(page_addr, NVM_CMD_ERASE_WRITE_BOOT_PAGE_gc);
}

void nvm_user_sig_read_buffer(flash_addr_t address, void *buf, uint16_t len);
void nvm_user_sig_write_buffer(flash_addr_t address, const void *buf,
	uint16_t len, bool b_blank_check);

/**
 * \brief Erase the user calibration section page
 *
 * Erase the user calibration section page. There is only one page, so no
 * parameters are needed.
 */
static inline void nvm_flash_erase_user_section(void)
{
	nvm_wait_until_ready();
	nvm_common_spm(0, NVM_CMD_ERASE_USER_SIG_ROW_gc);
}

/**
 * \brief Write the user calibration section page
 *
 * Write a the user calibration section page with the data stored in the
 * page buffer. The page needs to be erased before the write to avoid
 * corruption of the data written. There is only one page, so no
 * parameters are needed.
 */
static inline void nvm_flash_write_user_page(void)
{
	nvm_wait_until_ready();
	nvm_common_spm(0, NVM_CMD_WRITE_USER_SIG_ROW_gc);
}

//! @}

/**
 * \defgroup nvm_fuse_lock_group NVM driver fuse and lock bits handling
 * \ingroup nvm_group
 * \brief Functions for reading fuses and writing lock bits.
 *
 * The Fuses are used to set important system functions and can only be written
 * from an external programming interface. The application software can read
 * the fuses. The fuses are used to configure reset sources such as Brown-out
 * Detector and Watchdog, Start-up configuration, JTAG enable and JTAG user ID.
 *
 * The Lock bits are used to set protection level on the different flash
 * sections. They are used to block read and/or write on the different flash
 * sections. Lock bits can be written from en external programmer and from the
 * application software to set a more strict protection level, but not to set a
 * less strict protection level. Chip erase is the only way to erase the lock
 * bits. The lock bits are erased after the rest of the flash memory is erased.
 * An unprogrammed fuse or lock bit will have the value one, while a programmed
 * fuse or lock bit will have the value zero.
 * Both fuses and lock bits are reprogrammable like the Flash Program memory.
 *
 * \note The functions in this module are modifying the NVM.CMD register.
 *       If the application are using program space access in interrupts
 *       (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts
 *       needs to be disabled when running EEPROM access functions. If not
 *       the program space reads will be corrupted.
 * @{
 */

// The different fuse bytes
enum fuse_byte_t {
	FUSEBYTE0 = 0,
	FUSEBYTE1 = 1,
	FUSEBYTE2 = 2,
	FUSEBYTE3 = 3, // not used on current devices
	FUSEBYTE4 = 4,
	FUSEBYTE5 = 5,
};

uint8_t nvm_fuses_read(enum fuse_byte_t fuse);

/**
 * \brief Program the lock bits.
 *
 * Program the lock bits to the given values. Lock bits can only be programmed
 * to a more secure setting than previously programmed. To clear lock bits, a
 * flash erase has to be issued.
 *
 * \param blbb_lock Boot loader section lock bits to program
 * \param blba_lock Application section lock bits to program
 * \param blbat_lock Application table section lock bits to program
 * \param lb_lock Flash/eeprom lock bits to program
 */
static inline void nvm_lock_bits_write(enum NVM_BLBB_enum blbb_lock,
	enum NVM_BLBA_enum blba_lock, enum NVM_BLBAT_enum blbat_lock,
	enum NVM_LB_enum lb_lock)
{
	nvm_wait_until_ready();
	NVM.DATA0 = (uint8_t)blbb_lock | (uint8_t)blba_lock | (uint8_t)blbat_lock |
		(uint8_t)lb_lock;
	nvm_issue_command(NVM_CMD_WRITE_LOCK_BITS_gc);
}

/**
 * \brief Program the BLBB lock bits.
 *
 * Program the lock bits for the boot loader section (BLBB). Other lock bits
 * (BLBA, BLBAT and LB) are not altered (ie. programmed to NOLOCK).
 *
 * \param blbb_lock Boot loader section lock bits to program
 */
static inline void nvm_blbb_lock_bits_write(enum NVM_BLBB_enum blbb_lock)
{
	nvm_lock_bits_write(blbb_lock, NVM_BLBA_NOLOCK_gc, NVM_BLBAT_NOLOCK_gc,
		NVM_LB_NOLOCK_gc);
}

/**
 * \brief Program the BLBA lock bits.
 *
 * Program the lock bits for the application section (BLBA). Other lock bits
 * (BLBB, BLBAT and LB) are not altered (ie. programmed to NOLOCK).
 *
 * \param blba_lock Application section lock bits to program
 */
static inline void nvm_blba_lock_bits_write(enum NVM_BLBA_enum blba_lock)
{
	nvm_lock_bits_write(NVM_BLBB_NOLOCK_gc, blba_lock, NVM_BLBAT_NOLOCK_gc,
		NVM_LB_NOLOCK_gc);
}

/**
 * \brief Program the BLBAT lock bits.
 *
 * Program the lock bits for the application table section (BLBAT). Other lock
 * bits (BLBB, BLBA and LB) are not altered (ie. programmed to NOLOCK).
 *
 * \param blbat_lock Application table section lock bits to program
 */
static inline void nvm_blbat_lock_bits_write(enum NVM_BLBAT_enum blbat_lock)
{
	nvm_lock_bits_write(NVM_BLBB_NOLOCK_gc, NVM_BLBA_NOLOCK_gc, blbat_lock,
		NVM_LB_NOLOCK_gc);
}

/**
 * \brief Program the LB lock bits.
 *
 * Program the lock bits for the flash and eeprom (LB). Other lock bits
 * (BLBB, BLBA and BLBAT) are not altered (ie. programmed to NOLOCK).
 *
 * \param lb_lock Flash/eeprom lock bits to program
 */
static inline void nvm_lb_lock_bits_write(enum NVM_LB_enum lb_lock)
{
	nvm_lock_bits_write(NVM_BLBB_NOLOCK_gc, NVM_BLBA_NOLOCK_gc,
		NVM_BLBAT_NOLOCK_gc, lb_lock);
}

//! @}

/**
 * \page xmega_nvm_quickstart Quick Start Guide for the XMEGA NVM Driver
 *
 * This is the quick start guide for the \ref nvm_group "NVM Driver", with
 * step-by-step instructions on how to configure and use the driver for
 * specific use cases.
 *
 * The section described below can be compiled into e.g. the main application
 * loop or any other function that will need to interface non-volatile memory.
 *
 * \section xmega_nvm_quickstart_basic Basic usage of the NVM driver
 * This section will present three use cases of the NVM driver. The first will
 * write a page to EEPROM and verify that it has been written, the second will
 * access the BOD-level fuse to verify that the level is correctly set, and the
 * third will read a chunk from the user signature row.
 *
 * \section xmega_nvm_quickstart_eeprom_case Use case 1: EEPROM
 *
 * The NVM driver has functions for interfacing many types of non-volatile
 * memory, including flash, EEPROM, fuses and lock bits. The example code
 * below will write a page to the internal EEPROM, and read it back to verify,
 * using memory mapped I/O.
 *
 * \section xmega_nvm_quickstart_eeprom_case_setup_steps Setup steps
 * There are no setup steps required for this use case.
 *
 * \subsection nvm_quickstart_eeprom_case_example_code Example code
 *
 * \code
 * #define EXAMPLE_PAGE 2
 * #define EXAMPLE_ADDR EXAMPLE_PAGE * EEPROM_PAGE_SIZE
 *
 * uint8_t write_page[EEPROM_PAGE_SIZE];
 * uint8_t read_page[EEPROM_PAGE_SIZE];
 *
 * fill_page_with_known_data(write_page);
 * fill_page_with_zeroes(read_page);
 *
 * nvm_eeprom_load_page_to_buffer(write_page);
 * nvm_eeprom_atomic_write_page(EXAMPLE_PAGE);
 *
 * nvm_eeprom_read_buffer(EXAMPLE_ADDR,
 *         read_page, EEPROM_PAGE_SIZE);
 *
 * check_if_pages_are_equal(write_page, read_page);
 * \endcode
 *
 * \subsection nvm_quickstart_eeprom_case_workflow Workflow
 *
 * -# We define where we would like to store our data, and we arbitrarily
 *    choose page 2 of EEPROM:
 *     - \code
 *       #define EXAMPLE_PAGE 2
 *       #define EXAMPLE_ADDR EXAMPLE_PAGE * EEPROM_PAGE_SIZE
 *       \endcode
 * -# Define two tables, one which contains the data which we will write,
 *    and one which we will read the data into:
 *     - \code
 *     uint8_t write_page[EEPROM_PAGE_SIZE];
 *     uint8_t read_page[EEPROM_PAGE_SIZE];
 *       \endcode
 * -# Fill the tables with our data, and zero out the read table:
 *     - \code
 *       fill_page_with_known_data(write_page);
 *       fill_page_with_zeroes(read_page);
 *       \endcode
 *     - \note These functions are undeclared, you should replace them with
 *             your own appropriate functions.
 * -# We load our page into a temporary EEPROM page buffer:
 *     - \code
 *       nvm_eeprom_load_page_to_buffer(write_page);
 *       \endcode
 *     - \attention The function used above will not work if memory mapping
 *                  is enabled.
 * -# Do an atomic write of the page from buffer into the specified page:
 *     - \code
 *       nvm_eeprom_atomic_write_page(EXAMPLE_PAGE);
 *       \endcode
 *     - \note The function \ref nvm_eeprom_atomic_write_page() erases the
 *             page before writing the new one. For non-atomic (split)
 *             writing without deleting, see \ref nvm_eeprom_split_write_page()
 * -# Read the page back into our read_page[] table:
 *     - \code
 *       nvm_eeprom_read_buffer(EXAMPLE_ADDR,
 *               read_page, EEPROM_PAGE_SIZE);
 *       \endcode
 * -# Verify that the page is equal to the one that was written earlier:
 *     - \code
 *       check_if_pages_are_equal(write_page, read_page);
 *       \endcode
 *     - \note This function is not declared, you should replace it with your
 *             own appropriate function.
 *
 * \section xmega_nvm_quickstart_fuse_case Use case 2: Fuses
 *
 * The NVM driver has functions for reading fuses.
 * See \ref nvm_fuse_lock_group.
 *
 * We would like to check whether the Brown-out Detection level is set to
 * 2.1V. This is set by programming the fuses when the chip is connected
 * to a suitable programmer. The fuse is a part of FUSEBYTE5. If the BODLVL
 * is correct, we turn on LED0.
 *
 * \section xmega_nvm_quickstart_fuse_case_setup_steps Setup steps
 * There are no setup steps required for this use case.
 *
 * \subsection nvm_quickstart_fuse_case_example_code Example code
 * \code
 * uint8_t fuse_value;
 * fuse_value = nvm_fuses_read(FUSEBYTE5);
 *
 * if ((fuse_value & NVM_FUSES_BODLVL_gm) == BODLVL_2V1_gc) {
 *     gpio_set_pin_low(LED0_GPIO);
 * }
 * \endcode
 *
 * \subsection nvm_quickstart_fuse_case_workflow Workflow
 *
 * -# Create a variable to store the fuse contents:
 *     - \code
 *       uint8_t fuse_value;
 *       \endcode
 * -# The fuse value we are interested in, BODLVL, is stored in FUSEBYTE5.
 *    We call the function \ref nvm_fuses_read() to read the fuse into our
 *    variable:
 *     - \code
 *       fuse_value = nvm_fuses_read(FUSEBYTE5);
 *       \endcode
 * -# This ends the reading portion, but we would like to see whether the
 *    BOD-level is correct, and if it is, light up an LED:
 *     - \code
 *       if ((fuse_value & NVM_FUSES_BODLVL_gm) == BODLVL_2V1_gc) {
 *           gpio_set_pin_low(LED0_GPIO);
 *       }
 *       \endcode
 *
 * \section xmega_nvm_quickstart_signature_case Use case 3: Signature row
 *
 * The NVM driver has functions for reading the signature row of the device.
 * Here we will simply read 16 bytes from the user signature row, assuming
 * we need what is stored there.
 *
 * \section xmega_nvm_quickstart_signature_row_setup_steps Setup steps
 * There are no setup steps required for this use case.
 *
 * \subsection xmega_nvm_quickstart_signature_row_example_code Example code
 *
 * \code
 * #define START_ADDR 0x10
 * #define DATA_LENGTH 16
 *
 * uint8_t values[LENGTH];
 * uint8_t i;
 *
 * for (i = 0; i < DATA_LENGTH; i++) {
 *     values[i] = nvm_read_user_signature_row(START_ADDR + i);
 * }
 * \endcode
 *
 * \subsection nvm_quickstart_signature_case_workflow Workflow
 *
 * -# Define starting address and length of data segment, and create
 *    variables needed to store and process the data:
 *     - \code
 *       #define START_ADDR 0x10
 *       #define DATA_LENGTH 16
 *
 *       uint8_t values[LENGTH];
 *       uint8_t i;
 *       \endcode
 * -# Iterate through the user signature row, and store our desired data:
 *     - \code
 *       for (i = 0; i < DATA_LENGTH; i++) {
 *           values[i] = nvm_read_user_signature_row(START_ADDR + i);
 *       }
 *       \endcode
 *
 */

#ifdef __cplusplus
}
#endif

#endif /* NVM_H */
