You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
808 lines
21 KiB
808 lines
21 KiB
/* |
|
* Copyright (c) 2020 Laczen |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/* |
|
* This driver emulates an EEPROM device in flash. |
|
* |
|
* The emulation represents the EEPROM in flash as a region that is a direct |
|
* map of the EEPROM data (EEPROM data) followed by a region where changes to |
|
* the EEPROM data (EEPROM changes) are stored. The combination of EEPROM data |
|
* and EEPROM changes form a EEPROM page (see drawing below). Changes to EEPROM |
|
* data are written as address-data combinations. The size of such a combination |
|
* is determined by the flash write block size and the size of the EEPROM |
|
* (required address space), with a minimum of 4 byte. |
|
* |
|
* When there is no more space to store changes a new EEPROM page is taken into |
|
* use. This copies the existing data to the EEPROM data area of the new page. |
|
* During this copying the write that is performed is applied at the same time. |
|
* The old page is then invalidated. |
|
* |
|
* The EEPROM page needs to be a multiple of a flash page size. Multiple EEPROM |
|
* pages are also supported and increases the number of writes that can be |
|
* performed. |
|
* |
|
* The representation of the EEPROM on flash is shown in the next graph. |
|
* |
|
* |-----------------------------------------------------------------------| |
|
* ||----------------------| |----------------------| |-------------------|| |
|
* || EEPROM data | | | |-Flash page--------|| |
|
* || | | | | |
|
* || size = EEPROM size | | | | |
|
* ||----------------------| |----------------------| ... | |
|
* || EEPROM changes: | | | | |
|
* || (address, new data) | | | | |
|
* || | | | | |
|
* || XX| | XX| | |
|
* ||--EEPROM page 0-------| |--EEPROM page 1-------| | |
|
* |------------------------------------------------------------Partition--| |
|
* XX: page validity marker: all 0x00: page invalid |
|
* |
|
* Internally the address of an EEPROM byte is represented by a uint32_t (this |
|
* should be sufficient in all cases). In case the EEPROM size is smaller than |
|
* 64kB only a uint16_t is used to store changes. In this case the change stored |
|
* for a 4 byte flash write block size are a combination of 2 byte address and |
|
* 2 byte data. |
|
* |
|
* The EEPROM size, pagesize and the flash partition used for the EEPROM are |
|
* defined in the dts. The flash partition should allow at least two EEPROM |
|
* pages. |
|
* |
|
*/ |
|
|
|
#define DT_DRV_COMPAT zephyr_emu_eeprom |
|
|
|
#define EEPROM_EMU_VERSION 0 |
|
#define EEPROM_EMU_MAGIC 0x45454d55 /* EEMU in hex */ |
|
|
|
#include <zephyr/drivers/eeprom.h> |
|
#include <zephyr/drivers/flash.h> |
|
#include <zephyr/kernel.h> |
|
#define LOG_LEVEL CONFIG_EEPROM_LOG_LEVEL |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(eeprom_emulator); |
|
|
|
struct eeprom_emu_config { |
|
/* EEPROM size */ |
|
size_t size; |
|
/* EEPROM is read-only */ |
|
bool readonly; |
|
/* Page size used to emulate the EEPROM, contains one area of EEPROM |
|
* size and a area to store changes. |
|
*/ |
|
size_t page_size; |
|
/* Offset of the flash partition used to emulate the EEPROM */ |
|
off_t flash_offset; |
|
/* Size of the flash partition to emulate the EEPROM */ |
|
size_t flash_size; |
|
/* Delay the erase of EEPROM pages until the complete partition is used. |
|
*/ |
|
bool partitionerase; |
|
/* Size of a change block */ |
|
uint8_t flash_cbs; |
|
uint8_t *rambuf; |
|
/* Device of the flash partition used to emulate the EEPROM */ |
|
const struct device *flash_dev; |
|
}; |
|
|
|
struct eeprom_emu_data { |
|
/* Offset in current (EEPROM) page where next change is written */ |
|
off_t write_offset; |
|
/* Offset of the current (EEPROM) page */ |
|
off_t page_offset; |
|
struct k_mutex lock; |
|
}; |
|
|
|
/* read/write context */ |
|
struct eeprom_emu_ctx { |
|
const void *data; /* pointer to data */ |
|
const size_t len; /* data length */ |
|
const off_t address; /* eeprom address */ |
|
size_t rlen; /* data remaining (unprocessed) length */ |
|
}; |
|
|
|
/* |
|
* basic flash read, only used with offset aligned to flash write block size |
|
*/ |
|
static inline int eeprom_emu_flash_read(const struct device *dev, off_t offset, |
|
uint8_t *blk, size_t len) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
|
|
return flash_read(dev_config->flash_dev, dev_config->flash_offset + |
|
offset, blk, len); |
|
} |
|
|
|
/* |
|
* basic flash write, only used with offset aligned to flash write block size |
|
*/ |
|
static inline int eeprom_emu_flash_write(const struct device *dev, off_t offset, |
|
const uint8_t *blk, size_t len) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
int rc; |
|
|
|
rc = flash_write(dev_config->flash_dev, dev_config->flash_offset + |
|
offset, blk, len); |
|
return rc; |
|
} |
|
|
|
/* |
|
* basic flash erase, only used with offset aligned to flash page and len a |
|
* multiple of the flash page size |
|
*/ |
|
static inline int eeprom_emu_flash_erase(const struct device *dev, off_t offset, |
|
size_t len) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
int rc; |
|
|
|
rc = flash_erase(dev_config->flash_dev, dev_config->flash_offset + |
|
offset, len); |
|
return rc; |
|
} |
|
|
|
/* |
|
* eeprom_emu_page_invalidate: invalidate a page by writing all zeros at the end |
|
*/ |
|
static int eeprom_emu_page_invalidate(const struct device *dev, off_t offset) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
uint8_t buf[dev_config->flash_cbs]; |
|
|
|
LOG_DBG("Invalidating page at [0x%tx]", (ptrdiff_t)offset); |
|
|
|
memset(buf, 0x00, sizeof(buf)); |
|
|
|
offset += (dev_config->page_size - sizeof(buf)); |
|
return eeprom_emu_flash_write(dev, offset, buf, sizeof(buf)); |
|
} |
|
|
|
/* |
|
* eeprom_emu_get_address: read the address from a change block |
|
*/ |
|
static uint32_t eeprom_emu_get_address(const struct device *dev, |
|
const uint8_t *blk) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
uint32_t address = 0U; |
|
|
|
blk += dev_config->flash_cbs / 2; |
|
for (int i = 0; i < sizeof(address); i++) { |
|
if (2 * i == dev_config->flash_cbs) { |
|
break; |
|
} |
|
|
|
address += ((uint32_t)(*blk) << (8 * i)); |
|
blk++; |
|
} |
|
|
|
return address; |
|
} |
|
|
|
/* |
|
* eeprom_emu_set_change: create change blocks from data in blk and address |
|
*/ |
|
static void eeprom_emu_set_change(const struct device *dev, |
|
const uint32_t address, const uint8_t *data, |
|
uint8_t *blk) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
|
|
for (int i = 0; i < (dev_config->flash_cbs / 2); i++) { |
|
(*blk++) = (*data++); |
|
} |
|
|
|
for (int i = 0; i < (dev_config->flash_cbs / 2); i++) { |
|
if (i < sizeof(address)) { |
|
(*blk++) = (uint8_t)(((address >> (8 * i)) & 0xff)); |
|
} else { |
|
(*blk++) = 0xff; |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/* |
|
* eeprom_emu_is_word_used: check if word is not empty |
|
*/ |
|
static int eeprom_emu_is_word_used(const struct device *dev, const uint8_t *blk) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
|
|
for (int i = 0; i < dev_config->flash_cbs; i++) { |
|
if ((*blk++) != 0xff) { |
|
return 1; |
|
} |
|
|
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* eeprom_emu_word_read: read basic word (cbs byte of data) item from |
|
* address directly from flash. |
|
*/ |
|
static int eeprom_emu_word_read(const struct device *dev, off_t address, |
|
uint8_t *data) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
const struct eeprom_emu_data *dev_data = dev->data; |
|
uint8_t buf[dev_config->flash_cbs]; |
|
off_t direct_address; |
|
int rc; |
|
|
|
direct_address = dev_data->page_offset + address; |
|
|
|
/* Direct flash read */ |
|
rc = eeprom_emu_flash_read(dev, direct_address, data, sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
/* Process changes written to flash */ |
|
off_t offset, ch_address; |
|
bool mc1 = false, mc2 = false; |
|
|
|
offset = dev_data->write_offset; |
|
while (((!mc1) || (!mc2)) && (offset > dev_config->size)) { |
|
offset -= sizeof(buf); |
|
/* read the change */ |
|
rc = eeprom_emu_flash_read(dev, dev_data->page_offset + offset, |
|
buf, sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
/* get the address from a change block */ |
|
ch_address = eeprom_emu_get_address(dev, buf); |
|
if ((!mc1) && (ch_address == address)) { |
|
memcpy(data, buf, sizeof(buf)/2); |
|
mc1 = true; |
|
} |
|
|
|
if ((!mc2) && (ch_address == (address + sizeof(buf)/2))) { |
|
memcpy(data + sizeof(buf)/2, buf, sizeof(buf)/2); |
|
mc2 = true; |
|
} |
|
|
|
} |
|
|
|
return rc; |
|
} |
|
|
|
/* Update data specified in ctx from flash */ |
|
static int eeprom_emu_flash_get(const struct device *dev, |
|
struct eeprom_emu_ctx *ctx) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
off_t address = ctx->address + ctx->len - ctx->rlen; |
|
uint8_t *data8 = (uint8_t *)(ctx->data); |
|
uint8_t buf[dev_config->flash_cbs]; |
|
const off_t addr_jmp = address & (sizeof(buf) - 1); |
|
size_t len; |
|
int rc; |
|
|
|
data8 += (ctx->len - ctx->rlen); |
|
len = MIN((sizeof(buf) - addr_jmp), ctx->rlen); |
|
rc = eeprom_emu_word_read(dev, address - addr_jmp, buf); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
memcpy(data8, buf + addr_jmp, len); |
|
ctx->rlen -= len; |
|
|
|
return rc; |
|
} |
|
|
|
/* |
|
* eeprom_emu_compactor: start a new EEPROM page and copy existing data to the |
|
* new page. During copy update the data with present write data. Invalidate |
|
* the old page. |
|
*/ |
|
static int eeprom_emu_compactor(const struct device *dev, |
|
struct eeprom_emu_ctx *ctx) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
struct eeprom_emu_data *dev_data = dev->data; |
|
off_t next_page_offset; |
|
int rc = 0; |
|
|
|
LOG_DBG("Compactor called for page at [0x%tx]", |
|
(ptrdiff_t)dev_data->page_offset); |
|
|
|
next_page_offset = dev_data->page_offset + dev_config->page_size; |
|
if (next_page_offset >= dev_config->flash_size) { |
|
next_page_offset = 0; |
|
} |
|
|
|
if (!dev_config->partitionerase) { |
|
/* erase the new page */ |
|
rc = eeprom_emu_flash_erase(dev, next_page_offset, |
|
dev_config->page_size); |
|
} else if (next_page_offset == 0) { |
|
/* erase the entire partition */ |
|
rc = eeprom_emu_flash_erase(dev, next_page_offset, |
|
dev_config->flash_size); |
|
} else { |
|
rc = 0; |
|
} |
|
|
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if (dev_config->rambuf && (ctx != NULL)) { |
|
rc = eeprom_emu_flash_write(dev, next_page_offset, |
|
dev_config->rambuf, |
|
dev_config->size); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
ctx->rlen = 0; |
|
} else { |
|
off_t rd_offset = 0; |
|
uint8_t buf[dev_config->flash_cbs]; |
|
|
|
/* reset the context if available */ |
|
if (ctx != NULL) { |
|
ctx->rlen = ctx->len; |
|
} |
|
|
|
/* copy existing data */ |
|
while (rd_offset < dev_config->size) { |
|
|
|
rc = eeprom_emu_word_read(dev, rd_offset, buf); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if ((ctx != NULL) && (ctx->len) && |
|
(rd_offset > (ctx->address - sizeof(buf)))) { |
|
/* overwrite buf data with context data */ |
|
uint8_t *data8 = (uint8_t *)(ctx->data); |
|
off_t address, addr_jmp; |
|
size_t len; |
|
|
|
address = ctx->address + ctx->len - ctx->rlen; |
|
addr_jmp = address & (sizeof(buf) - 1); |
|
len = MIN((sizeof(buf) - addr_jmp), ctx->rlen); |
|
data8 += (ctx->len - ctx->rlen); |
|
memcpy(buf + addr_jmp, data8, len); |
|
ctx->rlen -= len; |
|
} |
|
|
|
if (eeprom_emu_is_word_used(dev, buf)) { |
|
rc = eeprom_emu_flash_write(dev, |
|
next_page_offset + |
|
rd_offset, buf, |
|
sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
} |
|
|
|
rd_offset += sizeof(buf); |
|
} |
|
|
|
} |
|
|
|
if ((dev_config->partitionerase) && (next_page_offset == 0)) { |
|
/* no need to invalidate previous page as it has been deleted */ |
|
rc = 0; |
|
} else { |
|
/* invalidate the old page */ |
|
rc = eeprom_emu_page_invalidate(dev, dev_data->page_offset); |
|
} |
|
|
|
if (!rc) { |
|
dev_data->write_offset = dev_config->size; |
|
dev_data->page_offset = next_page_offset; |
|
} |
|
return rc; |
|
} |
|
|
|
/* |
|
* eeprom_emu_word_write: write basic word (cbs bytes of data) item to address, |
|
*/ |
|
static int eeprom_emu_word_write(const struct device *dev, off_t address, |
|
const uint8_t *data, |
|
struct eeprom_emu_ctx *ctx) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
struct eeprom_emu_data *dev_data = dev->data; |
|
uint8_t buf[dev_config->flash_cbs], tmp[dev_config->flash_cbs]; |
|
off_t direct_address, wraddr; |
|
int rc; |
|
|
|
direct_address = dev_data->page_offset + address; |
|
|
|
rc = eeprom_emu_flash_read(dev, direct_address, buf, sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if (!eeprom_emu_is_word_used(dev, buf)) { |
|
if (eeprom_emu_is_word_used(dev, data)) { |
|
rc = eeprom_emu_flash_write(dev, direct_address, data, |
|
sizeof(buf)); |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
rc = eeprom_emu_word_read(dev, address, buf); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if (!memcmp(buf, data, sizeof(buf))) { |
|
/* data has not changed */ |
|
return rc; |
|
} |
|
|
|
wraddr = address; |
|
/* store change */ |
|
for (uint8_t i = 0; i < 2; i++) { |
|
if (memcmp(&buf[i*sizeof(buf)/2], data, sizeof(buf)/2)) { |
|
eeprom_emu_set_change(dev, wraddr, data, tmp); |
|
rc = eeprom_emu_flash_write(dev, dev_data->page_offset + |
|
dev_data->write_offset, tmp, |
|
sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
dev_data->write_offset += sizeof(buf); |
|
if ((dev_data->write_offset + sizeof(buf)) >= |
|
dev_config->page_size) { |
|
rc = eeprom_emu_compactor(dev, ctx); |
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
data += sizeof(buf)/2; |
|
wraddr += sizeof(buf)/2; |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
/* Update flash with data specified in ctx */ |
|
static int eeprom_emu_flash_set(const struct device *dev, |
|
struct eeprom_emu_ctx *ctx) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
off_t address = ctx->address + ctx->len - ctx->rlen; |
|
uint8_t *data8 = (uint8_t *)(ctx->data); |
|
uint8_t buf[dev_config->flash_cbs]; |
|
const off_t addr_jmp = address & (sizeof(buf) - 1); |
|
size_t len; |
|
int rc; |
|
|
|
data8 += (ctx->len - ctx->rlen); |
|
len = MIN((sizeof(buf) - addr_jmp), ctx->rlen); |
|
rc = eeprom_emu_word_read(dev, address - addr_jmp, buf); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
memcpy(buf + addr_jmp, data8, len); |
|
rc = eeprom_emu_word_write(dev, address - addr_jmp, buf, ctx); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if (ctx->rlen) { |
|
ctx->rlen -= len; |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
static int eeprom_emu_range_is_valid(const struct device *dev, off_t address, |
|
size_t len) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
|
|
if ((address + len) <= dev_config->size) { |
|
return 1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int eeprom_emu_read(const struct device *dev, off_t address, void *data, |
|
size_t len) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
struct eeprom_emu_data *dev_data = dev->data; |
|
struct eeprom_emu_ctx ctx = { |
|
.data = data, |
|
.len = len, |
|
.address = address, |
|
.rlen = len, |
|
}; |
|
int rc = 0; |
|
|
|
/* Nothing to do */ |
|
if (!len) { |
|
return 0; |
|
} |
|
|
|
/* Error checking */ |
|
if ((!data) || (!eeprom_emu_range_is_valid(dev, address, len))) { |
|
return -EINVAL; |
|
} |
|
|
|
if (!device_is_ready(dev_config->flash_dev)) { |
|
LOG_ERR("flash device is not ready"); |
|
return -EIO; |
|
} |
|
|
|
/* Handle normal case */ |
|
LOG_DBG("EEPROM read at [0x%tx] length[%zu]", (ptrdiff_t)address, len); |
|
k_mutex_lock(&dev_data->lock, K_FOREVER); |
|
|
|
/* read from rambuffer if possible */ |
|
if (dev_config->rambuf) { |
|
memcpy(data, dev_config->rambuf + address, len); |
|
} else { |
|
/* read from flash if no rambuffer */ |
|
while (ctx.rlen) { |
|
rc = eeprom_emu_flash_get(dev, &ctx); |
|
if (rc) { |
|
break; |
|
} |
|
|
|
} |
|
} |
|
|
|
k_mutex_unlock(&dev_data->lock); |
|
|
|
return rc; |
|
} |
|
|
|
static int eeprom_emu_write(const struct device *dev, off_t address, |
|
const void *data, size_t len) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
struct eeprom_emu_data *dev_data = dev->data; |
|
struct eeprom_emu_ctx ctx = { |
|
.data = data, |
|
.len = len, |
|
.address = address, |
|
.rlen = len, |
|
}; |
|
int rc = 0; |
|
|
|
/* Nothing to do */ |
|
if (!len) { |
|
return 0; |
|
} |
|
|
|
/* Error checking */ |
|
if ((!data) || (!eeprom_emu_range_is_valid(dev, address, len))) { |
|
return -EINVAL; |
|
} |
|
|
|
if (dev_config->readonly) { |
|
LOG_ERR("attempt to write to read-only device"); |
|
return -EACCES; |
|
} |
|
|
|
if (!device_is_ready(dev_config->flash_dev)) { |
|
LOG_ERR("flash device is not ready"); |
|
return -EIO; |
|
} |
|
|
|
/* Handle normal case */ |
|
LOG_DBG("EEPROM write at [0x%tx] length[%zu]", (ptrdiff_t)address, len); |
|
|
|
k_mutex_lock(&dev_data->lock, K_FOREVER); |
|
|
|
/* first update the rambuffer */ |
|
if (dev_config->rambuf) { |
|
memcpy(dev_config->rambuf + address, data, len); |
|
} |
|
|
|
/* second update the flash */ |
|
while (ctx.rlen) { |
|
rc = eeprom_emu_flash_set(dev, &ctx); |
|
if (rc) { |
|
break; |
|
} |
|
|
|
} |
|
|
|
k_mutex_unlock(&dev_data->lock); |
|
|
|
return rc; |
|
} |
|
|
|
static size_t eeprom_emu_size(const struct device *dev) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
|
|
return dev_config->size; |
|
} |
|
|
|
static int eeprom_emu_init(const struct device *dev) |
|
{ |
|
const struct eeprom_emu_config *dev_config = dev->config; |
|
struct eeprom_emu_data *dev_data = dev->data; |
|
off_t offset; |
|
uint8_t buf[dev_config->flash_cbs]; |
|
int rc = 0; |
|
|
|
k_mutex_init(&dev_data->lock); |
|
if (!device_is_ready(dev_config->flash_dev)) { |
|
__ASSERT(0, "Could not get flash device binding"); |
|
return -ENODEV; |
|
} |
|
|
|
/* Find the page offset */ |
|
dev_data->page_offset = 0U; |
|
dev_data->write_offset = dev_config->page_size - sizeof(buf); |
|
while (dev_data->page_offset < dev_config->flash_size) { |
|
offset = dev_data->page_offset + dev_data->write_offset; |
|
rc = eeprom_emu_flash_read(dev, offset, buf, sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if (!eeprom_emu_is_word_used(dev, buf)) { |
|
break; |
|
} |
|
|
|
dev_data->page_offset += dev_config->page_size; |
|
} |
|
|
|
if (dev_data->page_offset == dev_config->flash_size) { |
|
__ASSERT(0, "All pages are invalid, is this a EEPROM area?"); |
|
return -EINVAL; |
|
} |
|
|
|
dev_data->write_offset = dev_config->size; |
|
|
|
/* Update the write offset */ |
|
while ((dev_data->write_offset + sizeof(buf)) < dev_config->page_size) { |
|
offset = dev_data->page_offset + dev_data->write_offset; |
|
rc = eeprom_emu_flash_read(dev, offset, buf, sizeof(buf)); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
if (!eeprom_emu_is_word_used(dev, buf)) { |
|
break; |
|
} |
|
|
|
dev_data->write_offset += sizeof(buf); |
|
} |
|
|
|
/* dev_data->write_offset reaches last possible location, compaction |
|
* might have been interrupted: call eeprom_emu_compactor again, but |
|
* only in case we are using a write-enabled eeprom |
|
*/ |
|
if ((!dev_config->readonly) && |
|
((dev_data->write_offset + sizeof(buf)) >= dev_config->page_size)) { |
|
rc = eeprom_emu_compactor(dev, NULL); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
} |
|
|
|
/* Fill the ram buffer if enabled */ |
|
if (dev_config->rambuf) { |
|
offset = 0; |
|
while (offset < dev_config->size) { |
|
rc = eeprom_emu_word_read(dev, offset, buf); |
|
if (rc) { |
|
return rc; |
|
} |
|
|
|
memcpy(dev_config->rambuf + offset, buf, sizeof(buf)); |
|
offset += sizeof(buf); |
|
} |
|
|
|
} |
|
|
|
return rc; |
|
} |
|
|
|
static DEVICE_API(eeprom, eeprom_emu_api) = { |
|
.read = eeprom_emu_read, |
|
.write = eeprom_emu_write, |
|
.size = eeprom_emu_size, |
|
}; |
|
|
|
#define EEPROM_PARTITION(n) DT_INST_PHANDLE_BY_IDX(n, partition, 0) |
|
|
|
#define PART_WBS(part) \ |
|
DT_PROP(COND_CODE_1(DT_NODE_HAS_COMPAT(DT_GPARENT(part), soc_nv_flash),\ |
|
(DT_GPARENT(part)), (DT_PARENT(part))), write_block_size) |
|
|
|
#define PART_CBS(part, size) (PART_WBS(part) < 4) ? \ |
|
((size > KB(64)) ? 8 : 4) : PART_WBS(part) |
|
|
|
#define PART_DEV_ID(part) \ |
|
COND_CODE_1(DT_NODE_HAS_COMPAT(DT_GPARENT(part), soc_nv_flash), \ |
|
(DT_PARENT(DT_GPARENT(part))), (DT_GPARENT(part))) |
|
|
|
#define PART_DEV(part) \ |
|
DEVICE_DT_GET(PART_DEV_ID(part)) |
|
|
|
#define RECALC_SIZE(size, cbs) \ |
|
(size % cbs) ? ((size + cbs - 1) & ~(cbs - 1)) : size |
|
|
|
#define ASSERT_SIZE_PAGESIZE_VALID(size, pagesize, readonly) \ |
|
BUILD_ASSERT(readonly ? (size <= pagesize) : (4*size <= 3*pagesize), \ |
|
"EEPROM size to big for pagesize") |
|
|
|
#define ASSERT_PAGESIZE_PARTSIZE_VALID(pagesize, partsize) \ |
|
BUILD_ASSERT(partsize % pagesize == 0U, \ |
|
"Partition size not a multiple of pagesize") |
|
|
|
#define ASSERT_PAGESIZE_SIZE(pagesize, partsize, onepage) \ |
|
BUILD_ASSERT(onepage ? (partsize >= pagesize) : (partsize > pagesize),\ |
|
"Partition size to small") |
|
|
|
#define EEPROM_EMU_READ_ONLY(n) \ |
|
DT_INST_PROP(n, read_only) || \ |
|
DT_PROP(EEPROM_PARTITION(n), read_only) |
|
|
|
#define EEPROM_EMU_ONEPAGE(n) \ |
|
EEPROM_EMU_READ_ONLY(n) || DT_INST_PROP(n, partition_erase) |
|
|
|
#define EEPROM_EMU_ENABLE_RAMBUF(n) \ |
|
COND_CODE_1(DT_INST_PROP(n, rambuf), (1), \ |
|
(COND_CODE_1(DT_INST_PROP(n, partition_erase), (1), (0)))) |
|
|
|
#define EEPROM_EMU_RAMBUF(n) \ |
|
COND_CODE_0(EEPROM_EMU_ENABLE_RAMBUF(n), (), \ |
|
(static uint8_t eeprom_emu_##n##_rambuf[DT_INST_PROP(n, size)];)) |
|
|
|
#define EEPROM_EMU_RAMBUF_LINK(n) \ |
|
COND_CODE_0(EEPROM_EMU_ENABLE_RAMBUF(n), (NULL), \ |
|
(eeprom_emu_##n##_rambuf)) |
|
|
|
#define EEPROM_EMU_INIT(n) \ |
|
ASSERT_SIZE_PAGESIZE_VALID(DT_INST_PROP(n, size), \ |
|
DT_INST_PROP(n, pagesize), EEPROM_EMU_ONEPAGE(n)); \ |
|
ASSERT_PAGESIZE_PARTSIZE_VALID(DT_INST_PROP(n, pagesize), \ |
|
DT_REG_SIZE(EEPROM_PARTITION(n))); \ |
|
ASSERT_PAGESIZE_SIZE(DT_INST_PROP(n, pagesize), \ |
|
DT_REG_SIZE(EEPROM_PARTITION(n)), EEPROM_EMU_ONEPAGE(n)); \ |
|
EEPROM_EMU_RAMBUF(n) \ |
|
static const struct eeprom_emu_config eeprom_emu_##n##_config = { \ |
|
.size = RECALC_SIZE( \ |
|
DT_INST_PROP(n, size), \ |
|
(PART_CBS(EEPROM_PARTITION(n), DT_INST_PROP(n, size))) \ |
|
), \ |
|
.readonly = EEPROM_EMU_READ_ONLY(n), \ |
|
.page_size = DT_INST_PROP(n, pagesize), \ |
|
.flash_offset = DT_REG_ADDR(EEPROM_PARTITION(n)), \ |
|
.flash_size = DT_REG_SIZE(EEPROM_PARTITION(n)), \ |
|
.partitionerase = DT_INST_PROP(n, partition_erase), \ |
|
.flash_cbs = PART_CBS(EEPROM_PARTITION(n), \ |
|
DT_INST_PROP(n, size)), \ |
|
.flash_dev = PART_DEV(EEPROM_PARTITION(n)),\ |
|
.rambuf = EEPROM_EMU_RAMBUF_LINK(n), \ |
|
}; \ |
|
static struct eeprom_emu_data eeprom_emu_##n##_data; \ |
|
DEVICE_DT_INST_DEFINE(n, &eeprom_emu_init, \ |
|
NULL, &eeprom_emu_##n##_data, \ |
|
&eeprom_emu_##n##_config, POST_KERNEL, \ |
|
CONFIG_EEPROM_INIT_PRIORITY, &eeprom_emu_api); \ |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(EEPROM_EMU_INIT)
|
|
|