/*
 * Copyright (c) 2019 Linaro Limited
 * Copyright (c) 2020 STMicroelectronics
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define LOG_DOMAIN flash_stm32wb
#define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_DOMAIN);

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <string.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/init.h>
#include <soc.h>
#include <zephyr/sys/__assert.h>

#include "flash_stm32.h"
#include "stm32_hsem.h"
#if defined(CONFIG_BT)
#include "shci.h"
#endif

#define STM32WBX_PAGE_SHIFT	12

/*
 * Up to 255 4K pages
 */
static uint32_t get_page(off_t offset)
{
	return offset >> STM32WBX_PAGE_SHIFT;
}

static inline void flush_cache(FLASH_TypeDef *regs)
{
	if (regs->ACR & FLASH_ACR_DCEN) {
		regs->ACR &= ~FLASH_ACR_DCEN;
		/* Datasheet: DCRST: Data cache reset
		 * This bit can be written only when the data cache is disabled
		 */
		regs->ACR |= FLASH_ACR_DCRST;
		regs->ACR &= ~FLASH_ACR_DCRST;
		regs->ACR |= FLASH_ACR_DCEN;
	}

	if (regs->ACR & FLASH_ACR_ICEN) {
		regs->ACR &= ~FLASH_ACR_ICEN;
		/* Datasheet: ICRST: Instruction cache reset :
		 * This bit can be written only when the instruction cache
		 * is disabled
		 */
		regs->ACR |= FLASH_ACR_ICRST;
		regs->ACR &= ~FLASH_ACR_ICRST;
		regs->ACR |= FLASH_ACR_ICEN;
	}
}

static int write_dword(const struct device *dev, off_t offset, uint64_t val)
{
	volatile uint32_t *flash = (uint32_t *)(offset + FLASH_STM32_BASE_ADDRESS);
	FLASH_TypeDef *regs = FLASH_STM32_REGS(dev);
	uint32_t tmp;
	int ret, rc;
	uint32_t cpu1_sem_status;
	uint32_t cpu2_sem_status = 0;
	uint32_t key;

	/* if the control register is locked, do not fail silently */
	if (regs->CR & FLASH_CR_LOCK) {
		return -EIO;
	}

	/* Check if this double word is erased and value isn't 0.
	 *
	 * It is allowed to write only zeros over an already written dword
	 * See 3.3.8 in reference manual.
	 */
	if ((flash[0] != 0xFFFFFFFFUL ||
	     flash[1] != 0xFFFFFFFFUL) && val != 0UL) {
		LOG_ERR("Word at offs %ld not erased", (long)offset);
		return -EIO;
	}

	ret = flash_stm32_check_status(dev);
	if (ret < 0) {
		return -EIO;
	}

	/* Implementation of STM32 AN5289, proposed in STM32WB Cube Application
	 * BLE_RfWithFlash
	 * https://github.com/STMicroelectronics/STM32CubeWB/tree/master/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash
	 */

	do {
		/**
		 * When the PESD bit mechanism is used by CPU2 to protect its
		 * timing, the PESD bit should be polled here.
		 * If the PESD is set, the CPU1 will be stalled when reading
		 * literals from an ISR that may occur after the flash
		 * processing has been requested but suspended due to the PESD
		 * bit.
		 *
		 * Note: This code is required only when the PESD mechanism is
		 * used to protect the CPU2 timing.
		 * However, keeping that code make it compatible with both
		 * mechanisms.
		 */
		while (LL_FLASH_IsActiveFlag_OperationSuspended()) {
			;
		}

		/* Enter critical section */
		key = irq_lock();

		/**
		 *  Depending on the application implementation, in case a
		 *  multitasking is possible with an OS, it should be checked
		 *  here if another task in the application disallowed flash
		 *  processing to protect some latency in critical code
		 *  execution.
		 *  When flash processing is ongoing, the CPU cannot access the
		 *  flash anymore.Trying to access the flash during that time
		 *  stalls the CPU.
		 *  The only way for CPU1 to disallow flash processing is to
		 *  take CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID.
		 */
		cpu1_sem_status = LL_HSEM_GetStatus(HSEM,
			CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID);
		if (cpu1_sem_status == 0) {
			/**
			 *  Check now if the CPU2 disallows flash processing to
			 *  protect its timing. If the semaphore is locked, the
			 *  CPU2 does not allow flash processing
			 *
			 *  Note: By default, the CPU2 uses the PESD mechanism
			 *  to protect its timing, therefore, it is useless to
			 *  get/release the semaphore.
			 *
			 *  However, keeping that code make it compatible with
			 *  both mechanisms.
			 *  The protection by semaphore is enabled on CPU2 side
			 *  with the command SHCI_C2_SetFlashActivityControl()
			 *
			 */
			cpu2_sem_status = LL_HSEM_1StepLock(HSEM,
				CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID);
			if (cpu2_sem_status == 0) {
				/**
				 * When CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is
				 * taken, it is allowed to only write one
				 * single 64bits data.
				 * When several 64bits data need to be erased,
				 * the application shall first exit from the
				 * critical section and try again.
				 */
				/* Set the PG bit */
				regs->CR |= FLASH_CR_PG;

				/* Flush the register write */
				tmp = regs->CR;

				/* Perform the data write operation at desired
				 * memory address
				 */
				flash[0] = (uint32_t)val;
				flash[1] = (uint32_t)(val >> 32);

				/**
				 *  Release the semaphore to give the
				 *  opportunity to CPU2 to protect its timing
				 *  versus the next flash operation by taking
				 *  this semaphore.
				 *  Note that the CPU2 is polling on this
				 *  semaphore so CPU1 shall release it as fast
				 *  as possible.
				 *  This is why this code is protected by a
				 *  critical section.
				 */
				LL_HSEM_ReleaseLock(HSEM,
					CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID,
					0);
			}
		}

		/* Exit critical section */
		irq_unlock(key);

	} while (cpu2_sem_status || cpu1_sem_status);

	/* Wait until the BSY bit is cleared */
	rc = flash_stm32_wait_flash_idle(dev);

	/* Clear the PG bit */
	regs->CR &= (~FLASH_CR_PG);

	return rc;
}

