From 2738e9f9a866bcc2958d87c37d8d77f83310c13b Mon Sep 17 00:00:00 2001 From: Joakim Gebart 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 +#include +#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 + */ + +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; +}