mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
6d61381d2a
The expandable GPIO API requires the comparison of structured GPIO types. This means that inline functions must be used instead of direct comparisons. For the migration process, drivers must first be changed so that they use the inline comparison functions.
263 lines
6.4 KiB
C
263 lines
6.4 KiB
C
/*
|
|
* 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 "bitarithm.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 int _at25xxx_write_page(const at25xxx_t *dev, uint32_t page, uint32_t offset, const void *data, size_t len)
|
|
{
|
|
assert(offset < PAGE_SIZE);
|
|
|
|
/* write no more than to the end of the current page to prevent wrap-around */
|
|
size_t remaining = PAGE_SIZE - offset;
|
|
len = min(len, remaining);
|
|
uint32_t pos = _pos(CMD_WRITE, page * PAGE_SIZE + offset);
|
|
|
|
/* 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_page(const at25xxx_t *dev, uint32_t page, uint32_t offset, const void *data, size_t len)
|
|
{
|
|
int res;
|
|
|
|
getbus(dev);
|
|
res = _at25xxx_write_page(dev, page, offset, data, len);
|
|
spi_release(dev->params.spi);
|
|
|
|
return res;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* page size is always a power of two */
|
|
const uint32_t page_shift = bitarithm_msb(PAGE_SIZE);
|
|
const uint32_t page_mask = PAGE_SIZE - 1;
|
|
|
|
uint32_t page = pos >> page_shift;
|
|
uint32_t offset = pos & page_mask;
|
|
|
|
getbus(dev);
|
|
|
|
while (len) {
|
|
ssize_t written = _at25xxx_write_page(dev, page, offset, d, len);
|
|
|
|
if (written < 0) {
|
|
res = written;
|
|
break;
|
|
}
|
|
|
|
len -= written;
|
|
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
|
|
d += written;
|
|
page += (offset + written) >> page_shift;
|
|
offset = (offset + written) & page_mask;
|
|
}
|
|
|
|
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];
|
|
|
|
if (pos + len > dev->params.size) {
|
|
return -ERANGE;
|
|
}
|
|
|
|
memset(data, val, sizeof(data));
|
|
|
|
/* page size is always a power of two */
|
|
const uint32_t page_shift = bitarithm_msb(PAGE_SIZE);
|
|
const uint32_t page_mask = PAGE_SIZE - 1;
|
|
|
|
uint32_t page = pos >> page_shift;
|
|
uint32_t offset = pos & page_mask;
|
|
|
|
getbus(dev);
|
|
|
|
while (len) {
|
|
size_t written = _at25xxx_write_page(dev, page, offset, data, min(len, sizeof(data)));
|
|
len -= written;
|
|
page += (offset + written) >> page_shift;
|
|
offset = (offset + written) & page_mask;
|
|
}
|
|
|
|
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 (gpio_is_valid(dev->params.wp_pin)) {
|
|
gpio_init(dev->params.wp_pin, GPIO_OUT);
|
|
gpio_set(dev->params.wp_pin);
|
|
}
|
|
|
|
if (gpio_is_valid(dev->params.hold_pin)) {
|
|
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;
|
|
}
|