static int erase_page(const struct device *dev, uint32_t page)
{
	uint32_t cpu1_sem_status;
	uint32_t cpu2_sem_status = 0;
	uint32_t key;

	FLASH_TypeDef *regs = FLASH_STM32_REGS(dev);
	int rc;

	/* if the control register is locked, do not fail silently */
	if (regs->CR & FLASH_CR_LOCK) {
		return -EIO;
	}

	/* Check that no Flash memory operation is ongoing */
	rc = flash_stm32_wait_flash_idle(dev);
	if (rc < 0) {
		return rc;
	}

	/*
	 * If an erase operation in Flash memory also concerns data in the data
	 * or instruction cache, the user has to ensure that these data
	 * are rewritten before they are accessed during code execution.
	 */
	flush_cache(regs);

	/* Implementation of STM32 AN5289, proposed in STM32WB Cube Application
	 * BLE_RfWithFlash
	 * https://github.com/STMicroelectronics/STM32CubeWB/tree/master/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash
	 */

	do {
		/**
		 * When the PESD bit mechanism is used by CPU2 to protect its
		 * timing, the PESD bit should be polled here.
		 * If the PESD is set, the CPU1 will be stalled when reading
		 * literals from an ISR that may occur after the flash
		 * processing has been requested but suspended due to the PESD
		 * bit.
		 *
		 * Note: This code is required only when the PESD mechanism is
		 * used to protect the CPU2 timing.
		 * However, keeping that code make it compatible with both
		 * mechanisms.
		 */
		while (LL_FLASH_IsActiveFlag_OperationSuspended()) {
			;
		}

		/* Enter critical section */
		key = irq_lock();

		/**
		 *  Depending on the application implementation, in case a
		 *  multitasking is possible with an OS, it should be checked
		 *  here if another task in the application disallowed flash
		 *  processing to protect some latency in critical code
		 *  execution.
		 *  When flash processing is ongoing, the CPU cannot access the
		 *  flash anymore.Trying to access the flash during that time
		 *  stalls the CPU.
		 *  The only way for CPU1 to disallow flash processing is to
		 *  take CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID.
		 */
		cpu1_sem_status = LL_HSEM_GetStatus(HSEM,
			CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID);
		if (cpu1_sem_status == 0) {
			/**
			 *  Check now if the CPU2 disallows flash processing to
			 *  protect its timing. If the semaphore is locked, the
			 *  CPU2 does not allow flash processing
			 *
			 *  Note: By default, the CPU2 uses the PESD mechanism
			 *  to protect its timing, therefore, it is useless to
			 *  get/release the semaphore.
			 *
			 *  However, keeping that code make it compatible with
			 *  both mechanisms.
			 *  The protection by semaphore is enabled on CPU2 side
			 *  with the command SHCI_C2_SetFlashActivityControl()
			 *
			 */
			cpu2_sem_status = LL_HSEM_1StepLock(HSEM,
				CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID);
			if (cpu2_sem_status == 0) {
				/**
				 * When CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is
				 * taken, it is allowed to only erase one
				 * sector.
				 * When several sectors need to be erased,
				 * the application shall first exit from the
				 * critical section and try again.
				 */
				regs->CR |= FLASH_CR_PER;
				regs->CR &= ~FLASH_CR_PNB_Msk;
				regs->CR |= page << FLASH_CR_PNB_Pos;

				regs->CR |= FLASH_CR_STRT;

				/**
				 *  Release the semaphore to give the
				 *  opportunity to CPU2 to protect its timing
				 *  versus the next flash operation by taking
				 *  this semaphore.
				 *  Note that the CPU2 is polling on this
				 *  semaphore so CPU1 shall release it as fast
				 *  as possible.
				 *  This is why this code is protected by a
				 *  critical section.
				 */
				LL_HSEM_ReleaseLock(HSEM,
					CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID,
					0);
			}
		}

		/* Exit critical section */
		irq_unlock(key);

	} while (cpu2_sem_status || cpu1_sem_status);


	/* 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(const struct device *dev,
				 unsigned int offset,
				 unsigned int len)
{
	int i, rc = 0;

#if defined(CONFIG_BT)
	/**
	 *  Notify the CPU2 that some flash erase activity may be executed
	 *  On reception of this command, the CPU2 enables the BLE timing
	 *  protection versus flash erase processing.
	 *  The Erase flash activity will be executed only when the BLE RF is
	 *  idle for at least 25ms.
	 *  The CPU2 will prevent all flash activity (write or erase) in all
	 *  cases when the BL RF Idle is shorter than 25ms.
	 */
	SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON);
