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 <andreas@sandberg.pp.se>
This commit is contained in:
parent
b9da052e51
commit
69f0e3b15f
5 changed files with 176 additions and 65 deletions
|
@ -7,12 +7,13 @@
|
||||||
config SOC_FLASH_STM32
|
config SOC_FLASH_STM32
|
||||||
bool "STM32 flash driver"
|
bool "STM32 flash driver"
|
||||||
depends on SOC_FAMILY_STM32
|
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
|
select FLASH_HAS_DRIVER_ENABLED
|
||||||
default y
|
default y
|
||||||
select SOC_FLASH_STM32_V1 if SOC_SERIES_STM32F0X
|
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_STM32F1X
|
||||||
select SOC_FLASH_STM32_V1 if SOC_SERIES_STM32F3X
|
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_STM32G0X
|
||||||
select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32F4X
|
select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32F4X
|
||||||
select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32F7X
|
select FLASH_PAGE_LAYOUT if SOC_SERIES_STM32F7X
|
||||||
|
|
|
@ -36,6 +36,9 @@ LOG_MODULE_REGISTER(flash_stm32, CONFIG_FLASH_LOG_LEVEL);
|
||||||
/* STM32F7: maximum erase time of 4s for a 256K sector */
|
/* STM32F7: maximum erase time of 4s for a 256K sector */
|
||||||
#elif defined(CONFIG_SOC_SERIES_STM32F7X)
|
#elif defined(CONFIG_SOC_SERIES_STM32F7X)
|
||||||
#define STM32_FLASH_MAX_ERASE_TIME 4000
|
#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 */
|
/* STM32L4: maximum erase time of 24.47ms for a 2K sector */
|
||||||
#elif defined(CONFIG_SOC_SERIES_STM32L4X)
|
#elif defined(CONFIG_SOC_SERIES_STM32L4X)
|
||||||
#define STM32_FLASH_MAX_ERASE_TIME 25
|
#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
|
#define CFG_HW_FLASH_SEMID 2
|
||||||
|
|
||||||
static const struct flash_parameters flash_stm32_parameters = {
|
static const struct flash_parameters flash_stm32_parameters = {
|
||||||
#if DT_PROP(DT_INST(0, soc_nv_flash), write_block_size)
|
.write_block_size = FLASH_STM32_WRITE_BLOCK_SIZE,
|
||||||
.write_block_size = DT_PROP(DT_INST(0, soc_nv_flash), 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
|
#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,
|
.erase_value = 0xff,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(CONFIG_MULTITHREADING)
|
#if defined(CONFIG_MULTITHREADING)
|
||||||
|
@ -278,13 +280,39 @@ static int flash_stm32_write_protection(struct device *dev, bool enable)
|
||||||
flash_stm32_sem_give(dev);
|
flash_stm32_sem_give(dev);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(FLASH_CR_LOCK)
|
||||||
|
if (enable) {
|
||||||
regs->CR |= FLASH_CR_LOCK;
|
regs->CR |= FLASH_CR_LOCK;
|
||||||
LOG_DBG("Enable write protection");
|
|
||||||
} else {
|
} else {
|
||||||
if (regs->CR & FLASH_CR_LOCK) {
|
if (regs->CR & FLASH_CR_LOCK) {
|
||||||
regs->KEYR = FLASH_KEY1;
|
regs->KEYR = FLASH_KEY1;
|
||||||
regs->KEYR = FLASH_KEY2;
|
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");
|
LOG_DBG("Disable write protection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,15 @@ struct flash_stm32_priv {
|
||||||
struct k_sem sem;
|
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_PRIV(dev) ((struct flash_stm32_priv *)((dev)->driver_data))
|
||||||
#define FLASH_STM32_REGS(dev) (FLASH_STM32_PRIV(dev)->regs)
|
#define FLASH_STM32_REGS(dev) (FLASH_STM32_PRIV(dev)->regs)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2017 BayLibre, SAS
|
* Copyright (c) 2017 BayLibre, SAS
|
||||||
* Copyright (c) 2019 Linaro Limited
|
* Copyright (c) 2019 Linaro Limited
|
||||||
|
* Copyright (c) 2020 Andreas Sandberg
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -17,30 +18,113 @@ LOG_MODULE_REGISTER(flash_stm32generic, CONFIG_FLASH_LOG_LEVEL);
|
||||||
|
|
||||||
#include "flash_stm32.h"
|
#include "flash_stm32.h"
|
||||||
|
|
||||||
/* offset and len must be aligned on 2 for write
|
#if FLASH_STM32_WRITE_BLOCK_SIZE == 8
|
||||||
* positive and not beyond end of flash
|
typedef uint64_t flash_prg_t;
|
||||||
*/
|
#elif FLASH_STM32_WRITE_BLOCK_SIZE == 4
|
||||||
bool flash_stm32_valid_range(struct device *dev, off_t offset, uint32_t len,
|
typedef uint32_t flash_prg_t;
|
||||||
bool write)
|
#elif FLASH_STM32_WRITE_BLOCK_SIZE == 2
|
||||||
{
|
typedef uint16_t flash_prg_t;
|
||||||
return (!write || (offset % 2 == 0 && len % 2 == 0U)) &&
|
#else
|
||||||
flash_stm32_range_exists(dev, offset, len);
|
#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)
|
static unsigned int get_page(off_t offset)
|
||||||
{
|
{
|
||||||
return offset / FLASH_PAGE_SIZE;
|
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);
|
FLASH_TypeDef *regs = FLASH_STM32_REGS(dev);
|
||||||
uint32_t tmp;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* if the control register is locked, do not fail silently */
|
/* 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;
|
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 */
|
/* Check if this half word is erased */
|
||||||
if (*flash != 0xFFFF) {
|
if (*flash != FLASH_ERASED_VALUE) {
|
||||||
|
LOG_DBG("Flash location not erased");
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set the PG bit */
|
/* Enable writing */
|
||||||
regs->CR |= FLASH_CR_PG;
|
write_enable(regs);
|
||||||
|
|
||||||
/* Flush the register write */
|
/* Make sure the register write has taken effect */
|
||||||
tmp = regs->CR;
|
__DSB();
|
||||||
|
|
||||||
/* Perform the data write operation at the desired memory address */
|
/* Perform the data write operation at the desired memory address */
|
||||||
*flash = val;
|
*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 */
|
/* Wait until the BSY bit is cleared */
|
||||||
rc = flash_stm32_wait_flash_idle(dev);
|
rc = flash_stm32_wait_flash_idle(dev);
|
||||||
|
|
||||||
/* Clear the PG bit */
|
/* Disable writing */
|
||||||
regs->CR &= (~FLASH_CR_PG);
|
write_disable(regs);
|
||||||
|
|
||||||
return rc;
|
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);
|
FLASH_TypeDef *regs = FLASH_STM32_REGS(dev);
|
||||||
uint32_t page_address = CONFIG_FLASH_BASE_ADDRESS;
|
int i, rc = 0;
|
||||||
uint32_t tmp;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
/* if the control register is locked, do not fail silently */
|
/* 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;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,35 +186,12 @@ static int erase_page(struct device *dev, unsigned int page)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate the flash page address */
|
for (i = get_page(offset); i <= get_page(offset + len - 1); ++i) {
|
||||||
page_address += page * FLASH_PAGE_SIZE;
|
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) {
|
if (rc < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -132,9 +204,11 @@ int flash_stm32_write_range(struct device *dev, unsigned int offset,
|
||||||
const void *data, unsigned int len)
|
const void *data, unsigned int len)
|
||||||
{
|
{
|
||||||
int i, rc = 0;
|
int i, rc = 0;
|
||||||
|
const flash_prg_t *values = (const flash_prg_t *)data;
|
||||||
|
|
||||||
for (i = 0; i < len; i += 2, offset += 2U) {
|
for (i = 0; i < len / sizeof(flash_prg_t); i++) {
|
||||||
rc = write_hword(dev, offset, ((const uint16_t *) data)[i>>1]);
|
rc = write_value(dev, offset + i * sizeof(flash_prg_t),
|
||||||
|
values[i]);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
label = "RTC_0";
|
label = "RTC_0";
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Driver does not currently support the l0 flash controller */
|
|
||||||
flash: flash-controller@40022000 {
|
flash: flash-controller@40022000 {
|
||||||
compatible = "st,stm32-flash-controller", "st,stm32l0-flash-controller";
|
compatible = "st,stm32-flash-controller", "st,stm32l0-flash-controller";
|
||||||
label = "FLASH_CTRL";
|
label = "FLASH_CTRL";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue