|  | /* | 
|  | * Copyright (c) 2017 BayLibre, SAS | 
|  | * Copyright (c) 2019 Linaro Limited | 
|  | * Copyright (c) 2020 Andreas Sandberg | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(flash_stm32generic, CONFIG_FLASH_LOG_LEVEL); | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <string.h> | 
|  | #include <zephyr/drivers/flash.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <soc.h> | 
|  |  | 
|  | #include "flash_stm32.h" | 
|  |  | 
|  | #if FLASH_STM32_WRITE_BLOCK_SIZE == 8 | 
|  | typedef uint64_t flash_prg_t; | 
|  | #elif FLASH_STM32_WRITE_BLOCK_SIZE == 4 | 
|  | typedef uint32_t flash_prg_t; | 
|  | #elif FLASH_STM32_WRITE_BLOCK_SIZE == 2 | 
|  | typedef uint16_t flash_prg_t; | 
|  | #else | 
|  | #error Unknown write block size | 
|  | #endif | 
|  |  | 
|  | #if defined(FLASH_CR_PER) | 
|  | #define FLASH_ERASED_VALUE ((flash_prg_t)-1) | 
|  | #elif defined(FLASH_PECR_ERASE) | 
|  | #define FLASH_ERASED_VALUE 0 | 
|  | #else | 
|  | #error Unknown erase value | 
|  | #endif | 
|  |  | 
|  | static unsigned int get_page(off_t offset) | 
|  | { | 
|  | return offset / FLASH_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | #if defined(FLASH_CR_PER) | 
|  | static int is_flash_locked(FLASH_TypeDef *regs) | 
|  | { | 
|  | return !!(regs->CR & FLASH_CR_LOCK); | 
|  | } | 
|  |  | 
|  | static void write_enable(FLASH_TypeDef *regs) | 
|  | { | 
|  | regs->CR |= FLASH_CR_PG; | 
|  | } | 
|  |  | 
|  | static void write_disable(FLASH_TypeDef *regs) | 
|  | { | 
|  | regs->CR &= (~FLASH_CR_PG); | 
|  | } | 
|  |  | 
|  | static void erase_page_begin(FLASH_TypeDef *regs, unsigned int page) | 
|  | { | 
|  | /* Set the PER bit and select the page you wish to erase */ | 
|  | regs->CR |= FLASH_CR_PER; | 
|  | regs->AR = CONFIG_FLASH_BASE_ADDRESS + page * FLASH_PAGE_SIZE; | 
|  |  | 
|  | __DSB(); | 
|  |  | 
|  | /* Set the STRT bit */ | 
|  | regs->CR |= FLASH_CR_STRT; | 
|  | } | 
|  |  | 
|  | static void erase_page_end(FLASH_TypeDef *regs) | 
|  | { | 
|  | regs->CR &= ~FLASH_CR_PER; | 
|  | } | 
|  |  | 
|  | #else | 
|  |  | 
|  | static int is_flash_locked(FLASH_TypeDef *regs) | 
|  | { | 
|  | return !!(regs->PECR & FLASH_PECR_PRGLOCK); | 
|  | } | 
|  |  | 
|  | static void write_enable(FLASH_TypeDef *regs) | 
|  | { | 
|  | /* Only used for half-page programming on L1x */ | 
|  | #if !defined(CONFIG_SOC_SERIES_STM32L1X) | 
|  | regs->PECR |= FLASH_PECR_PROG; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static void write_disable(FLASH_TypeDef *regs) | 
|  | { | 
|  | /* Clear the PG bit */ | 
|  | regs->PECR &= ~FLASH_PECR_PROG; | 
|  | } | 
|  |  | 
|  | static void erase_page_begin(FLASH_TypeDef *regs, unsigned int page) | 
|  | { | 
|  | volatile flash_prg_t *page_base = (flash_prg_t *)( | 
|  | CONFIG_FLASH_BASE_ADDRESS + page * FLASH_PAGE_SIZE); | 
|  | /* Enable programming in erase mode. An erase is triggered by | 
|  | * writing 0 to the first word of a page. | 
|  | */ | 
|  | regs->PECR |= FLASH_PECR_ERASE; | 
|  | regs->PECR |= FLASH_PECR_PROG; | 
|  |  | 
|  | __DSB(); | 
|  |  | 
|  | *page_base = 0; | 
|  | } | 
|  |  | 
|  | static void erase_page_end(FLASH_TypeDef *regs) | 
|  | { | 
|  | /* Disable programming */ | 
|  | regs->PECR &= ~FLASH_PECR_PROG; | 
|  | regs->PECR &= ~FLASH_PECR_ERASE; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int write_value(const struct device *dev, off_t offset, | 
|  | flash_prg_t val) | 
|  | { | 
|  | volatile flash_prg_t *flash = (flash_prg_t *)( | 
|  | offset + CONFIG_FLASH_BASE_ADDRESS); | 
|  | FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); | 
|  | int rc; | 
|  |  | 
|  | /* if the control register is locked, do not fail silently */ | 
|  | if (is_flash_locked(regs)) { | 
|  | LOG_ERR("Flash is locked"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Check that no Flash main memory operation is ongoing */ | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* Check if this half word is erased */ | 
|  | if (*flash != FLASH_ERASED_VALUE) { | 
|  | LOG_DBG("Flash location not erased"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Enable writing */ | 
|  | write_enable(regs); | 
|  |  | 
|  | /* Make sure the register write has taken effect */ | 
|  | __DSB(); | 
|  |  | 
|  | /* Perform the data write operation at the desired memory address */ | 
|  | *flash = val; | 
|  |  | 
|  | /* Wait until the BSY bit is cleared */ | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  |  | 
|  | /* Disable writing */ | 
|  | write_disable(regs); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* offset and len must be aligned on 2 for write | 
|  | * positive and not beyond end of flash | 
|  | */ | 
|  | bool flash_stm32_valid_range(const struct device *dev, off_t offset, | 
|  | uint32_t len, | 
|  | bool write) | 
|  | { | 
|  | return (!write || (offset % 2 == 0 && len % 2 == 0U)) && | 
|  | flash_stm32_range_exists(dev, offset, len); | 
|  | } | 
|  |  | 
|  | int flash_stm32_block_erase_loop(const struct device *dev, | 
|  | unsigned int offset, | 
|  | unsigned int len) | 
|  | { | 
|  | FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); | 
|  | int i, rc = 0; | 
|  |  | 
|  | /* if the control register is locked, do not fail silently */ | 
|  | if (is_flash_locked(regs)) { | 
|  | LOG_ERR("Flash is locked"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Check that no Flash memory operation is ongoing */ | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | for (i = get_page(offset); i <= get_page(offset + len - 1); ++i) { | 
|  | erase_page_begin(regs, i); | 
|  | __DSB(); | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  | erase_page_end(regs); | 
|  |  | 
|  | if (rc < 0) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | int flash_stm32_write_range(const struct device *dev, unsigned int offset, | 
|  | const void *data, unsigned int len) | 
|  | { | 
|  | int i, rc = 0; | 
|  | flash_prg_t value; | 
|  |  | 
|  | for (i = 0; i < len / sizeof(flash_prg_t); i++) { | 
|  | memcpy(&value, | 
|  | (const uint8_t *)data + i * sizeof(flash_prg_t), | 
|  | sizeof(flash_prg_t)); | 
|  | rc = write_value(dev, offset + i * sizeof(flash_prg_t), value); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | void flash_stm32_page_layout(const struct device *dev, | 
|  | const struct flash_pages_layout **layout, | 
|  | size_t *layout_size) | 
|  | { | 
|  | static struct flash_pages_layout flash_layout = { | 
|  | .pages_count = 0, | 
|  | .pages_size = 0, | 
|  | }; | 
|  |  | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (flash_layout.pages_count == 0) { | 
|  | #if defined(CONFIG_SOC_SERIES_STM32F3X) | 
|  | flash_layout.pages_count = | 
|  | DT_REG_SIZE(DT_INST(0, soc_nv_flash)) / FLASH_PAGE_SIZE; | 
|  | #else | 
|  | flash_layout.pages_count = (CONFIG_FLASH_SIZE * 1024) / | 
|  | FLASH_PAGE_SIZE; | 
|  | #endif | 
|  | flash_layout.pages_size = FLASH_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | *layout = &flash_layout; | 
|  | *layout_size = 1; | 
|  | } |