2016-07-15 10:40:56 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 Eistec AB
|
|
|
|
* 2017 OTA keys S.A.
|
|
|
|
*
|
|
|
|
* 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_mtd_spi_nor
|
|
|
|
* @{
|
|
|
|
*
|
|
|
|
* @file
|
|
|
|
* @brief Driver for serial flash memory attached to SPI
|
|
|
|
*
|
|
|
|
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
|
|
|
|
* @author Vincent Dupont <vincent@otakeys.com>
|
|
|
|
*
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
#include "mtd.h"
|
|
|
|
#if MODULE_XTIMER
|
|
|
|
#include "xtimer.h"
|
|
|
|
#include "timex.h"
|
|
|
|
#else
|
|
|
|
#include "thread.h"
|
|
|
|
#endif
|
|
|
|
#include "byteorder.h"
|
|
|
|
#include "mtd_spi_nor.h"
|
|
|
|
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
|
|
#include "debug.h"
|
|
|
|
#define ENABLE_TRACE (0)
|
|
|
|
|
|
|
|
#if ENABLE_TRACE
|
|
|
|
#define TRACE(...) DEBUG(__VA_ARGS__)
|
|
|
|
#else
|
|
|
|
#define TRACE(...)
|
|
|
|
#endif
|
|
|
|
|
2020-03-05 10:09:13 +01:00
|
|
|
/* after power up, on an invalid JEDEC ID, wait and read N times */
|
|
|
|
#ifndef MTD_POWER_UP_WAIT_FOR_ID
|
|
|
|
#define MTD_POWER_UP_WAIT_FOR_ID (0x0F)
|
|
|
|
#endif
|
|
|
|
|
2018-01-22 14:04:35 +01:00
|
|
|
#define MTD_32K (32768ul)
|
|
|
|
#define MTD_32K_ADDR_MASK (0x7FFF)
|
|
|
|
#define MTD_4K (4096ul)
|
|
|
|
#define MTD_4K_ADDR_MASK (0xFFF)
|
|
|
|
|
2020-05-14 22:06:22 +02:00
|
|
|
#define MBIT_AS_BYTES ((1024 * 1024) / 8)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief JEDEC memory manufacturer ID codes.
|
|
|
|
*
|
|
|
|
* see http://www.softnology.biz/pdf/JEP106AV.pdf
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
#define JEDEC_BANK(n) ((n) << 8)
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
SPI_NOR_JEDEC_ATMEL = 0x1F | JEDEC_BANK(1),
|
|
|
|
} jedec_manuf_t;
|
|
|
|
/** @} */
|
|
|
|
|
2016-07-15 10:40:56 +02:00
|
|
|
static int mtd_spi_nor_init(mtd_dev_t *mtd);
|
|
|
|
static int mtd_spi_nor_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t size);
|
|
|
|
static int mtd_spi_nor_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t size);
|
|
|
|
static int mtd_spi_nor_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t size);
|
|
|
|
static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power);
|
|
|
|
|
|
|
|
const mtd_desc_t mtd_spi_nor_driver = {
|
|
|
|
.init = mtd_spi_nor_init,
|
|
|
|
.read = mtd_spi_nor_read,
|
|
|
|
.write = mtd_spi_nor_write,
|
|
|
|
.erase = mtd_spi_nor_erase,
|
|
|
|
.power = mtd_spi_nor_power,
|
|
|
|
};
|
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
static void mtd_spi_acquire(const mtd_spi_nor_t *dev)
|
|
|
|
{
|
|
|
|
spi_acquire(dev->params->spi, dev->params->cs,
|
|
|
|
dev->params->mode, dev->params->clk);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mtd_spi_release(const mtd_spi_nor_t *dev)
|
|
|
|
{
|
|
|
|
spi_release(dev->params->spi);
|
|
|
|
}
|
|
|
|
|
2020-05-14 22:06:22 +02:00
|
|
|
static bool mtd_spi_manuf_match(const mtd_jedec_id_t *id, jedec_manuf_t manuf)
|
|
|
|
{
|
|
|
|
return manuf == ((id->bank << 8) | id->manuf);
|
|
|
|
}
|
|
|
|
|
2016-07-15 10:40:56 +02:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Send command opcode followed by address, followed by a read to buffer
|
|
|
|
*
|
|
|
|
* @param[in] dev pointer to device descriptor
|
|
|
|
* @param[in] opcode command opcode
|
|
|
|
* @param[in] addr address (big endian)
|
|
|
|
* @param[out] dest read buffer
|
|
|
|
* @param[in] count number of bytes to read after the address has been sent
|
|
|
|
*/
|
2017-06-29 22:46:16 +02:00
|
|
|
static void mtd_spi_cmd_addr_read(const mtd_spi_nor_t *dev, uint8_t opcode,
|
2018-03-05 15:54:08 +01:00
|
|
|
be_uint32_t addr, void *dest, uint32_t count)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
|
|
|
TRACE("mtd_spi_cmd_addr_read: %p, %02x, (%02x %02x %02x %02x), %p, %" PRIu32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)dev, (unsigned int)opcode, addr.u8[0], addr.u8[1], addr.u8[2],
|
|
|
|
addr.u8[3], dest, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
uint8_t *addr_buf = &addr.u8[4 - dev->params->addr_width];
|
2016-07-15 10:40:56 +02:00
|
|
|
if (ENABLE_TRACE) {
|
|
|
|
TRACE("mtd_spi_cmd_addr_read: addr:");
|
2020-02-10 20:39:49 +01:00
|
|
|
for (unsigned int i = 0; i < dev->params->addr_width; ++i) {
|
2016-07-15 10:40:56 +02:00
|
|
|
TRACE(" %02x", addr_buf[i]);
|
|
|
|
}
|
|
|
|
TRACE("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* Send opcode followed by address */
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_byte(dev->params->spi, dev->params->cs, true, opcode);
|
|
|
|
spi_transfer_bytes(dev->params->spi, dev->params->cs, true,
|
|
|
|
(char *)addr_buf, NULL, dev->params->addr_width);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
/* Read data */
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_bytes(dev->params->spi, dev->params->cs, false,
|
|
|
|
NULL, dest, count);
|
2018-03-05 15:54:08 +01:00
|
|
|
} while (0);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Send command opcode followed by address, followed by a write from buffer
|
|
|
|
*
|
|
|
|
* @param[in] dev pointer to device descriptor
|
|
|
|
* @param[in] opcode command opcode
|
|
|
|
* @param[in] addr address (big endian)
|
|
|
|
* @param[out] src write buffer
|
|
|
|
* @param[in] count number of bytes to write after the opcode has been sent
|
|
|
|
*/
|
2017-06-29 22:46:16 +02:00
|
|
|
static void mtd_spi_cmd_addr_write(const mtd_spi_nor_t *dev, uint8_t opcode,
|
2018-03-05 15:54:08 +01:00
|
|
|
be_uint32_t addr, const void *src, uint32_t count)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
|
|
|
TRACE("mtd_spi_cmd_addr_write: %p, %02x, (%02x %02x %02x %02x), %p, %" PRIu32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)dev, (unsigned int)opcode, addr.u8[0], addr.u8[1], addr.u8[2],
|
|
|
|
addr.u8[3], src, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
uint8_t *addr_buf = &addr.u8[4 - dev->params->addr_width];
|
2016-07-15 10:40:56 +02:00
|
|
|
if (ENABLE_TRACE) {
|
|
|
|
TRACE("mtd_spi_cmd_addr_write: addr:");
|
2020-02-10 20:39:49 +01:00
|
|
|
for (unsigned int i = 0; i < dev->params->addr_width; ++i) {
|
2016-07-15 10:40:56 +02:00
|
|
|
TRACE(" %02x", addr_buf[i]);
|
|
|
|
}
|
|
|
|
TRACE("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* Send opcode followed by address */
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_byte(dev->params->spi, dev->params->cs, true, opcode);
|
2016-07-15 10:40:56 +02:00
|
|
|
bool cont = (count > 0); /* only keep CS asserted when there is data that follows */
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_bytes(dev->params->spi, dev->params->cs, cont,
|
|
|
|
(char *)addr_buf, NULL, dev->params->addr_width);
|
2016-07-15 10:40:56 +02:00
|
|
|
/* Write data */
|
|
|
|
if (cont) {
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_bytes(dev->params->spi, dev->params->cs,
|
|
|
|
false, (void *)src, NULL, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
2018-03-05 15:54:08 +01:00
|
|
|
} while (0);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Send command opcode followed by a read to buffer
|
|
|
|
*
|
|
|
|
* @param[in] dev pointer to device descriptor
|
|
|
|
* @param[in] opcode command opcode
|
|
|
|
* @param[out] dest read buffer
|
|
|
|
* @param[in] count number of bytes to write after the opcode has been sent
|
|
|
|
*/
|
2018-03-05 15:54:08 +01:00
|
|
|
static void mtd_spi_cmd_read(const mtd_spi_nor_t *dev, uint8_t opcode, void *dest, uint32_t count)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
|
|
|
TRACE("mtd_spi_cmd_read: %p, %02x, %p, %" PRIu32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)dev, (unsigned int)opcode, dest, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_regs(dev->params->spi, dev->params->cs, opcode, NULL, dest, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Send command opcode followed by a write from buffer
|
|
|
|
*
|
|
|
|
* @param[in] dev pointer to device descriptor
|
|
|
|
* @param[in] opcode command opcode
|
|
|
|
* @param[out] src write buffer
|
|
|
|
* @param[in] count number of bytes to write after the opcode has been sent
|
|
|
|
*/
|
2018-03-05 15:54:08 +01:00
|
|
|
static void __attribute__((unused)) mtd_spi_cmd_write(const mtd_spi_nor_t *dev, uint8_t opcode, const void *src, uint32_t count)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
|
|
|
TRACE("mtd_spi_cmd_write: %p, %02x, %p, %" PRIu32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)dev, (unsigned int)opcode, src, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_regs(dev->params->spi, dev->params->cs, opcode,
|
|
|
|
(void *)src, NULL, count);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Send command opcode
|
|
|
|
*
|
|
|
|
* @param[in] dev pointer to device descriptor
|
|
|
|
* @param[in] opcode command opcode
|
|
|
|
*/
|
2017-06-29 22:46:16 +02:00
|
|
|
static void mtd_spi_cmd(const mtd_spi_nor_t *dev, uint8_t opcode)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
|
|
|
TRACE("mtd_spi_cmd: %p, %02x\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)dev, (unsigned int)opcode);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_byte(dev->params->spi, dev->params->cs, false, opcode);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Compute 8 bit parity
|
|
|
|
*/
|
|
|
|
static inline uint8_t parity8(uint8_t x)
|
|
|
|
{
|
|
|
|
/* Taken from http://stackoverflow.com/a/21618038/1805713 */
|
|
|
|
x ^= x >> 4;
|
|
|
|
x ^= x >> 2;
|
|
|
|
x ^= x >> 1;
|
|
|
|
return (x & 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Read JEDEC ID
|
|
|
|
*/
|
2017-06-29 22:46:16 +02:00
|
|
|
static int mtd_spi_read_jedec_id(const mtd_spi_nor_t *dev, mtd_jedec_id_t *out)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
|
|
|
/* not using above read functions because of variable length rdid response */
|
|
|
|
int status = 0;
|
|
|
|
mtd_jedec_id_t jedec;
|
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
DEBUG("mtd_spi_read_jedec_id: rdid=0x%02x\n",
|
|
|
|
(unsigned int)dev->params->opcode->rdid);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
/* Send opcode */
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_byte(dev->params->spi, dev->params->cs, true, dev->params->opcode->rdid);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
/* Read manufacturer ID */
|
|
|
|
jedec.bank = 1;
|
|
|
|
while (status == 0) {
|
2020-02-10 20:39:49 +01:00
|
|
|
jedec.manuf = spi_transfer_byte(dev->params->spi,
|
|
|
|
dev->params->cs, true, 0);
|
2016-07-15 10:40:56 +02:00
|
|
|
if (jedec.manuf == JEDEC_NEXT_BANK) {
|
|
|
|
/* next bank, see JEP106 */
|
|
|
|
DEBUG("mtd_spi_read_jedec_id: manuf bank incr\n");
|
|
|
|
++jedec.bank;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (parity8(jedec.manuf) == 0) {
|
|
|
|
/* saw even parity, we expected odd parity => parity error */
|
|
|
|
DEBUG("mtd_spi_read_jedec_id: Parity error (0x%02x)\n", (unsigned int)jedec.manuf);
|
|
|
|
status = -2;
|
|
|
|
break;
|
|
|
|
}
|
2020-04-14 10:52:47 +02:00
|
|
|
if (jedec.manuf == 0xFF || jedec.manuf == 0x00) {
|
|
|
|
DEBUG_PUTS("mtd_spi_read_jedec_id: failed to read manufacturer ID");
|
|
|
|
status = -3;
|
|
|
|
break;
|
|
|
|
}
|
2016-07-15 10:40:56 +02:00
|
|
|
else {
|
|
|
|
/* all OK! */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DEBUG("mtd_spi_read_jedec_id: bank=%u manuf=0x%02x\n", (unsigned int)jedec.bank,
|
2018-03-05 15:54:08 +01:00
|
|
|
(unsigned int)jedec.manuf);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
/* Read device ID */
|
|
|
|
if (status == 0) {
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_transfer_bytes(dev->params->spi, dev->params->cs, false, NULL,
|
|
|
|
(char *)&jedec.device[0], sizeof(jedec.device));
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
DEBUG("mtd_spi_read_jedec_id: device=0x%02x, 0x%02x\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(unsigned int)jedec.device[0], (unsigned int)jedec.device[1]);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
if (status == 0) {
|
|
|
|
*out = jedec;
|
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2020-05-14 22:06:22 +02:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* @brief Get Flash capacity based on JEDEC ID
|
|
|
|
*
|
|
|
|
* @note The way the capacity is encoded differs between vendors.
|
|
|
|
* This formula has been tested with flash chips from Adesto,
|
|
|
|
* ISSI, Micron and Spansion, but it might not cover all cases.
|
|
|
|
* Please extend the function if necessary.
|
|
|
|
*/
|
|
|
|
static uint32_t mtd_spi_nor_get_size(const mtd_jedec_id_t *id)
|
|
|
|
{
|
|
|
|
/* old Atmel (now Adesto) parts use 5 lower bits of device ID 1 for density */
|
|
|
|
if (mtd_spi_manuf_match(id, SPI_NOR_JEDEC_ATMEL) &&
|
|
|
|
/* ID 2 is used to encode the product version, usually 1 or 2 */
|
|
|
|
(id->device[1] & ~0x3) == 0) {
|
|
|
|
return (0x1F & id->device[0]) * MBIT_AS_BYTES;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* everyone else seems to use device ID 2 for density */
|
|
|
|
return 1 << id->device[1];
|
|
|
|
}
|
|
|
|
|
2018-06-14 14:15:11 +02:00
|
|
|
static inline void wait_for_write_complete(const mtd_spi_nor_t *dev, uint32_t us)
|
2016-07-15 10:40:56 +02:00
|
|
|
{
|
2018-06-14 14:15:11 +02:00
|
|
|
unsigned i = 0, j = 0;
|
|
|
|
uint32_t div = 2;
|
2020-02-10 22:26:19 +01:00
|
|
|
#if ENABLE_DEBUG && defined(MODULE_XTIMER)
|
|
|
|
uint32_t diff = xtimer_now_usec();
|
|
|
|
#endif
|
2016-07-15 10:40:56 +02:00
|
|
|
do {
|
|
|
|
uint8_t status;
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd_read(dev, dev->params->opcode->rdsr, &status, sizeof(status));
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
TRACE("mtd_spi_nor: wait device status = 0x%02x\n", (unsigned int)status);
|
|
|
|
if ((status & 1) == 0) { /* TODO magic number */
|
|
|
|
break;
|
|
|
|
}
|
2018-06-14 14:15:11 +02:00
|
|
|
i++;
|
2016-07-15 10:40:56 +02:00
|
|
|
#if MODULE_XTIMER
|
2018-06-14 14:15:11 +02:00
|
|
|
if (us) {
|
|
|
|
xtimer_usleep(us);
|
|
|
|
/* reduce the waiting time quickly if the estimate was too short,
|
|
|
|
* but still avoid busy (yield) waiting */
|
|
|
|
if (us > 2 * XTIMER_BACKOFF) {
|
|
|
|
us -= (us / div);
|
|
|
|
div++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
us = 2 * XTIMER_BACKOFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
j++;
|
|
|
|
thread_yield();
|
|
|
|
}
|
2016-07-15 10:40:56 +02:00
|
|
|
#else
|
2018-06-14 14:15:11 +02:00
|
|
|
(void)div;
|
|
|
|
(void) us;
|
2016-07-15 10:40:56 +02:00
|
|
|
thread_yield();
|
|
|
|
#endif
|
|
|
|
} while (1);
|
2020-02-10 22:26:19 +01:00
|
|
|
DEBUG("wait loop %u times, yield %u times", i, j);
|
|
|
|
#if ENABLE_DEBUG && defined(MODULE_XTIMER)
|
|
|
|
diff = xtimer_now_usec() - diff;
|
|
|
|
DEBUG(", total wait %"PRIu32"us", diff);
|
|
|
|
#endif
|
|
|
|
DEBUG("\n");
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mtd_spi_nor_init(mtd_dev_t *mtd)
|
|
|
|
{
|
|
|
|
DEBUG("mtd_spi_nor_init: %p\n", (void *)mtd);
|
|
|
|
mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd;
|
|
|
|
|
|
|
|
DEBUG("mtd_spi_nor_init: -> spi: %lx, cs: %lx, opcodes: %p\n",
|
2020-02-10 20:39:49 +01:00
|
|
|
(unsigned long)dev->params->spi, (unsigned long)dev->params->cs, (void *)dev->params->opcode);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
DEBUG("mtd_spi_nor_init: %" PRIu32 " bytes "
|
2018-03-05 15:54:08 +01:00
|
|
|
"(%" PRIu32 " sectors, %" PRIu32 " bytes/sector, "
|
|
|
|
"%" PRIu32 " pages, "
|
|
|
|
"%" PRIu32 " pages/sector, %" PRIu32 " bytes/page)\n",
|
|
|
|
mtd->pages_per_sector * mtd->sector_count * mtd->page_size,
|
|
|
|
mtd->sector_count, mtd->pages_per_sector * mtd->page_size,
|
|
|
|
mtd->pages_per_sector * mtd->sector_count,
|
|
|
|
mtd->pages_per_sector, mtd->page_size);
|
2020-02-10 20:39:49 +01:00
|
|
|
DEBUG("mtd_spi_nor_init: Using %u byte addresses\n", dev->params->addr_width);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
if (dev->params->addr_width == 0) {
|
2016-07-15 10:40:56 +02:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* CS */
|
|
|
|
DEBUG("mtd_spi_nor_init: CS init\n");
|
2020-02-10 20:39:49 +01:00
|
|
|
spi_init_cs(dev->params->spi, dev->params->cs);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-03-05 10:09:13 +01:00
|
|
|
/* power up the MTD device*/
|
|
|
|
DEBUG("mtd_spi_nor_init: power up MTD device");
|
|
|
|
if (mtd_spi_nor_power(mtd, MTD_POWER_UP)) {
|
|
|
|
DEBUG("mtd_spi_nor_init: failed to power up MTD device");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_acquire(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
int res = mtd_spi_read_jedec_id(dev, &dev->jedec_id);
|
|
|
|
if (res < 0) {
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_release(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
DEBUG("mtd_spi_nor_init: Found chip with ID: (%d, 0x%02x, 0x%02x, 0x%02x)\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
dev->jedec_id.bank, dev->jedec_id.manuf, dev->jedec_id.device[0], dev->jedec_id.device[1]);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-05-14 22:06:22 +02:00
|
|
|
/* derive density from JEDEC ID */
|
|
|
|
if (mtd->sector_count == 0) {
|
|
|
|
mtd->sector_count = mtd_spi_nor_get_size(&dev->jedec_id)
|
|
|
|
/ (mtd->pages_per_sector * mtd->page_size);
|
|
|
|
}
|
|
|
|
|
2016-07-15 10:40:56 +02:00
|
|
|
uint8_t status;
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd_read(dev, dev->params->opcode->rdsr, &status, sizeof(status));
|
|
|
|
mtd_spi_release(dev);
|
2018-03-05 15:50:44 +01:00
|
|
|
|
2016-07-15 10:40:56 +02:00
|
|
|
DEBUG("mtd_spi_nor_init: device status = 0x%02x\n", (unsigned int)status);
|
|
|
|
|
|
|
|
/* check whether page size and sector size are powers of two (most chips' are)
|
|
|
|
* and compute the number of shifts needed to get the page and sector addresses
|
|
|
|
* from a byte address */
|
|
|
|
uint8_t shift = 0;
|
|
|
|
uint32_t page_size = mtd->page_size;
|
|
|
|
uint32_t mask = 0;
|
|
|
|
if ((page_size & (page_size - 1)) == 0) {
|
|
|
|
while ((page_size >> shift) > 1) {
|
|
|
|
++shift;
|
|
|
|
}
|
|
|
|
mask = (UINT32_MAX << shift);
|
|
|
|
}
|
|
|
|
dev->page_addr_mask = mask;
|
|
|
|
dev->page_addr_shift = shift;
|
|
|
|
DEBUG("mtd_spi_nor_init: page_addr_mask = 0x%08" PRIx32 ", page_addr_shift = %u\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
mask, (unsigned int)shift);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
mask = 0;
|
|
|
|
shift = 0;
|
|
|
|
uint32_t sector_size = mtd->page_size * mtd->pages_per_sector;
|
|
|
|
if ((sector_size & (sector_size - 1)) == 0) {
|
|
|
|
while ((sector_size >> shift) > 1) {
|
|
|
|
++shift;
|
|
|
|
}
|
|
|
|
mask = (UINT32_MAX << shift);
|
|
|
|
}
|
|
|
|
dev->sec_addr_mask = mask;
|
|
|
|
dev->sec_addr_shift = shift;
|
|
|
|
|
|
|
|
DEBUG("mtd_spi_nor_init: sec_addr_mask = 0x%08" PRIx32 ", sec_addr_shift = %u\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
mask, (unsigned int)shift);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mtd_spi_nor_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t size)
|
|
|
|
{
|
|
|
|
DEBUG("mtd_spi_nor_read: %p, %p, 0x%" PRIx32 ", 0x%" PRIx32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)mtd, dest, addr, size);
|
2017-06-29 22:46:16 +02:00
|
|
|
const mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd;
|
2016-07-15 10:40:56 +02:00
|
|
|
size_t chipsize = mtd->page_size * mtd->pages_per_sector * mtd->sector_count;
|
|
|
|
if (addr > chipsize) {
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
if ((addr + size) > chipsize) {
|
|
|
|
size = chipsize - addr;
|
|
|
|
}
|
|
|
|
if (size == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
be_uint32_t addr_be = byteorder_htonl(addr);
|
2018-03-05 15:50:44 +01:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_acquire(dev);
|
|
|
|
mtd_spi_cmd_addr_read(dev, dev->params->opcode->read, addr_be, dest, size);
|
|
|
|
mtd_spi_release(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
2020-04-17 19:41:43 +02:00
|
|
|
return 0;
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mtd_spi_nor_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t size)
|
|
|
|
{
|
|
|
|
uint32_t total_size = mtd->page_size * mtd->pages_per_sector * mtd->sector_count;
|
2018-03-05 15:54:08 +01:00
|
|
|
|
2016-07-15 10:40:56 +02:00
|
|
|
DEBUG("mtd_spi_nor_write: %p, %p, 0x%" PRIx32 ", 0x%" PRIx32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)mtd, src, addr, size);
|
2016-07-15 10:40:56 +02:00
|
|
|
if (size == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
2017-06-29 22:46:16 +02:00
|
|
|
const mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd;
|
2016-07-15 10:40:56 +02:00
|
|
|
if (size > mtd->page_size) {
|
|
|
|
DEBUG("mtd_spi_nor_write: ERR: page program >1 page (%" PRIu32 ")!\n", mtd->page_size);
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
if (dev->page_addr_mask &&
|
|
|
|
((addr & dev->page_addr_mask) != ((addr + size - 1) & dev->page_addr_mask))) {
|
|
|
|
DEBUG("mtd_spi_nor_write: ERR: page program spans page boundary!\n");
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
if (addr + size > total_size) {
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
be_uint32_t addr_be = byteorder_htonl(addr);
|
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_acquire(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
/* write enable */
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd(dev, dev->params->opcode->wren);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
/* Page program */
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd_addr_write(dev, dev->params->opcode->page_program, addr_be, src, size);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
/* waiting for the command to complete before returning */
|
2018-06-14 14:15:11 +02:00
|
|
|
wait_for_write_complete(dev, 0);
|
2018-03-05 15:50:44 +01:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_release(dev);
|
2020-04-17 19:41:43 +02:00
|
|
|
return 0;
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mtd_spi_nor_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t size)
|
|
|
|
{
|
|
|
|
DEBUG("mtd_spi_nor_erase: %p, 0x%" PRIx32 ", 0x%" PRIx32 "\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
(void *)mtd, addr, size);
|
2016-07-15 10:40:56 +02:00
|
|
|
mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd;
|
|
|
|
uint32_t sector_size = mtd->page_size * mtd->pages_per_sector;
|
|
|
|
uint32_t total_size = sector_size * mtd->sector_count;
|
|
|
|
|
|
|
|
if (dev->sec_addr_mask &&
|
|
|
|
((addr & ~dev->sec_addr_mask) != 0)) {
|
|
|
|
/* This is not a requirement in hardware, but it helps in catching
|
|
|
|
* software bugs (the erase-all-your-files kind) */
|
|
|
|
DEBUG("addr = %" PRIx32 " ~dev->erase_addr_mask = %" PRIx32 "", addr, ~dev->sec_addr_mask);
|
|
|
|
DEBUG("mtd_spi_nor_erase: ERR: erase addr not aligned on %" PRIu32 " byte boundary.\n",
|
2018-03-05 15:54:08 +01:00
|
|
|
sector_size);
|
2016-07-15 10:40:56 +02:00
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
if (addr + size > total_size) {
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
2018-01-22 14:04:35 +01:00
|
|
|
if (size % sector_size != 0) {
|
2016-07-15 10:40:56 +02:00
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
2018-01-22 14:04:35 +01:00
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_acquire(dev);
|
2018-01-22 14:04:35 +01:00
|
|
|
while (size) {
|
2018-06-14 14:15:11 +02:00
|
|
|
uint32_t us;
|
2018-01-22 14:04:35 +01:00
|
|
|
be_uint32_t addr_be = byteorder_htonl(addr);
|
|
|
|
/* write enable */
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd(dev, dev->params->opcode->wren);
|
2018-01-22 14:04:35 +01:00
|
|
|
|
|
|
|
if (size == total_size) {
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd(dev, dev->params->opcode->chip_erase);
|
2018-01-22 14:04:35 +01:00
|
|
|
size -= total_size;
|
2020-02-10 22:28:42 +01:00
|
|
|
us = dev->params->wait_chip_erase;
|
2018-01-22 14:04:35 +01:00
|
|
|
}
|
2020-02-10 20:39:49 +01:00
|
|
|
else if ((dev->params->flag & SPI_NOR_F_SECT_32K) && (size >= MTD_32K) &&
|
2018-01-22 14:04:35 +01:00
|
|
|
((addr & MTD_32K_ADDR_MASK) == 0)) {
|
|
|
|
/* 32 KiB blocks can be erased with block erase command */
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd_addr_write(dev, dev->params->opcode->block_erase_32k, addr_be, NULL, 0);
|
2018-01-22 14:04:35 +01:00
|
|
|
addr += MTD_32K;
|
|
|
|
size -= MTD_32K;
|
2020-02-10 22:28:42 +01:00
|
|
|
us = dev->params->wait_32k_erase;
|
2018-01-22 14:04:35 +01:00
|
|
|
}
|
2020-02-10 20:39:49 +01:00
|
|
|
else if ((dev->params->flag & SPI_NOR_F_SECT_4K) && (size >= MTD_4K) &&
|
2018-01-22 14:04:35 +01:00
|
|
|
((addr & MTD_4K_ADDR_MASK) == 0)) {
|
|
|
|
/* 4 KiB sectors can be erased with sector erase command */
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd_addr_write(dev, dev->params->opcode->sector_erase, addr_be, NULL, 0);
|
2018-01-22 14:04:35 +01:00
|
|
|
addr += MTD_4K;
|
|
|
|
size -= MTD_4K;
|
2020-02-10 22:28:42 +01:00
|
|
|
us = dev->params->wait_4k_erase;
|
2018-01-22 14:04:35 +01:00
|
|
|
}
|
|
|
|
else {
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd_addr_write(dev, dev->params->opcode->block_erase, addr_be, NULL, 0);
|
2016-07-15 10:40:56 +02:00
|
|
|
addr += sector_size;
|
2018-01-22 14:04:35 +01:00
|
|
|
size -= sector_size;
|
2020-02-10 22:28:42 +01:00
|
|
|
us = dev->params->wait_sector_erase;
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
2018-01-22 14:04:35 +01:00
|
|
|
|
|
|
|
/* waiting for the command to complete before continuing */
|
2018-06-14 14:15:11 +02:00
|
|
|
wait_for_write_complete(dev, us);
|
2016-07-15 10:40:56 +02:00
|
|
|
}
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_release(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power)
|
|
|
|
{
|
|
|
|
mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd;
|
|
|
|
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_acquire(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
switch (power) {
|
|
|
|
case MTD_POWER_UP:
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd(dev, dev->params->opcode->wake);
|
2020-03-05 10:09:13 +01:00
|
|
|
#if defined(MODULE_XTIMER)
|
|
|
|
/* No sense in trying multiple times if no xtimer to wait between
|
|
|
|
reads */
|
|
|
|
uint8_t retries = 0;
|
|
|
|
int res = 0;
|
|
|
|
do {
|
|
|
|
xtimer_usleep(dev->params->wait_chip_wake_up);
|
|
|
|
res = mtd_spi_read_jedec_id(dev, &dev->jedec_id);
|
|
|
|
retries++;
|
|
|
|
} while (res < 0 || retries < MTD_POWER_UP_WAIT_FOR_ID);
|
|
|
|
if (res < 0) {
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
#endif
|
2016-07-15 10:40:56 +02:00
|
|
|
break;
|
|
|
|
case MTD_POWER_DOWN:
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_cmd(dev, dev->params->opcode->sleep);
|
2016-07-15 10:40:56 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-02-10 20:39:49 +01:00
|
|
|
mtd_spi_release(dev);
|
2016-07-15 10:40:56 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|