From 2738e9f9a866bcc2958d87c37d8d77f83310c13b Mon Sep 17 00:00:00 2001
From: Joakim Gebart <joakim.gebart@eistec.se>
Date: Mon, 26 Jan 2015 09:08:12 +0100
Subject: [PATCH] nvram_spi: Implement generic SPI NVRAM driver.

---
 drivers/nvram_spi/Makefile    |   3 +
 drivers/nvram_spi/nvram-spi.c | 283 ++++++++++++++++++++++++++++++++++
 2 files changed, 286 insertions(+)
 create mode 100644 drivers/nvram_spi/Makefile
 create mode 100644 drivers/nvram_spi/nvram-spi.c

diff --git a/drivers/nvram_spi/Makefile b/drivers/nvram_spi/Makefile
new file mode 100644
index 0000000000..bc459313fc
--- /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 0000000000..4ae873b554
--- /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;
+}
-- 
GitLab