diff --git a/drivers/include/nvram-spi.h b/drivers/include/nvram-spi.h
new file mode 100644
index 0000000000000000000000000000000000000000..a945b3adb84fd6e9f26f4f4785630ea0253cfc28
--- /dev/null
+++ b/drivers/include/nvram-spi.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for more
+ * details.
+ */
+
+/**
+ * @ingroup     nvram
+ * @{
+ *
+ * @file
+ *
+ * @brief       Device interface for various SPI connected NVRAM.
+ *
+ * Tested on:
+ * - Cypress/Ramtron FM25L04B.
+ *
+ * @author      Joakim Gebart <joakim.gebart@eistec.se>
+ */
+
+#ifndef DRIVERS_NVRAM_SPI_H_
+#define DRIVERS_NVRAM_SPI_H_
+
+#include <stdint.h>
+#include "nvram.h"
+#include "periph/spi.h"
+#include "periph/gpio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Bus parameters for SPI NVRAM.
+ */
+typedef struct nvram_spi_params {
+    /** @brief RIOT SPI device */
+    spi_t spi;
+    /** @brief Chip select pin */
+    gpio_t cs;
+    /** @brief Number of address bytes following each read/write command. */
+    uint8_t address_count;
+} nvram_spi_params_t;
+
+/**
+ * @brief Initialize an nvram_t structure with SPI settings.
+ *
+ * This will also initialize the CS pin as a GPIO output, without pull resistors.
+ *
+ * @param[out] dev          Pointer to NVRAM device descriptor
+ * @param[out] spi_params   Pointer to SPI settings
+ * @param[in]  size         Device capacity
+ *
+ * @return                  0 on success
+ * @return                  <0 on errors
+ */
+int nvram_spi_init(nvram_t *dev, nvram_spi_params_t *spi_params, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRIVERS_NVRAM_SPI_H_ */
+/** @} */
diff --git a/drivers/include/nvram.h b/drivers/include/nvram.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ec41e953c220cd3aed1b77df9c1485af8a60900
--- /dev/null
+++ b/drivers/include/nvram.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for more
+ * details.
+ */
+
+/**
+ * @defgroup    nvram Non-volatile RAM
+ * @ingroup     drivers
+ * @brief       Non-volatile RAM interface
+ *
+ * This API is designed around non-volatile memories which do not need blockwise
+ * erase, such as ferro-electric RAM (FRAM) or magneto-resistive RAM (MRAM).
+ *
+ * This interface is not suitable for flash memories.
+ *
+ * @{
+ *
+ * @file
+ *
+ * @brief       Generic non-volatile RAM driver interface
+ * @author      Joakim Gebart <joakim.gebart@eistec.se>
+ */
+
+#ifndef DRIVERS_NVRAM_H_
+#define DRIVERS_NVRAM_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Forward declaration in order to declare function pointers which take this
+ * type as a parameter within the struct. */
+struct nvram;
+
+/**
+ * @brief Device descriptor for generic NVRAM devices.
+ */
+typedef struct nvram {
+    /**
+     * @brief Pointer to device-specific read function
+     *
+     * Copy data from system memory to NVRAM.
+     *
+     * @param[in]  dev   Pointer to NVRAM device descriptor
+     * @param[out] dst   Pointer to the first byte in the system memory address space
+     * @param[in]  src   Starting address in the NVRAM device address space
+     * @param[in]  len   Number of bytes to copy
+     *
+     * @return           Number of bytes read on success
+     * @return           <0 on errors
+     */
+    int (*read)(struct nvram *dev, uint8_t *dst, uint32_t src, size_t size);
+
+    /**
+     * @brief Pointer to device-specific write function
+     *
+     * Copy data from NVRAM to system memory.
+     *
+     * @param[in]  dev   Pointer to NVRAM device descriptor
+     * @param[in]  src   Pointer to the first byte in the system memory address space
+     * @param[in]  dst   Starting address in the NVRAM device address space
+     * @param[in]  len   Number of bytes to copy
+     *
+     * @return           Number of bytes written on success
+     * @return           <0 on errors
+     */
+    int (*write)(struct nvram *dev, uint8_t *src, uint32_t dst, size_t size);
+
+    /** @brief Device capacity */
+    size_t size;
+
+    /** @brief Device-specific parameters, if any. */
+    void *extra;
+} nvram_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRIVERS_NVRAM_H_ */
+/** @} */
diff --git a/drivers/nvram_spi/Makefile b/drivers/nvram_spi/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..bc459313fce606ec33f7725b8e1855703ec3c55e
--- /dev/null
+++ b/drivers/nvram_spi/Makefile
@@ -0,0 +1,3 @@
+MODULE = nvram_spi
+
+include $(RIOTBASE)/Makefile.base
diff --git a/drivers/nvram_spi/nvram-spi.c b/drivers/nvram_spi/nvram-spi.c
new file mode 100644
index 0000000000000000000000000000000000000000..4ae873b554fd54a1909e017b6fef06ec6c5b1c7e
--- /dev/null
+++ b/drivers/nvram_spi/nvram-spi.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2015 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for more
+ * details.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include "nvram.h"
+#include "nvram-spi.h"
+#include "byteorder.h"
+#include "periph/spi.h"
+#include "periph/gpio.h"
+#include "hwtimer.h"
+
+/**
+ * @ingroup     nvram
+ * @{
+ *
+ * @file
+ *
+ * @brief       Device interface for various SPI connected NVRAM.
+ *
+ * Tested on:
+ * - Cypress/Ramtron FM25L04B.
+ *
+ * @author      Joakim Gebart <joakim.gebart@eistec.se>
+ */
+
+typedef enum {
+    /** WRITE command byte, 0b0000 0010 */
+    NVRAM_SPI_CMD_WRITE = 0x02,
+    /** READ command byte, 0b0000 0011 */
+    NVRAM_SPI_CMD_READ = 0x03,
+    /** WREN command byte, 0b0000 0110 */
+    NVRAM_SPI_CMD_WREN = 0x06,
+} nvram_spi_commands_t;
+
+/** @brief Delay to wait between toggling CS pin, on most chips this can probably be
+ * removed. */
+#define NVRAM_SPI_CS_TOGGLE_TICKS 1
+
+/**
+ * @brief Copy data from system memory to NVRAM.
+ *
+ * @param[in]  dev   Pointer to NVRAM device descriptor
+ * @param[in]  src   Pointer to the first byte in the system memory address space
+ * @param[in]  dst   Starting address in the NVRAM device address space
+ * @param[in]  len   Number of bytes to copy
+ *
+ * @return           Number of bytes written on success
+ * @return           <0 on errors
+ */
+static int nvram_spi_write(nvram_t *dev, uint8_t *src, uint32_t dst, size_t len);
+
+/**
+ * @brief Copy data from NVRAM to system memory.
+ *
+ * @param[in]  dev   Pointer to NVRAM device descriptor
+ * @param[out] dst   Pointer to the first byte in the system memory address space
+ * @param[in]  src   Starting address in the NVRAM device address space
+ * @param[in]  len   Number of bytes to copy
+ *
+ * @return           Number of bytes read on success
+ * @return           <0 on errors
+ */
+static int nvram_spi_read(nvram_t *dev, uint8_t *dst, uint32_t src, size_t len);
+
+/**
+ * @brief Copy data from system memory to NVRAM.
+ *
+ * This is a special form of the WRITE command used by some Ramtron/Cypress
+ * 4Kbit FRAM devices which puts the 9th address bit inside the command byte to
+ * be able to use one byte for addressing instead of two.
+ *
+ * @param[in]  dev   Pointer to NVRAM device descriptor
+ * @param[in]  src   Pointer to the first byte in the system memory address space
+ * @param[in]  dst   Starting address in the NVRAM device address space
+ * @param[in]  len   Number of bytes to copy
+ *
+ * @return           Number of bytes written on success
+ * @return           <0 on errors
+ */
+static int nvram_spi_write_9bit_addr(nvram_t *dev, uint8_t *src, uint32_t dst, size_t len);
+
+/**
+ * @brief Copy data from NVRAM to system memory.
+ *
+ * This is a special form of the READ command used by some Ramtron/Cypress 4Kbit
+ * FRAM devices which puts the 9th address bit inside the command byte to be
+ * able to use one byte for addressing instead of two.
+ *
+ * @param[in]  dev   Pointer to NVRAM device descriptor
+ * @param[out] dst   Pointer to the first byte in the system memory address space
+ * @param[in]  src   Starting address in the NVRAM device address space
+ * @param[in]  len   Number of bytes to copy
+ *
+ * @return           Number of bytes read on success
+ * @return           <0 on errors
+ */
+static int nvram_spi_read_9bit_addr(nvram_t *dev, uint8_t *dst, uint32_t src, size_t len);
+
+int nvram_spi_init(nvram_t *dev, nvram_spi_params_t *spi_params, size_t size)
+{
+    dev->size = size;
+    if (size > 0x100 && spi_params->address_count == 1) {
+        dev->write = nvram_spi_write_9bit_addr;
+        dev->read = nvram_spi_read_9bit_addr;
+    } else {
+        dev->write = nvram_spi_write;
+        dev->read = nvram_spi_read;
+    }
+    dev->extra = spi_params;
+
+    gpio_init_out(spi_params->cs, GPIO_NOPULL);
+    gpio_set(spi_params->cs);
+
+    return 0;
+}
+
+static int nvram_spi_write(nvram_t *dev, uint8_t *src, uint32_t dst, size_t len)
+{
+    nvram_spi_params_t *spi_dev = (nvram_spi_params_t *) dev->extra;
+    int status;
+    union {
+        uint32_t u32;
+        char c[4];
+    } addr;
+    /* Address is expected by the device as big-endian, i.e. network byte order,
+     * we utilize the network byte order macros here. */
+    addr.u32 = HTONL(dst);
+    /* Acquire exclusive bus access */
+    spi_acquire(spi_dev->spi);
+    /* Assert CS */
+    gpio_clear(spi_dev->cs);
+    /* Enable writes */
+    status = spi_transfer_byte(spi_dev->spi, NVRAM_SPI_CMD_WREN, NULL);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Release CS */
+    gpio_set(spi_dev->cs);
+    hwtimer_spin(NVRAM_SPI_CS_TOGGLE_TICKS);
+    /* Re-assert CS */
+    gpio_clear(spi_dev->cs);
+    /* Write command and address */
+    status = spi_transfer_regs(spi_dev->spi, NVRAM_SPI_CMD_WRITE,
+                      &addr.c[sizeof(addr.c) - spi_dev->address_count], NULL,
+                      spi_dev->address_count);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Keep holding CS and write data */
+    status = spi_transfer_bytes(spi_dev->spi, (char *)src, NULL, len);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Release CS */
+    gpio_set(spi_dev->cs);
+    /* Release exclusive bus access */
+    spi_release(spi_dev->spi);
+    return status;
+}
+
+static int nvram_spi_read(nvram_t *dev, uint8_t *dst, uint32_t src, size_t len)
+{
+    nvram_spi_params_t *spi_dev = (nvram_spi_params_t *) dev->extra;
+    int status;
+    union {
+        uint32_t u32;
+        char c[4];
+    } addr;
+    /* Address is expected by the device as big-endian, i.e. network byte order,
+     * we utilize the network byte order macros here. */
+    addr.u32 = HTONL(src);
+    /* Acquire exclusive bus access */
+    spi_acquire(spi_dev->spi);
+    /* Assert CS */
+    gpio_clear(spi_dev->cs);
+    /* Write command and address */
+    status = spi_transfer_regs(spi_dev->spi, NVRAM_SPI_CMD_READ,
+                               &addr.c[sizeof(addr.c) - spi_dev->address_count],
+                               NULL, spi_dev->address_count);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Keep holding CS and read data */
+    status = spi_transfer_bytes(spi_dev->spi, NULL, (char *)dst, len);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Release CS */
+    gpio_set(spi_dev->cs);
+    /* Release exclusive bus access */
+    spi_release(spi_dev->spi);
+    /* status contains the number of bytes actually read from the SPI bus. */
+    return status;
+}
+
+
+static int nvram_spi_write_9bit_addr(nvram_t *dev, uint8_t *src, uint32_t dst, size_t len)
+{
+    nvram_spi_params_t *spi_dev = (nvram_spi_params_t *) dev->extra;
+    int status;
+    uint8_t cmd;
+    uint8_t addr;
+    cmd = NVRAM_SPI_CMD_WRITE;
+    /* The upper address bit is mixed into the command byte on certain devices,
+     * probably just to save a byte in the SPI transfer protocol. */
+    if (dst > 0xff) {
+        cmd |= 0x08;
+    }
+    /* LSB of address */
+    addr = (dst & 0xff);
+    spi_acquire(spi_dev->spi);
+    gpio_clear(spi_dev->cs);
+    /* Enable writes */
+    status = spi_transfer_byte(spi_dev->spi, NVRAM_SPI_CMD_WREN, NULL);
+    if (status < 0)
+    {
+        return status;
+    }
+    gpio_set(spi_dev->cs);
+    hwtimer_spin(NVRAM_SPI_CS_TOGGLE_TICKS);
+    gpio_clear(spi_dev->cs);
+    /* Write command and address */
+    status = spi_transfer_reg(spi_dev->spi, cmd, addr, NULL);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Keep holding CS and write data */
+    status = spi_transfer_bytes(spi_dev->spi, (char *)src, NULL, len);
+    if (status < 0)
+    {
+        return status;
+    }
+    gpio_set(spi_dev->cs);
+    spi_release(spi_dev->spi);
+    /* status contains the number of bytes actually written to the SPI bus. */
+    return status;
+}
+
+static int nvram_spi_read_9bit_addr(nvram_t *dev, uint8_t *dst, uint32_t src, size_t len)
+{
+    nvram_spi_params_t *spi_dev = (nvram_spi_params_t *) dev->extra;
+    int status;
+    uint8_t cmd;
+    uint8_t addr;
+    cmd = NVRAM_SPI_CMD_READ;
+    /* The upper address bit is mixed into the command byte on certain devices,
+     * probably just to save a byte in the SPI transfer protocol. */
+    if (src > 0xff) {
+        cmd |= 0x08;
+    }
+    /* LSB of address */
+    addr = (src & 0xff);
+    spi_acquire(spi_dev->spi);
+    gpio_clear(spi_dev->cs);
+    /* Write command and address */
+    status = spi_transfer_reg(spi_dev->spi, (char)cmd, addr, NULL);
+    if (status < 0)
+    {
+        return status;
+    }
+    /* Keep holding CS and read data */
+    status = spi_transfer_bytes(spi_dev->spi, NULL, (char *)dst, len);
+    if (status < 0)
+    {
+        return status;
+    }
+    gpio_set(spi_dev->cs);
+    spi_release(spi_dev->spi);
+    /* status contains the number of bytes actually read from the SPI bus. */
+    return status;
+}
diff --git a/tests/driver_nvram_spi/Makefile b/tests/driver_nvram_spi/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d764782ba9fe5ab1f05e7c906bd7a97d828a4f39
--- /dev/null
+++ b/tests/driver_nvram_spi/Makefile
@@ -0,0 +1,34 @@
+APPLICATION = driver_nvram_spi
+include ../Makefile.tests_common
+
+FEATURES_REQUIRED = periph_spi periph_gpio
+
+USEMODULE += nvram_spi
+USEMODULE += vtimer
+
+ifneq (,$(TEST_NVRAM_SPI_DEV))
+  CFLAGS += -DTEST_NVRAM_SPI_DEV=$(TEST_NVRAM_SPI_DEV)
+else
+  # set arbitrary default
+  CFLAGS += -DTEST_NVRAM_SPI_DEV=SPI_0
+endif
+ifneq (,$(TEST_NVRAM_SPI_CS))
+  CFLAGS += -DTEST_NVRAM_SPI_CS=$(TEST_NVRAM_SPI_CS)
+else
+  # set arbitrary default
+  CFLAGS += -DTEST_NVRAM_SPI_CS=GPIO_0
+endif
+ifneq (,$(TEST_NVRAM_SPI_SIZE))
+  CFLAGS += -DTEST_NVRAM_SPI_SIZE=$(TEST_NVRAM_SPI_SIZE)
+else
+  # set tiny arbitrary default
+  CFLAGS += -DTEST_NVRAM_SPI_SIZE=64
+endif
+ifneq (,$(TEST_NVRAM_SPI_ADDRESS_COUNT))
+  CFLAGS += -DTEST_NVRAM_SPI_ADDRESS_COUNT=$(TEST_NVRAM_SPI_ADDRESS_COUNT)
+else
+  # set 1 address byte by default, increase if using a larger module for test.
+  CFLAGS += -DTEST_NVRAM_SPI_ADDRESS_COUNT=1
+endif
+
+include $(RIOTBASE)/Makefile.include
diff --git a/tests/driver_nvram_spi/README.md b/tests/driver_nvram_spi/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..202200a24acbeb1e05b1d628d3dbf89ed6e2ea5d
--- /dev/null
+++ b/tests/driver_nvram_spi/README.md
@@ -0,0 +1,12 @@
+# About
+This is a manual test application for the SPI NVRAM driver.
+
+# Usage
+This test application will initialize the SPI bus and NVRAM device with the
+following parameters:
+
+ - Baudrate: 10 MHz (overridable by setting TEST_NVRAM_SPI_SPEED)
+ - SPI config: SPI_CONF_FIRST_RISING (overridable by setting TEST_NVRAM_SPI_CONF)
+
+The memory will be overwritten by the test application. The original contents
+will not be restored after the test.
diff --git a/tests/driver_nvram_spi/main.c b/tests/driver_nvram_spi/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..8329046d54c7f86ddbe3c02016f038c71b09404f
--- /dev/null
+++ b/tests/driver_nvram_spi/main.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup tests
+ * @{
+ *
+ * @file
+ * @brief       Test application for the SPI NVRAM driver
+ *
+ * @author      Joakim Gebart <joakim.gebart@eistec.se
+ *
+ * @}
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "board.h"
+#include "vtimer.h"
+#include "periph/spi.h"
+#include "nvram-spi.h"
+
+#ifndef TEST_NVRAM_SPI_DEV
+#error "TEST_NVRAM_SPI_DEV not defined"
+#endif
+#ifndef TEST_NVRAM_SPI_CS
+#error "TEST_NVRAM_SPI_CS not defined"
+#endif
+#ifndef TEST_NVRAM_SPI_SIZE
+#error "TEST_NVRAM_SPI_SIZE not defined"
+#endif
+#ifndef TEST_NVRAM_SPI_ADDRESS_COUNT
+#error "TEST_NVRAM_SPI_ADDRESS_COUNT not defined"
+#endif
+
+#ifdef TEST_NVRAM_SPI_CONF
+#define SPI_CONF    (TEST_NVRAM_SPI_CONF)
+#else
+#define SPI_CONF    (SPI_CONF_FIRST_RISING)
+#endif
+
+#ifdef TEST_NVRAM_SPI_SPEED
+#define SPI_SPEED   (TEST_NVRAM_SPI_SPEED)
+#else
+#define SPI_SPEED   (SPI_SPEED_10MHZ)
+#endif
+
+/* This will only work on small memories. Modify if you need to test NVRAM
+ * memories which do not fit inside free RAM */
+static uint8_t buf_out[TEST_NVRAM_SPI_SIZE];
+static uint8_t buf_in[TEST_NVRAM_SPI_SIZE];
+
+/**
+ * @brief xxd-like printing of a binary buffer
+ */
+static void print_buffer(const uint8_t * buf, size_t length) {
+    static const unsigned int bytes_per_line = 16;
+    static const unsigned int bytes_per_group = 2;
+    unsigned long i = 0;
+    while (i < length) {
+        unsigned int col;
+        for (col = 0; col < bytes_per_line; ++col) {
+            /* Print hex data */
+            if (col == 0) {
+                printf("\n%08lx: ", i);
+            }
+            else if ((col % bytes_per_group) == 0) {
+                putchar(' ');
+            }
+            if ((i + col) < length) {
+                printf("%02hhx", buf[i + col]);
+            } else {
+                putchar(' ');
+                putchar(' ');
+            }
+        }
+        putchar(' ');
+        for (col = 0; col < bytes_per_line; ++col) {
+            if ((i + col) < length) {
+                /* Echo only printable chars */
+                if (isprint(buf[i + col])) {
+                    putchar(buf[i + col]);
+                } else {
+                    putchar('.');
+                }
+            } else {
+                putchar(' ');
+            }
+        }
+        i += bytes_per_line;
+    }
+    /* end with a newline */
+    puts("");
+}
+
+/* weak PRNG for generating "random" test data */
+static uint8_t lcg_rand8(void) {
+    static const uint32_t a = 1103515245;
+    static const uint32_t c = 12345;
+    static uint32_t val = 123456; /* seed value */
+    val = val * a + c;
+    return (val >> 16) & 0xff;
+}
+
+int main(void)
+{
+    uint32_t i;
+    nvram_spi_params_t spi_params = {
+        .spi = TEST_NVRAM_SPI_DEV,
+        .cs = TEST_NVRAM_SPI_CS,
+        .address_count = TEST_NVRAM_SPI_ADDRESS_COUNT,
+    };
+    nvram_t dev;
+    timex_t start_delay = {
+        .seconds = 10,
+        .microseconds = 0,
+    };
+
+    puts("NVRAM SPI test application starting...");
+    printf("Initializing SPI_%i... ", TEST_NVRAM_SPI_DEV);
+    if (spi_init_master(TEST_NVRAM_SPI_DEV, SPI_CONF, SPI_SPEED_10MHZ) == 0) {
+        puts("[OK]");
+    }
+    else {
+        puts("[Failed]\n");
+        return 1;
+    }
+
+    puts("Initializing NVRAM SPI device descriptor... ");
+    if (nvram_spi_init(&dev, &spi_params, TEST_NVRAM_SPI_SIZE) == 0) {
+        puts("[OK]");
+    }
+    else {
+        puts("[Failed]\n");
+        return 1;
+    }
+
+    puts("NVRAM SPI init done.\n");
+
+    puts("!!! This test will erase everything on the NVRAM !!!");
+    puts("!!! Unplug/reset/halt device now if this is not acceptable !!!");
+    puts("Waiting for 10 seconds before continuing...");
+    vtimer_sleep(start_delay);
+
+    puts("Reading current memory contents...");
+    for (i = 0; i < TEST_NVRAM_SPI_SIZE; ++i) {
+        if (dev.read(&dev, &buf_in[i], i, 1) != 1) {
+            puts("[Failed]\n");
+            return 1;
+        }
+    }
+    puts("[OK]");
+    puts("NVRAM contents before test:");
+    print_buffer(buf_in, sizeof(buf_in));
+
+    puts("Writing bytewise 0xFF to device");
+
+    memset(buf_out, 0xff, sizeof(buf_out));
+    for (i = 0; i < TEST_NVRAM_SPI_SIZE; ++i) {
+        if (dev.write(&dev, &buf_out[i], i, 1) != 1) {
+            puts("[Failed]\n");
+            return 1;
+        }
+        if (buf_out[i] != 0xff) {
+            puts("nvram_spi_write modified *src!");
+            printf(" i = %08lx\n", (unsigned long) i);
+            puts("[Failed]\n");
+            return 1;
+        }
+    }
+
+    puts("Reading back blockwise");
+    memset(buf_in, 0x00, sizeof(buf_in));
+    if (dev.read(&dev, buf_in, 0, TEST_NVRAM_SPI_SIZE) != TEST_NVRAM_SPI_SIZE) {
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+    puts("Verifying contents...");
+    if (memcmp(buf_in, buf_out, TEST_NVRAM_SPI_SIZE) != 0) {
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+
+    puts("Writing blockwise address complement to device");
+    for (i = 0; i < TEST_NVRAM_SPI_SIZE; ++i) {
+        buf_out[i] = (~(i)) & 0xff;
+    }
+    if (dev.write(&dev, buf_out, 0, TEST_NVRAM_SPI_SIZE) != TEST_NVRAM_SPI_SIZE) {
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+    puts("buf_out:");
+    print_buffer(buf_out, sizeof(buf_out));
+    puts("Reading back blockwise");
+    memset(buf_in, 0x00, sizeof(buf_in));
+    if (dev.read(&dev, buf_in, 0, TEST_NVRAM_SPI_SIZE) != TEST_NVRAM_SPI_SIZE) {
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+    puts("Verifying contents...");
+    if (memcmp(buf_in, buf_out, TEST_NVRAM_SPI_SIZE) != 0) {
+        puts("buf_in:");
+        print_buffer(buf_in, sizeof(buf_in));
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+
+    puts("Generating pseudo-random test data...");
+
+    for (i = 0; i < TEST_NVRAM_SPI_SIZE; ++i) {
+        buf_out[i] = lcg_rand8();
+    }
+
+    puts("buf_out:");
+    print_buffer(buf_out, sizeof(buf_out));
+
+    puts("Writing blockwise data to device");
+    if (dev.write(&dev, buf_out, 0, TEST_NVRAM_SPI_SIZE) != TEST_NVRAM_SPI_SIZE) {
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+
+    puts("Reading back blockwise");
+    memset(buf_in, 0x00, sizeof(buf_in));
+    if (dev.read(&dev, buf_in, 0, TEST_NVRAM_SPI_SIZE) != TEST_NVRAM_SPI_SIZE) {
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+    puts("Verifying contents...");
+    if (memcmp(buf_in, buf_out, TEST_NVRAM_SPI_SIZE) != 0) {
+        puts("buf_in:");
+        print_buffer(buf_in, sizeof(buf_in));
+        puts("[Failed]\n");
+        return 1;
+    }
+    puts("[OK]");
+
+    puts("All tests passed!");
+
+    while(1);
+
+    return 0;
+}