/** * \file * * \brief SAM Non Volatile Memory driver * * Copyright (C) 2012-2016 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 * */ /* * Support and FAQ: visit Atmel Support */ #include "nvm.h" #include #include #include /** * \internal Internal device instance struct * * This struct contains information about the NVM module which is * often used by the different functions. The information is loaded * into the struct in the nvm_init() function. */ struct _nvm_module { /** Number of bytes contained per page. */ uint16_t page_size; /** Total number of pages in the NVM memory. */ uint16_t number_of_pages; /** If \c false, a page write command will be issued automatically when the * page buffer is full. */ bool manual_page_write; }; /** * \internal Instance of the internal device struct */ static struct _nvm_module _nvm_dev; /** * \internal Pointer to the NVM MEMORY region start address */ #define NVM_MEMORY ((volatile uint16_t *)FLASH_ADDR) /** * \internal Pointer to the NVM USER MEMORY region start address */ #define NVM_USER_MEMORY ((volatile uint16_t *)NVMCTRL_USER) /** * \brief Sets the up the NVM hardware module based on the configuration. * * Writes a given configuration of an NVM controller configuration to the * hardware module, and initializes the internal device struct. * * \param[in] config Configuration settings for the NVM controller * * \note The security bit must be cleared in order successfully use this * function. This can only be done by a chip erase. * * \return Status of the configuration procedure. * * \retval STATUS_OK If the initialization was a success * \retval STATUS_BUSY If the module was busy when the operation was attempted * \retval STATUS_ERR_IO If the security bit has been set, preventing the * EEPROM and/or auxiliary space configuration from being * altered */ enum status_code nvm_set_config( const struct nvm_config *const config) { /* Sanity check argument */ Assert(config); /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30) /* Turn on the digital interface clock */ system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB, MCLK_APBBMASK_NVMCTRL); #else /* Turn on the digital interface clock */ system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB, PM_APBBMASK_NVMCTRL); #endif /* Clear error flags */ nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK; /* Check if the module is busy */ if (!nvm_is_ready()) { return STATUS_BUSY; } #if (!SAMC20) && (!SAMC21) /* Writing configuration to the CTRLB register */ nvm_module->CTRLB.reg = NVMCTRL_CTRLB_SLEEPPRM(config->sleep_power_mode) | ((config->manual_page_write & 0x01) << NVMCTRL_CTRLB_MANW_Pos) | NVMCTRL_CTRLB_RWS(config->wait_states) | ((config->disable_cache & 0x01) << NVMCTRL_CTRLB_CACHEDIS_Pos) | NVMCTRL_CTRLB_READMODE(config->cache_readmode); #else uint8_t cache_disable_value = 0; if (config->disable_rww_cache == false) { cache_disable_value = 0x02; } else { cache_disable_value = (config->disable_cache & 0x01); } /* Writing configuration to the CTRLB register */ nvm_module->CTRLB.reg = NVMCTRL_CTRLB_SLEEPPRM(config->sleep_power_mode) | ((config->manual_page_write & 0x01) << NVMCTRL_CTRLB_MANW_Pos) | NVMCTRL_CTRLB_RWS(config->wait_states) | (cache_disable_value << NVMCTRL_CTRLB_CACHEDIS_Pos) | NVMCTRL_CTRLB_READMODE(config->cache_readmode); #endif /* Initialize the internal device struct */ _nvm_dev.page_size = (8 << nvm_module->PARAM.bit.PSZ); _nvm_dev.number_of_pages = nvm_module->PARAM.bit.NVMP; _nvm_dev.manual_page_write = config->manual_page_write; /* If the security bit is set, the auxiliary space cannot be written */ if (nvm_module->STATUS.reg & NVMCTRL_STATUS_SB) { return STATUS_ERR_IO; } return STATUS_OK; } /** * \brief Executes a command on the NVM controller. * * Executes an asynchronous command on the NVM controller, to perform a requested * action such as an NVM page read or write operation. * * \note The function will return before the execution of the given command is * completed. * * \param[in] command Command to issue to the NVM controller * \param[in] address Address to pass to the NVM controller in NVM memory * space * \param[in] parameter Parameter to pass to the NVM controller, not used * for this driver * * \return Status of the attempt to execute a command. * * \retval STATUS_OK If the command was accepted and execution * is now in progress * \retval STATUS_BUSY If the NVM controller was already busy * executing a command when the new command * was issued * \retval STATUS_ERR_IO If the command was invalid due to memory or * security locking * \retval STATUS_ERR_INVALID_ARG If the given command was invalid or * unsupported * \retval STATUS_ERR_BAD_ADDRESS If the given address was invalid */ enum status_code nvm_execute_command( const enum nvm_command command, const uint32_t address, const uint32_t parameter) { uint32_t ctrlb_bak; /* Check that the address given is valid */ if (address > ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages) && !(address >= NVMCTRL_AUX0_ADDRESS && address <= NVMCTRL_AUX1_ADDRESS )){ #ifdef FEATURE_NVM_RWWEE if (address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR) || address < NVMCTRL_RWW_EEPROM_ADDR){ return STATUS_ERR_BAD_ADDRESS; } #else return STATUS_ERR_BAD_ADDRESS; #endif } /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; /* Turn off cache before issuing flash commands */ ctrlb_bak = nvm_module->CTRLB.reg; #if (SAMC20) || (SAMC21) nvm_module->CTRLB.reg = ((ctrlb_bak &(~(NVMCTRL_CTRLB_CACHEDIS(0x2)))) | NVMCTRL_CTRLB_CACHEDIS(0x1)); #else nvm_module->CTRLB.reg = ctrlb_bak | NVMCTRL_CTRLB_CACHEDIS; #endif /* Clear error flags */ nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK; /* Check if the module is busy */ if (!nvm_is_ready()) { /* Restore the setting */ nvm_module->CTRLB.reg = ctrlb_bak; return STATUS_BUSY; } switch (command) { /* Commands requiring address (protected) */ case NVM_COMMAND_ERASE_AUX_ROW: case NVM_COMMAND_WRITE_AUX_ROW: /* Auxiliary space cannot be accessed if the security bit is set */ if (nvm_module->STATUS.reg & NVMCTRL_STATUS_SB) { /* Restore the setting */ nvm_module->CTRLB.reg = ctrlb_bak; return STATUS_ERR_IO; } /* Set address, command will be issued elsewhere */ nvm_module->ADDR.reg = (uintptr_t)&NVM_MEMORY[address / 4]; break; /* Commands requiring address (unprotected) */ case NVM_COMMAND_ERASE_ROW: case NVM_COMMAND_WRITE_PAGE: case NVM_COMMAND_LOCK_REGION: case NVM_COMMAND_UNLOCK_REGION: #ifdef FEATURE_NVM_RWWEE case NVM_COMMAND_RWWEE_ERASE_ROW: case NVM_COMMAND_RWWEE_WRITE_PAGE: #endif /* Set address, command will be issued elsewhere */ nvm_module->ADDR.reg = (uintptr_t)&NVM_MEMORY[address / 4]; break; /* Commands not requiring address */ case NVM_COMMAND_PAGE_BUFFER_CLEAR: case NVM_COMMAND_SET_SECURITY_BIT: case NVM_COMMAND_ENTER_LOW_POWER_MODE: case NVM_COMMAND_EXIT_LOW_POWER_MODE: break; default: /* Restore the setting */ nvm_module->CTRLB.reg = ctrlb_bak; return STATUS_ERR_INVALID_ARG; } /* Set command */ nvm_module->CTRLA.reg = command | NVMCTRL_CTRLA_CMDEX_KEY; /* Wait for the NVM controller to become ready */ while (!nvm_is_ready()) { } /* Restore the setting */ nvm_module->CTRLB.reg = ctrlb_bak; return STATUS_OK; } /** * \brief Updates an arbitrary section of a page with new data. * * Writes from a buffer to a given page in the NVM memory, retaining any * unmodified data already stored in the page. * * \note If manual write mode is enable, the write command must be executed after * this function, otherwise the data will not write to NVM from page buffer. * * \warning This routine is unsafe if data integrity is critical; a system reset * during the update process will result in up to one row of data being * lost. If corruption must be avoided in all circumstances (including * power loss or system reset) this function should not be used. * * \param[in] destination_address Destination page address to write to * \param[in] buffer Pointer to buffer where the data to write is * stored * \param[in] offset Number of bytes to offset the data write in * the page * \param[in] length Number of bytes in the page to update * * \return Status of the attempt to update a page. * * \retval STATUS_OK Requested NVM memory page was successfully * read * \retval STATUS_BUSY NVM controller was busy when the operation * was attempted * \retval STATUS_ERR_BAD_ADDRESS The requested address was outside the * acceptable range of the NVM memory region * \retval STATUS_ERR_INVALID_ARG The supplied length and offset was invalid */ enum status_code nvm_update_buffer( const uint32_t destination_address, uint8_t *const buffer, uint16_t offset, uint16_t length) { enum status_code error_code = STATUS_OK; uint8_t row_buffer[NVMCTRL_ROW_PAGES][NVMCTRL_PAGE_SIZE]; /* Ensure the read does not overflow the page size */ if ((offset + length) > _nvm_dev.page_size) { return STATUS_ERR_INVALID_ARG; } /* Calculate the starting row address of the page to update */ uint32_t row_start_address = destination_address & ~((_nvm_dev.page_size * NVMCTRL_ROW_PAGES) - 1); /* Read in the current row contents */ for (uint32_t i = 0; i < NVMCTRL_ROW_PAGES; i++) { do { error_code = nvm_read_buffer( row_start_address + (i * _nvm_dev.page_size), row_buffer[i], _nvm_dev.page_size); } while (error_code == STATUS_BUSY); if (error_code != STATUS_OK) { return error_code; } } /* Calculate the starting page in the row that is to be updated */ uint8_t page_in_row = (destination_address % (_nvm_dev.page_size * NVMCTRL_ROW_PAGES)) / _nvm_dev.page_size; /* Update the specified bytes in the page buffer */ for (uint32_t i = 0; i < length; i++) { row_buffer[page_in_row][offset + i] = buffer[i]; } system_interrupt_enter_critical_section(); /* Erase the row */ do { error_code = nvm_erase_row(row_start_address); } while (error_code == STATUS_BUSY); if (error_code != STATUS_OK) { system_interrupt_leave_critical_section(); return error_code; } /* Write the updated row contents to the erased row */ for (uint32_t i = 0; i < NVMCTRL_ROW_PAGES; i++) { do { error_code = nvm_write_buffer( row_start_address + (i * _nvm_dev.page_size), row_buffer[i], _nvm_dev.page_size); } while (error_code == STATUS_BUSY); if (error_code != STATUS_OK) { system_interrupt_leave_critical_section(); return error_code; } } system_interrupt_leave_critical_section(); return error_code; } /** * \brief Writes a number of bytes to a page in the NVM memory region. * * Writes from a buffer to a given page address in the NVM memory. * * \param[in] destination_address Destination page address to write to * \param[in] buffer Pointer to buffer where the data to write is * stored * \param[in] length Number of bytes in the page to write * * \note If writing to a page that has previously been written to, the page's * row should be erased (via \ref nvm_erase_row()) before attempting to * write new data to the page. * * \note For SAM D21 RWW devices, see \c SAMD21_64K, command \c NVM_COMMAND_RWWEE_WRITE_PAGE * must be executed before any other commands after writing a page, * refer to errata 13588. * * \note If manual write mode is enabled, the write command must be executed after * this function, otherwise the data will not write to NVM from page buffer. * * \return Status of the attempt to write a page. * * \retval STATUS_OK Requested NVM memory page was successfully * read * \retval STATUS_BUSY NVM controller was busy when the operation * was attempted * \retval STATUS_ERR_BAD_ADDRESS The requested address was outside the * acceptable range of the NVM memory region or * not aligned to the start of a page * \retval STATUS_ERR_INVALID_ARG The supplied write length was invalid */ enum status_code nvm_write_buffer( const uint32_t destination_address, const uint8_t *buffer, uint16_t length) { #ifdef FEATURE_NVM_RWWEE bool is_rww_eeprom = false; #endif /* Check if the destination address is valid */ if (destination_address > ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)) { #ifdef FEATURE_NVM_RWWEE if (destination_address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR) || destination_address < NVMCTRL_RWW_EEPROM_ADDR){ return STATUS_ERR_BAD_ADDRESS; } is_rww_eeprom = true; #else return STATUS_ERR_BAD_ADDRESS; #endif } /* Check if the write address not aligned to the start of a page */ if (destination_address & (_nvm_dev.page_size - 1)) { return STATUS_ERR_BAD_ADDRESS; } /* Check if the write length is longer than an NVM page */ if (length > _nvm_dev.page_size) { return STATUS_ERR_INVALID_ARG; } /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; /* Check if the module is busy */ if (!nvm_is_ready()) { return STATUS_BUSY; } /* Erase the page buffer before buffering new data */ nvm_module->CTRLA.reg = NVM_COMMAND_PAGE_BUFFER_CLEAR | NVMCTRL_CTRLA_CMDEX_KEY; /* Check if the module is busy */ while (!nvm_is_ready()) { /* Force-wait for the buffer clear to complete */ } /* Clear error flags */ nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK; uint32_t nvm_address = destination_address / 2; /* NVM _must_ be accessed as a series of 16-bit words, perform manual copy * to ensure alignment */ for (uint16_t i = 0; i < length; i += 2) { uint16_t data; /* Copy first byte of the 16-bit chunk to the temporary buffer */ data = buffer[i]; /* If we are not at the end of a write request with an odd byte count, * store the next byte of data as well */ if (i < (length - 1)) { data |= (buffer[i + 1] << 8); } /* Store next 16-bit chunk to the NVM memory space */ NVM_MEMORY[nvm_address++] = data; } /* If automatic page write mode is enable, then perform a manual NVM * write when the length of data to be programmed is less than page size */ if ((_nvm_dev.manual_page_write == false) && (length < NVMCTRL_PAGE_SIZE)) { #ifdef FEATURE_NVM_RWWEE return ((is_rww_eeprom) ? (nvm_execute_command(NVM_COMMAND_RWWEE_WRITE_PAGE,destination_address, 0)): (nvm_execute_command(NVM_COMMAND_WRITE_PAGE,destination_address, 0))); #else return nvm_execute_command(NVM_COMMAND_WRITE_PAGE, destination_address, 0); #endif } return STATUS_OK; } /** * \brief Reads a number of bytes from a page in the NVM memory region. * * Reads a given number of bytes from a given page address in the NVM memory * space into a buffer. * * \param[in] source_address Source page address to read from * \param[out] buffer Pointer to a buffer where the content of the read * page will be stored * \param[in] length Number of bytes in the page to read * * \return Status of the page read attempt. * * \retval STATUS_OK Requested NVM memory page was successfully * read * \retval STATUS_BUSY NVM controller was busy when the operation * was attempted * \retval STATUS_ERR_BAD_ADDRESS The requested address was outside the * acceptable range of the NVM memory region or * not aligned to the start of a page * \retval STATUS_ERR_INVALID_ARG The supplied read length was invalid */ enum status_code nvm_read_buffer( const uint32_t source_address, uint8_t *const buffer, uint16_t length) { /* Check if the source address is valid */ if (source_address > ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)) { #ifdef FEATURE_NVM_RWWEE if (source_address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR) || source_address < NVMCTRL_RWW_EEPROM_ADDR){ return STATUS_ERR_BAD_ADDRESS; } #else return STATUS_ERR_BAD_ADDRESS; #endif } /* Check if the read address is not aligned to the start of a page */ if (source_address & (_nvm_dev.page_size - 1)) { return STATUS_ERR_BAD_ADDRESS; } /* Check if the write length is longer than an NVM page */ if (length > _nvm_dev.page_size) { return STATUS_ERR_INVALID_ARG; } /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; /* Check if the module is busy */ if (!nvm_is_ready()) { return STATUS_BUSY; } /* Clear error flags */ nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK; uint32_t page_address = source_address / 2; /* NVM _must_ be accessed as a series of 16-bit words, perform manual copy * to ensure alignment */ for (uint16_t i = 0; i < length; i += 2) { /* Fetch next 16-bit chunk from the NVM memory space */ uint16_t data = NVM_MEMORY[page_address++]; /* Copy first byte of the 16-bit chunk to the destination buffer */ buffer[i] = (data & 0xFF); /* If we are not at the end of a read request with an odd byte count, * store the next byte of data as well */ if (i < (length - 1)) { buffer[i + 1] = (data >> 8); } } return STATUS_OK; } /** * \brief Erases a row in the NVM memory space. * * Erases a given row in the NVM memory region. * * \param[in] row_address Address of the row to erase * * \return Status of the NVM row erase attempt. * * \retval STATUS_OK Requested NVM memory row was successfully * erased * \retval STATUS_BUSY NVM controller was busy when the operation * was attempted * \retval STATUS_ERR_BAD_ADDRESS The requested row address was outside the * acceptable range of the NVM memory region or * not aligned to the start of a row * \retval STATUS_ABORTED NVM erased error */ enum status_code nvm_erase_row( const uint32_t row_address) { #ifdef FEATURE_NVM_RWWEE bool is_rww_eeprom = false; #endif /* Check if the row address is valid */ if (row_address > ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)) { #ifdef FEATURE_NVM_RWWEE if (row_address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR) || row_address < NVMCTRL_RWW_EEPROM_ADDR){ return STATUS_ERR_BAD_ADDRESS; } is_rww_eeprom = true; #else return STATUS_ERR_BAD_ADDRESS; #endif } /* Check if the address to erase is not aligned to the start of a row */ if (row_address & ((_nvm_dev.page_size * NVMCTRL_ROW_PAGES) - 1)) { return STATUS_ERR_BAD_ADDRESS; } /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; /* Check if the module is busy */ if (!nvm_is_ready()) { return STATUS_BUSY; } /* Clear error flags */ nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK; /* Set address and command */ nvm_module->ADDR.reg = (uintptr_t)&NVM_MEMORY[row_address / 4]; #ifdef SAMD21_64K if (is_rww_eeprom) { NVM_MEMORY[row_address / 2] = 0x0; } #endif #ifdef FEATURE_NVM_RWWEE nvm_module->CTRLA.reg = ((is_rww_eeprom) ? (NVM_COMMAND_RWWEE_ERASE_ROW | NVMCTRL_CTRLA_CMDEX_KEY): (NVM_COMMAND_ERASE_ROW | NVMCTRL_CTRLA_CMDEX_KEY)); #else nvm_module->CTRLA.reg = NVM_COMMAND_ERASE_ROW | NVMCTRL_CTRLA_CMDEX_KEY; #endif while (!nvm_is_ready()) { } /* There existed error in NVM erase operation */ if ((enum nvm_error)(nvm_module->STATUS.reg & NVM_ERRORS_MASK) != NVM_ERROR_NONE) { return STATUS_ABORTED; } return STATUS_OK; } /** * \brief Reads the parameters of the NVM controller. * * Retrieves the page size, number of pages, and other configuration settings * of the NVM region. * * \param[out] parameters Parameter structure, which holds page size and * number of pages in the NVM memory */ void nvm_get_parameters( struct nvm_parameters *const parameters) { /* Sanity check parameters */ Assert(parameters); /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; /* Clear error flags */ nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK; /* Read out from the PARAM register */ uint32_t param_reg = nvm_module->PARAM.reg; /* Mask out page size exponent and convert to a number of bytes */ parameters->page_size = 8 << ((param_reg & NVMCTRL_PARAM_PSZ_Msk) >> NVMCTRL_PARAM_PSZ_Pos); /* Mask out number of pages count */ parameters->nvm_number_of_pages = (param_reg & NVMCTRL_PARAM_NVMP_Msk) >> NVMCTRL_PARAM_NVMP_Pos; #ifdef FEATURE_NVM_RWWEE /* Mask out rwwee number of pages count */ parameters->rww_eeprom_number_of_pages = (param_reg & NVMCTRL_PARAM_RWWEEP_Msk) >> NVMCTRL_PARAM_RWWEEP_Pos; #endif /* Read the current EEPROM fuse value from the USER row */ uint16_t eeprom_fuse_value = (NVM_USER_MEMORY[NVMCTRL_FUSES_EEPROM_SIZE_Pos / 16] & NVMCTRL_FUSES_EEPROM_SIZE_Msk) >> NVMCTRL_FUSES_EEPROM_SIZE_Pos; /* Translate the EEPROM fuse byte value to a number of NVM pages */ if (eeprom_fuse_value == 7) { parameters->eeprom_number_of_pages = 0; } else { parameters->eeprom_number_of_pages = NVMCTRL_ROW_PAGES << (6 - eeprom_fuse_value); } /* Read the current BOOTSZ fuse value from the USER row */ uint16_t boot_fuse_value = (NVM_USER_MEMORY[NVMCTRL_FUSES_BOOTPROT_Pos / 16] & NVMCTRL_FUSES_BOOTPROT_Msk) >> NVMCTRL_FUSES_BOOTPROT_Pos; /* Translate the BOOTSZ fuse byte value to a number of NVM pages */ if (boot_fuse_value == 7) { parameters->bootloader_number_of_pages = 0; } else { parameters->bootloader_number_of_pages = NVMCTRL_ROW_PAGES << (7 - boot_fuse_value); } } /** * \brief Checks whether the page region is locked. * * Extracts the region to which the given page belongs and checks whether * that region is locked. * * \param[in] page_number Page number to be checked * * \return Page lock status. * * \retval true Page is locked * \retval false Page is not locked * */ bool nvm_is_page_locked(uint16_t page_number) { uint16_t pages_in_region; uint16_t region_number; #ifdef FEATURE_NVM_RWWEE Assert(page_number < _nvm_dev.number_of_pages); #endif /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; /* Get number of pages in a region */ pages_in_region = _nvm_dev.number_of_pages / 16; /* Get region for given page */ region_number = page_number / pages_in_region; return !(nvm_module->LOCK.reg & (1 << region_number)); } ///@cond INTERNAL /** * \internal * * \brief Translate fusebit words into struct content. * */ static void _nvm_translate_raw_fusebits_to_struct ( uint32_t *raw_user_row, struct nvm_fusebits *fusebits) { fusebits->bootloader_size = (enum nvm_bootloader_size) ((raw_user_row[0] & NVMCTRL_FUSES_BOOTPROT_Msk) >> NVMCTRL_FUSES_BOOTPROT_Pos); fusebits->eeprom_size = (enum nvm_eeprom_emulator_size) ((raw_user_row[0] & NVMCTRL_FUSES_EEPROM_SIZE_Msk) >> NVMCTRL_FUSES_EEPROM_SIZE_Pos); #if (SAML21) || (SAML22) || (SAMR30) fusebits->bod33_level = (uint8_t) ((raw_user_row[0] & FUSES_BOD33USERLEVEL_Msk) >> FUSES_BOD33USERLEVEL_Pos); fusebits->bod33_enable = (bool) (!((raw_user_row[0] & FUSES_BOD33_DIS_Msk) >> FUSES_BOD33_DIS_Pos)); fusebits->bod33_action = (enum nvm_bod33_action) ((raw_user_row[0] & FUSES_BOD33_ACTION_Msk) >> FUSES_BOD33_ACTION_Pos); fusebits->bod33_hysteresis = (bool) ((raw_user_row[1] & FUSES_BOD33_HYST_Msk) >> FUSES_BOD33_HYST_Pos); #elif (SAMD20) || (SAMD21) || (SAMR21)|| (SAMDA1) || (SAMD09) || (SAMD10) || (SAMD11) || (SAMHA1) fusebits->bod33_level = (uint8_t) ((raw_user_row[0] & FUSES_BOD33USERLEVEL_Msk) >> FUSES_BOD33USERLEVEL_Pos); fusebits->bod33_enable = (bool) ((raw_user_row[0] & FUSES_BOD33_EN_Msk) >> FUSES_BOD33_EN_Pos); fusebits->bod33_action = (enum nvm_bod33_action) ((raw_user_row[0] & FUSES_BOD33_ACTION_Msk) >> FUSES_BOD33_ACTION_Pos); fusebits->bod33_hysteresis = (bool) ((raw_user_row[1] & FUSES_BOD33_HYST_Msk) >> FUSES_BOD33_HYST_Pos); #elif (SAMC20) || (SAMC21) fusebits->bodvdd_level = (uint8_t) ((raw_user_row[0] & FUSES_BODVDDUSERLEVEL_Msk) >> FUSES_BODVDDUSERLEVEL_Pos); fusebits->bodvdd_enable = (bool) (!((raw_user_row[0] & FUSES_BODVDD_DIS_Msk) >> FUSES_BODVDD_DIS_Pos)); fusebits->bodvdd_action = (enum nvm_bod33_action) ((raw_user_row[0] & FUSES_BODVDD_ACTION_Msk) >> FUSES_BODVDD_ACTION_Pos); fusebits->bodvdd_hysteresis = (raw_user_row[1] & FUSES_BODVDD_HYST_Msk) >> FUSES_BODVDD_HYST_Pos; #endif #ifdef FEATURE_BOD12 #ifndef FUSES_BOD12USERLEVEL_Pos #define FUSES_BOD12USERLEVEL_Pos 17 #define FUSES_BOD12USERLEVEL_Msk (0x3Ful << FUSES_BOD12USERLEVEL_Pos) #endif #ifndef FUSES_BOD12_DIS_Pos #define FUSES_BOD12_DIS_Pos 23 #define FUSES_BOD12_DIS_Msk (0x1ul << FUSES_BOD12_DIS_Pos) #endif #ifndef FUSES_BOD12_ACTION_Pos #define FUSES_BOD12_ACTION_Pos 24 #define FUSES_BOD12_ACTION_Msk (0x3ul << FUSES_BOD12_ACTION_Pos) #endif fusebits->bod12_level = (uint8_t) ((raw_user_row[0] & FUSES_BOD12USERLEVEL_Msk) >> FUSES_BOD12USERLEVEL_Pos); fusebits->bod12_enable = (bool) (!((raw_user_row[0] & FUSES_BOD12_DIS_Msk) >> FUSES_BOD12_DIS_Pos)); fusebits->bod12_action = (enum nvm_bod12_action) ((raw_user_row[0] & FUSES_BOD12_ACTION_Msk) >> FUSES_BOD33_ACTION_Pos); fusebits->bod12_hysteresis = (bool) ((raw_user_row[1] & FUSES_BOD12_HYST_Msk) >> FUSES_BOD12_HYST_Pos); #endif fusebits->wdt_enable = (bool) ((raw_user_row[0] & WDT_FUSES_ENABLE_Msk) >> WDT_FUSES_ENABLE_Pos); fusebits->wdt_always_on = (bool) ((raw_user_row[0] & WDT_FUSES_ALWAYSON_Msk) >> WDT_FUSES_ALWAYSON_Pos); fusebits->wdt_timeout_period = (uint8_t) ((raw_user_row[0] & WDT_FUSES_PER_Msk) >> WDT_FUSES_PER_Pos); #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30) fusebits->wdt_window_timeout = (enum nvm_wdt_window_timeout) ((raw_user_row[1] & WDT_FUSES_WINDOW_Msk) >> WDT_FUSES_WINDOW_Pos); #else /* WDT Windows timout lay between two 32-bit words in the user row. Because only one bit lays in word[0], bits in word[1] must be left sifted by one to make the correct number */ fusebits->wdt_window_timeout = (enum nvm_wdt_window_timeout) (((raw_user_row[0] & WDT_FUSES_WINDOW_0_Msk) >> WDT_FUSES_WINDOW_0_Pos) | ((raw_user_row[1] & WDT_FUSES_WINDOW_1_Msk) << 1)); #endif fusebits->wdt_early_warning_offset = (enum nvm_wdt_early_warning_offset) ((raw_user_row[1] & WDT_FUSES_EWOFFSET_Msk) >> WDT_FUSES_EWOFFSET_Pos); fusebits->wdt_window_mode_enable_at_poweron = (bool) ((raw_user_row[1] & WDT_FUSES_WEN_Msk) >> WDT_FUSES_WEN_Pos); fusebits->lockbits = (uint16_t) ((raw_user_row[1] & NVMCTRL_FUSES_REGION_LOCKS_Msk) >> NVMCTRL_FUSES_REGION_LOCKS_Pos); } ///@endcond /** * \brief Get fuses from user row. * * Read out the fuse settings from the user row. * * \param[in] fusebits Pointer to a 64-bit wide memory buffer of type struct nvm_fusebits * * \return Status of read fuses attempt. * * \retval STATUS_OK This function will always return STATUS_OK */ enum status_code nvm_get_fuses ( struct nvm_fusebits *fusebits) { enum status_code error_code = STATUS_OK; uint32_t raw_fusebits[2]; /* Make sure the module is ready */ while (!nvm_is_ready()) { } /* Read the fuse settings in the user row, 64 bit */ ((uint16_t*)&raw_fusebits)[0] = (uint16_t)NVM_MEMORY[NVMCTRL_USER / 2]; ((uint16_t*)&raw_fusebits)[1] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 1]; ((uint16_t*)&raw_fusebits)[2] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 2]; ((uint16_t*)&raw_fusebits)[3] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 3]; _nvm_translate_raw_fusebits_to_struct(raw_fusebits, fusebits); return error_code; } /** * \brief Set fuses from user row. * * Set fuse settings from the user row. * * \note When writing to the user row, the values do not get loaded by the * other modules on the device until a device reset occurs. * * \param[in] fusebits Pointer to a 64-bit wide memory buffer of type struct nvm_fusebits * * \return Status of read fuses attempt. * * \retval STATUS_OK This function will always return STATUS_OK * * \retval STATUS_BUSY If the NVM controller was already busy * executing a command when the new command * was issued * \retval STATUS_ERR_IO If the command was invalid due to memory or * security locking * \retval STATUS_ERR_INVALID_ARG If the given command was invalid or * unsupported * \retval STATUS_ERR_BAD_ADDRESS If the given address was invalid */ enum status_code nvm_set_fuses(struct nvm_fusebits *fb) { uint32_t fusebits[2]; enum status_code error_code = STATUS_OK; if (fb == NULL) { return STATUS_ERR_INVALID_ARG; } /* Read the fuse settings in the user row, 64 bit */ fusebits[0] = *((uint32_t *)NVMCTRL_AUX0_ADDRESS); fusebits[1] = *(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1); /* Set user fuses bit */ fusebits[0] &= (~NVMCTRL_FUSES_BOOTPROT_Msk); fusebits[0] |= NVMCTRL_FUSES_BOOTPROT(fb->bootloader_size); fusebits[0] &= (~NVMCTRL_FUSES_EEPROM_SIZE_Msk); fusebits[0] |= NVMCTRL_FUSES_EEPROM_SIZE(fb->eeprom_size); #if (SAML21) || (SAML22) || (SAMR30) fusebits[0] &= (~FUSES_BOD33USERLEVEL_Msk); fusebits[0] |= FUSES_BOD33USERLEVEL(fb->bod33_level); fusebits[0] &= (~FUSES_BOD33_DIS_Msk); fusebits[0] |= (!fb->bod33_enable) << FUSES_BOD33_DIS_Pos; fusebits[0] &= (~FUSES_BOD33_ACTION_Msk); fusebits[0] |= fb->bod33_action << FUSES_BOD33_ACTION_Pos; fusebits[1] &= (~FUSES_BOD33_HYST_Msk); fusebits[1] |= fb->bod33_hysteresis << FUSES_BOD33_HYST_Pos; #elif (SAMD20) || (SAMD21) || (SAMR21) || (SAMDA1) || (SAMD09) || (SAMD10) || (SAMD11) || (SAMHA1) fusebits[0] &= (~FUSES_BOD33USERLEVEL_Msk); fusebits[0] |= FUSES_BOD33USERLEVEL(fb->bod33_level); fusebits[0] &= (~FUSES_BOD33_EN_Msk); fusebits[0] |= (fb->bod33_enable) << FUSES_BOD33_EN_Pos; fusebits[0] &= (~FUSES_BOD33_ACTION_Msk); fusebits[0] |= fb->bod33_action << FUSES_BOD33_ACTION_Pos; fusebits[1] &= (~FUSES_BOD33_HYST_Msk); fusebits[1] |= fb->bod33_hysteresis << FUSES_BOD33_HYST_Pos; #elif (SAMC20) || (SAMC21) fusebits[0] &= (~FUSES_BODVDDUSERLEVEL_Msk); fusebits[0] |= FUSES_BODVDDUSERLEVEL(fb->bodvdd_level); fusebits[0] &= (~FUSES_BODVDD_DIS_Msk); fusebits[0] |= (!fb->bodvdd_enable) << FUSES_BODVDD_DIS_Pos; fusebits[0] &= (~FUSES_BODVDD_ACTION_Msk); fusebits[0] |= fb->bodvdd_action << FUSES_BODVDD_ACTION_Pos; fusebits[1] &= (~FUSES_BODVDD_HYST_Msk); fusebits[1] |= fb->bodvdd_hysteresis << FUSES_BODVDD_HYST_Pos; #endif fusebits[0] &= (~WDT_FUSES_ENABLE_Msk); fusebits[0] |= fb->wdt_enable << WDT_FUSES_ENABLE_Pos; fusebits[0] &= (~WDT_FUSES_ALWAYSON_Msk); fusebits[0] |= (fb->wdt_always_on) << WDT_FUSES_ALWAYSON_Pos; fusebits[0] &= (~WDT_FUSES_PER_Msk); fusebits[0] |= fb->wdt_timeout_period << WDT_FUSES_PER_Pos; #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30) fusebits[1] &= (~WDT_FUSES_WINDOW_Msk); fusebits[1] |= fb->wdt_window_timeout << WDT_FUSES_WINDOW_Pos; #else /* WDT Windows timout lay between two 32-bit words in the user row. the last one bit lays in word[0], and the other bits in word[1] */ fusebits[0] &= (~WDT_FUSES_WINDOW_0_Msk); fusebits[0] |= (fb->wdt_window_timeout & 0x1) << WDT_FUSES_WINDOW_0_Pos; fusebits[1] &= (~WDT_FUSES_WINDOW_1_Msk); fusebits[1] |= (fb->wdt_window_timeout >> 1) << WDT_FUSES_WINDOW_1_Pos; #endif fusebits[1] &= (~WDT_FUSES_EWOFFSET_Msk); fusebits[1] |= fb->wdt_early_warning_offset << WDT_FUSES_EWOFFSET_Pos; fusebits[1] &= (~WDT_FUSES_WEN_Msk); fusebits[1] |= fb->wdt_window_mode_enable_at_poweron << WDT_FUSES_WEN_Pos; fusebits[1] &= (~NVMCTRL_FUSES_REGION_LOCKS_Msk); fusebits[1] |= fb->lockbits << NVMCTRL_FUSES_REGION_LOCKS_Pos; #ifdef FEATURE_BOD12 #ifndef FUSES_BOD12USERLEVEL_Pos #define FUSES_BOD12USERLEVEL_Pos 17 #define FUSES_BOD12USERLEVEL_Msk (0x3Ful << FUSES_BOD12USERLEVEL_Pos) #endif #ifndef FUSES_BOD12_DIS_Pos #define FUSES_BOD12_DIS_Pos 23 #define FUSES_BOD12_DIS_Msk (0x1ul << FUSES_BOD12_DIS_Pos) #endif #ifndef FUSES_BOD12_ACTION_Pos #define FUSES_BOD12_ACTION_Pos 24 #define FUSES_BOD12_ACTION_Msk (0x3ul << FUSES_BOD12_ACTION_Pos) #endif fusebits[0] &= (~FUSES_BOD12USERLEVEL_Msk); fusebits[0] |= ((FUSES_BOD12USERLEVEL_Msk & ((fb->bod12_level) << FUSES_BOD12USERLEVEL_Pos))); fusebits[0] &= (~FUSES_BOD12_DIS_Msk); fusebits[0] |= (!fb->bod12_enable) << FUSES_BOD12_DIS_Pos; fusebits[0] &= (~FUSES_BOD12_ACTION_Msk); fusebits[0] |= fb->bod12_action << FUSES_BOD12_ACTION_Pos; fusebits[1] &= (~FUSES_BOD12_HYST_Msk); fusebits[1] |= fb->bod12_hysteresis << FUSES_BOD12_HYST_Pos; #endif error_code = nvm_execute_command(NVM_COMMAND_ERASE_AUX_ROW,NVMCTRL_AUX0_ADDRESS,0); if (error_code != STATUS_OK) { return error_code; } error_code = nvm_execute_command(NVM_COMMAND_PAGE_BUFFER_CLEAR,NVMCTRL_AUX0_ADDRESS,0); if (error_code != STATUS_OK) { return error_code; } *((uint32_t *)NVMCTRL_AUX0_ADDRESS) = fusebits[0]; *(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1) = fusebits[1]; error_code = nvm_execute_command(NVM_COMMAND_WRITE_AUX_ROW,NVMCTRL_AUX0_ADDRESS,0); if (error_code != STATUS_OK) { return error_code; } return error_code; }