/*---------------------------------------------------------------------------*/ static int write(unsigned long addr, const unsigned char * buffer, unsigned long len) { enum status_code ret; unsigned long page_addr; int copy_len, i; if (!opened) { return -1; } /* Check if the row address is valid */ if ((addr + len) > ((uint32_t)parameters.page_size * parameters.nvm_number_of_pages)) { WARN("addr is outside flash region"); return -1; } while (len > 0) { /* Determine start address of page */ page_addr = (addr / SECTOR_SIZE) * SECTOR_SIZE; /* Copy content of current page to tmp_buffer */ memcpy(tmp_buffer, (void *)page_addr, SECTOR_SIZE); /* Wait until flash is ready */ while (!nvm_is_ready()); /* Erase current page */ nvm_erase_row(page_addr); /* Determine number of bytes to copy */ copy_len = min(len, (page_addr + SECTOR_SIZE) - addr); /* Update tmp_buffer with content from buffer */ memcpy(&tmp_buffer[addr - page_addr], buffer, copy_len); /* Update buffer, len and addr */ len -= copy_len; addr += copy_len; buffer += copy_len; /* Wait until flash is ready */ while (!nvm_is_ready()); /* Write contents of tmp_buffer to flash in NVMCTRL_ROW_PAGES steps */ for (i = 0; i < NVMCTRL_ROW_PAGES; i++) { if ((ret = nvm_write_buffer(page_addr + i * PAGE_SIZE, &tmp_buffer[i * PAGE_SIZE], PAGE_SIZE)) != STATUS_OK) { WARN("nvm_write_buffer error: %d", ret); return -1; } } } /* Return OK */ return 1; }
/*---------------------------------------------------------------------------*/ static int erase(unsigned long from, unsigned long to) { enum status_code ret; unsigned long addr; if (!opened) { return -1; } from = (from / SECTOR_SIZE) * SECTOR_SIZE; to = (to / SECTOR_SIZE) * SECTOR_SIZE; for(addr = from; addr < to; addr += SECTOR_SIZE) { TRACE("addr: %ld", addr); if ((ret = nvm_erase_row(addr)) != STATUS_OK) { WARN("nvm_erase_row error: %d", ret); return -1; } watchdog_periodic(); } while (!nvm_is_ready()); return 1; }
/** * \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 a 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); } }
otError utilsFlashStatusWait(uint32_t aTimeout) { otError error = OT_ERROR_BUSY; uint32_t start = otPlatAlarmMilliGetNow(); do { if (nvm_is_ready()) { error = OT_ERROR_NONE; break; } } while (aTimeout && ((otPlatAlarmMilliGetNow() - start) < aTimeout)); return error; }
status_code_t nvm_sam0_read(mem_type_t mem, uint32_t address, uint8_t *const buffer, uint32_t len) { switch (mem) { /* Get a pointer to the module hardware instance */ Nvmctrl *const nvm_module = NVMCTRL; case INT_FLASH: /* 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 = 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 < len; 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 < (len - 1)) { buffer[i + 1] = (data >> 8); } } break; default: return ERR_INVALID_ARG; }
/** * \brief Sets the up the NVM hardware module based on the configuration. * * Writes a given configuration of a 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; /* Turn on the digital interface clock */ system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB, PM_APBBMASK_NVMCTRL); /* Clear error flags */ nvm_module->STATUS.reg |= NVMCTRL_STATUS_MASK; /* Check if the module is busy */ if (!nvm_is_ready()) { return STATUS_BUSY; } /* 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); /* 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; }
/*---------------------------------------------------------------------------*/ static int open(void) { if (!opened) { struct nvm_config config_nvm; nvm_get_config_defaults(&config_nvm); nvm_set_config(&config_nvm); nvm_get_parameters(¶meters); TRACE("bootloader_number_of_pages: %ld, eeprom_number_of_pages: %ld, nvm_number_of_pages: %d, page_size: %d", parameters.bootloader_number_of_pages, parameters.eeprom_number_of_pages, parameters.nvm_number_of_pages, parameters.page_size); while (!nvm_is_ready()); opened = 1; } return 1; }
/** * \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 SAMD21 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 enable, 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 a 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 Executes a command on the NVM controller. * * Executes an asynchronous command on the NVM controller, to perform a requested * action such as a 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 temp; /* 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 */ temp = nvm_module->CTRLB.reg; #if (SAMC20) || (SAMC21) nvm_module->CTRLB.reg = ((temp &(~(NVMCTRL_CTRLB_CACHEDIS(0x2)))) | NVMCTRL_CTRLB_CACHEDIS(0x1)); #else nvm_module->CTRLB.reg = temp | NVMCTRL_CTRLB_CACHEDIS; #endif /* Clear error flags */ nvm_module->STATUS.reg |= NVMCTRL_STATUS_MASK; /* Check if the module is busy */ if (!nvm_is_ready()) { 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) { 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: 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 = temp; return STATUS_OK; }