/* * Copyright (c) 2018 Google LLC. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam0_nvmctrl #define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL #include LOG_MODULE_REGISTER(flash_sam0); #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 DT_INST_PROP(0, lock_regions) #define LOCK_REGION_SIZE (FLASH_SIZE / LOCK_REGIONS) #if defined(NVMCTRL_BLOCK_SIZE) #define ROW_SIZE NVMCTRL_BLOCK_SIZE #elif defined(NVMCTRL_ROW_SIZE) #define ROW_SIZE NVMCTRL_ROW_SIZE #endif #define PAGES_PER_ROW (ROW_SIZE / FLASH_PAGE_SIZE) #define FLASH_MEM(_a) ((uint32_t *)((uint8_t *)((_a) + CONFIG_FLASH_BASE_ADDRESS))) struct flash_sam0_data { #if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES uint8_t buf[ROW_SIZE]; off_t offset; #endif #if defined(CONFIG_MULTITHREADING) struct k_sem sem; #endif }; #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 const struct flash_parameters flash_sam0_parameters = { #if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES .write_block_size = 1, #else .write_block_size = DT_PROP(DT_INST(0, soc_nv_flash), write_block_size), #endif .erase_value = 0xff, }; static int flash_sam0_write_protection(const struct device *dev, bool enable); static inline void flash_sam0_sem_take(const struct device *dev) { #if defined(CONFIG_MULTITHREADING) struct flash_sam0_data *ctx = dev->data; k_sem_take(&ctx->sem, K_FOREVER); #endif } static inline void flash_sam0_sem_give(const struct device *dev) { #if defined(CONFIG_MULTITHREADING) struct flash_sam0_data *ctx = dev->data; k_sem_give(&ctx->sem); #endif } 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) { #ifdef NVMCTRL_STATUS_READY while (NVMCTRL->STATUS.bit.READY == 0) { } #else while (NVMCTRL->INTFLAG.bit.READY == 0) { } #endif } static int flash_sam0_check_status(off_t offset) { flash_sam0_wait_ready(); #ifdef NVMCTRL_INTFLAG_PROGE NVMCTRL_INTFLAG_Type status = NVMCTRL->INTFLAG; /* Clear any flags */ NVMCTRL->INTFLAG.reg = status.reg; #else NVMCTRL_STATUS_Type status = NVMCTRL->STATUS; /* Clear any flags */ NVMCTRL->STATUS = status; #endif 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(const struct device *dev, off_t offset, const void *data) { const uint32_t *src = data; const uint32_t *end = src + FLASH_PAGE_SIZE / sizeof(*src); uint32_t *dst = FLASH_MEM(offset); int err; #ifdef NVMCTRL_CTRLA_CMD_PBC NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY; #else NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_PBC | NVMCTRL_CTRLB_CMDEX_KEY; #endif flash_sam0_wait_ready(); /* Ensure writes happen 32 bits at a time. */ for (; src != end; src++, dst++) { *dst = UNALIGNED_GET((uint32_t *)src); } #ifdef NVMCTRL_CTRLA_CMD_WP NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_WP | NVMCTRL_CTRLA_CMDEX_KEY; #else NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_WP | NVMCTRL_CTRLB_CMDEX_KEY; #endif 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(const struct device *dev, off_t offset) { *FLASH_MEM(offset) = 0U; #ifdef NVMCTRL_CTRLA_CMD_ER NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_ER | NVMCTRL_CTRLA_CMDEX_KEY; #else NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_EB | NVMCTRL_CTRLB_CMDEX_KEY; #endif return flash_sam0_check_status(offset); } #if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES static int flash_sam0_commit(const struct device *dev) { struct flash_sam0_data *ctx = dev->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(const struct device *dev, off_t offset, const void *data, size_t len) { struct flash_sam0_data *ctx = dev->data; const uint8_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); err = flash_sam0_write_protection(dev, false); if (err == 0) { 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); } int err2 = flash_sam0_write_protection(dev, true); if (!err) { err = err2; } flash_sam0_sem_give(dev); return err; } #else /* CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES */ static int flash_sam0_write(const struct device *dev, off_t offset, const void *data, size_t len) { const uint8_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); err = flash_sam0_write_protection(dev, false); if (err == 0) { for (idx = 0; idx < len; idx += FLASH_PAGE_SIZE) { err = flash_sam0_write_page(dev, offset + idx, &pdata[idx]); if (err != 0) { break; } } } int err2 = flash_sam0_write_protection(dev, true); if (!err) { err = err2; } flash_sam0_sem_give(dev); return err; } #endif static int flash_sam0_read(const 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, (uint8_t *)CONFIG_FLASH_BASE_ADDRESS + offset, len); return 0; } static int flash_sam0_erase(const 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); err = flash_sam0_write_protection(dev, false); if (err == 0) { for (size_t addr = offset; addr < offset + size; addr += ROW_SIZE) { err = flash_sam0_erase_row(dev, addr); if (err != 0) { break; } } } int err2 = flash_sam0_write_protection(dev, true); if (!err) { err = err2; } flash_sam0_sem_give(dev); return err; } static int flash_sam0_write_protection(const struct device *dev, bool enable) { off_t offset; int err; for (offset = 0; offset < CONFIG_FLASH_SIZE * 1024; offset += LOCK_REGION_SIZE) { NVMCTRL->ADDR.reg = offset + CONFIG_FLASH_BASE_ADDRESS; #ifdef NVMCTRL_CTRLA_CMD_LR 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; } #else if (enable) { NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_LR | NVMCTRL_CTRLB_CMDEX_KEY; } else { NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_UR | NVMCTRL_CTRLB_CMDEX_KEY; } #endif err = flash_sam0_check_status(offset); if (err != 0) { goto done; } } done: return err; } #if CONFIG_FLASH_PAGE_LAYOUT void flash_sam0_page_layout(const struct device *dev, const struct flash_pages_layout **layout, size_t *layout_size) { *layout = &flash_sam0_pages_layout; *layout_size = 1; } #endif static const struct flash_parameters * flash_sam0_get_parameters(const struct device *dev) { ARG_UNUSED(dev); return &flash_sam0_parameters; } static int flash_sam0_init(const struct device *dev) { #if defined(CONFIG_MULTITHREADING) struct flash_sam0_data *ctx = dev->data; k_sem_init(&ctx->sem, 1, 1); #endif #ifdef PM_APBBMASK_NVMCTRL /* Ensure the clock is on. */ PM->APBBMASK.bit.NVMCTRL_ = 1; #else MCLK->APBBMASK.reg |= MCLK_APBBMASK_NVMCTRL; #endif #ifdef NVMCTRL_CTRLB_MANW /* Require an explicit write command */ NVMCTRL->CTRLB.bit.MANW = 1; #endif return flash_sam0_write_protection(dev, false); } static const struct flash_driver_api flash_sam0_api = { .erase = flash_sam0_erase, .write = flash_sam0_write, .read = flash_sam0_read, .get_parameters = flash_sam0_get_parameters, #ifdef CONFIG_FLASH_PAGE_LAYOUT .page_layout = flash_sam0_page_layout, #endif }; static struct flash_sam0_data flash_sam0_data_0; DEVICE_DT_INST_DEFINE(0, flash_sam0_init, NULL, &flash_sam0_data_0, NULL, POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, &flash_sam0_api);