From 69f0e3b15f75019a831f69b01939482759be410c Mon Sep 17 00:00:00 2001 From: Andreas Sandberg Date: Mon, 11 May 2020 13:39:39 +0100 Subject: [PATCH] drivers: flash: Add support for STM32L0X Add support for STM32L0X using the generic STM32 backend. This is quite a significant change since the L0 series uses a slightly different flash controller. Refactor the generic backend to better support different block sizes and the L0's register interface. Signed-off-by: Andreas Sandberg --- drivers/flash/Kconfig.stm32 | 3 +- drivers/flash/flash_stm32.c | 44 ++++++-- drivers/flash/flash_stm32.h | 9 ++ drivers/flash/flash_stm32_v1.c | 184 +++++++++++++++++++++++---------- dts/arm/st/l0/stm32l0.dtsi | 1 - 5 files changed, 176 insertions(+), 65 deletions(-) diff --git a/drivers/flash/Kconfig.stm32 b/drivers/flash/Kconfig.stm32 index 9b9a072891f..d33ef8ade66 100644 --- a/drivers/flash/Kconfig.stm32 +++ b/drivers/flash/Kconfig.stm32 @@ -7,12 +7,13 @@ config SOC_FLASH_STM32 bool "STM32 flash driver" depends on SOC_FAMILY_STM32 - depends on (SOC_SERIES_STM32F0X || SOC_SERIES_STM32F1X || SOC_SERIES_STM32F3X || SOC_SERIES_STM32F4X || SOC_SERIES_STM32F7X || SOC_SERIES_STM32L4X || SOC_SERIES_STM32WBX || SOC_SERIES_STM32G0X || SOC_SERIES_STM32G4X) + depends on (SOC_SERIES_STM32F0X || SOC_SERIES_STM32F1X || SOC_SERIES_STM32F3X || SOC_SERIES_STM32F4X || SOC_SERIES_STM32F7X || SOC_SERIES_STM32L0X ||SOC_SERIES_STM32L4X || SOC_SERIES_STM32WBX || SOC_SERIES_STM32G0X || SOC_SERIES_STM32G4X) select FLASH_HAS_DRIVER_ENABLED default y select SOC_FLASH_STM32_V1 if SOC_SERIES_STM32F0X select SOC_FLASH_STM32_V1 if SOC_SERIES_STM32F1X select SOC_FLASH_STM32_V1 if SOC_SERIES_STM32F3X + select SOC_FLASH_STM32_V1 if SOC_SERIES_STM32L0X select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32G0X select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32F4X select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32F7X diff --git a/drivers/flash/flash_stm32.c b/drivers/flash/flash_stm32.c index 4e15e40c399..a32638b2ed2 100644 --- a/drivers/flash/flash_stm32.c +++ b/drivers/flash/flash_stm32.c @@ -36,6 +36,9 @@ LOG_MODULE_REGISTER(flash_stm32, CONFIG_FLASH_LOG_LEVEL); /* STM32F7: maximum erase time of 4s for a 256K sector */ #elif defined(CONFIG_SOC_SERIES_STM32F7X) #define STM32_FLASH_MAX_ERASE_TIME 4000 +/* STM32L0: maximum erase time of 3.2ms for a 128B page */ +#elif defined(CONFIG_SOC_SERIES_STM32L0X) +#define STM32_FLASH_MAX_ERASE_TIME 4 /* STM32L4: maximum erase time of 24.47ms for a 2K sector */ #elif defined(CONFIG_SOC_SERIES_STM32L4X) #define STM32_FLASH_MAX_ERASE_TIME 25 @@ -58,15 +61,14 @@ LOG_MODULE_REGISTER(flash_stm32, CONFIG_FLASH_LOG_LEVEL); #define CFG_HW_FLASH_SEMID 2 static const struct flash_parameters flash_stm32_parameters = { -#if DT_PROP(DT_INST(0, soc_nv_flash), write_block_size) - .write_block_size = DT_PROP(DT_INST(0, soc_nv_flash), write_block_size), + .write_block_size = FLASH_STM32_WRITE_BLOCK_SIZE, + /* Some SoCs (L0/L1) use an EEPROM under the hood. Distinguish + * between them based on the presence of the PECR register. */ +#if defined(FLASH_PECR_ERASE) + .erase_value = 0, #else -#error Flash write block size not available - /* Flash Write block size is extracted from device tree */ - /* as flash node property 'write-block-size' */ -#endif - /* WARNING: This value may be not valid for L0/L1 chips */ .erase_value = 0xff, +#endif }; #if defined(CONFIG_MULTITHREADING) @@ -278,13 +280,39 @@ static int flash_stm32_write_protection(struct device *dev, bool enable) flash_stm32_sem_give(dev); return rc; } + } + +#if defined(FLASH_CR_LOCK) + if (enable) { regs->CR |= FLASH_CR_LOCK; - LOG_DBG("Enable write protection"); } else { if (regs->CR & FLASH_CR_LOCK) { regs->KEYR = FLASH_KEY1; regs->KEYR = FLASH_KEY2; } + } +#else + if (enable) { + regs->PECR |= FLASH_PECR_PRGLOCK; + regs->PECR |= FLASH_PECR_PELOCK; + } else { + if (regs->PECR & FLASH_PECR_PRGLOCK) { + LOG_DBG("Disabling write protection"); + regs->PEKEYR = FLASH_PEKEY1; + regs->PEKEYR = FLASH_PEKEY2; + regs->PRGKEYR = FLASH_PRGKEY1; + regs->PRGKEYR = FLASH_PRGKEY2; + } + if (FLASH->PECR & FLASH_PECR_PRGLOCK) { + LOG_ERR("Unlock failed"); + rc = -EIO; + } + } +#endif + + if (enable) { + LOG_DBG("Enable write protection"); + } else { LOG_DBG("Disable write protection"); } diff --git a/drivers/flash/flash_stm32.h b/drivers/flash/flash_stm32.h index ab1c94f00b3..43659de6329 100644 --- a/drivers/flash/flash_stm32.h +++ b/drivers/flash/flash_stm32.h @@ -32,6 +32,15 @@ struct flash_stm32_priv { struct k_sem sem; }; +#if DT_PROP(DT_INST(0, soc_nv_flash), write_block_size) +#define FLASH_STM32_WRITE_BLOCK_SIZE \ + DT_PROP(DT_INST(0, soc_nv_flash), write_block_size) +#else +#error Flash write block size not available + /* Flash Write block size is extracted from device tree */ + /* as flash node property 'write-block-size' */ +#endif + #define FLASH_STM32_PRIV(dev) ((struct flash_stm32_priv *)((dev)->driver_data)) #define FLASH_STM32_REGS(dev) (FLASH_STM32_PRIV(dev)->regs) diff --git a/drivers/flash/flash_stm32_v1.c b/drivers/flash/flash_stm32_v1.c index b32dfff06ea..9b18a19ea16 100644 --- a/drivers/flash/flash_stm32_v1.c +++ b/drivers/flash/flash_stm32_v1.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2017 BayLibre, SAS * Copyright (c) 2019 Linaro Limited + * Copyright (c) 2020 Andreas Sandberg * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,30 +18,113 @@ LOG_MODULE_REGISTER(flash_stm32generic, CONFIG_FLASH_LOG_LEVEL); #include "flash_stm32.h" -/* offset and len must be aligned on 2 for write - * positive and not beyond end of flash - */ -bool flash_stm32_valid_range(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); -} +#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; } -static int write_hword(struct device *dev, off_t offset, uint16_t val) +#if defined(FLASH_CR_PER) +static int is_flash_locked(FLASH_TypeDef *regs) { - volatile uint16_t *flash = (uint16_t *)(offset + CONFIG_FLASH_BASE_ADDRESS); + 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) +{ + regs->PECR |= FLASH_PECR_PROG; +} + +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(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); - uint32_t tmp; int rc; /* if the control register is locked, do not fail silently */ - if (regs->CR & FLASH_CR_LOCK) { + if (is_flash_locked(regs)) { + LOG_ERR("Flash is locked"); return -EIO; } @@ -51,15 +135,16 @@ static int write_hword(struct device *dev, off_t offset, uint16_t val) } /* Check if this half word is erased */ - if (*flash != 0xFFFF) { + if (*flash != FLASH_ERASED_VALUE) { + LOG_DBG("Flash location not erased"); return -EIO; } - /* Set the PG bit */ - regs->CR |= FLASH_CR_PG; + /* Enable writing */ + write_enable(regs); - /* Flush the register write */ - tmp = regs->CR; + /* Make sure the register write has taken effect */ + __DSB(); /* Perform the data write operation at the desired memory address */ *flash = val; @@ -67,21 +152,31 @@ static int write_hword(struct device *dev, off_t offset, uint16_t val) /* Wait until the BSY bit is cleared */ rc = flash_stm32_wait_flash_idle(dev); - /* Clear the PG bit */ - regs->CR &= (~FLASH_CR_PG); + /* Disable writing */ + write_disable(regs); return rc; } -static int erase_page(struct device *dev, unsigned int page) +/* offset and len must be aligned on 2 for write + * positive and not beyond end of flash + */ +bool flash_stm32_valid_range(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(struct device *dev, unsigned int offset, + unsigned int len) { FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); - uint32_t page_address = CONFIG_FLASH_BASE_ADDRESS; - uint32_t tmp; - int rc; + int i, rc = 0; /* if the control register is locked, do not fail silently */ - if (regs->CR & FLASH_CR_LOCK) { + if (is_flash_locked(regs)) { + LOG_ERR("Flash is locked"); return -EIO; } @@ -91,35 +186,12 @@ static int erase_page(struct device *dev, unsigned int page) return rc; } - /* Calculate the flash page address */ - page_address += page * FLASH_PAGE_SIZE; + 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); - /* Set the PER bit and select the page you wish to erase */ - regs->CR |= FLASH_CR_PER; - regs->AR = page_address; - - /* Set the STRT bit */ - regs->CR |= FLASH_CR_STRT; - - /* flush the register write */ - tmp = regs->CR; - - /* Wait for the BSY bit */ - rc = flash_stm32_wait_flash_idle(dev); - - regs->CR &= ~FLASH_CR_PER; - - return rc; -} - -int flash_stm32_block_erase_loop(struct device *dev, unsigned int offset, - unsigned int len) -{ - int i, rc = 0; - - i = get_page(offset); - for (; i <= get_page(offset + len - 1) ; ++i) { - rc = erase_page(dev, i); if (rc < 0) { break; } @@ -132,9 +204,11 @@ int flash_stm32_write_range(struct device *dev, unsigned int offset, const void *data, unsigned int len) { int i, rc = 0; + const flash_prg_t *values = (const flash_prg_t *)data; - for (i = 0; i < len; i += 2, offset += 2U) { - rc = write_hword(dev, offset, ((const uint16_t *) data)[i>>1]); + for (i = 0; i < len / sizeof(flash_prg_t); i++) { + rc = write_value(dev, offset + i * sizeof(flash_prg_t), + values[i]); if (rc < 0) { return rc; } diff --git a/dts/arm/st/l0/stm32l0.dtsi b/dts/arm/st/l0/stm32l0.dtsi index de25ca8645c..7227864a6bd 100644 --- a/dts/arm/st/l0/stm32l0.dtsi +++ b/dts/arm/st/l0/stm32l0.dtsi @@ -42,7 +42,6 @@ label = "RTC_0"; }; - /* Driver does not currently support the l0 flash controller */ flash: flash-controller@40022000 { compatible = "st,stm32-flash-controller", "st,stm32l0-flash-controller"; label = "FLASH_CTRL";