From 6194b7676c8705cd08b4120037d44771e3ca787f Mon Sep 17 00:00:00 2001 From: Aurelien Jarno Date: Sun, 4 Nov 2018 21:41:22 +0100 Subject: [PATCH] drivers: flash: add flash driver support for Atmel SAM E70 This patch adds a flash driver for the Atmel SAM E70 SoC. The driver has been kept simple by considering that the flash is only composed of 8-KiB blocks. Indeed an area at the beginning of the flash might be erased with a smaller granularity, and the other blocks can also be erased with a higher granularity. It also only handles the global read/write protection, not the 128-KiB lock regions. A write error is returned if a region is locked. Signed-off-by: Aurelien Jarno --- drivers/flash/CMakeLists.txt | 1 + drivers/flash/Kconfig | 2 + drivers/flash/Kconfig.sam | 15 + drivers/flash/flash_sam.c | 356 ++++++++++++++++++ dts/arm/atmel/same70.dtsi | 25 +- dts/arm/atmel/same70q21.dtsi | 12 +- .../atmel,sam-flash-controller.yaml | 25 ++ .../atmel_sam/same70/Kconfig.defconfig.series | 5 + soc/arm/atmel_sam/same70/dts_fixup.h | 3 + 9 files changed, 435 insertions(+), 9 deletions(-) create mode 100644 drivers/flash/Kconfig.sam create mode 100644 drivers/flash/flash_sam.c create mode 100644 dts/bindings/flash_controller/atmel,sam-flash-controller.yaml diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 5bfb8e76f15..8809ff25bc9 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -8,6 +8,7 @@ zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_MCUX soc_flash_mcux.c) zephyr_library_sources_ifdef(CONFIG_FLASH_PAGE_LAYOUT flash_page_layout.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE flash_handlers.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SAM0 flash_sam0.c) +zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SAM flash_sam.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NIOS2_QSPI soc_flash_nios2_qspi.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_GECKO flash_gecko.c) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index eb4e66c73d9..b610c581d70 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -95,6 +95,8 @@ source "drivers/flash/Kconfig.stm32" source "drivers/flash/Kconfig.sam0" +source "drivers/flash/Kconfig.sam" + source "drivers/flash/Kconfig.w25qxxdv" endif diff --git a/drivers/flash/Kconfig.sam b/drivers/flash/Kconfig.sam new file mode 100644 index 00000000000..145510dd97a --- /dev/null +++ b/drivers/flash/Kconfig.sam @@ -0,0 +1,15 @@ +# Kconfig - Atmel SAM flash driver config +# +# Copyright (c) 2018 Aurelien Jarno +# SPDX-License-Identifier: Apache-2.0 + +if SOC_FAMILY_SAM + +menuconfig SOC_FLASH_SAM + bool "Atmel SAM flash driver" + select FLASH_HAS_PAGE_LAYOUT + select FLASH_HAS_DRIVER_ENABLED + help + Enable the Atmel SAM series internal flash driver. + +endif diff --git a/drivers/flash/flash_sam.c b/drivers/flash/flash_sam.c new file mode 100644 index 00000000000..63d14705a1f --- /dev/null +++ b/drivers/flash/flash_sam.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2018 Aurelien Jarno + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL +#include +LOG_MODULE_REGISTER(flash_sam0); + +/* + * The SAM flash memories use very different granularity for writing, + * erasing and locking. In addition the first sector is composed of two + * 8-KiB small sectors with a minimum 512-byte erase size, while the + * other sectors have a minimum 8-KiB erase size. + * + * For simplicity reasons this flash controller driver only addresses the + * flash by 8-KiB blocks (called "pages" in the Zephyr terminology). + */ + + +/* + * We only use block mode erases. The datasheet gives a maximum erase time + * of 200ms for a 8KiB block. + */ +#define SAM_FLASH_TIMEOUT (K_MSEC(220)) + +struct flash_sam_dev_cfg { + Efc *regs; +}; + +struct flash_sam_dev_data { + struct k_sem sem; +}; + +#define DEV_CFG(dev) \ + ((const struct flash_sam_dev_cfg *const)(dev)->config->config_info) + +#define DEV_DATA(dev) \ + ((struct flash_sam_dev_data *const)(dev)->driver_data) + + +static inline void flash_sam_sem_take(struct device *dev) +{ + k_sem_take(&DEV_DATA(dev)->sem, K_FOREVER); +} + +static inline void flash_sam_sem_give(struct device *dev) +{ + k_sem_give(&DEV_DATA(dev)->sem); +} + +/* Check that the offset is within the flash */ +static bool flash_sam_valid_range(struct device *dev, off_t offset, size_t len) +{ + if (offset > CONFIG_FLASH_SIZE * 1024) { + return false; + } + + if (len && ((offset + len - 1) > (CONFIG_FLASH_SIZE * 1024))) { + return false; + } + + return true; +} + +/* Convert an offset in the flash into a page number */ +static off_t flash_sam_get_page(off_t offset) +{ + return offset / IFLASH_PAGE_SIZE; +} + +/* + * This function checks for errors and waits for the end of the + * previous command. + */ +static int flash_sam_wait_ready(struct device *dev) +{ + Efc *const efc = DEV_CFG(dev)->regs; + + u64_t timeout_time = k_uptime_get() + SAM_FLASH_TIMEOUT; + u32_t fsr; + + do { + fsr = efc->EEFC_FSR; + + /* Flash Error Status */ + if (fsr & EEFC_FSR_FLERR) { + return -EIO; + } + /* Flash Lock Error Status */ + if (fsr & EEFC_FSR_FLOCKE) { + return -EACCES; + } + /* Flash Command Error */ + if (fsr & EEFC_FSR_FCMDE) { + return -EINVAL; + } + + /* + * ECC error bits are intentionally not checked as they + * might be set outside of the programming code. + */ + + /* Check for timeout */ + if (k_uptime_get() > timeout_time) { + return -ETIMEDOUT; + } + } while (!(fsr & EEFC_FSR_FRDY)); + + return 0; +} + +/* This function writes a single page, either fully or partially. */ +static int flash_sam_write_page(struct device *dev, off_t offset, + const void *data, size_t len) +{ + Efc *const efc = DEV_CFG(dev)->regs; + const u32_t *src = data; + u32_t *dst = (u32_t *)((u8_t *)CONFIG_FLASH_BASE_ADDRESS + offset); + + LOG_DBG("offset = %x, len = %zu", offset, len); + + /* We need to copy the data using 32-bit accesses */ + for (; len > 0; len -= sizeof(*src)) { + *dst++ = *src++; + } + __DSB(); + + /* Trigger the flash write */ + efc->EEFC_FCR = EEFC_FCR_FKEY_PASSWD | + EEFC_FCR_FARG(flash_sam_get_page(offset)) | + EEFC_FCR_FCMD_WP; + __DSB(); + + /* Wait for the flash write to finish */ + return flash_sam_wait_ready(dev); +} + +/* Write data to the flash, page by page */ +static int flash_sam_write(struct device *dev, off_t offset, + const void *data, size_t len) +{ + int rc; + const u8_t *data8 = data; + + LOG_DBG("offset = %x, len = %zu", offset, len); + + /* Check that the offset is within the flash */ + if (!flash_sam_valid_range(dev, offset, len)) { + return -EINVAL; + } + + if (!len) { + return 0; + } + + /* + * Check that the offset and length are multiples of the write + * block size. + */ + if ((offset % FLASH_WRITE_BLOCK_SIZE) != 0) { + return -EINVAL; + } + if ((len % FLASH_WRITE_BLOCK_SIZE) != 0) { + return -EINVAL; + } + + flash_sam_sem_take(dev); + + rc = flash_sam_wait_ready(dev); + if (rc < 0) { + return rc; + } + + while (len > 0) { + size_t eop_len, write_len; + + /* Maximum size without crossing a page */ + eop_len = -(offset | ~(IFLASH_PAGE_SIZE - 1)); + write_len = min(len, eop_len); + + rc = flash_sam_write_page(dev, offset, data8, write_len); + if (rc < 0) { + goto done; + } + + offset += write_len; + data8 += write_len; + len -= write_len; + } + +done: + flash_sam_sem_give(dev); + + return rc; +} + +/* Read data from flash */ +static int flash_sam_read(struct device *dev, off_t offset, void *data, + size_t len) +{ + LOG_DBG("offset = %x, len = %zu", offset, len); + + if (!flash_sam_valid_range(dev, offset, len)) { + return -EINVAL; + } + + memcpy(data, (u8_t *)CONFIG_FLASH_BASE_ADDRESS + offset, len); + + return 0; +} + +/* Erase a single 8KiB block */ +static int flash_sam_erase_block(struct device *dev, off_t offset) +{ + Efc *const efc = DEV_CFG(dev)->regs; + + LOG_DBG("offset = %x", offset); + + efc->EEFC_FCR = EEFC_FCR_FKEY_PASSWD | + EEFC_FCR_FARG(flash_sam_get_page(offset) | 2) | + EEFC_FCR_FCMD_EPA; + __DSB(); + + return flash_sam_wait_ready(dev); +} + +/* Erase multiple blocks */ +static int flash_sam_erase(struct device *dev, off_t offset, size_t len) +{ + int rc = 0; + off_t i; + + LOG_DBG("offset = %x, len = %zu", offset, len); + + if (!flash_sam_valid_range(dev, offset, len)) { + return -EINVAL; + } + + if (!len) { + return 0; + } + + /* + * Check that the offset and length are multiples of the write + * erase block size. + */ + if ((offset % FLASH_ERASE_BLOCK_SIZE) != 0) { + return -EINVAL; + } + if ((len % FLASH_ERASE_BLOCK_SIZE) != 0) { + return -EINVAL; + } + + flash_sam_sem_take(dev); + + /* Loop through the pages to erase */ + for (i = offset; i < offset + len; i += FLASH_ERASE_BLOCK_SIZE) { + rc = flash_sam_erase_block(dev, i); + if (rc < 0) { + goto done; + } + } + +done: + flash_sam_sem_give(dev); + + /* + * Invalidate the cache addresses corresponding to the erased blocks, + * so that they really appear as erased. + */ + SCB_InvalidateDCache_by_Addr((void *)(CONFIG_FLASH_BASE_ADDRESS + offset), len); + + return rc; +} + +/* Enable or disable the write protection */ +static int flash_sam_write_protection(struct device *dev, bool enable) +{ + Efc *const efc = DEV_CFG(dev)->regs; + int rc = 0; + + flash_sam_sem_take(dev); + + if (enable) { + rc = flash_sam_wait_ready(dev); + if (rc < 0) { + goto done; + } + efc->EEFC_WPMR = EEFC_WPMR_WPKEY_PASSWD | EEFC_WPMR_WPEN; + } else { + efc->EEFC_WPMR = EEFC_WPMR_WPKEY_PASSWD; + } + +done: + flash_sam_sem_give(dev); + return rc; +} + +#if CONFIG_FLASH_PAGE_LAYOUT +/* + * The notion of pages is different in Zephyr and in the SAM documentation. + * Here a page refers to the granularity at which the flash can be erased. + */ +static const struct flash_pages_layout flash_sam_pages_layout = { + .pages_count = (CONFIG_FLASH_SIZE * 1024) / FLASH_ERASE_BLOCK_SIZE, + .pages_size = FLASH_ERASE_BLOCK_SIZE, +}; + +void flash_sam_page_layout(struct device *dev, + const struct flash_pages_layout **layout, + size_t *layout_size) +{ + *layout = &flash_sam_pages_layout; + *layout_size = 1; +} +#endif + +static int flash_sam_init(struct device *dev) +{ + struct flash_sam_dev_data *const data = DEV_DATA(dev); + + k_sem_init(&data->sem, 1, 1); + + return 0; +} + +static const struct flash_driver_api flash_sam_api = { + .write_protection = flash_sam_write_protection, + .erase = flash_sam_erase, + .write = flash_sam_write, + .read = flash_sam_read, +#ifdef CONFIG_FLASH_PAGE_LAYOUT + .page_layout = flash_sam_page_layout, +#endif + .write_block_size = FLASH_WRITE_BLOCK_SIZE, +}; + +static const struct flash_sam_dev_cfg flash_sam_cfg = { + .regs = (Efc *)DT_FLASH_DEV_BASE_ADDRESS, +}; + +static struct flash_sam_dev_data flash_sam_data; + +DEVICE_AND_API_INIT(flash_sam, DT_FLASH_DEV_NAME, + flash_sam_init, &flash_sam_data, &flash_sam_cfg, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &flash_sam_api); diff --git a/dts/arm/atmel/same70.dtsi b/dts/arm/atmel/same70.dtsi index d4e11c7a845..77e480b74a4 100644 --- a/dts/arm/atmel/same70.dtsi +++ b/dts/arm/atmel/same70.dtsi @@ -21,17 +21,32 @@ }; }; - flash0: flash@400000 { - compatible = "soc-nv-flash"; - label = "FLASH_0"; - }; - sram0: memory@20400000 { device_type = "memory"; compatible = "mmio-sram"; }; soc { + flash-controller@400e0c00 { + compatible = "atmel,sam-flash-controller"; + label = "FLASH_CTRL"; + reg = <0x400e0c00 0x200>; + interrupts = <6 0>; + peripheral-id = <6>; + + #address-cells = <1>; + #size-cells = <1>; + + flash0: flash@400000 { + compatible = "soc-nv-flash"; + label = "FLASH_E70"; + + write-block-size = <16>; + erase-block-size = <8192>; + }; + + }; + wdog: watchdog@400e1850 { compatible = "atmel,sam-watchdog"; reg = <0x400e1850 0xc>; diff --git a/dts/arm/atmel/same70q21.dtsi b/dts/arm/atmel/same70q21.dtsi index 6c92db08032..054d9378cc0 100644 --- a/dts/arm/atmel/same70q21.dtsi +++ b/dts/arm/atmel/same70q21.dtsi @@ -8,11 +8,15 @@ #include / { - flash0: flash@400000 { - reg = <0x00400000 DT_SIZE_K(2048)>; - }; - sram0: memory@20400000 { reg = <0x20400000 DT_SIZE_K(384)>; }; + + soc { + flash-controller@400e0c00 { + flash0: flash@400000 { + reg = <0x00400000 DT_SIZE_K(2048)>; + }; + }; + }; }; diff --git a/dts/bindings/flash_controller/atmel,sam-flash-controller.yaml b/dts/bindings/flash_controller/atmel,sam-flash-controller.yaml new file mode 100644 index 00000000000..641e36c1b84 --- /dev/null +++ b/dts/bindings/flash_controller/atmel,sam-flash-controller.yaml @@ -0,0 +1,25 @@ +# +# Copyright (c) 2018, Aurelien Jarno +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +title: Atmel SAM Flash Controller +version: 0.1 + +description: > + This binding gives a base representation of the Atmel SAM Enhanced Embedded Flash Controller + +inherits: + !include flash-controller.yaml + +properties: + compatible: + constraint: "atmel,sam-flash-controller" + + peripheral-id: + type: int + description: peripheral ID + generation: define + category: required +... diff --git a/soc/arm/atmel_sam/same70/Kconfig.defconfig.series b/soc/arm/atmel_sam/same70/Kconfig.defconfig.series index c163ba1eafc..9822a0451b9 100644 --- a/soc/arm/atmel_sam/same70/Kconfig.defconfig.series +++ b/soc/arm/atmel_sam/same70/Kconfig.defconfig.series @@ -78,4 +78,9 @@ config ENTROPY_SAM_RNG default y endif # ENTROPY_GENERATOR +if FLASH +config SOC_FLASH_SAM + default y +endif # FLASH + endif # SOC_SERIES_SAME70 diff --git a/soc/arm/atmel_sam/same70/dts_fixup.h b/soc/arm/atmel_sam/same70/dts_fixup.h index 7258bf2c032..cab7da1ddc6 100644 --- a/soc/arm/atmel_sam/same70/dts_fixup.h +++ b/soc/arm/atmel_sam/same70/dts_fixup.h @@ -136,4 +136,7 @@ #define DT_ENTROPY_SAM_TRNG_NAME DT_ATMEL_SAM_TRNG_40070000_LABEL #define CONFIG_ENTROPY_NAME DT_ATMEL_SAM_TRNG_40070000_LABEL +#define DT_FLASH_DEV_BASE_ADDRESS DT_ATMEL_SAM_FLASH_CONTROLLER_400E0C00_BASE_ADDRESS +#define DT_FLASH_DEV_NAME DT_ATMEL_SAM_FLASH_CONTROLLER_400E0C00_LABEL + /* End of SoC Level DTS fixup file */