From cd92dd139bb9f0c73eb7f890d19a03f7e2b25aca Mon Sep 17 00:00:00 2001 From: Michael Hope Date: Wed, 22 Nov 2017 22:53:29 +0100 Subject: [PATCH] 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 --- drivers/flash/CMakeLists.txt | 1 + drivers/flash/Kconfig | 2 + drivers/flash/Kconfig.sam0 | 32 +++ drivers/flash/flash_sam0.c | 376 +++++++++++++++++++++++++++++++++++ dts/arm/atmel/samd21.dtsi | 2 + 5 files changed, 413 insertions(+) create mode 100644 drivers/flash/Kconfig.sam0 create mode 100644 drivers/flash/flash_sam0.c diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index e5af3918ecf..c34ee6588ef 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -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_FLASH_PAGE_LAYOUT flash_page_layout.c) zephyr_sources_ifdef(CONFIG_USERSPACE flash_handlers.c) +zephyr_sources_ifdef(CONFIG_SOC_FLASH_SAM0 flash_sam0.c) if(CONFIG_SOC_SERIES_STM32F0X) zephyr_sources_ifdef(CONFIG_SOC_FLASH_STM32 diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index d4588633210..64a1fecd3c9 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -164,3 +164,5 @@ config SOC_FLASH_MCUX_DEV_NAME Specify the device name for the flash driver. source "drivers/flash/Kconfig.stm32" + +source "drivers/flash/Kconfig.sam0" diff --git a/drivers/flash/Kconfig.sam0 b/drivers/flash/Kconfig.sam0 new file mode 100644 index 00000000000..3fbd2c77679 --- /dev/null +++ b/drivers/flash/Kconfig.sam0 @@ -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 diff --git a/drivers/flash/flash_sam0.c b/drivers/flash/flash_sam0.c new file mode 100644 index 00000000000..97476e03411 --- /dev/null +++ b/drivers/flash/flash_sam0.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2018 Google LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define SYS_LOG_DOMAIN "flash" +#include + +#include +#include +#include +#include +#include +#include + +/* + * 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); diff --git a/dts/arm/atmel/samd21.dtsi b/dts/arm/atmel/samd21.dtsi index b6be91e8791..bfa92218c9d 100644 --- a/dts/arm/atmel/samd21.dtsi +++ b/dts/arm/atmel/samd21.dtsi @@ -19,7 +19,9 @@ }; flash0: flash@0 { + compatible = "soc-nv-flash"; reg = <0 0x40000>; + write-block-size = <64>; }; sram0: memory@20000000 {