Commit 1de48c36 authored by Aksel Skauge Mellbye's avatar Aksel Skauge Mellbye Committed by Carles Cufi
Browse files

drivers: flash: silabs: Introduce Silabs Series 2 flash driver

Introduce separate flash driver for Silabs Series 2. This driver
is forked from the Gecko flash driver with no changes outside of
formatting and naming.

Signed-off-by: default avatarAksel Skauge Mellbye <aksel.mellbye@silabs.com>
parent da3d029b
Loading
Loading
Loading
Loading
+1 −0
Changes for drivers/flash/CMakeLists.txt: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_RV32M1 soc_flash_rv32m1.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SAM flash_sam.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SAM0 flash_sam0.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SI32 flash_si32.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SILABS_S2 soc_flash_silabs_series2.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SILABS_SIWX91X soc_flash_silabs_siwx91x.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SMARTBOND flash_smartbond.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_TELINK_B91 soc_flash_b91.c)
+1 −0
Changes for drivers/flash/Kconfig: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -197,6 +197,7 @@ source "drivers/flash/Kconfig.rv32m1"
source "drivers/flash/Kconfig.sam"
source "drivers/flash/Kconfig.sam0"
source "drivers/flash/Kconfig.si32"
source "drivers/flash/Kconfig.silabs"
source "drivers/flash/Kconfig.simulator"
source "drivers/flash/Kconfig.siwx91x"
source "drivers/flash/Kconfig.smartbond"
+14 −0
Changes for drivers/flash/Kconfig.silabs: 14 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright (c) 2025 Silicon Laboratories Inc.
# SPDX-License-Identifier: Apache-2.0

config SOC_FLASH_SILABS_S2
	bool "Silicon Labs Series 2 flash driver"
	default y
	depends on DT_HAS_SILABS_SERIES2_FLASH_CONTROLLER_ENABLED
	select FLASH_HAS_DRIVER_ENABLED
	select FLASH_HAS_PAGE_LAYOUT
	select SOC_GECKO_MSC
	select FLASH_HAS_EXPLICIT_ERASE
	select MPU_ALLOW_FLASH_WRITE if ARM_MPU
	help
	  Enable Silicon Labs Series 2 internal flash driver.
+226 −0
Changes for drivers/flash/soc_flash_silabs_series2.c: 226 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
 * Copyright (c) 2018, Piotr Mienkowski
 * Copyright (c) 2025 Silicon Laboratories Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT     silabs_series2_flash_controller
#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash)

#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <soc.h>
#include <em_msc.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(flash_silabs, CONFIG_FLASH_LOG_LEVEL);

struct flash_silabs_data {
	struct k_sem lock;
};

struct flash_silabs_config {
	struct flash_parameters flash_parameters;
	struct flash_pages_layout page_layout;
};

static bool flash_silabs_read_range_is_valid(off_t offset, uint32_t size)
{
	return (offset >= 0) && (offset < DT_REG_SIZE(SOC_NV_FLASH_NODE)) &&
	       ((DT_REG_SIZE(SOC_NV_FLASH_NODE) - offset) >= size);
}

static bool flash_silabs_write_range_is_valid(off_t offset, uint32_t size)
{
	/* Note:
	 * - A flash address to write to must be aligned to words.
	 * - Number of bytes to write must be divisible by 4.
	 */
	return flash_silabs_read_range_is_valid(offset, size) &&
	       IS_ALIGNED(offset, sizeof(uint32_t)) && IS_ALIGNED(size, 4);
}

static void flash_silabs_write_protection(bool enable)
{
	if (enable) {
		/* Lock the MSC module. */
		MSC->LOCK = 0;
	} else {
		/* Unlock the MSC module. */
		MSC->LOCK = MSC_LOCK_LOCKKEY_UNLOCK;
	}
}

static int flash_silabs_read(const struct device *dev, off_t offset, void *data, size_t size)
{
	if (!flash_silabs_read_range_is_valid(offset, size)) {
		return -EINVAL;
	}

	if (!size) {
		return 0;
	}

	memcpy(data, (uint8_t *)DT_REG_ADDR(SOC_NV_FLASH_NODE) + offset, size);

	return 0;
}

static int flash_silabs_write(const struct device *dev, off_t offset, const void *data, size_t size)
{
	struct flash_silabs_data *const dev_data = dev->data;
	void *address;
	int ret = 0;

	if (!flash_silabs_write_range_is_valid(offset, size)) {
		return -EINVAL;
	}

	if (!size) {
		return 0;
	}

	k_sem_take(&dev_data->lock, K_FOREVER);
	flash_silabs_write_protection(false);

	address = (uint8_t *)DT_REG_ADDR(SOC_NV_FLASH_NODE) + offset;
	ret = MSC_WriteWord(address, data, size);
	if (ret < 0) {
		ret = -EIO;
	}

	flash_silabs_write_protection(true);
	k_sem_give(&dev_data->lock);

	return ret;
}

static int flash_silabs_erase_block(off_t offset, size_t size)
{
	void *address;
	int ret = 0;

	for (off_t tmp = offset; tmp < offset + size;
	     tmp += DT_PROP(SOC_NV_FLASH_NODE, erase_block_size)) {
		address = (uint8_t *)DT_REG_ADDR(SOC_NV_FLASH_NODE) + tmp;
		ret = MSC_ErasePage(address);
		if (ret < 0) {
			ret = -EIO;
			break;
		}
	}

	return ret;
}

static int flash_silabs_erase(const struct device *dev, off_t offset, size_t size)
{
	struct flash_silabs_data *const dev_data = dev->data;
	int ret;

	if (!flash_silabs_read_range_is_valid(offset, size)) {
		return -EINVAL;
	}

	if (!IS_ALIGNED(offset, DT_PROP(SOC_NV_FLASH_NODE, erase_block_size))) {
		LOG_ERR("offset 0x%lx: not on a page boundary", (long)offset);
		return -EINVAL;
	}

	if (!IS_ALIGNED(size, DT_PROP(SOC_NV_FLASH_NODE, erase_block_size))) {
		LOG_ERR("size %zu: not multiple of a page size", size);
		return -EINVAL;
	}

	if (!size) {
		return 0;
	}

	k_sem_take(&dev_data->lock, K_FOREVER);
	flash_silabs_write_protection(false);

	ret = flash_silabs_erase_block(offset, size);

	flash_silabs_write_protection(true);
	k_sem_give(&dev_data->lock);

	return ret;
}

#if CONFIG_FLASH_PAGE_LAYOUT
void flash_silabs_page_layout(const struct device *dev, const struct flash_pages_layout **layout,
			      size_t *layout_size)
{
	const struct flash_silabs_config *const config = dev->config;

	*layout = &config->page_layout;
	*layout_size = 1;
}
#endif

static const struct flash_parameters *flash_silabs_get_parameters(const struct device *dev)
{
	const struct flash_silabs_config *const config = dev->config;

	return &config->flash_parameters;
}

static int flash_silabs_get_size(const struct device *dev, uint64_t *size)
{
	ARG_UNUSED(dev);

	*size = (uint64_t)DT_REG_SIZE(SOC_NV_FLASH_NODE);

	return 0;
}

static int flash_silabs_init(const struct device *dev)
{
	struct flash_silabs_data *const dev_data = dev->data;

	MSC_Init();

	/* Lock the MSC module. */
	flash_silabs_write_protection(true);

	LOG_INF("Device %s initialized", dev->name);

	return 0;
}

static DEVICE_API(flash, flash_silabs_driver_api) = {
	.read = flash_silabs_read,
	.write = flash_silabs_write,
	.erase = flash_silabs_erase,
	.get_parameters = flash_silabs_get_parameters,
	.get_size = flash_silabs_get_size,
#ifdef CONFIG_FLASH_PAGE_LAYOUT
	.page_layout = flash_silabs_page_layout,
#endif
};

/* clang-format off */
static struct flash_silabs_data flash_silabs_data_0 = {
	.lock = Z_SEM_INITIALIZER(flash_silabs_data_0.lock, 1, 1),
};

static const struct flash_silabs_config flash_silabs_config_0 = {
	.flash_parameters = {
		.write_block_size = DT_PROP(SOC_NV_FLASH_NODE, write_block_size),
		.erase_value = 0xff,
	},
	.page_layout = {
		.pages_count = DT_REG_SIZE(SOC_NV_FLASH_NODE) /
				DT_PROP(SOC_NV_FLASH_NODE, erase_block_size),
		.pages_size = DT_PROP(SOC_NV_FLASH_NODE, erase_block_size),
	},
};
/* clang-format on */

DEVICE_DT_INST_DEFINE(0, flash_silabs_init, NULL, &flash_silabs_data_0, &flash_silabs_config_0,
		      POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, &flash_silabs_driver_api);
+8 −0
Changes for dts/bindings/flash_controller/silabs,series2-flash-controller.yaml: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright (c) 2025 Silicon Laboratories Inc.
# SPDX-License-Identifier: Apache-2.0

description: Silicon Labs Series 2 flash controller

compatible: "silabs,series2-flash-controller"

include: flash-controller.yaml