flash: add a driver for the internal flash on the SAM0 series.
The SAM0 has a 64 byte page (the programing unit) with 4 pages to a row (the erase unit). This driver implements a read/modify/write to emulate the byte level writes used by NFFS. Signed-off-by: Michael Hope <mlhx@google.com>
This commit is contained in:
parent
5aa0d88b58
commit
cd92dd139b
5 changed files with 413 additions and 0 deletions
|
@ -4,6 +4,7 @@ zephyr_sources_ifdef(CONFIG_SOC_FLASH_NRF5 soc_flash_nrf5.c)
|
||||||
zephyr_sources_ifdef(CONFIG_SOC_FLASH_MCUX soc_flash_mcux.c)
|
zephyr_sources_ifdef(CONFIG_SOC_FLASH_MCUX soc_flash_mcux.c)
|
||||||
zephyr_sources_ifdef(CONFIG_FLASH_PAGE_LAYOUT flash_page_layout.c)
|
zephyr_sources_ifdef(CONFIG_FLASH_PAGE_LAYOUT flash_page_layout.c)
|
||||||
zephyr_sources_ifdef(CONFIG_USERSPACE flash_handlers.c)
|
zephyr_sources_ifdef(CONFIG_USERSPACE flash_handlers.c)
|
||||||
|
zephyr_sources_ifdef(CONFIG_SOC_FLASH_SAM0 flash_sam0.c)
|
||||||
|
|
||||||
if(CONFIG_SOC_SERIES_STM32F0X)
|
if(CONFIG_SOC_SERIES_STM32F0X)
|
||||||
zephyr_sources_ifdef(CONFIG_SOC_FLASH_STM32
|
zephyr_sources_ifdef(CONFIG_SOC_FLASH_STM32
|
||||||
|
|
|
@ -164,3 +164,5 @@ config SOC_FLASH_MCUX_DEV_NAME
|
||||||
Specify the device name for the flash driver.
|
Specify the device name for the flash driver.
|
||||||
|
|
||||||
source "drivers/flash/Kconfig.stm32"
|
source "drivers/flash/Kconfig.stm32"
|
||||||
|
|
||||||
|
source "drivers/flash/Kconfig.sam0"
|
||||||
|
|
32
drivers/flash/Kconfig.sam0
Normal file
32
drivers/flash/Kconfig.sam0
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Kconfig - Atmel SAM0 flash driver config
|
||||||
|
#
|
||||||
|
# Copyright (c) 2018 Google LLC.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
if FLASH && SOC_FAMILY_SAM0
|
||||||
|
|
||||||
|
menuconfig SOC_FLASH_SAM0
|
||||||
|
bool
|
||||||
|
prompt "Atmel SAM0 flash driver"
|
||||||
|
default n
|
||||||
|
select FLASH_HAS_PAGE_LAYOUT
|
||||||
|
help
|
||||||
|
Enable the Atmel SAM0 series internal flash driver.
|
||||||
|
|
||||||
|
config SOC_FLASH_SAM0_DEV_NAME
|
||||||
|
string "Flash device name"
|
||||||
|
depends on SOC_FLASH_SAM0
|
||||||
|
default "FLASH_0"
|
||||||
|
help
|
||||||
|
Specify the device name for the flash driver.
|
||||||
|
|
||||||
|
config SOC_FLASH_SAM0_EMULATE_BYTE_PAGES
|
||||||
|
bool
|
||||||
|
prompt "Emulate byte-sized pages"
|
||||||
|
depends on SOC_FLASH_SAM0
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Emulate a device with byte-sized pages by doing a
|
||||||
|
read/modify/erase/write. Needed for NFFS.
|
||||||
|
|
||||||
|
endif
|
376
drivers/flash/flash_sam0.c
Normal file
376
drivers/flash/flash_sam0.c
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Google LLC.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SYS_LOG_DOMAIN "flash"
|
||||||
|
#include <logging/sys_log.h>
|
||||||
|
|
||||||
|
#include <device.h>
|
||||||
|
#include <flash.h>
|
||||||
|
#include <init.h>
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <soc.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zephyr and the SAM0 series use different and conflicting names for
|
||||||
|
* the erasable units and programmable units:
|
||||||
|
*
|
||||||
|
* The erase unit is a row, which is a 'page' in Zephyr terms.
|
||||||
|
* The program unit is a page, which is a 'write_block' in Zephyr.
|
||||||
|
*
|
||||||
|
* This file uses the SAM0 names internally and the Zephyr names in
|
||||||
|
* any error messages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of lock regions. The number is fixed and the region size
|
||||||
|
* grows with the flash size.
|
||||||
|
*/
|
||||||
|
#define LOCK_REGIONS 16
|
||||||
|
#define LOCK_REGION_SIZE (FLASH_SIZE / LOCK_REGIONS)
|
||||||
|
|
||||||
|
#define PAGES_PER_ROW 4
|
||||||
|
#define ROW_SIZE (FLASH_PAGE_SIZE * PAGES_PER_ROW)
|
||||||
|
|
||||||
|
#define FLASH_MEM(_a) ((u32_t *)((u8_t *)((_a) + CONFIG_FLASH_BASE_ADDRESS)))
|
||||||
|
|
||||||
|
struct flash_sam0_data {
|
||||||
|
#if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES
|
||||||
|
u8_t buf[ROW_SIZE];
|
||||||
|
off_t offset;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct k_sem sem;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flash_pages_layout flash_sam0_pages_layout = {
|
||||||
|
.pages_count = CONFIG_FLASH_SIZE * 1024 / ROW_SIZE,
|
||||||
|
.pages_size = ROW_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void flash_sam0_sem_take(struct device *dev)
|
||||||
|
{
|
||||||
|
struct flash_sam0_data *ctx = dev->driver_data;
|
||||||
|
|
||||||
|
k_sem_take(&ctx->sem, K_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void flash_sam0_sem_give(struct device *dev)
|
||||||
|
{
|
||||||
|
struct flash_sam0_data *ctx = dev->driver_data;
|
||||||
|
|
||||||
|
k_sem_give(&ctx->sem);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_valid_range(off_t offset, size_t len)
|
||||||
|
{
|
||||||
|
if (offset < 0) {
|
||||||
|
SYS_LOG_WRN("%x: before start of flash", offset);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if ((offset + len) > CONFIG_FLASH_SIZE * 1024) {
|
||||||
|
SYS_LOG_WRN("%x: ends past the end of flash", offset);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flash_sam0_wait_ready(void)
|
||||||
|
{
|
||||||
|
while (NVMCTRL->INTFLAG.bit.READY == 0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_check_status(off_t offset)
|
||||||
|
{
|
||||||
|
NVMCTRL_STATUS_Type status;
|
||||||
|
|
||||||
|
flash_sam0_wait_ready();
|
||||||
|
|
||||||
|
status = NVMCTRL->STATUS;
|
||||||
|
/* Clear any flags */
|
||||||
|
NVMCTRL->STATUS = status;
|
||||||
|
|
||||||
|
if (status.bit.PROGE) {
|
||||||
|
SYS_LOG_ERR("programming error at 0x%x", offset);
|
||||||
|
return -EIO;
|
||||||
|
} else if (status.bit.LOCKE) {
|
||||||
|
SYS_LOG_ERR("lock error at 0x%x", offset);
|
||||||
|
return -EROFS;
|
||||||
|
} else if (status.bit.NVME) {
|
||||||
|
SYS_LOG_ERR("NVM error at 0x%x", offset);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_write_page(struct device *dev, off_t offset,
|
||||||
|
const void *data)
|
||||||
|
{
|
||||||
|
const u32_t *src = data;
|
||||||
|
const u32_t *end = src + FLASH_PAGE_SIZE / sizeof(*src);
|
||||||
|
u32_t *dst = FLASH_MEM(offset);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY;
|
||||||
|
flash_sam0_wait_ready();
|
||||||
|
|
||||||
|
/* Ensure writes happen 32 bits at a time. */
|
||||||
|
for (; src != end; src++, dst++) {
|
||||||
|
*dst = *src;
|
||||||
|
}
|
||||||
|
|
||||||
|
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_WP | NVMCTRL_CTRLA_CMDEX_KEY;
|
||||||
|
|
||||||
|
err = flash_sam0_check_status(offset);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(data, FLASH_MEM(offset), FLASH_PAGE_SIZE) != 0) {
|
||||||
|
SYS_LOG_ERR("verify error at offset 0x%x", offset);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_erase_row(struct device *dev, off_t offset)
|
||||||
|
{
|
||||||
|
*FLASH_MEM(offset) = 0;
|
||||||
|
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_ER | NVMCTRL_CTRLA_CMDEX_KEY;
|
||||||
|
|
||||||
|
return flash_sam0_check_status(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES
|
||||||
|
|
||||||
|
static int flash_sam0_commit(struct device *dev)
|
||||||
|
{
|
||||||
|
struct flash_sam0_data *ctx = dev->driver_data;
|
||||||
|
int err;
|
||||||
|
int page;
|
||||||
|
off_t offset = ctx->offset;
|
||||||
|
|
||||||
|
ctx->offset = 0;
|
||||||
|
|
||||||
|
if (offset == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = flash_sam0_erase_row(dev, offset);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (page = 0; page < PAGES_PER_ROW; page++) {
|
||||||
|
err = flash_sam0_write_page(
|
||||||
|
dev, offset + page * FLASH_PAGE_SIZE,
|
||||||
|
&ctx->buf[page * FLASH_PAGE_SIZE]);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_write(struct device *dev, off_t offset,
|
||||||
|
const void *data, size_t len)
|
||||||
|
{
|
||||||
|
struct flash_sam0_data *ctx = dev->driver_data;
|
||||||
|
const u8_t *pdata = data;
|
||||||
|
off_t addr;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
SYS_LOG_DBG("%x: len %u", offset, len);
|
||||||
|
|
||||||
|
err = flash_sam0_valid_range(offset, len);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_sam0_sem_take(dev);
|
||||||
|
|
||||||
|
for (addr = offset; addr < offset + len; addr++) {
|
||||||
|
off_t base = addr & ~(ROW_SIZE - 1);
|
||||||
|
|
||||||
|
if (base != ctx->offset) {
|
||||||
|
/* Started a new row. Flush any pending ones. */
|
||||||
|
flash_sam0_commit(dev);
|
||||||
|
memcpy(ctx->buf, (void *)base, sizeof(ctx->buf));
|
||||||
|
ctx->offset = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->buf[addr % ROW_SIZE] = *pdata++;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_sam0_commit(dev);
|
||||||
|
flash_sam0_sem_give(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES */
|
||||||
|
|
||||||
|
static int flash_sam0_write(struct device *dev, off_t offset,
|
||||||
|
const void *data, size_t len)
|
||||||
|
{
|
||||||
|
const u8_t *pdata = data;
|
||||||
|
int err;
|
||||||
|
size_t idx;
|
||||||
|
|
||||||
|
err = flash_sam0_valid_range(offset, len);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((offset % FLASH_PAGE_SIZE) != 0) {
|
||||||
|
SYS_LOG_WRN("%x: not on a write block boundrary", offset);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((len % FLASH_PAGE_SIZE) != 0) {
|
||||||
|
SYS_LOG_WRN("%x: not a integer number of write blocks", len);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_sam0_sem_take(dev);
|
||||||
|
|
||||||
|
for (idx = 0; idx < len; idx += FLASH_PAGE_SIZE) {
|
||||||
|
err = flash_sam0_write_page(dev, offset + idx, &pdata[idx]);
|
||||||
|
if (err != 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
flash_sam0_sem_give(dev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int flash_sam0_read(struct device *dev, off_t offset, void *data,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = flash_sam0_valid_range(offset, len);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data, (u8_t *)CONFIG_FLASH_BASE_ADDRESS + offset, len);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_erase(struct device *dev, off_t offset, size_t size)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = flash_sam0_valid_range(offset, ROW_SIZE);
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((offset % ROW_SIZE) != 0) {
|
||||||
|
SYS_LOG_WRN("%x: not on a page boundrary", offset);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((size % ROW_SIZE) != 0) {
|
||||||
|
SYS_LOG_WRN("%x: not a integer number of pages", size);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_sam0_sem_take(dev);
|
||||||
|
|
||||||
|
for (size_t addr = offset; addr < offset + size; addr += ROW_SIZE) {
|
||||||
|
err = flash_sam0_erase_row(dev, addr);
|
||||||
|
if (err != 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
flash_sam0_sem_give(dev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_write_protection(struct device *dev, bool enable)
|
||||||
|
{
|
||||||
|
off_t offset;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
flash_sam0_sem_take(dev);
|
||||||
|
|
||||||
|
for (offset = 0; offset < CONFIG_FLASH_SIZE * 1024;
|
||||||
|
offset += LOCK_REGION_SIZE) {
|
||||||
|
*FLASH_MEM(offset) = 0;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_LR |
|
||||||
|
NVMCTRL_CTRLA_CMDEX_KEY;
|
||||||
|
} else {
|
||||||
|
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_UR |
|
||||||
|
NVMCTRL_CTRLA_CMDEX_KEY;
|
||||||
|
}
|
||||||
|
err = flash_sam0_check_status(offset);
|
||||||
|
if (err != 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
flash_sam0_sem_give(dev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void flash_sam0_page_layout(struct device *dev,
|
||||||
|
const struct flash_pages_layout **layout,
|
||||||
|
size_t *layout_size)
|
||||||
|
{
|
||||||
|
*layout = &flash_sam0_pages_layout;
|
||||||
|
*layout_size = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_sam0_init(struct device *dev)
|
||||||
|
{
|
||||||
|
struct flash_sam0_data *ctx = dev->driver_data;
|
||||||
|
|
||||||
|
k_sem_init(&ctx->sem, 1, 1);
|
||||||
|
|
||||||
|
/* Ensure the clock is on. */
|
||||||
|
PM->APBBMASK.bit.NVMCTRL_ = 1;
|
||||||
|
/* Require an explicit write command */
|
||||||
|
NVMCTRL->CTRLB.bit.MANW = 1;
|
||||||
|
|
||||||
|
return flash_sam0_write_protection(dev, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flash_driver_api flash_sam0_api = {
|
||||||
|
.write_protection = flash_sam0_write_protection,
|
||||||
|
.erase = flash_sam0_erase,
|
||||||
|
.write = flash_sam0_write,
|
||||||
|
.read = flash_sam0_read,
|
||||||
|
#ifdef CONFIG_FLASH_PAGE_LAYOUT
|
||||||
|
.page_layout = flash_sam0_page_layout,
|
||||||
|
#endif
|
||||||
|
.write_block_size = FLASH_PAGE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct flash_sam0_data flash_sam0_data_0;
|
||||||
|
|
||||||
|
DEVICE_AND_API_INIT(flash_sam0, CONFIG_SOC_FLASH_SAM0_DEV_NAME,
|
||||||
|
flash_sam0_init, &flash_sam0_data_0, NULL, POST_KERNEL,
|
||||||
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &flash_sam0_api);
|
|
@ -19,7 +19,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
flash0: flash@0 {
|
flash0: flash@0 {
|
||||||
|
compatible = "soc-nv-flash";
|
||||||
reg = <0 0x40000>;
|
reg = <0 0x40000>;
|
||||||
|
write-block-size = <64>;
|
||||||
};
|
};
|
||||||
|
|
||||||
sram0: memory@20000000 {
|
sram0: memory@20000000 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue