From 09be96a6aa56734f13a7a7b7c5c1c5f29f014364 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft <scott@chickadee.tech> Date: Fri, 2 Sep 2016 17:00:30 -0700 Subject: [PATCH] atmel/samd: Add filesystem support. 64k is stored in flash. --- atmel-samd/Makefile | 11 +- atmel-samd/asf/sam0/drivers/nvm/nvm.c | 1146 +++++++++++++++++++++ atmel-samd/asf/sam0/drivers/nvm/nvm.h | 944 +++++++++++++++++ atmel-samd/asf/version.h | 3 + atmel-samd/boards/samd21x18-bootloader.ld | 2 +- atmel-samd/boards/samd21x18.ld | 2 +- atmel-samd/builtin_open.c | 30 + atmel-samd/fatfs_port.c | 46 + atmel-samd/main.c | 123 ++- atmel-samd/modmachine.c | 2 + atmel-samd/moduos.c | 385 +++++++ atmel-samd/mpconfigport.h | 27 +- atmel-samd/storage.c | 290 ++++++ atmel-samd/storage.h | 47 + 14 files changed, 3046 insertions(+), 12 deletions(-) create mode 100644 atmel-samd/asf/sam0/drivers/nvm/nvm.c create mode 100644 atmel-samd/asf/sam0/drivers/nvm/nvm.h create mode 100644 atmel-samd/asf/version.h create mode 100644 atmel-samd/builtin_open.c create mode 100644 atmel-samd/fatfs_port.c create mode 100644 atmel-samd/moduos.c create mode 100644 atmel-samd/storage.c create mode 100644 atmel-samd/storage.h diff --git a/atmel-samd/Makefile b/atmel-samd/Makefile index f57f837cfe69..a4b08a59d15a 100644 --- a/atmel-samd/Makefile +++ b/atmel-samd/Makefile @@ -35,6 +35,7 @@ HAL_DIR=hal/$(MCU_SERIES) INC += -I. INC += -I.. INC += -I../lib/mp-readline +INC += -I../lib/timeutils INC += -Iasf/common/boards/ INC += -Iasf/common/services/sleepmgr/ INC += -Iasf/common/services/usb/ @@ -99,7 +100,7 @@ endif #Debugging/Optimization ifeq ($(DEBUG), 1) -CFLAGS += -O0 -ggdb +CFLAGS += -Os -ggdb else CFLAGS += -Os -DNDEBUG endif @@ -119,6 +120,7 @@ endif SRC_ASF = $(addprefix asf/sam0/,\ drivers/adc/adc_sam_d_r/adc.c \ drivers/dac/dac_sam_d_c/dac.c \ + drivers/nvm/nvm.c \ drivers/port/port.c \ drivers/sercom/sercom.c \ drivers/sercom/sercom_interrupt.c \ @@ -137,15 +139,19 @@ SRC_ASF = $(addprefix asf/sam0/,\ ) SRC_C = \ + builtin_open.c \ + fatfs_port.c \ main.c \ modmachine.c \ modmachine_adc.c \ modmachine_dac.c \ modmachine_pin.c \ modmachine_pwm.c \ + moduos.c \ modutime.c \ mphalport.c \ pin_named_pins.c \ + storage.c \ uart.c \ asf/common/services/sleepmgr/samd/sleepmgr.c \ asf/common/services/usb/class/cdc/device/udi_cdc.c \ @@ -157,6 +163,9 @@ SRC_C = \ asf/sam0/utils/cmsis/samd21/source/system_samd21.c \ asf/sam0/utils/syscalls/gcc/syscalls.c \ boards/$(BOARD)/pins.c \ + lib/fatfs/ff.c \ + lib/fatfs/option/ccsbcs.c \ + lib/timeutils/timeutils.c \ lib/utils/stdout_helpers.c \ lib/utils/printf.c \ lib/utils/pyexec.c \ diff --git a/atmel-samd/asf/sam0/drivers/nvm/nvm.c b/atmel-samd/asf/sam0/drivers/nvm/nvm.c new file mode 100644 index 000000000000..53a0d7beb391 --- /dev/null +++ b/atmel-samd/asf/sam0/drivers/nvm/nvm.c @@ -0,0 +1,1146 @@ +/** + * \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 <a href="http://www.atmel.com/design-support/">Atmel Support</a> + */ +#include "nvm.h" +#include <system.h> +#include <system_interrupt.h> +#include <string.h> + +/** + * \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;e4 + } + 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) + 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; +#else + fusebits->bod33_level = (uint8_t) + ((raw_user_row[0] & SYSCTRL_FUSES_BOD33USERLEVEL_Msk) + >> SYSCTRL_FUSES_BOD33USERLEVEL_Pos); + + fusebits->bod33_enable = (bool) + ((raw_user_row[0] & SYSCTRL_FUSES_BOD33_EN_Msk) + >> SYSCTRL_FUSES_BOD33_EN_Pos); + + fusebits->bod33_action = (enum nvm_bod33_action) + ((raw_user_row[0] & SYSCTRL_FUSES_BOD33_ACTION_Msk) + >> SYSCTRL_FUSES_BOD33_ACTION_Pos); + + fusebits->bod33_hysteresis = (bool) + ((raw_user_row[1] & SYSCTRL_FUSES_BOD33_HYST_Msk) + >> SYSCTRL_FUSES_BOD33_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 */ + raw_fusebits[0] = ((uint32_t)(NVM_MEMORY[NVMCTRL_USER / 2] & 0xffff) << 16) + + (uint32_t)(NVM_MEMORY[(NVMCTRL_USER / 2) + 1] & 0xffff); + raw_fusebits[1] = ((uint32_t)(NVM_MEMORY[(NVMCTRL_USER / 2) + 2] & 0xffff) << 16) + + (uint32_t)(NVM_MEMORY[(NVMCTRL_USER / 2) + 3] & 0xffff); + + _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) + 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; + +#else + fusebits[0] &= (~SYSCTRL_FUSES_BOD33USERLEVEL_Msk); + fusebits[0] |= SYSCTRL_FUSES_BOD33USERLEVEL(fb->bod33_level); + + fusebits[0] &= (~SYSCTRL_FUSES_BOD33_EN_Msk); + fusebits[0] |= (fb->bod33_enable) << SYSCTRL_FUSES_BOD33_EN_Pos; + + fusebits[0] &= (~SYSCTRL_FUSES_BOD33_ACTION_Msk); + fusebits[0] |= fb->bod33_action << SYSCTRL_FUSES_BOD33_ACTION_Pos; + + fusebits[1] &= (~SYSCTRL_FUSES_BOD33_HYST_Msk); + fusebits[1] |= fb->bod33_hysteresis << SYSCTRL_FUSES_BOD33_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; +} diff --git a/atmel-samd/asf/sam0/drivers/nvm/nvm.h b/atmel-samd/asf/sam0/drivers/nvm/nvm.h new file mode 100644 index 000000000000..99245dd4a369 --- /dev/null +++ b/atmel-samd/asf/sam0/drivers/nvm/nvm.h @@ -0,0 +1,944 @@ +/** + * \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 <a href="http://www.atmel.com/design-support/">Atmel Support</a> + */ +#ifndef NVM_H_INCLUDED +#define NVM_H_INCLUDED + +/** + * \defgroup asfdoc_sam0_nvm_group SAM Non-Volatile Memory (NVM) Driver + * + * This driver for Atmel® | SMART ARM®-based microcontrollers provides + * an interface for the configuration and management of non-volatile memories + * within the device, for partitioning, erasing, reading, and writing of data. + * + * The following peripheral is used by this module: + * - NVM (Non-Volatile Memory) + * + * The following devices can use this module: + * - Atmel | SMART SAM D20/D21 + * - Atmel | SMART SAM R21 + * - Atmel | SMART SAM D09/D10/D11 + * - Atmel | SMART SAM L21/L22 + * - Atmel | SMART SAM DA1 + * - Atmel | SMART SAM C20/C21 + * - Atmel | SMART SAM R30 + * + * The outline of this documentation is as follows: + * - \ref asfdoc_sam0_nvm_prerequisites + * - \ref asfdoc_sam0_nvm_module_overview + * - \ref asfdoc_sam0_nvm_special_considerations + * - \ref asfdoc_sam0_nvm_extra_info + * - \ref asfdoc_sam0_nvm_examples + * - \ref asfdoc_sam0_nvm_api_overview + * + * + * \section asfdoc_sam0_nvm_prerequisites Prerequisites + * + * There are no prerequisites for this module. + * + * + * \section asfdoc_sam0_nvm_module_overview Module Overview + * + * The Non-Volatile Memory (NVM) module provides an interface to the device's + * Non-Volatile Memory controller, so that memory pages can be written, read, + * erased, and reconfigured in a standardized manner. + * + * \subsection asfdoc_sam0_nvm_features Driver Feature Macro Definition + * <table> + * <tr> + * <th>Driver feature macro</th> + * <th>Supported devices</th> + * </tr> + * <tr> + * <td>FEATURE_NVM_RWWEE</td> + * <td>SAM L21/L22, SAM D21-64K, SAM DA1, SAM C20/C21, SAM R30</td> + * </tr> + * <tr> + * <td>FEATURE_BOD12</td> + * <td>SAM L21, SAMR30</td> + * </tr> + * </table> + * \note The specific features are only available in the driver when the + * selected device supports those features. + * + * \subsection asfdoc_sam0_nvm_module_overview_regions Memory Regions + * The NVM memory space of the SAM devices is divided into two sections: + * a Main Array section, and an Auxiliary space section. The Main Array space + * can be configured to have an (emulated) EEPROM and/or boot loader section. + * The memory layout with the EEPROM and bootloader partitions is shown in + * \ref asfdoc_sam0_nvm_module_mem_layout "the figure below". + * + * \anchor asfdoc_sam0_nvm_module_mem_layout + * \dot + * digraph memory_layout { + * size="5,5" + * node [shape=plaintext, fontname=arial] + * memory [label=< + * <table border="0" cellborder="1" cellspacing="0" > + * <tr> + * <td align="right" border="0"> End of NVM Memory </td> + * <td rowspan="3" align="center"> Reserved EEPROM Section </td> + * </tr> + * <tr> + * <td align="right" border="0"> </td> + * </tr> + * <tr> + * <td align="right" border="0"> Start of EEPROM Memory </td> + * </tr> + * <tr> + * <td align="right" border="0"> End of Application Memory </td> + * <td rowspan="3" align="center"> Application Section </td> + * </tr> + * <tr> + * <td height="300" align="right" border="0"> </td> + * </tr> + * <tr> + * <td align="right" border="0"> Start of Application Memory </td> + * </tr> + * <tr> + * <td align="right" border="0"> End of Bootloader Memory </td> + * <td rowspan="3" align="center"> BOOT Section </td> + * </tr> + * <tr> + * <td align="right" border="0"> </td> + * </tr> + * <tr> + * <td align="right" border="0"> Start of NVM Memory</td> + * </tr> + * </table> + * >] + * } + * \enddot + * + * The Main Array is divided into rows and pages, where each row contains four + * pages. The size of each page may vary from 8-1024 bytes dependent of the + * device. Device specific parameters such as the page size and total number of + * pages in the NVM memory space are available via the \ref nvm_get_parameters() + * function. + * + * An NVM page number and address can be computed via the following equations: + * + * \f[ PageNum = (RowNum \times 4) + PagePosInRow \f] + * \f[ PageAddr = PageNum \times PageSize \f] + * + * \ref asfdoc_sam0_nvm_module_row_layout "The figure below" shows an example + * of the memory page and address values associated with logical row 7 of the + * NVM memory space. + * + * \anchor asfdoc_sam0_nvm_module_row_layout + * \dot + * digraph row_layout { + * size="4,4" + * node [shape=plaintext, fontname=arial] + * row [label=< + * <table border="0" cellborder="1" cellspacing="0"> + * <tr> + * <td align="right" border ="0"> Row 0x07 </td> + * <td > Page 0x1F </td> + * <td > Page 0x1E </td> + * <td > Page 0x1D </td> + * <td > Page 0x1C </td> + * </tr> + * <tr> + * <td align="right" border ="0"> Address </td> + * <td border="0"> 0x7C0 </td> + * <td border="0"> 0x780 </td> + * <td border="0"> 0x740 </td> + * <td border="0"> 0x700 </td> + * </tr> + * </table> + * >] + * } + * \enddot + * + * \subsection asfdoc_sam0_nvm_module_overview_locking_regions Region Lock Bits + * As mentioned in \ref asfdoc_sam0_nvm_module_overview_regions, the main + * block of the NVM memory is divided into a number of individually addressable + * pages. These pages are grouped into 16 equal sized regions, where each region + * can be locked separately issuing an \ref NVM_COMMAND_LOCK_REGION command or + * by writing the LOCK bits in the User Row. Rows reserved for the EEPROM + * section are not affected by the lock bits or commands. + * + * \note By using the \ref NVM_COMMAND_LOCK_REGION or + * \ref NVM_COMMAND_UNLOCK_REGION commands the settings will remain in + * effect until the next device reset. By changing the default lock + * setting for the regions, the auxiliary space must to be written, + * however the adjusted configuration will not take effect until the next + * device reset. + * + * \note If the \ref asfdoc_sam0_nvm_special_consideration_security_bit is + * set, the auxiliary space cannot be written to. Clearing of the security + * bit can only be performed by a full chip erase. + * + * \subsection asfdoc_sam0_nvm_module_overview_sub_rw Read/Write + * Reading from the NVM memory can be performed using direct addressing into the + * NVM memory space, or by calling the \ref nvm_read_buffer() function. + * + * Writing to the NVM memory must be performed by the \ref nvm_write_buffer() + * function - additionally, a manual page program command must be issued if + * the NVM controller is configured in manual page writing mode. + * + * Before a page can be updated, the associated NVM memory row must be erased + * first via the \ref nvm_erase_row() function. Writing to a non-erased page + * will result in corrupt data being stored in the NVM memory space. + * + * \section asfdoc_sam0_nvm_special_considerations Special Considerations + * + * \subsection asfdoc_sam0_nvm_special_consideration_pageerase Page Erasure + * The granularity of an erase is per row, while the granularity of a write is + * per page. Thus, if the user application is modifying only one page of a row, + * the remaining pages in the row must be buffered and the row erased, as an + * erase is mandatory before writing to a page. + * + * \subsection asfdoc_sam0_nvm_special_consideration_clocks Clocks + * The user must ensure that the driver is configured with a proper number of + * wait states when the CPU is running at high frequencies. + * + * \subsection asfdoc_sam0_nvm_special_consideration_security_bit Security Bit + * The User Row in the Auxiliary Space cannot be read or written when + * the Security Bit is set. The Security Bit can be set by using passing + * \ref NVM_COMMAND_SET_SECURITY_BIT to the \ref nvm_execute_command() function, + * or it will be set if one tries to access a locked region. See + * \ref asfdoc_sam0_nvm_module_overview_locking_regions. + * + * The Security Bit can only be cleared by performing a chip erase. + * + * + * \section asfdoc_sam0_nvm_extra_info Extra Information + * + * For extra information, see \ref asfdoc_sam0_nvm_extra. This includes: + * - \ref asfdoc_sam0_nvm_extra_acronyms + * - \ref asfdoc_sam0_nvm_extra_dependencies + * - \ref asfdoc_sam0_nvm_extra_errata + * - \ref asfdoc_sam0_nvm_extra_history + * + * + * \section asfdoc_sam0_nvm_examples Examples + * + * For a list of examples related to this driver, see + * \ref asfdoc_sam0_nvm_exqsg. + * + * + * \section asfdoc_sam0_nvm_api_overview API Overview + * @{ + */ + +#include <compiler.h> +#include <status_codes.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Define SAMD21-64K devices */ +#if defined(SAMD21E15L) || defined(SAMD21E16L) || defined(__SAMD21E15L__) || defined(__SAMD21E16L__) \ + || defined(SAMD21E15B) || defined(SAMD21E16B) || defined(__SAMD21E15B__) || defined(__SAMD21E16B__) \ + || defined(SAMD21E15BU) || defined(SAMD21E16BU) || defined(__SAMD21E15BU__) || defined(__SAMD21E16BU__) \ + || defined(SAMD21G15L) || defined(SAMD21G16L) || defined(__SAMD21G15L__) || defined(__SAMD21G16L__) \ + || defined(SAMD21G15B) || defined(SAMD21G16B) || defined(__SAMD21G15B__) || defined(__SAMD21G16B__) \ + || defined(SAMD21J15B) || defined(SAMD21J16B) || defined(__SAMD21J15B__) || defined(__SAMD21J16B__) + +# define SAMD21_64K + +#endif + +/** + * \name Driver Feature Definition + * + * Define NVM features set according to the different device families. + * @{ +*/ +#if (SAML21) || (SAML22) || (SAMDA1) || (SAMC20) || (SAMC21) || (SAMR30) || defined(SAMD21_64K) || defined(__DOXYGEN__) +/** Read while write EEPROM emulation feature. */ +# define FEATURE_NVM_RWWEE +#endif +#if (SAML21) || (SAMR30) || defined(__DOXYGEN__) +/** Brown-out detector internal to the voltage regulator for VDDCORE. */ +#define FEATURE_BOD12 +#endif +/*@}*/ + +#if !defined(__DOXYGEN__) +/** + * \brief Mask for the error flags in the status register. + */ +# define NVM_ERRORS_MASK (NVMCTRL_STATUS_PROGE | \ + NVMCTRL_STATUS_LOCKE | \ + NVMCTRL_STATUS_NVME) +#endif + +/** + * \brief NVM error flags. + * + * Possible NVM controller error codes, which can be returned by the NVM + * controller after a command is issued. + */ +enum nvm_error { + /** No errors */ + NVM_ERROR_NONE = 0, + /** Lock error, a locked region was attempted accessed */ + NVM_ERROR_LOCK = NVMCTRL_STATUS_NVME | NVMCTRL_STATUS_LOCKE, + /** Program error, invalid command was executed */ + NVM_ERROR_PROG = NVMCTRL_STATUS_NVME | NVMCTRL_STATUS_PROGE, +}; + +/** + * \brief NVM controller commands. + */ +enum nvm_command { + /** Erases the addressed memory row */ + NVM_COMMAND_ERASE_ROW = NVMCTRL_CTRLA_CMD_ER, + + /** Write the contents of the page buffer to the addressed memory page */ + NVM_COMMAND_WRITE_PAGE = NVMCTRL_CTRLA_CMD_WP, + + /** Erases the addressed auxiliary memory row. + * + * \note This command can only be given when the security bit is not set. + */ + NVM_COMMAND_ERASE_AUX_ROW = NVMCTRL_CTRLA_CMD_EAR, + + /** Write the contents of the page buffer to the addressed auxiliary memory + * row. + * + * \note This command can only be given when the security bit is not set. + */ + NVM_COMMAND_WRITE_AUX_ROW = NVMCTRL_CTRLA_CMD_WAP, + + /** Locks the addressed memory region, preventing further modifications + * until the region is unlocked or the device is erased + */ + NVM_COMMAND_LOCK_REGION = NVMCTRL_CTRLA_CMD_LR, + + /** Unlocks the addressed memory region, allowing the region contents to be + * modified + */ + NVM_COMMAND_UNLOCK_REGION = NVMCTRL_CTRLA_CMD_UR, + + /** Clears the page buffer of the NVM controller, resetting the contents to + * all zero values + */ + NVM_COMMAND_PAGE_BUFFER_CLEAR = NVMCTRL_CTRLA_CMD_PBC, + + /** Sets the device security bit, disallowing the changing of lock bits and + * auxiliary row data until a chip erase has been performed + */ + NVM_COMMAND_SET_SECURITY_BIT = NVMCTRL_CTRLA_CMD_SSB, + + /** Enter power reduction mode in the NVM controller to reduce the power + * consumption of the system + */ + NVM_COMMAND_ENTER_LOW_POWER_MODE = NVMCTRL_CTRLA_CMD_SPRM, + + /** Exit power reduction mode in the NVM controller to allow other NVM + * commands to be issued + */ + NVM_COMMAND_EXIT_LOW_POWER_MODE = NVMCTRL_CTRLA_CMD_CPRM, +#ifdef FEATURE_NVM_RWWEE + /** Read while write (RWW) EEPROM area erase row */ + NVM_COMMAND_RWWEE_ERASE_ROW = NVMCTRL_CTRLA_CMD_RWWEEER, + /** RWW EEPROM write page */ + NVM_COMMAND_RWWEE_WRITE_PAGE = NVMCTRL_CTRLA_CMD_RWWEEWP, +#endif +}; + +/** + * \brief NVM controller power reduction mode configurations. + * + * Power reduction modes of the NVM controller, to conserve power while the + * device is in sleep. + */ +enum nvm_sleep_power_mode { + /** NVM controller exits low-power mode on first access after sleep */ + NVM_SLEEP_POWER_MODE_WAKEONACCESS = NVMCTRL_CTRLB_SLEEPPRM_WAKEONACCESS_Val, + /** NVM controller exits low-power mode when the device exits sleep mode */ + NVM_SLEEP_POWER_MODE_WAKEUPINSTANT = NVMCTRL_CTRLB_SLEEPPRM_WAKEUPINSTANT_Val, + /** Power reduction mode in the NVM controller disabled */ + NVM_SLEEP_POWER_MODE_ALWAYS_AWAKE = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val, +}; + +/** + * \brief NVM controller cache readmode configuration. + * + * Control how the NVM cache prefetch data from flash. + * + */ +enum nvm_cache_readmode { + /** The NVM Controller (cache system) does not insert wait states on + * a cache miss. Gives the best system performance. + */ + NVM_CACHE_READMODE_NO_MISS_PENALTY, + /** Reduces power consumption of the cache system, but inserts a + * wait state each time there is a cache miss + */ + NVM_CACHE_READMODE_LOW_POWER, + /** The cache system ensures that a cache hit or miss takes the same + * amount of time, determined by the number of programmed flash + * wait states + */ + NVM_CACHE_READMODE_DETERMINISTIC, +}; + +/** + * \brief NVM controller configuration structure. + * + * Configuration structure for the NVM controller within the device. + */ +struct nvm_config { + /** Power reduction mode during device sleep */ + enum nvm_sleep_power_mode sleep_power_mode; + /** Manual write mode; if enabled, pages loaded into the NVM buffer will + * not be written until a separate write command is issued. If disabled, + * writing to the last byte in the NVM page buffer will trigger an automatic + * write. + * + * \note If a partial page is to be written, a manual write command must be + * executed in either mode. + */ + bool manual_page_write; + /** Number of wait states to insert when reading from flash, to prevent + * invalid data from being read at high clock frequencies + */ + uint8_t wait_states; + + /** + * Setting this to true will disable the pre-fetch cache in front of the + * NVM controller + */ + bool disable_cache; +#if (SAMC20) || (SAMC21) + /** + * Setting this to true will disable the pre-fetch RWW cache in front of the + * NVM controller. + * If RWW cache is enabled, NVM cache will also be enabled. + */ + bool disable_rww_cache; +#endif + /** + * Select the mode for how the cache will pre-fetch data from the flash + */ + enum nvm_cache_readmode cache_readmode; +}; + +/** + * \brief NVM memory parameter structure. + * + * Structure containing the memory layout parameters of the NVM module. + */ +struct nvm_parameters { + /** Number of bytes per page */ + uint8_t page_size; + /** Number of pages in the main array */ + uint16_t nvm_number_of_pages; + /** Size of the emulated EEPROM memory section configured in the NVM + * auxiliary memory space */ + uint32_t eeprom_number_of_pages; + /** Size of the Bootloader memory section configured in the NVM auxiliary + * memory space */ + uint32_t bootloader_number_of_pages; +#ifdef FEATURE_NVM_RWWEE + /** Number of pages in read while write EEPROM (RWWEE) emulation area */ + uint16_t rww_eeprom_number_of_pages; +#endif +}; + +/** + * \brief Bootloader size. + * + * Available bootloader protection sizes in kilobytes. + * + */ +enum nvm_bootloader_size { + /** Boot Loader Size is 32768 bytes */ + NVM_BOOTLOADER_SIZE_128, + /** Boot Loader Size is 16384 bytes */ + NVM_BOOTLOADER_SIZE_64, + /** Boot Loader Size is 8192 bytes */ + NVM_BOOTLOADER_SIZE_32, + /** Boot Loader Size is 4096 bytes */ + NVM_BOOTLOADER_SIZE_16, + /** Boot Loader Size is 2048 bytes */ + NVM_BOOTLOADER_SIZE_8, + /** Boot Loader Size is 1024 bytes */ + NVM_BOOTLOADER_SIZE_4, + /** Boot Loader Size is 512 bytes */ + NVM_BOOTLOADER_SIZE_2, + /** Boot Loader Size is 0 bytes */ + NVM_BOOTLOADER_SIZE_0, +}; + +/** + * \brief EEPROM emulator size. + * + * Available space in flash dedicated for EEPROM emulator in bytes. + * + */ +enum nvm_eeprom_emulator_size { + /** EEPROM Size for EEPROM emulation is 16384 bytes */ + NVM_EEPROM_EMULATOR_SIZE_16384, + /** EEPROM Size for EEPROM emulation is 8192 bytes */ + NVM_EEPROM_EMULATOR_SIZE_8192, + /** EEPROM Size for EEPROM emulation is 4096 bytes */ + NVM_EEPROM_EMULATOR_SIZE_4096, + /** EEPROM Size for EEPROM emulation is 2048 bytes */ + NVM_EEPROM_EMULATOR_SIZE_2048, + /** EEPROM Size for EEPROM emulation is 1024 bytes */ + NVM_EEPROM_EMULATOR_SIZE_1024, + /** EEPROM Size for EEPROM emulation is 512 bytes */ + NVM_EEPROM_EMULATOR_SIZE_512, + /** EEPROM Size for EEPROM emulation is 256 bytes */ + NVM_EEPROM_EMULATOR_SIZE_256, + /** EEPROM Size for EEPROM emulation is 0 bytes */ + NVM_EEPROM_EMULATOR_SIZE_0, +}; + +/** + * \brief BOD33 Action. + * + * What action should be triggered when BOD33 is detected. + * + */ +enum nvm_bod33_action { + /** No action */ + NVM_BOD33_ACTION_NONE, + /** The BOD33 generates a reset */ + NVM_BOD33_ACTION_RESET, + /** The BOD33 generates an interrupt */ + NVM_BOD33_ACTION_INTERRUPT, +}; + +#ifdef FEATURE_BOD12 +/** + * \brief BOD12 Action. + * + * What action should be triggered when BOD12 is detected. + * + */ +enum nvm_bod12_action { + /** No action */ + NVM_BOD12_ACTION_NONE, + /** The BOD12 generates a reset */ + NVM_BOD12_ACTION_RESET, + /** The BOD12 generates an interrupt */ + NVM_BOD12_ACTION_INTERRUPT, +}; +#endif + +/** + * \brief WDT Window time-out period. + * + * Window mode time-out period in clock cycles. + * + */ +enum nvm_wdt_window_timeout { + /** 8 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_8, + /** 16 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_16, + /** 32 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_32, + /** 64 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_64, + /** 128 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_128, + /** 256 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_256, + /** 512 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_512, + /** 1024 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_1024, + /** 2048 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_2048, + /** 4096 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_4096, + /** 8192 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_8192, + /** 16384 clock cycles */ + NVM_WDT_WINDOW_TIMEOUT_PERIOD_16384, +}; + +/** + * \brief WDT Early warning offset. + * + * This setting determine how many GCLK_WDT cycles before a watchdog time-out period + * an early warning interrupt should be triggered. + * + */ +enum nvm_wdt_early_warning_offset { + /** 8 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_8, + /** 16 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_16, + /** 32 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_32, + /** 64 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_64, + /** 128 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_128, + /** 256 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_256, + /** 512 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_512, + /** 1024 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_1024, + /** 2048 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_2048, + /** 4096 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_4096, + /** 8192 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_8192, + /** 16384 clock cycles */ + NVM_WDT_EARLY_WARNING_OFFSET_16384, +}; + +/** + * \brief NVM user row fuse setting structure. + * + * This structure contain the layout of the first 64 bits of the user row + * which contain the fuse settings. + */ +struct nvm_fusebits { + /** Bootloader size */ + enum nvm_bootloader_size bootloader_size; + /** EEPROM emulation area size */ + enum nvm_eeprom_emulator_size eeprom_size; +#if (SAMC20) || (SAMC21) + /** BODVDD Threshold level at power on */ + uint8_t bodvdd_level; + /** BODVDD Enable at power on */ + bool bodvdd_enable; + /** BODVDD Action at power on */ + enum nvm_bod33_action bodvdd_action; + /* BODVDD Hysteresis at power on */ + bool bodvdd_hysteresis; +#else + /** BOD33 Threshold level at power on */ + uint8_t bod33_level; + /** BOD33 Enable at power on */ + bool bod33_enable; + /** BOD33 Action at power on */ + enum nvm_bod33_action bod33_action; + /* BOD33 Hysteresis at power on */ + bool bod33_hysteresis; +#endif + /** WDT Enable at power on */ + bool wdt_enable; + /** WDT Always-on at power on */ + bool wdt_always_on; + /** WDT Period at power on */ + uint8_t wdt_timeout_period; + /** WDT Window mode time-out at power on */ + enum nvm_wdt_window_timeout wdt_window_timeout; + /** WDT Early warning interrupt time offset at power on */ + enum nvm_wdt_early_warning_offset wdt_early_warning_offset; + /** WDT Window mode enabled at power on */ + bool wdt_window_mode_enable_at_poweron; + /** NVM Lock bits */ + uint16_t lockbits; +#ifdef FEATURE_BOD12 + /** BOD12 Threshold level at power on */ + uint8_t bod12_level; + /** BOD12 Enable at power on */ + bool bod12_enable; + /** BOD12 Action at power on */ + enum nvm_bod12_action bod12_action; + /* BOD12 Hysteresis at power on */ + bool bod12_hysteresis; +#endif +}; + +/** + * \name Configuration and Initialization + * @{ + */ + +/** + * \brief Initializes an NVM controller configuration structure to defaults. + * + * Initializes a given NVM controller configuration structure to a set of + * known default values. This function should be called on all new + * instances of these configuration structures before being modified by the + * user application. + * + * The default configuration is as follows: + * \li Power reduction mode enabled after sleep mode until first NVM access + * \li Automatic page write mode disabled + * \li Number of FLASH wait states left unchanged + * + * \param[out] config Configuration structure to initialize to default values + * + */ +static inline void nvm_get_config_defaults( + struct nvm_config *const config) +{ + /* Sanity check the parameters */ + Assert(config); + + /* Write the default configuration for the NVM configuration */ + config->sleep_power_mode = NVM_SLEEP_POWER_MODE_WAKEONACCESS; + config->manual_page_write = true; + config->wait_states = NVMCTRL->CTRLB.bit.RWS; + config->disable_cache = false; +#if (SAMC20) || (SAMC21) + config->disable_rww_cache = false; +#endif + config->cache_readmode = NVM_CACHE_READMODE_NO_MISS_PENALTY; +} + +enum status_code nvm_set_config( + const struct nvm_config *const config); + +/** + * \brief Checks if the NVM controller is ready to accept a new command. + * + * Checks the NVM controller to determine if it is currently busy execution an + * operation, or ready for a new command. + * + * \return Busy state of the NVM controller. + * + * \retval true If the hardware module is ready for a new command + * \retval false If the hardware module is busy executing a command + * + */ +static inline bool nvm_is_ready(void) +{ + /* Get a pointer to the module hardware instance */ + Nvmctrl *const nvm_module = NVMCTRL; + + return nvm_module->INTFLAG.reg & NVMCTRL_INTFLAG_READY; +} + +/** @} */ + +/** + * \name NVM Access Management + * @{ + */ + +void nvm_get_parameters( + struct nvm_parameters *const parameters); + +enum status_code nvm_write_buffer( + const uint32_t destination_address, + const uint8_t *buffer, + uint16_t length); + +enum status_code nvm_read_buffer( + const uint32_t source_address, + uint8_t *const buffer, + uint16_t length); + +enum status_code nvm_update_buffer( + const uint32_t destination_address, + uint8_t *const buffer, + uint16_t offset, + uint16_t length); + +enum status_code nvm_erase_row( + const uint32_t row_address); + +enum status_code nvm_execute_command( + const enum nvm_command command, + const uint32_t address, + const uint32_t parameter); + +enum status_code nvm_get_fuses(struct nvm_fusebits *fusebits); +enum status_code nvm_set_fuses(struct nvm_fusebits *fb); + +bool nvm_is_page_locked(uint16_t page_number); + +/** + * \brief Retrieves the error code of the last issued NVM operation. + * + * Retrieves the error code from the last executed NVM operation. Once + * retrieved, any error state flags in the controller are cleared. + * + * \note The \ref nvm_is_ready() function is an exception. Thus, errors + * retrieved after running this function should be valid for the function + * executed before \ref nvm_is_ready(). + * + * \return Error caused by the last NVM operation. + * + * \retval NVM_ERROR_NONE No error occurred in the last NVM operation + * + * \retval NVM_ERROR_LOCK The last NVM operation attempted to access a locked + * region + * \retval NVM_ERROR_PROG An invalid NVM command was issued + */ +static inline enum nvm_error nvm_get_error(void) +{ + enum nvm_error ret_val; + + /* Get a pointer to the module hardware instance */ + Nvmctrl *const nvm_module = NVMCTRL; + + /* Mask out non-error bits */ + ret_val = (enum nvm_error)(nvm_module->STATUS.reg & NVM_ERRORS_MASK); + + /* Clear error flags */ + nvm_module->STATUS.reg = NVM_ERRORS_MASK; + + /* Return error code from the NVM controller */ + return ret_val; +} + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** @} */ + +/** + * \page asfdoc_sam0_nvm_extra Extra Information for NVM Driver + * + * \section asfdoc_sam0_nvm_extra_acronyms Acronyms + * The table below presents the acronyms used in this module: + * + * <table> + * <tr> + * <th>Acronym</th> + * <th>Description</th> + * </tr> + * <tr> + * <td>NVM</td> + * <td>Non-Volatile Memory</td> + * </tr> + * <tr> + * <td>EEPROM</td> + * <td>Electrically Erasable Programmable Read-Only Memory</td> + * </tr> + * </table> + * + * + * \section asfdoc_sam0_nvm_extra_dependencies Dependencies + * This driver has the following dependencies: + * + * - None + * + * + * \section asfdoc_sam0_nvm_extra_errata Errata + * There are no errata related to this driver. + * + * + * \section asfdoc_sam0_nvm_extra_history Module History + * An overview of the module history is presented in the table below, with + * details on the enhancements and fixes made to the module since its first + * release. The current version of this corresponds to the newest version in + * the table. + * + * <table> + * <tr> + * <th>Changelog</th> + * </tr> + * <tr> + * <td>Removed BOD12 reference, removed nvm_set_fuses() API</td> + * </tr> + * <tr> + * <td>Added functions to read/write fuse settings</td> + * </tr> + * <tr> + * <td>Added support for NVM cache configuration</td> + * </tr> + * <tr> + * <td>Updated initialization function to also enable the digital interface + * clock to the module if it is disabled</td> + * </tr> + * <tr> + * <td>Initial Release</td> + * </tr> + * </table> + */ + +/** + * \page asfdoc_sam0_nvm_exqsg Examples for NVM Driver + * + * This is a list of the available Quick Start guides (QSGs) and example + * applications for \ref asfdoc_sam0_nvm_group. QSGs are simple examples with + * step-by-step instructions to configure and use this driver in a selection of + * use cases. Note that a QSG can be compiled as a standalone application or be + * added to the user application. + * + * - \subpage asfdoc_sam0_nvm_basic_use_case + * + * \page asfdoc_sam0_nvm_document_revision_history Document Revision History + * + * <table> + * <tr> + * <th>Doc. Rev.</th> + * <th>Date</th> + * <th>Comments</th> + * </tr> + * <tr> + * <td>42114E</td> + * <td>12/2015</td> + * <td>Added support for SAM L21/L22, SAM C21, SAM D09, SAMR30 and SAM DA1</td> + * </tr> + * <tr> + * <td>42114D</td> + * <td>12/2014</td> + * <td>Added support for SAM R21 and SAM D10/D11</td> + * </tr> + * <tr> + * <td>42114C</td> + * <td>01/2014</td> + * <td>Added support for SAM D21</td> + * </tr> + * <tr> + * <td>42114B</td> + * <td>06/2013</td> + * <td>Corrected documentation typos</td> + * </tr> + * <tr> + * <td>42114A</td> + * <td>06/2013</td> + * <td>Initial document release</td> + * </tr> + * </table> + */ + +#endif /* NVM_H_INCLUDED */ diff --git a/atmel-samd/asf/version.h b/atmel-samd/asf/version.h new file mode 100644 index 000000000000..de93d07ac238 --- /dev/null +++ b/atmel-samd/asf/version.h @@ -0,0 +1,3 @@ +// Update this when the library is updated. Its not included by default. + +#define ASF_VERSION "3.32.0" diff --git a/atmel-samd/boards/samd21x18-bootloader.ld b/atmel-samd/boards/samd21x18-bootloader.ld index 974d78626faf..89ff084fb7ad 100644 --- a/atmel-samd/boards/samd21x18-bootloader.ld +++ b/atmel-samd/boards/samd21x18-bootloader.ld @@ -5,7 +5,7 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000-0x2000 /* Leave 8KiB for the bootloader. */ + FLASH (rx) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000 - 0x2000 - 0x010000 /* Leave 8KiB for the bootloader and 64k for the flash file system. */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/samd21x18.ld b/atmel-samd/boards/samd21x18.ld index 6c1e6c01e1bd..0f1168c578a8 100644 --- a/atmel-samd/boards/samd21x18.ld +++ b/atmel-samd/boards/samd21x18.ld @@ -5,7 +5,7 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 - 0x010000 /* Leave 64k for the flash file system. */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/builtin_open.c b/atmel-samd/builtin_open.c new file mode 100644 index 000000000000..697eec8eaaf8 --- /dev/null +++ b/atmel-samd/builtin_open.c @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "extmod/vfs_fat_file.h" + +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, fatfs_builtin_open); diff --git a/atmel-samd/fatfs_port.c b/atmel-samd/fatfs_port.c new file mode 100644 index 000000000000..dcb02f61306e --- /dev/null +++ b/atmel-samd/fatfs_port.c @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mphal.h" +#include "py/runtime.h" +#include "lib/fatfs/ff.h" /* FatFs lower layer API */ +#include "lib/fatfs/diskio.h" /* FatFs lower layer API */ + +const PARTITION VolToPart[MICROPY_FATFS_VOLUMES] = { + {0, 1}, // Logical drive 0 ==> Physical drive 0, 1st partition + {1, 0}, // Logical drive 1 ==> Physical drive 1 (auto detection) + {2, 0}, // Logical drive 2 ==> Physical drive 2 (auto detection) + {3, 0}, // Logical drive 3 ==> Physical drive 3 (auto detection) + /* + {0, 2}, // Logical drive 2 ==> Physical drive 0, 2nd partition + {0, 3}, // Logical drive 3 ==> Physical drive 0, 3rd partition + */ +}; + +DWORD get_fattime(void) { + // TODO(tannewt): Support the RTC. + return ((2016) << 25) | ((9) << 21) | ((1) << 16) | ((16) << 11) | ((43) << 5) | (35 / 2); +} diff --git a/atmel-samd/main.c b/atmel-samd/main.c index 15b281cbc530..c4db13470961 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -8,7 +8,10 @@ #include "py/runtime.h" #include "py/repl.h" #include "py/gc.h" + +#include "lib/fatfs/ff.h" #include "lib/utils/pyexec.h" +#include "extmod/fsusermount.h" #include "asf/common/services/sleepmgr/sleepmgr.h" #include "asf/common/services/usb/udc/udc.h" @@ -19,8 +22,11 @@ #include "mpconfigboard.h" #include "modmachine_pin.h" +#include "storage.h" #include "uart.h" +fs_user_mount_t fs_user_mount_flash; + void do_str(const char *src, mp_parse_input_kind_t input_kind) { mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); if (lex == NULL) { @@ -41,6 +47,114 @@ void do_str(const char *src, mp_parse_input_kind_t input_kind) { } } +static const char fresh_boot_py[] = +"# boot.py -- run on boot-up\r\n" +"# can run arbitrary Python, but best to keep it minimal\r\n" +"\r\n" +; + +static const char fresh_main_py[] = +"# main.py -- put your code here!\r\n" +; + +static const char fresh_readme_txt[] = +"This is a MicroPython board\r\n" +"\r\n" +"You can get started right away by writing your Python code in 'main.py'.\r\n" +"\r\n" +"For a serial prompt:\r\n" +" - Windows: you need to go to 'Device manager', right click on the unknown device,\r\n" +" then update the driver software, using the 'pybcdc.inf' file found on this drive.\r\n" +" Then use a terminal program like Hyperterminal or putty.\r\n" +" - Mac OS X: use the command: screen /dev/tty.usbmodem*\r\n" +" - Linux: use the command: screen /dev/ttyACM0\r\n" +"\r\n" +"Please visit http://micropython.org/help/ for further help.\r\n" +; + +// we don't make this function static because it needs a lot of stack and we +// want it to be executed without using stack within main() function +void init_flash_fs() { + // init the vfs object + fs_user_mount_t *vfs = &fs_user_mount_flash; + vfs->str = "/flash"; + vfs->len = 6; + vfs->flags = 0; + flash_init_vfs(vfs); + + // put the flash device in slot 0 (it will be unused at this point) + MP_STATE_PORT(fs_user_mount)[0] = vfs; + + // try to mount the flash + FRESULT res = f_mount(&vfs->fatfs, vfs->str, 1); + + if (res == FR_NO_FILESYSTEM) { + // no filesystem, or asked to reset it, so create a fresh one + + res = f_mkfs("/flash", 0, 0); + if (res == FR_OK) { + // success creating fresh LFS + } else { + printf("PYB: can't create flash filesystem\n"); + MP_STATE_PORT(fs_user_mount)[0] = NULL; + return; + } + + // set label + f_setlabel("/flash/internalflash"); + + // create empty main.py + FIL fp; + f_open(&fp, "/flash/main.py", FA_WRITE | FA_CREATE_ALWAYS); + UINT n; + f_write(&fp, fresh_main_py, sizeof(fresh_main_py) - 1 /* don't count null terminator */, &n); + f_close(&fp); + + // TODO(tannewt): Create an .inf driver file for Windows. + + // create readme file + f_open(&fp, "/flash/README.txt", FA_WRITE | FA_CREATE_ALWAYS); + f_write(&fp, fresh_readme_txt, sizeof(fresh_readme_txt) - 1 /* don't count null terminator */, &n); + f_close(&fp); + } else if (res == FR_OK) { + // mount successful + } else { + printf("PYB: can't mount flash\n"); + MP_STATE_PORT(fs_user_mount)[0] = NULL; + return; + } + + // The current directory is used as the boot up directory. + // It is set to the internal flash filesystem by default. + f_chdrive("/flash"); + + // Make sure we have a /flash/boot.py. Create it if needed. + FILINFO fno; +#if _USE_LFN + fno.lfname = NULL; + fno.lfsize = 0; +#endif + res = f_stat("/flash/boot.py", &fno); + if (res == FR_OK) { + if (fno.fattrib & AM_DIR) { + // exists as a directory + // TODO handle this case + // see http://elm-chan.org/fsw/ff/img/app2.c for a "rm -rf" implementation + } else { + // exists as a file, good! + } + } else { + // doesn't exist, create fresh file + + FIL fp; + f_open(&fp, "/flash/boot.py", FA_WRITE | FA_CREATE_ALWAYS); + UINT n; + f_write(&fp, fresh_boot_py, sizeof(fresh_boot_py) - 1 /* don't count null terminator */, &n); + // TODO check we could write n bytes + f_close(&fp); + } +} + static char *stack_top; static char heap[8192]; @@ -63,6 +177,10 @@ int main(int argc, char **argv) { pin_init0(); + // Initialise the local flash filesystem. + // Create it if needed, mount in on /flash, and set it as current dir. + init_flash_fs(); + #if MICROPY_REPL_EVENT_DRIVEN pyexec_event_repl_init(); for (;;) { @@ -98,11 +216,6 @@ mp_import_stat_t mp_import_stat(const char *path) { return MP_IMPORT_STAT_NO_EXIST; } -mp_obj_t mp_builtin_open(uint n_args, const mp_obj_t *args, mp_map_t *kwargs) { - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); - void mp_keyboard_interrupt(void) { MP_STATE_VM(mp_pending_exception) = MP_STATE_PORT(mp_kbd_exception); } diff --git a/atmel-samd/modmachine.c b/atmel-samd/modmachine.c index d59cf84cfab4..e5ba5ae987f6 100644 --- a/atmel-samd/modmachine.c +++ b/atmel-samd/modmachine.c @@ -35,6 +35,7 @@ #include "modmachine_dac.h" #include "modmachine_pin.h" #include "modmachine_pwm.h" +#include "storage.h" #if MICROPY_PY_MACHINE @@ -44,6 +45,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&dac_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&pwm_type) }, + { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&flash_type) }, }; STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); diff --git a/atmel-samd/moduos.c b/atmel-samd/moduos.c new file mode 100644 index 000000000000..cd98abfbdf02 --- /dev/null +++ b/atmel-samd/moduos.c @@ -0,0 +1,385 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdint.h> +#include <string.h> + +#include "py/mpstate.h" +#include "py/objtuple.h" +#include "py/objstr.h" +#include "genhdr/mpversion.h" +#include "lib/fatfs/ff.h" +#include "lib/fatfs/diskio.h" +#include "timeutils.h" +#include "extmod/vfs_fat_file.h" +#include "extmod/fsusermount.h" + +/// \module os - basic "operating system" services +/// +/// The `os` module contains functions for filesystem access and `urandom`. +/// +/// The filesystem has `/` as the root directory, and the available physical +/// drives are accessible from here. They are currently: +/// +/// /flash -- the internal flash filesystem +/// /sd -- the SD card (if it exists) +/// +/// On boot up, the current directory is `/flash` if no SD card is inserted, +/// otherwise it is `/sd`. + +STATIC const qstr os_uname_info_fields[] = { + MP_QSTR_sysname, MP_QSTR_nodename, + MP_QSTR_release, MP_QSTR_version, MP_QSTR_machine +}; +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_sysname_obj, "samd21"); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, "samd21"); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_release_obj, MICROPY_VERSION_STRING); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); +STATIC MP_DEFINE_ATTRTUPLE( + os_uname_info_obj, + os_uname_info_fields, + 5, + (mp_obj_t)&os_uname_info_sysname_obj, + (mp_obj_t)&os_uname_info_nodename_obj, + (mp_obj_t)&os_uname_info_release_obj, + (mp_obj_t)&os_uname_info_version_obj, + (mp_obj_t)&os_uname_info_machine_obj +); + +STATIC mp_obj_t os_uname(void) { + return (mp_obj_t)&os_uname_info_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_uname_obj, os_uname); + +/// \function chdir(path) +/// Change current directory. +STATIC mp_obj_t os_chdir(mp_obj_t path_in) { + const char *path; + path = mp_obj_str_get_str(path_in); + + FRESULT res = f_chdrive(path); + + if (res == FR_OK) { + res = f_chdir(path); + } + + if (res != FR_OK) { + // TODO should be mp_type_FileNotFoundError + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "No such file or directory: '%s'", path)); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_chdir_obj, os_chdir); + +/// \function getcwd() +/// Get the current directory. +STATIC mp_obj_t os_getcwd(void) { + char buf[MICROPY_ALLOC_PATH_MAX + 1]; + FRESULT res = f_getcwd(buf, sizeof buf); + + if (res != FR_OK) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(fresult_to_errno_table[res]))); + } + + return mp_obj_new_str(buf, strlen(buf), false); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_getcwd_obj, os_getcwd); + +/// \function listdir([dir]) +/// With no argument, list the current directory. Otherwise list the given directory. +STATIC mp_obj_t os_listdir(mp_uint_t n_args, const mp_obj_t *args) { + bool is_str_type = true; + const char *path; + if (n_args == 1) { + if (mp_obj_get_type(args[0]) == &mp_type_bytes) { + is_str_type = false; + } + path = mp_obj_str_get_str(args[0]); + } else { + path = ""; + } + + // "hack" to list root directory + if (path[0] == '/' && path[1] == '\0') { + mp_obj_t dir_list = mp_obj_new_list(0, NULL); + for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) { + fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i]; + if (vfs != NULL) { + mp_obj_list_append(dir_list, mp_obj_new_str(vfs->str + 1, vfs->len - 1, false)); + } + } + return dir_list; + } + + return fat_vfs_listdir(path, is_str_type); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(os_listdir_obj, 0, 1, os_listdir); + +/// \function mkdir(path) +/// Create a new directory. +STATIC mp_obj_t os_mkdir(mp_obj_t path_o) { + const char *path = mp_obj_str_get_str(path_o); + FRESULT res = f_mkdir(path); + switch (res) { + case FR_OK: + return mp_const_none; + case FR_EXIST: + // TODO should be FileExistsError + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "File exists: '%s'", path)); + default: + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Error creating directory '%s'", path)); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_mkdir_obj, os_mkdir); + +/// \function remove(path) +/// Remove a file. +STATIC mp_obj_t os_remove(mp_obj_t path_o) { + const char *path = mp_obj_str_get_str(path_o); + // TODO check that path is actually a file before trying to unlink it + FRESULT res = f_unlink(path); + switch (res) { + case FR_OK: + return mp_const_none; + default: + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Error removing file '%s'", path)); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_remove_obj, os_remove); + +/// \function rename(old_path, new_path) +/// Rename a file +STATIC mp_obj_t os_rename(mp_obj_t path_in, mp_obj_t path_out) { + const char *old_path = mp_obj_str_get_str(path_in); + const char *new_path = mp_obj_str_get_str(path_out); + FRESULT res = f_rename(old_path, new_path); + switch (res) { + case FR_OK: + return mp_const_none; + default: + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Error renaming file '%s' to '%s'", old_path, new_path)); + } + +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(os_rename_obj, os_rename); + +/// \function rmdir(path) +/// Remove a directory. +STATIC mp_obj_t os_rmdir(mp_obj_t path_o) { + const char *path = mp_obj_str_get_str(path_o); + // TODO check that path is actually a directory before trying to unlink it + FRESULT res = f_unlink(path); + switch (res) { + case FR_OK: + return mp_const_none; + default: + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Error removing directory '%s'", path)); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_rmdir_obj, os_rmdir); + +// Checks for path equality, ignoring trailing slashes: +// path_equal(/, /) -> true +// path_equal(/flash//, /flash) -> true +// second argument must be in canonical form (meaning no trailing slash, unless it's just /) +STATIC bool path_equal(const char *path, const char *path_canonical) { + for (; *path_canonical != '\0' && *path == *path_canonical; ++path, ++path_canonical) { + } + if (*path_canonical != '\0') { + return false; + } + for (; *path == '/'; ++path) { + } + return *path == '\0'; +} + +/// \function stat(path) +/// Get the status of a file or directory. +STATIC mp_obj_t os_stat(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + FILINFO fno; +#if _USE_LFN + fno.lfname = NULL; + fno.lfsize = 0; +#endif + + FRESULT res; + if (path_equal(path, "/")) { + // stat root directory + fno.fsize = 0; + fno.fdate = 0; + fno.ftime = 0; + fno.fattrib = AM_DIR; + } else { + res = FR_NO_PATH; + for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) { + fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i]; + if (vfs != NULL && path_equal(path, vfs->str)) { + // stat mounted device directory + fno.fsize = 0; + fno.fdate = 0; + fno.ftime = 0; + fno.fattrib = AM_DIR; + res = FR_OK; + } + } + if (res == FR_NO_PATH) { + // stat normal file + res = f_stat(path, &fno); + } + if (res != FR_OK) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, + MP_OBJ_NEW_SMALL_INT(fresult_to_errno_table[res]))); + } + } + + mp_obj_tuple_t *t = mp_obj_new_tuple(10, NULL); + mp_int_t mode = 0; + if (fno.fattrib & AM_DIR) { + mode |= 0x4000; // stat.S_IFDIR + } else { + mode |= 0x8000; // stat.S_IFREG + } + mp_int_t seconds = timeutils_seconds_since_2000( + 1980 + ((fno.fdate >> 9) & 0x7f), + (fno.fdate >> 5) & 0x0f, + fno.fdate & 0x1f, + (fno.ftime >> 11) & 0x1f, + (fno.ftime >> 5) & 0x3f, + 2 * (fno.ftime & 0x1f) + ); + t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev + t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink + t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid + t->items[6] = MP_OBJ_NEW_SMALL_INT(fno.fsize); // st_size + t->items[7] = MP_OBJ_NEW_SMALL_INT(seconds); // st_atime + t->items[8] = MP_OBJ_NEW_SMALL_INT(seconds); // st_mtime + t->items[9] = MP_OBJ_NEW_SMALL_INT(seconds); // st_ctime + + return t; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_stat_obj, os_stat); + +STATIC mp_obj_t os_statvfs(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + DWORD nclst; + FATFS *fatfs; + FRESULT res = f_getfree(path, &nclst, &fatfs); + if (res != FR_OK) { + goto error; + } + + mp_obj_tuple_t *t = mp_obj_new_tuple(10, NULL); + + t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * 512); // f_bsize - block size + t->items[1] = t->items[0]; // f_frsize - fragment size + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // f_blocks - total number of blocks + t->items[3] = MP_OBJ_NEW_SMALL_INT(nclst); // f_bfree - number of free blocks + t->items[4] = t->items[3]; // f_bavail - free blocks avail to unpriviledged users + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files - # inodes + t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree - # free inodes + t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail - # free inodes avail to unpriviledges users + t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags + t->items[9] = MP_OBJ_NEW_SMALL_INT(_MAX_LFN); // f_namemax + + return t; + +error: + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(fresult_to_errno_table[res]))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_statvfs_obj, os_statvfs); + +/// \function sync() +/// Sync all filesystems. +STATIC mp_obj_t os_sync(void) { + disk_ioctl(0, CTRL_SYNC, NULL); + disk_ioctl(1, CTRL_SYNC, NULL); + disk_ioctl(2, CTRL_SYNC, NULL); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(mod_os_sync_obj, os_sync); + +#if MICROPY_HW_ENABLE_RNG +/// \function urandom(n) +/// Return a bytes object with n random bytes, generated by the hardware +/// random number generator. +STATIC mp_obj_t os_urandom(mp_obj_t num) { + mp_int_t n = mp_obj_get_int(num); + vstr_t vstr; + vstr_init_len(&vstr, n); + for (int i = 0; i < n; i++) { + vstr.buf[i] = rng_get(); + } + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_urandom_obj, os_urandom); +#endif + +STATIC const mp_map_elem_t os_module_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_uos) }, + + { MP_OBJ_NEW_QSTR(MP_QSTR_uname), (mp_obj_t)&os_uname_obj }, + + { MP_OBJ_NEW_QSTR(MP_QSTR_chdir), (mp_obj_t)&os_chdir_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_getcwd), (mp_obj_t)&os_getcwd_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_listdir), (mp_obj_t)&os_listdir_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_mkdir), (mp_obj_t)&os_mkdir_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_remove), (mp_obj_t)&os_remove_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_rename),(mp_obj_t)&os_rename_obj}, + { MP_OBJ_NEW_QSTR(MP_QSTR_rmdir), (mp_obj_t)&os_rmdir_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_stat), (mp_obj_t)&os_stat_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_statvfs), (mp_obj_t)&os_statvfs_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_unlink), (mp_obj_t)&os_remove_obj }, // unlink aliases to remove + + { MP_OBJ_NEW_QSTR(MP_QSTR_sync), (mp_obj_t)&mod_os_sync_obj }, + + /// \constant sep - separation character used in paths + { MP_OBJ_NEW_QSTR(MP_QSTR_sep), MP_OBJ_NEW_QSTR(MP_QSTR__slash_) }, + +#if MICROPY_HW_ENABLE_RNG + { MP_OBJ_NEW_QSTR(MP_QSTR_urandom), (mp_obj_t)&os_urandom_obj }, +#endif + + // these are MicroPython extensions + { MP_OBJ_NEW_QSTR(MP_QSTR_mount), (mp_obj_t)&fsuser_mount_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_umount), (mp_obj_t)&fsuser_umount_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_mkfs), (mp_obj_t)&fsuser_mkfs_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); + +const mp_obj_module_t uos_module = { + .base = { &mp_type_module }, + .name = MP_QSTR_uos, + .globals = (mp_obj_dict_t*)&os_module_globals, +}; diff --git a/atmel-samd/mpconfigport.h b/atmel-samd/mpconfigport.h index c4a0199be991..549731b31058 100644 --- a/atmel-samd/mpconfigport.h +++ b/atmel-samd/mpconfigport.h @@ -3,6 +3,8 @@ #ifndef __INCLUDED_MPCONFIGPORT_H #define __INCLUDED_MPCONFIGPORT_H +#define MICROPY_PY_SYS_PLATFORM "Atmel SAMD21" + // options to control how Micro Python is built #define MICROPY_QSTR_BYTES_IN_HASH (1) @@ -28,8 +30,8 @@ #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) #define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) #define MICROPY_PY_ASYNC_AWAIT (0) -#define MICROPY_PY_BUILTINS_BYTEARRAY (0) -#define MICROPY_PY_BUILTINS_MEMORYVIEW (0) +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) #define MICROPY_PY_BUILTINS_ENUMERATE (0) #define MICROPY_PY_BUILTINS_FILTER (0) #define MICROPY_PY_BUILTINS_FROZENSET (0) @@ -40,8 +42,8 @@ #define MICROPY_PY_BUILTINS_MIN_MAX (0) #define MICROPY_PY___FILE__ (0) #define MICROPY_PY_GC (0) -#define MICROPY_PY_ARRAY (0) -#define MICROPY_PY_ATTRTUPLE (0) +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ATTRTUPLE (1) #define MICROPY_PY_COLLECTIONS (0) #define MICROPY_PY_MATH (0) #define MICROPY_PY_CMATH (0) @@ -53,6 +55,16 @@ #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +// fatfs configuration used in ffconf.h +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_LFN_CODE_PAGE (437) /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ +#define MICROPY_FATFS_USE_LABEL (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_VOLUMES (4) +#define MICROPY_FATFS_MULTI_PARTITION (1) +#define MICROPY_FSUSERMOUNT (1) + +#define MICROPY_VFS_FAT (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_REPL_AUTO_INDENT (1) @@ -78,16 +90,23 @@ typedef long mp_off_t; #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&mp_builtin_open_obj }, + // extra built in modules to add to the list of known ones extern const struct _mp_obj_module_t machine_module; +extern const struct _mp_obj_module_t uos_module; extern const struct _mp_obj_module_t utime_module; #define MICROPY_PORT_BUILTIN_MODULES \ { MP_OBJ_NEW_QSTR(MP_QSTR_umachine), (mp_obj_t)&machine_module }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_uos), (mp_obj_t)&uos_module }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&utime_module } \ #define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \ { MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&machine_module }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&uos_module }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&utime_module } \ // board specific definitions diff --git a/atmel-samd/storage.c b/atmel-samd/storage.c new file mode 100644 index 000000000000..a4037da4f621 --- /dev/null +++ b/atmel-samd/storage.c @@ -0,0 +1,290 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdint.h> +#include <string.h> + +#include "py/obj.h" +#include "py/runtime.h" +#include "lib/fatfs/ff.h" +#include "extmod/fsusermount.h" + +#include "asf/sam0/drivers/nvm/nvm.h" + +#include "storage.h" + +#define TOTAL_FLASH_SIZE 0x010000 + +#define FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_FLASH_SIZE) +#define FLASH_PART1_START_BLOCK (0x100) +#define FLASH_PART1_NUM_BLOCKS (TOTAL_FLASH_SIZE / FLASH_BLOCK_SIZE) + +static bool flash_is_initialised = false; + +void storage_init(void) { + if (!flash_is_initialised) { + struct nvm_config config_nvm; + nvm_get_config_defaults(&config_nvm); + config_nvm.manual_page_write = false; + nvm_set_config(&config_nvm); + flash_is_initialised = true; + } +} + +uint32_t storage_get_block_size(void) { + return FLASH_BLOCK_SIZE; +} + +uint32_t storage_get_block_count(void) { + return FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS; +} + +void storage_flush(void) { +} + +static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) { + buf[0] = boot; + + if (num_blocks == 0) { + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + } else { + buf[1] = 0xff; + buf[2] = 0xff; + buf[3] = 0xff; + } + + buf[4] = type; + + if (num_blocks == 0) { + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + } else { + buf[5] = 0xff; + buf[6] = 0xff; + buf[7] = 0xff; + } + + buf[8] = start_block; + buf[9] = start_block >> 8; + buf[10] = start_block >> 16; + buf[11] = start_block >> 24; + + buf[12] = num_blocks; + buf[13] = num_blocks >> 8; + buf[14] = num_blocks >> 16; + buf[15] = num_blocks >> 24; +} + +static uint32_t convert_block_to_flash_addr(uint32_t block) { + if (FLASH_PART1_START_BLOCK <= block && block < FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS) { + // a block in partition 1 + block -= FLASH_PART1_START_BLOCK; + return FLASH_MEM_SEG1_START_ADDR + block * FLASH_BLOCK_SIZE; + } + // bad block + return -1; +} + +bool storage_read_block(uint8_t *dest, uint32_t block) { + //printf("RD %u\n", block); + if (block == 0) { + // fake the MBR so we can decide on our own partition table + + for (int i = 0; i < 446; i++) { + dest[i] = 0; + } + + build_partition(dest + 446, 0, 0x01 /* FAT12 */, FLASH_PART1_START_BLOCK, FLASH_PART1_NUM_BLOCKS); + build_partition(dest + 462, 0, 0, 0, 0); + build_partition(dest + 478, 0, 0, 0, 0); + build_partition(dest + 494, 0, 0, 0, 0); + + dest[510] = 0x55; + dest[511] = 0xaa; + + return true; + + } else { + // non-MBR block, get data from flash memory + uint32_t src = convert_block_to_flash_addr(block); + if (src == -1) { + // bad block number + return false; + } + enum status_code error_code; + // A block is made up of multiple pages. Read each page + // sequentially. + for (int i = 0; i < FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { + do + { + error_code = nvm_read_buffer(src + i * NVMCTRL_PAGE_SIZE, + dest + i * NVMCTRL_PAGE_SIZE, + NVMCTRL_PAGE_SIZE); + } while (error_code == STATUS_BUSY); + } + return true; + } +} + +bool storage_write_block(const uint8_t *src, uint32_t block) { + if (block == 0) { + // can't write MBR, but pretend we did + return true; + + } else { + // non-MBR block, copy to cache + volatile uint32_t dest = convert_block_to_flash_addr(block); + if (dest == -1) { + // bad block number + return false; + } + enum status_code error_code; + // A block is formed by two rows of flash. We must erase each row + // before we write back to it. + do + { + error_code = nvm_erase_row(dest); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + do + { + error_code = nvm_erase_row(dest + NVMCTRL_ROW_SIZE); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + + // A block is made up of multiple pages. Write each page + // sequentially. + for (int i = 0; i < FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { + do + { + error_code = nvm_write_buffer(dest + i * NVMCTRL_PAGE_SIZE, + src + i * NVMCTRL_PAGE_SIZE, + NVMCTRL_PAGE_SIZE); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + } + return true; + } +} + +mp_uint_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!storage_read_block(dest + i * FLASH_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!storage_write_block(src + i * FLASH_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +/******************************************************************************/ +// MicroPython bindings +// +// Expose the flash as an object with the block protocol. + +// there is a singleton Flash object +STATIC const mp_obj_base_t flash_obj = {&flash_type}; + +STATIC mp_obj_t flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return singleton object + return (mp_obj_t)&flash_obj; +} + +STATIC mp_obj_t flash_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); + mp_uint_t ret = storage_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_readblocks_obj, flash_readblocks); + +STATIC mp_obj_t flash_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + mp_uint_t ret = storage_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_writeblocks_obj, flash_writeblocks); + +STATIC mp_obj_t flash_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case BP_IOCTL_INIT: storage_init(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_DEINIT: storage_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly + case BP_IOCTL_SYNC: storage_flush(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(storage_get_block_count()); + case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(storage_get_block_size()); + default: return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_ioctl_obj, flash_ioctl); + +STATIC const mp_map_elem_t flash_locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&flash_readblocks_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&flash_writeblocks_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&flash_ioctl_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(flash_locals_dict, flash_locals_dict_table); + +const mp_obj_type_t flash_type = { + { &mp_type_type }, + .name = MP_QSTR_Flash, + .make_new = flash_make_new, + .locals_dict = (mp_obj_t)&flash_locals_dict, +}; + +void flash_init_vfs(fs_user_mount_t *vfs) { + vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; + vfs->readblocks[0] = (mp_obj_t)&flash_readblocks_obj; + vfs->readblocks[1] = (mp_obj_t)&flash_obj; + vfs->readblocks[2] = (mp_obj_t)storage_read_blocks; // native version + vfs->writeblocks[0] = (mp_obj_t)&flash_writeblocks_obj; + vfs->writeblocks[1] = (mp_obj_t)&flash_obj; + vfs->writeblocks[2] = (mp_obj_t)storage_write_blocks; // native version + vfs->u.ioctl[0] = (mp_obj_t)&flash_ioctl_obj; + vfs->u.ioctl[1] = (mp_obj_t)&flash_obj; +} diff --git a/atmel-samd/storage.h b/atmel-samd/storage.h new file mode 100644 index 000000000000..9c1105244dda --- /dev/null +++ b/atmel-samd/storage.h @@ -0,0 +1,47 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define FLASH_BLOCK_SIZE (512) + +#define STORAGE_SYSTICK_MASK (0x1ff) // 512ms +#define STORAGE_IDLE_TICK(tick) (((tick) & STORAGE_SYSTICK_MASK) == 2) + +void storage_init(void); +uint32_t storage_get_block_size(void); +uint32_t storage_get_block_count(void); +void storage_irq_handler(void); +void storage_flush(void); +bool storage_read_block(uint8_t *dest, uint32_t block); +bool storage_write_block(const uint8_t *src, uint32_t block); + +// these return 0 on success, non-zero on error +mp_uint_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); +mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); + +extern const struct _mp_obj_type_t flash_type; + +struct _fs_user_mount_t; +void flash_init_vfs(struct _fs_user_mount_t *vfs);