1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 04:52:59 +01:00

drivers: add AT25xxx series EEPROM

This adds a driver for the ST M95xxx series SPI EEPROMs.
The driver has been tested with the M95M01 EEPROM, but should
work with other chips from that family.

SPI-EEPROMs from other vendors from the families AT25xxx, 25AAxxx,
25LCxxx, CAT25xxx & BR25Sxxx should also in the same way.
This commit is contained in:
Benjamin Valentin 2019-07-31 16:12:40 +02:00 committed by Benjamin Valentin
parent ab333f3fb2
commit 97fc2f2af1
7 changed files with 507 additions and 0 deletions

View File

@ -40,6 +40,11 @@ ifneq (,$(filter at24mac,$(USEMODULE)))
FEATURES_REQUIRED += periph_i2c
endif
ifneq (,$(filter at25xxx,$(USEMODULE)))
FEATURES_REQUIRED += periph_spi
USEMODULE += xtimer
endif
ifneq (,$(filter at30tse75x,$(USEMODULE)))
USEMODULE += xtimer
FEATURES_REQUIRED += periph_i2c

View File

@ -24,6 +24,10 @@ ifneq (,$(filter at24mac,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at24mac/include
endif
ifneq (,$(filter at25xxx,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at25xxx/include
endif
ifneq (,$(filter at86rf2xx,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at86rf2xx/include
endif

1
drivers/at25xxx/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

228
drivers/at25xxx/at25xxx.c Normal file
View File

@ -0,0 +1,228 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* 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 drivers_at25xxx
* @{
*
* @file
* @brief Driver for the AT25xxx family of SPI-EEPROMs.
* This also includes M95xxx, 25AAxxx, 25LCxxx,
* CAT25xxx & BR25Sxxx.
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include "at25xxx.h"
#include "at25xxx_constants.h"
#include "at25xxx_params.h"
#include "byteorder.h"
#include "xtimer.h"
#define POLL_DELAY_US (1000)
#ifndef min
#define min(a, b) ((a) > (b) ? (b) : (a))
#endif
#define PAGE_SIZE (dev->params.page_size)
#define ADDR_LEN (AT25XXX_PARAM_ADDR_LEN)
#define ADDR_MSK ((1UL << ADDR_LEN) - 1)
#ifndef AT25XXXX_SET_BUF_SIZE
/**
* @brief Adjust to configure buffer size
*/
#define AT225XXXX_SET_BUF_SIZE (64)
#endif
static inline int getbus(const at25xxx_t *dev)
{
return spi_acquire(dev->params.spi, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk);
}
static inline uint32_t _pos(uint8_t cmd, uint32_t pos)
{
/* first byte is CMD, then addr with MSB first */
pos = htonl((pos & ADDR_MSK) | ((uint32_t)cmd << ADDR_LEN));
pos >>= 8 * sizeof(pos) - (ADDR_LEN + 8);
return pos;
}
static inline bool _write_in_progress(const at25xxx_t *dev)
{
return spi_transfer_reg(dev->params.spi, dev->params.cs_pin, CMD_RDSR, 0) & SR_WIP;
}
static inline bool _write_enabled(const at25xxx_t *dev)
{
return spi_transfer_reg(dev->params.spi, dev->params.cs_pin, CMD_RDSR, 0) & SR_WEL;
}
static inline int _wait_until_eeprom_ready(const at25xxx_t *dev)
{
uint8_t tries = 10;
while (_write_in_progress(dev) && --tries) {
spi_release(dev->params.spi);
xtimer_usleep(POLL_DELAY_US);
getbus(dev);
}
return tries == 0 ? -ETIMEDOUT : 0;
}
static ssize_t _write_page(const at25xxx_t *dev, uint32_t pos, const void *data, size_t len)
{
/* write no more than to the end of the current page to prevent wrap-around */
size_t remaining = PAGE_SIZE - (pos & (PAGE_SIZE - 1));
len = min(len, remaining);
pos = _pos(CMD_WRITE, pos);
/* wait for previous write to finish - may take up to 5 ms */
int res = _wait_until_eeprom_ready(dev);
if (res) {
return res;
}
/* set write enable and wait for status change */
spi_transfer_byte(dev->params.spi, dev->params.cs_pin, false, CMD_WREN);
unsigned tries = 1000;
while (!_write_enabled(dev) && --tries) {}
if (tries == 0) {
return -ETIMEDOUT;
}
/* write the data */
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, true, &pos, NULL, 1 + ADDR_LEN / 8);
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, false, data, NULL, len);
return len;
}
int at25xxx_write(const at25xxx_t *dev, uint32_t pos, const void *data, size_t len)
{
int res = 0;
const uint8_t *d = data;
if (pos + len > dev->params.size) {
return -ERANGE;
}
getbus(dev);
while (len) {
ssize_t written = _write_page(dev, pos, d, len);
if (written < 0) {
res = written;
break;
}
len -= written;
pos += written;
d += written;
}
spi_release(dev->params.spi);
return res;
}
void at25xxx_write_byte(const at25xxx_t *dev, uint32_t pos, uint8_t data)
{
at25xxx_write(dev, pos, &data, sizeof(data));
}
int at25xxx_read(const at25xxx_t *dev, uint32_t pos, void *data, size_t len)
{
if (pos + len > dev->params.size) {
return -ERANGE;
}
getbus(dev);
/* wait for previous write to finish - may take up to 5 ms */
int res = _wait_until_eeprom_ready(dev);
if (res) {
return res;
}
pos = _pos(CMD_READ, pos);
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, true, &pos, NULL, 1 + ADDR_LEN / 8);
spi_transfer_bytes(dev->params.spi, dev->params.cs_pin, false, NULL, data, len);
spi_release(dev->params.spi);
return 0;
}
uint8_t at25xxx_read_byte(const at25xxx_t *dev, uint32_t pos)
{
uint8_t b;
at25xxx_read(dev, pos, &b, sizeof(b));
return b;
}
int at25xxx_set(const at25xxx_t *dev, uint32_t pos, uint8_t val, size_t len)
{
uint8_t data[AT225XXXX_SET_BUF_SIZE];
size_t total = 0;
if (pos + len > dev->params.size) {
return -ERANGE;
}
memset(data, val, sizeof(data));
getbus(dev);
while (len) {
size_t written = _write_page(dev, pos, data, min(len, sizeof(data)));
len -= written;
pos += written;
total += written;
}
spi_release(dev->params.spi);
return 0;
}
int at25xxx_clear(const at25xxx_t *dev, uint32_t pos, size_t len)
{
return at25xxx_set(dev, pos, 0, len);
}
int at25xxx_init(at25xxx_t *dev, const at25xxx_params_t *params)
{
dev->params = *params;
spi_init_cs(dev->params.spi, dev->params.cs_pin);
if (dev->params.wp_pin != GPIO_UNDEF) {
gpio_init(dev->params.wp_pin, GPIO_OUT);
gpio_set(dev->params.wp_pin);
}
if (dev->params.hold_pin != GPIO_UNDEF) {
gpio_init(dev->params.hold_pin, GPIO_OUT);
gpio_set(dev->params.hold_pin);
}
/* check if the SPI configuration is valid */
if (getbus(dev) != SPI_OK) {
return -1;
}
spi_release(dev->params.spi);
return 0;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* 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 drivers_at25xxx
* @{
*
* @file
* @brief Commands for the AT25xxx family of SPI-EEPROMs
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#ifndef AT25XXX_CONSTANTS_H
#define AT25XXX_CONSTANTS_H
#ifdef __cplusplus
extern "C" {
#endif
#define CMD_WREN (0x6) /**< Write Enable */
#define CMD_WRDI (0x4) /**< Write Disable */
#define CMD_RDSR (0x5) /**< Read Status Register */
#define CMD_WRSR (0x1) /**< Write Status Register */
#define CMD_READ (0x3) /**< Read from Memory Array */
#define CMD_WRITE (0x2) /**< Write to Memory Array */
#define SR_WIP (0x01) /**< Write In Progress */
#define SR_WEL (0x02) /**< Write Enable Latch */
#define SR_BP0 (0x04) /**< Block Protect 0 */
#define SR_BP1 (0x08) /**< Block Protect 1 */
#define SR_SRWD (0x80) /**< Status Register Write Disable */
#ifdef __cplusplus
}
#endif
#endif /* AT25XXX_CONSTANTS_H */

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* 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 drivers_at25xxx
* @{
*
* @file
* @brief Default configuration for the M95M01 EEPROM
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#ifndef AT25XXX_PARAMS_H
#define AT25XXX_PARAMS_H
#include "board.h"
#include "at25xxx.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Set default configuration parameters for the AT25XXX driver
* @{
*/
#ifndef AT25XXX_PARAM_SPI
#define AT25XXX_PARAM_SPI (SPI_DEV(0))
#endif
#ifndef AT25XXX_PARAM_SPI_CLK
#define AT25XXX_PARAM_SPI_CLK (SPI_CLK_5MHZ)
#endif
#ifndef AT25XXX_PARAM_CS
#define AT25XXX_PARAM_CS (GPIO_PIN(0, 0))
#endif
#ifndef AT25XXX_PARAM_WP
#define AT25XXX_PARAM_WP (GPIO_UNDEF)
#endif
#ifndef AT25XXX_PARAM_HOLD
#define AT25XXX_PARAM_HOLD (GPIO_UNDEF)
#endif
#ifndef AT25XXX_PARAM_SIZE
#define AT25XXX_PARAM_SIZE (128 * 1024UL) /* EEPROM size, in bytes */
#endif
#ifndef AT25XXX_PARAM_ADDR_LEN
#define AT25XXX_PARAM_ADDR_LEN (24) /* Address length, in bits */
#endif
#ifndef AT25XXX_PARAM_PAGE_SIZE
#define AT25XXX_PARAM_PAGE_SIZE (256) /* Page size, in bytes */
#endif
#ifndef AT25XXX_PARAMS
#define AT25XXX_PARAMS { .spi = AT25XXX_PARAM_SPI, \
.spi_clk = AT25XXX_PARAM_SPI_CLK, \
.cs_pin = AT25XXX_PARAM_CS, \
.wp_pin = AT25XXX_PARAM_WP, \
.hold_pin = AT25XXX_PARAM_HOLD, \
.size = AT25XXX_PARAM_SIZE, \
.page_size = AT25XXX_PARAM_PAGE_SIZE }
#endif
/**
* @brief AT25XXX configuration
*/
static const at25xxx_params_t at25xxx_params[] =
{
AT25XXX_PARAMS
};
#ifdef __cplusplus
}
#endif
#endif /* AT25XXX_PARAMS_H */

144
drivers/include/at25xxx.h Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2019 ML!PA Consulting GmbH
*
* 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 drivers_at25xxx AT25xxx family of SPI-EEPROMs
* @ingroup drivers_misc
*
* @brief This driver also support M95xxx, 25AAxxx, 25LCxxx, CAT25xxx & BR25Sxxx families
* @{
*
* @file
* @brief Driver for the AT25xxx series of EEPROMs
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#ifndef AT25XXX_H
#define AT25XXX_H
#include <stdint.h>
#include <stdbool.h>
#include "periph/spi.h"
#include "periph/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief struct holding all params needed for device initialization
*/
typedef struct {
spi_t spi; /**< SPI bus the device is connected to */
spi_clk_t spi_clk; /**< SPI clock speed to use */
spi_cs_t cs_pin; /**< GPIO pin connected to chip select */
gpio_t wp_pin; /**< GPIO pin connected to the write-protect pin */
gpio_t hold_pin; /**< GPIO pin connected to the hold pin */
uint32_t size; /**< Size of the EEPROM in bytes */
uint16_t page_size; /**< Page Size of the EEPROM in bytes */
} at25xxx_params_t;
/**
* @brief struct that represents an AT25XXX device
*/
typedef struct {
at25xxx_params_t params; /**< parameters */
} at25xxx_t;
/**
* @brief Initialize an AT25XXX device handle with AT25XXX parameters
*
* @param[in, out] dev AT25XXX device handle
* @param[in] params AT25XXX parameters
*
* @return 0 on success, -1 on failure
*
*/
int at25xxx_init(at25xxx_t *dev, const at25xxx_params_t *params);
/**
* @brief Read a byte at a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
*
* @return read byte
*/
uint8_t at25xxx_read_byte(const at25xxx_t *dev, uint32_t pos);
/**
* @brief Sequentially read @p len bytes from a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[out] data read buffer
* @param[in] len requested length to be read
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_read(const at25xxx_t *dev, uint32_t pos, void *data, size_t len);
/**
* @brief Write a byte at a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] data value to be written
*/
void at25xxx_write_byte(const at25xxx_t *dev, uint32_t pos, uint8_t data);
/**
* @brief Sequentially write @p len bytes from a given position @p pos
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] data write buffer
* @param[in] len requested length to be written
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_write(const at25xxx_t *dev, uint32_t pos, const void *data, size_t len);
/**
* @brief Set @p len bytes from a given position @p pos to the
* value @p val
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] val value to be set
* @param[in] len requested length to be written
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_set(const at25xxx_t *dev, uint32_t pos, uint8_t val, size_t len);
/**
* @brief Set @p len bytes from position @p pos to 0
*
* This is a wrapper around @see at25xxx_set.
*
* @param[in] dev AT25XXX device handle
* @param[in] pos position in EEPROM memory
* @param[in] len requested length to be written
*
* @return 0 on success
* @return -ERANGE if pos + len > EEPROM size
*/
int at25xxx_clear(const at25xxx_t *dev, uint32_t pos, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* AT25XXX_H */