blob: 6bd1b0437f21d9daa477e16595950765d6d268a1 [file] [log] [blame]
/*
* Copyright (c) 2018 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(flash_sam0);
#include <device.h>
#include <drivers/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;
};
#if CONFIG_FLASH_PAGE_LAYOUT
static const struct flash_pages_layout flash_sam0_pages_layout = {
.pages_count = CONFIG_FLASH_SIZE * 1024 / ROW_SIZE,
.pages_size = ROW_SIZE,
};
#endif
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) {
LOG_WRN("0x%lx: before start of flash", (long)offset);
return -EINVAL;
}
if ((offset + len) > CONFIG_FLASH_SIZE * 1024) {
LOG_WRN("0x%lx: ends past the end of flash", (long)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) {
LOG_ERR("programming error at 0x%lx", (long)offset);
return -EIO;
} else if (status.bit.LOCKE) {
LOG_ERR("lock error at 0x%lx", (long)offset);
return -EROFS;
} else if (status.bit.NVME) {
LOG_ERR("NVM error at 0x%lx", (long)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) {
LOG_ERR("verify error at offset 0x%lx", (long)offset);
return -EIO;
}
return 0;
}
static int flash_sam0_erase_row(struct device *dev, off_t offset)
{
*FLASH_MEM(offset) = 0U;
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;
LOG_DBG("0x%lx: len %zu", (long)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) {
LOG_WRN("0x%lx: not on a write block boundrary", (long)offset);
return -EINVAL;
}
if ((len % FLASH_PAGE_SIZE) != 0) {
LOG_WRN("%zu: 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) {
LOG_WRN("0x%lx: not on a page boundrary", (long)offset);
return -EINVAL;
}
if ((size % ROW_SIZE) != 0) {
LOG_WRN("%zu: 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) = 0U;
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;
}
#if CONFIG_FLASH_PAGE_LAYOUT
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;
}
#endif
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, DT_INST_0_ATMEL_SAM0_NVMCTRL_LABEL,
flash_sam0_init, &flash_sam0_data_0, NULL, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &flash_sam0_api);