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&reg; | SMART ARM&reg;-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);