#endif /* CONFIG_BT */

	i = get_page(offset);
	for (; i <= get_page(offset + len - 1) ; ++i) {
		rc = erase_page(dev, i);
		if (rc < 0) {
			break;
		}
	}

#if defined(CONFIG_BT)
	/**
	 *  Notify the CPU2 there will be no request anymore to erase the flash
	 *  On reception of this command, the CPU2 disables the BLE timing
	 *  protection versus flash erase processing
	 */
	SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF);
#endif /* CONFIG_BT */

	return rc;
}

int flash_stm32_write_range(const struct device *dev, unsigned int offset,
			    const void *data, unsigned int len)
{
	int i, rc = 0;

	for (i = 0; i < len; i += 8, offset += 8U) {
		rc = write_dword(dev, offset,
				UNALIGNED_GET((const uint64_t *) data + (i >> 3)));
		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 stm32wb_flash_layout = {
		.pages_count = 0,
		.pages_size = 0,
	};

	ARG_UNUSED(dev);

	if (stm32wb_flash_layout.pages_count == 0) {
		stm32wb_flash_layout.pages_count = FLASH_SIZE / FLASH_PAGE_SIZE;
		stm32wb_flash_layout.pages_size = FLASH_PAGE_SIZE;
	}

	*layout = &stm32wb_flash_layout;
	*layout_size = 1;
}

int flash_stm32_check_status(const struct device *dev)
{
	FLASH_TypeDef *regs = FLASH_STM32_REGS(dev);
	uint32_t error = 0;

	/* Save Flash errors */
	error = (regs->SR & FLASH_FLAG_SR_ERRORS);
	error |= (regs->ECCR & FLASH_FLAG_ECCC);

	/* Clear systematic Option and Engineering bits validity error */
	if (error & FLASH_FLAG_OPTVERR) {
		regs->SR |= FLASH_FLAG_SR_ERRORS;
		return 0;
	}

	if (error) {
		return -EIO;
	}

	return 0;
}
