/*
 * Copyright (c) 2023 Andes Technology Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT andestech_qspi_nor

#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/init.h>
#include <string.h>
#include <zephyr/logging/log.h>

#include "flash_andes_qspi.h"
#include "spi_nor.h"
#include "jesd216.h"
#include "flash_priv.h"

LOG_MODULE_REGISTER(flash_andes, CONFIG_FLASH_LOG_LEVEL);

/* ATCSPI200 transfer count is limited to 512 bytes. */
#define MAX_TRANSFER_CNT (512)

/* Indicates that an access command includes bytes for the address.
 * If not provided the opcode is not followed by address bytes.
 */
#define ANDES_ACCESS_ADDRESSED BIT(0)

/* Indicates that an access command is performing a write.  If not
 * provided access is a read.
 */
#define ANDES_ACCESS_WRITE BIT(7)

#define flash_andes_qspi_cmd_read(dev, opcode, dest, length)                   \
	flash_andes_qspi_access(dev, opcode, 0, 0, dest, length)
#define flash_andes_qspi_cmd_addr_read(dev, opcode, addr, dest, length)        \
	flash_andes_qspi_access(dev, opcode, ANDES_ACCESS_ADDRESSED, addr,     \
				dest, length)
#define flash_andes_qspi_cmd_write(dev, opcode)                                \
	flash_andes_qspi_access(dev, opcode, ANDES_ACCESS_WRITE, 0, NULL, 0)
#define flash_andes_qspi_cmd_addr_write(dev, opcode, addr, src, length)        \
	flash_andes_qspi_access(dev, opcode,                                   \
				ANDES_ACCESS_WRITE | ANDES_ACCESS_ADDRESSED,   \
				addr, (void *)src, length)


typedef void (*flash_andes_qspi_config_func_t)(void);
struct flash_andes_qspi_config {
	flash_andes_qspi_config_func_t cfg_func;
	uint32_t base;
	uint32_t irq_num;
	struct flash_parameters parameters;
	bool qspi_cfg_rom;
#if defined(CONFIG_FLASH_ANDES_QSPI_SFDP_DEVICETREE)
	uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];
	uint32_t flash_size;
	uint8_t bfp_len;
	const struct jesd216_bfp *bfp;
#ifdef CONFIG_FLASH_PAGE_LAYOUT
	struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_FLASH_ANDES_QSPI_SFDP_DEVICETREE */
};

struct flash_andes_qspi_data {
	struct k_sem sem;
	struct k_sem device_sync_sem;
	uint32_t tx_fifo_size;
	uint32_t rx_fifo_size;
	uint8_t *tx_buf;
	uint8_t *rx_buf;
	uint32_t tx_len;
	uint32_t rx_len;
	uint32_t tx_ptr; /* write pointer */
	uint32_t rx_ptr; /* read pointer */
	struct jesd216_erase_type erase_types[JESD216_NUM_ERASE_TYPES];
	uint16_t page_size;
#ifdef CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME
	uint32_t flash_size;
#ifdef CONFIG_FLASH_PAGE_LAYOUT
	struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif
};

static int flash_andes_qspi_write_protection_set(const struct device *dev,
						 bool write_protect);

/* Get pointer to array of supported erase types. */
static inline const struct jesd216_erase_type *
dev_erase_types(const struct device *dev)
{
	const struct flash_andes_qspi_data *dev_data = dev->data;

	return dev_data->erase_types;
}

/* Get the size of the flash device. */
static inline uint32_t dev_flash_size(const struct device *dev)
{
#ifdef CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME
	const struct flash_andes_qspi_data *dev_data = dev->data;

	return dev_data->flash_size;
#else /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */
	const struct flash_andes_qspi_config *config = dev->config;

	return config->flash_size;
#endif /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */
}

/* Get the flash device page size. */
static inline uint16_t dev_page_size(const struct device *dev)
{
	const struct flash_andes_qspi_data *dev_data = dev->data;

	return dev_data->page_size;
}

/*
 * @brief Send an SPI command
 *
 * @param dev Device struct
 * @param opcode The command to send
 * @param access flags that determine how the command is constructed.
 * @param addr The address to send
 * @param data The buffer to store or read the value
 * @param length The size of the buffer
 * @return 0 on success
 */
static int flash_andes_qspi_access(const struct device *const dev,
				   uint8_t opcode, uint8_t access, off_t addr,
				   void *data, size_t length)
{
	struct flash_andes_qspi_data *dev_data = dev->data;
	const struct flash_andes_qspi_config *config = dev->config;
	uint32_t base = config->base;

	bool is_addressed = (access & ANDES_ACCESS_ADDRESSED) != 0U;
	bool is_write = (access & ANDES_ACCESS_WRITE) != 0U;
	int ret = 0;

	uint32_t tctrl, int_msk;

	/* Command phase enable */
	tctrl = TCTRL_CMD_EN_MSK;
	if (is_addressed) {
		/* Enable and set ADDR len */
		sys_write32((sys_read32(QSPI_TFMAT(base)) |
			    (0x2 << TFMAT_ADDR_LEN_OFFSET)), QSPI_TFMAT(base));
		sys_write32(addr, QSPI_ADDR(base));
		/* Address phase enable */
		tctrl |= TCTRL_ADDR_EN_MSK;
	}

	if (length == 0) {
		if ((opcode == FLASH_ANDES_CMD_4PP) ||
		    (opcode == FLASH_ANDES_CMD_4READ)) {
			goto exit;
		}
		tctrl |= TRNS_MODE_NONE_DATA;
		int_msk = IEN_END_MSK;
	} else if (is_write) {
		dev_data->tx_ptr = 0;
		dev_data->tx_buf = (uint8_t *)data;
		dev_data->tx_len = length;

		tctrl |= (TRNS_MODE_WRITE_ONLY |
			 ((length - 1) << TCTRL_WR_TCNT_OFFSET));
		int_msk = IEN_TX_FIFO_MSK | IEN_END_MSK;
	} else {
		dev_data->rx_ptr = 0;
		dev_data->rx_buf = (uint8_t *)data;

		tctrl |= (TRNS_MODE_READ_ONLY |
			 ((length - 1) << TCTRL_RD_TCNT_OFFSET));
		int_msk = IEN_RX_FIFO_MSK | IEN_END_MSK;
	}

	switch (opcode) {
	case FLASH_ANDES_CMD_4PP:
		tctrl = ((tctrl & ~TCTRL_TRNS_MODE_MSK)	|
			DUAL_IO_MODE			|
			TCTRL_ADDR_FMT_MSK		|
			TCTRL_ADDR_EN_MSK		|
			TRNS_MODE_WRITE_ONLY);
		break;
	case FLASH_ANDES_CMD_4READ:
		tctrl = ((tctrl & ~TCTRL_TRNS_MODE_MSK)	|
			DUAL_IO_MODE			|
			TCTRL_ADDR_FMT_MSK		|
			TCTRL_ADDR_EN_MSK		|
			TRNS_MODE_DUMMY_READ		|
			DUMMY_CNT_3);
		break;
	case JESD216_CMD_READ_SFDP:
		tctrl = ((tctrl & ~TCTRL_TRNS_MODE_MSK)	|
			TCTRL_ADDR_EN_MSK		|
			TRNS_MODE_DUMMY_READ);
		break;
	default:
		break;
	}

	sys_write32(tctrl, QSPI_TCTRL(base));
	/* Enable TX/RX FIFO interrupts */
	sys_write32(int_msk, QSPI_INTEN(base));
	/* write CMD register to send command*/
	sys_write32(opcode, QSPI_CMD(base));
	k_sem_take(&dev_data->device_sync_sem, K_FOREVER);
exit:
	return ret;
}

/* Everything necessary to acquire owning access to the device. */
static void acquire_device(const struct device *dev)
{
	struct flash_andes_qspi_data *dev_data = dev->data;

	k_sem_take(&dev_data->sem, K_FOREVER);
}

/* Everything necessary to release access to the device. */
static void release_device(const struct device *dev)
{
	struct flash_andes_qspi_data *dev_data = dev->data;

	k_sem_give(&dev_data->sem);
}

/**
 * @brief Wait until the flash is ready
 *
 * @param dev The device structure
 * @return 0 on success, negative errno code otherwise
 */
static int flash_andes_qspi_wait_until_ready(const struct device *dev)
{
	int ret;
	uint8_t reg;

	do {
		ret = flash_andes_qspi_cmd_read(dev,
		FLASH_ANDES_CMD_RDSR, &reg, 1);
	} while (!ret && (reg & FLASH_ANDES_WIP_BIT));

	return ret;
}

#if defined(CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME) || \
	defined(CONFIG_FLASH_JESD216_API)
/*
 * @brief Read content from the SFDP hierarchy
 *
 * @note The device must be externally acquired before invoking this
 * function.
 *
 * @param dev Device struct
 * @param addr The address to send
 * @param data The buffer to store or read the value
 * @param length The size of the buffer
 * @return 0 on success, negative errno code otherwise
 */
static int read_sfdp(const struct device *const dev,
		     off_t addr, void *data, size_t length)
{
	/* READ_SFDP requires a 24-bit address followed by a single
	 * byte for a wait state.  This is effected by using 32-bit
	 * address by shifting the 24-bit address up 8 bits.
	 */
	return flash_andes_qspi_access(dev, JESD216_CMD_READ_SFDP,
				       ANDES_ACCESS_ADDRESSED,
				       addr, data, length);
}
#endif /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */

/**
 * @brief Write the status register.
 *
 * @note The device must be externally acquired before invoking this
 * function.
 *
 * @param dev Device struct
 * @param sr The new value of the status register
 *
 * @return 0 on success or a negative error code.
 */
static int flash_andes_qspi_wrsr(const struct device *dev,
				 uint8_t sr)
{
	int ret = flash_andes_qspi_cmd_write(dev, FLASH_ANDES_CMD_WREN);

	if (ret == 0) {
		ret = flash_andes_qspi_access(dev, FLASH_ANDES_CMD_WRSR,
					      ANDES_ACCESS_WRITE, 0, &sr,
					      sizeof(sr));
		flash_andes_qspi_wait_until_ready(dev);
	}

	return ret;
}

static int flash_andes_qspi_read(const struct device *dev,
				 off_t addr, void *dest, size_t size)
{
	const size_t flash_size = dev_flash_size(dev);
	size_t to_read = size;

	int ret;

	/* should be between 0 and flash size */
	if ((addr < 0 || addr >= flash_size || ((flash_size - addr) < size))) {
		return -EINVAL;
	}

	if (size == 0) {
		return 0;
	}

	acquire_device(dev);

	do {
		/* Get the adequate size to receive */
		to_read = MIN(MAX_TRANSFER_CNT, size);

		ret = flash_andes_qspi_cmd_addr_read(dev,
			FLASH_ANDES_CMD_4READ, addr, dest, to_read);

		if (ret != 0) {
			break;
		}

		size -= to_read;
		dest = (uint8_t *)dest + to_read;
		addr += to_read;

		flash_andes_qspi_wait_until_ready(dev);
	} while (size > 0);

	release_device(dev);
	return ret;
}

static int flash_andes_qspi_write(const struct device *dev, off_t addr,
				  const void *src, size_t size)
{
	const size_t flash_size = dev_flash_size(dev);
	const uint16_t page_size = dev_page_size(dev);
	size_t to_write = size;
	int ret = 0;

	/* should be between 0 and flash size */
	if ((addr < 0 || addr >= flash_size || ((flash_size - addr) < size))) {
		return -EINVAL;
	}

	if (size == 0) {
		return 0;
	}

	acquire_device(dev);

	ret = flash_andes_qspi_write_protection_set(dev, false);

	if (ret != 0) {
		goto out;
	}

	do {
		/* Get the adequate size to send*/
		to_write = MIN(page_size - (addr % page_size), size);

		flash_andes_qspi_cmd_write(dev, FLASH_ANDES_CMD_WREN);

		ret = flash_andes_qspi_cmd_addr_write(dev,
			FLASH_ANDES_CMD_4PP, addr, src, to_write);

		if (ret != 0) {
			break;
		}

		size -= to_write;
		src = (const uint8_t *)src + to_write;
		addr += to_write;

		flash_andes_qspi_wait_until_ready(dev);
	} while (size > 0);


	int ret2 = flash_andes_qspi_write_protection_set(dev, true);

	if (!ret) {
		ret = ret2;
	}

out:
	release_device(dev);
	return ret;
}

static int flash_andes_qspi_erase(const struct device *dev,
				  off_t addr, size_t size)
{
	const size_t flash_size = dev_flash_size(dev);
	int ret = 0;

	/* erase area must be subregion of device */
	if ((addr < 0 || addr >= flash_size || ((flash_size - addr) < size))) {
		return -EINVAL;
	}

	if (size == 0) {
		return 0;
	}

	/* address must be sector-aligned */
	if (!SPI_NOR_IS_SECTOR_ALIGNED(addr)) {
		return -EINVAL;
	}

	/* size must be a multiple of sectors */
	if ((size % SPI_NOR_SECTOR_SIZE) != 0) {
		return -EINVAL;
	}

	acquire_device(dev);

	ret = flash_andes_qspi_write_protection_set(dev, false);

	if (ret != 0) {
		goto out;
	}

	if (size == flash_size) {
		/* chip erase */
		flash_andes_qspi_cmd_write(dev, FLASH_ANDES_CMD_CE);
		size -= flash_size;
		flash_andes_qspi_wait_until_ready(dev);
	}

	while (size > 0) {
		const struct jesd216_erase_type *erase_types =
						dev_erase_types(dev);
		const struct jesd216_erase_type *bet = NULL;

		for (uint8_t ei = 0; ei < JESD216_NUM_ERASE_TYPES; ++ei) {
			const struct jesd216_erase_type *etp =
						&erase_types[ei];

			if ((etp->exp != 0) &&
				SPI_NOR_IS_ALIGNED(addr, etp->exp) &&
				SPI_NOR_IS_ALIGNED(size, etp->exp) &&
				((bet == NULL) || (etp->exp > bet->exp))) {
				bet = etp;
			}
		}

		if (bet != NULL) {
			flash_andes_qspi_cmd_addr_write(dev, bet->cmd,
							addr, NULL, 0);
			addr += BIT(bet->exp);
			size -= BIT(bet->exp);
		} else {
			LOG_DBG("Can't erase %zu at 0x%lx",
				size, (long)addr);
			ret = -EINVAL;
			break;
		}

		flash_andes_qspi_wait_until_ready(dev);
	}

	int ret2 = flash_andes_qspi_write_protection_set(dev, true);

	if (!ret) {
		ret = ret2;
	}

out:
	release_device(dev);

	return ret;
}

static int flash_andes_qspi_write_protection_set(const struct device *dev,
						 bool write_protect)
{
	return flash_andes_qspi_cmd_write(dev, (write_protect) ?
				FLASH_ANDES_CMD_WRDI : FLASH_ANDES_CMD_WREN);
}

#if defined(CONFIG_FLASH_JESD216_API)

static int flash_andes_qspi_sfdp_read(const struct device *dev, off_t addr,
				      void *dest, size_t size)
{
	acquire_device(dev);

	int ret = read_sfdp(dev, addr, dest, size);

	release_device(dev);

	return ret;
}

#endif /* CONFIG_FLASH_JESD216_API */

static int flash_andes_qspi_read_jedec_id(const struct device *dev,
					  uint8_t *id)
{
	if (id == NULL) {
		return -EINVAL;
	}

	acquire_device(dev);

	int ret = flash_andes_qspi_cmd_read(dev, FLASH_ANDES_CMD_RDID, id, 3);

	release_device(dev);

	return ret;
}

static int spi_nor_process_bfp(const struct device *dev,
			       const struct jesd216_param_header *php,
			       const struct jesd216_bfp *bfp)
{
	struct flash_andes_qspi_data *dev_data = dev->data;
	struct jesd216_erase_type *etp = dev_data->erase_types;
	const size_t flash_size = jesd216_bfp_density(bfp) / 8U;

	LOG_DBG("%s: %u MiBy flash", dev->name, (uint32_t)(flash_size >> 20));

	/* Copy over the erase types, preserving their order.  (The
	 * Sector Map Parameter table references them by index.)
	 */
	memset(dev_data->erase_types, 0, sizeof(dev_data->erase_types));
	for (uint8_t ti = 1; ti <= ARRAY_SIZE(dev_data->erase_types); ++ti) {
		if (jesd216_bfp_erase(bfp, ti, etp) == 0) {
			LOG_DBG("Erase %u with %02x",
				(uint32_t)BIT(etp->exp), etp->cmd);
		}
		++etp;
	}

	dev_data->page_size = jesd216_bfp_page_size(php, bfp);
#ifdef CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME
	dev_data->flash_size = flash_size;
#else /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */
	if (flash_size != dev_flash_size(dev)) {
		LOG_ERR("BFP flash size mismatch with devicetree");
		return -EINVAL;
	}
#endif /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */

	return 0;
}

static int spi_nor_process_sfdp(const struct device *dev)
{
	int ret;

#if defined(CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME)

	const uint8_t decl_nph = 2;
	union {
		/* We only process BFP so use one parameter block */
		uint8_t raw[JESD216_SFDP_SIZE(decl_nph)];
		struct jesd216_sfdp_header sfdp;
	} u_header;
	const struct jesd216_sfdp_header *hp = &u_header.sfdp;

	ret = read_sfdp(dev, 0, u_header.raw, sizeof(u_header.raw));
	if (ret != 0) {
		LOG_ERR("SFDP read failed: %d", ret);
		return ret;
	}

	uint32_t magic = jesd216_sfdp_magic(hp);

	if (magic != JESD216_SFDP_MAGIC) {
		LOG_ERR("SFDP magic %08x invalid", magic);
		return -EINVAL;
	}

	LOG_DBG("%s: SFDP v %u.%u AP %x with %u PH", dev->name,
		hp->rev_major, hp->rev_minor, hp->access, 1 + hp->nph);

	const struct jesd216_param_header *php = hp->phdr;
	const struct jesd216_param_header *phpe =
		php + MIN(decl_nph, 1 + hp->nph);

	while (php != phpe) {
		uint16_t id = jesd216_param_id(php);

		LOG_DBG("PH%zu: %04x rev %u.%u: %u DW @ %x",
			(php - hp->phdr), id, php->rev_major, php->rev_minor,
			php->len_dw, jesd216_param_addr(php));

		if (id == JESD216_SFDP_PARAM_ID_BFP) {
			union {
				uint32_t dw[MIN(php->len_dw, 20)];
				struct jesd216_bfp bfp;
			} u_param;
			const struct jesd216_bfp *bfp = &u_param.bfp;

			ret = read_sfdp(dev,
				jesd216_param_addr(php), u_param.dw, sizeof(u_param.dw));

			if (ret != 0) {
				break;
			}

			ret = spi_nor_process_bfp(dev, php, bfp);

			if (ret != 0) {
				break;
			}
		}
		++php;
	}
#elif defined(CONFIG_FLASH_ANDES_QSPI_SFDP_DEVICETREE)
	/* For devicetree we need to synthesize a parameter header and
	 * process the stored BFP data as if we had read it.
	 */
	const struct flash_andes_qspi_config *config = dev->config;
	struct jesd216_param_header bfp_hdr = {
		.len_dw = config->bfp_len,
	};

	ret = spi_nor_process_bfp(dev, &bfp_hdr, config->bfp);
#else
#error Unhandled SFDP choice
#endif
	return ret;
}

#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static int setup_pages_layout(const struct device *dev)
{
	int ret = 0;

#if defined(CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME)

	struct flash_andes_qspi_data *dev_data = dev->data;
	const size_t flash_size = dev_flash_size(dev);
	const uint32_t layout_page_size =
		CONFIG_FLASH_ANDES_QSPI_LAYOUT_PAGE_SIZE;
	uint8_t exponent = 0;

	/* Find the smallest erase size. */
	for (size_t i = 0; i < ARRAY_SIZE(dev_data->erase_types); ++i) {
		const struct jesd216_erase_type *etp =
				&dev_data->erase_types[i];

		if ((etp->cmd != 0) &&
		   ((exponent == 0) || (etp->exp < exponent))) {
			exponent = etp->exp;
		}
	}

	if (exponent == 0) {
		return -ENOTSUP;
	}

	uint32_t erase_size = BIT(exponent);

	/* Error if layout page size is not a multiple of smallest
	 * erase size.
	 */
	if ((layout_page_size % erase_size) != 0) {
		LOG_ERR("layout page %u not compatible with erase size %u",
			layout_page_size, erase_size);
		return -EINVAL;
	}

	/* Warn but accept layout page sizes that leave inaccessible
	 * space.
	 */
	if ((flash_size % layout_page_size) != 0) {
		LOG_WRN("layout page %u wastes space with device size %zu",
			layout_page_size, flash_size);
	}

	dev_data->layout.pages_size = layout_page_size;
	dev_data->layout.pages_count = flash_size / layout_page_size;
	LOG_DBG("layout %zu x %zu By pages", dev_data->layout.pages_count,
		dev_data->layout.pages_size);

#elif defined(CONFIG_FLASH_ANDES_QSPI_SFDP_DEVICETREE)
	const struct flash_andes_qspi_config *config = dev->config;
	const struct flash_pages_layout *layout = &config->layout;
	const size_t flash_size = dev_flash_size(dev);
	size_t layout_size = layout->pages_size * layout->pages_count;

	if (!SPI_NOR_IS_SECTOR_ALIGNED(layout->pages_size)) {
		LOG_ERR("ANDES_QSPI_FLASH_LAYOUT_PAGE_SIZE must be "
			"multiple of 4096");
		return -EINVAL;
	}

	if (flash_size != layout_size) {
		LOG_ERR("device size %zu mismatch %zu * %zu By pages",
			flash_size, layout->pages_count, layout->pages_size);
		return -EINVAL;
	}
#else /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */
#error Unhandled SFDP choice
#endif /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */
	return ret;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */

static int qspi_andes_configure(const struct device *dev)
{
	const struct flash_andes_qspi_config *config = dev->config;
	uint32_t base = config->base;

	/* Setting the divisor value to 0xff indicates the SCLK
	 * frequency should be the same as the spi_clock frequency.
	 */
	sys_set_bits(QSPI_TIMIN(base), TIMIN_SCLK_DIV_MSK);

	/* Set Master mode */
	sys_clear_bits(QSPI_TFMAT(base), TFMAT_SLVMODE_MSK);

	/* Disable data merge mode */
	sys_clear_bits(QSPI_TFMAT(base), TFMAT_DATA_MERGE_MSK);

	/* Set data length */
	sys_clear_bits(QSPI_TFMAT(base), TFMAT_DATA_LEN_MSK);
	sys_set_bits(QSPI_TFMAT(base), (7 << TFMAT_DATA_LEN_OFFSET));

	/* Set TX/RX FIFO threshold */
	sys_clear_bits(QSPI_CTRL(base), CTRL_TX_THRES_MSK);
	sys_clear_bits(QSPI_CTRL(base), CTRL_RX_THRES_MSK);

	sys_set_bits(QSPI_CTRL(base), TX_FIFO_THRESHOLD);
	sys_set_bits(QSPI_CTRL(base), RX_FIFO_THRESHOLD);

	return 0;
}

static void qspi_andes_irq_handler(const struct device *dev)
{
	struct flash_andes_qspi_data *data = dev->data;
	const struct flash_andes_qspi_config *config = dev->config;
	uint32_t base = config->base;

	uint32_t i, intr_status, spi_status;
	uint32_t rx_data, cur_tx_fifo_num, cur_rx_fifo_num;
	uint32_t tx_num = 0, tx_data = 0;

	intr_status = sys_read32(QSPI_INTST(base));

	if ((intr_status & INTST_TX_FIFO_INT_MSK) &&
	    !(intr_status & INTST_END_INT_MSK)) {

		spi_status = sys_read32(QSPI_STAT(base));
		cur_tx_fifo_num = GET_TX_NUM(base);

		tx_num = data->tx_fifo_size - cur_tx_fifo_num;

		if (tx_num > data->tx_len) {
			tx_num = data->tx_len;
		}

		for (i = tx_num; i > 0; i--) {
			tx_data = data->tx_buf[data->tx_ptr];
			sys_write32(tx_data, QSPI_DATA(base));
			data->tx_ptr++;
			if (data->tx_ptr == data->tx_len) {
				sys_clear_bits(QSPI_INTEN(base), IEN_TX_FIFO_MSK);
				break;
			}
		}
		sys_write32(INTST_TX_FIFO_INT_MSK, QSPI_INTST(base));
	}

	if (intr_status & INTST_RX_FIFO_INT_MSK) {
		cur_rx_fifo_num = GET_RX_NUM(base);

		for (i = cur_rx_fifo_num; i > 0; i--) {
			rx_data = sys_read32(QSPI_DATA(base));
			data->rx_buf[data->rx_ptr] = rx_data;
			data->rx_ptr++;
			if (data->rx_ptr == data->rx_len) {
				sys_clear_bits(QSPI_INTEN(base), IEN_RX_FIFO_MSK);
				break;
			}
		}
		sys_write32(INTST_RX_FIFO_INT_MSK, QSPI_INTST(base));
	}

	if (intr_status & INTST_END_INT_MSK) {

		/* Clear end interrupt */
		sys_write32(INTST_END_INT_MSK, QSPI_INTST(base));

		/* Disable all SPI interrupts */
		sys_write32(0, QSPI_INTEN(base));

		k_sem_give(&data->device_sync_sem);
	}
}

/**
 * @brief Initialize and configure the flash
 *
 * @param name The flash name
 * @return 0 on success, negative errno code otherwise
 */
static int flash_andes_qspi_init(const struct device *dev)
{
	const struct flash_andes_qspi_config *config = dev->config;
	struct flash_andes_qspi_data *dev_data = dev->data;
	uint32_t base = config->base;

	uint8_t ret, reg = (0x1UL << 6);
	uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];

	/* we should not configure the device we are running on */
	if (config->qspi_cfg_rom) {
		return -EINVAL;
	}

	k_sem_init(&dev_data->sem, 1, 1);
	k_sem_init(&dev_data->device_sync_sem, 0, 1);

	/* Get the TX/RX FIFO size of this device */
	dev_data->tx_fifo_size = TX_FIFO_SIZE(base);
	dev_data->rx_fifo_size = RX_FIFO_SIZE(base);

	config->cfg_func();
	irq_enable(config->irq_num);

	qspi_andes_configure(dev);

	ret = flash_andes_qspi_read_jedec_id(dev, jedec_id);
	if (ret != 0) {
		LOG_ERR("JEDEC ID read failed: %d", ret);
		return -ENODEV;
	}

#ifndef CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME

	if (memcmp(jedec_id, config->jedec_id, sizeof(jedec_id)) != 0) {
		LOG_ERR("Device id %02x %02x %02x does not match config"
			"%02x %02x %02x", jedec_id[0], jedec_id[1], jedec_id[2],
			config->jedec_id[0], config->jedec_id[1], config->jedec_id[2]);
		return -EINVAL;
	}
#endif

	ret = spi_nor_process_sfdp(dev);
	if (ret != 0) {
		LOG_ERR("SFDP read failed: %d", ret);
		return -ENODEV;
	}

#if defined(CONFIG_FLASH_PAGE_LAYOUT)
	ret = setup_pages_layout(dev);
	if (ret != 0) {
		LOG_ERR("layout setup failed: %d", ret);
		return -ENODEV;
	}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */

	/* Set status register QE bit. */
	flash_andes_qspi_wrsr(dev, reg);

	return 0;
}

#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static void flash_andes_qspi_pages_layout(const struct device *dev,
				     const struct flash_pages_layout **layout,
				     size_t *layout_size)
{
#ifdef CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME
	const struct flash_andes_qspi_data *dev_data = dev->data;

	*layout = &dev_data->layout;
#else /* CONFIG_FLASH_ANDES_QSPI_SFDP_RUNTIME */
	const struct flash_andes_qspi_config *config = dev->config;

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


static const struct flash_parameters *
flash_andes_qspi_get_parameters(const struct device *dev)
{
	const struct flash_andes_qspi_config *config = dev->config;

	return &config->parameters;
}

static DEVICE_API(flash, flash_andes_qspi_api) = {
	.read = flash_andes_qspi_read,
	.write = flash_andes_qspi_write,
	.erase = flash_andes_qspi_erase,
	.get_parameters = flash_andes_qspi_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
	.page_layout = flash_andes_qspi_pages_layout,
#endif
#if defined(CONFIG_FLASH_JESD216_API)
	.sfdp_read = flash_andes_qspi_sfdp_read,
	.read_jedec_id = flash_andes_qspi_read_jedec_id,
#endif
};

#if (CONFIG_XIP)
#define QSPI_FLASH_BASE DT_REG_ADDR_BY_IDX(DT_PARENT(DT_CHOSEN(zephyr_flash)), 1)
#define QSPI_FLASH_SIZE DT_REG_SIZE_BY_IDX(DT_PARENT(DT_CHOSEN(zephyr_flash)), 1)
#define QSPI_FLASH_END (QSPI_FLASH_BASE + QSPI_FLASH_SIZE)

#if ((QSPI_FLASH_BASE <= CONFIG_FLASH_BASE_ADDRESS) && \
	(CONFIG_FLASH_BASE_ADDRESS <= QSPI_FLASH_END))
#define QSPI_CFG_ROM(node_id) DT_SAME_NODE(node_id, DT_CHOSEN(zephyr_flash))
#else
#define QSPI_CFG_ROM(node_id) false
#endif

#else /* CONFIG_XIP */
#define QSPI_CFG_ROM(node_id) false
#endif /* CONFIG_XIP */

#define LAYOUT_PAGES_PROP(n)						\
	IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT,				\
		(.layout = {						\
			.pages_count = ((DT_INST_PROP(n, size) / 8) /	\
				CONFIG_FLASH_ANDES_QSPI_LAYOUT_PAGE_SIZE), \
			.pages_size =					\
				CONFIG_FLASH_ANDES_QSPI_LAYOUT_PAGE_SIZE, \
		},							\
		))

#define ANDES_QSPI_SFDP_DEVICETREE_CONFIG(n)				\
	IF_ENABLED(CONFIG_FLASH_ANDES_QSPI_SFDP_DEVICETREE,		\
		(							\
		static const __aligned(4) uint8_t bfp_data_##n[] =	\
			DT_INST_PROP(n, sfdp_bfp);			\
		))

#define ANDES_QSPI_SFDP_DEVICETREE_PROP(n)				\
	IF_ENABLED(CONFIG_FLASH_ANDES_QSPI_SFDP_DEVICETREE,		\
		(.jedec_id = DT_INST_PROP(n, jedec_id),			\
		.flash_size = DT_INST_PROP(n, size) / 8,		\
		.bfp_len = sizeof(bfp_data_##n) / 4,			\
		.bfp = (const struct jesd216_bfp *)bfp_data_##n,	\
		LAYOUT_PAGES_PROP(n)					\
		))

#define FLASH_ANDES_QSPI_INIT(n)					\
	static struct flash_andes_qspi_data flash_andes_qspi_data_##n;	\
	ANDES_QSPI_SFDP_DEVICETREE_CONFIG(n)				\
									\
	static void flash_andes_qspi_configure_##n(void);		\
	static const struct flash_andes_qspi_config			\
		flash_andes_qspi_config_##n = {				\
			.cfg_func = flash_andes_qspi_configure_##n,	\
			.base = DT_REG_ADDR(DT_INST_BUS(n)),		\
			.irq_num = DT_IRQN(DT_INST_BUS(n)),		\
			.parameters = {					\
				.write_block_size = 1,			\
				.erase_value = 0xff			\
			},						\
			.qspi_cfg_rom = QSPI_CFG_ROM(DT_DRV_INST(n)), \
			ANDES_QSPI_SFDP_DEVICETREE_PROP(n)		\
		};							\
									\
	DEVICE_DT_INST_DEFINE(n,					\
			&flash_andes_qspi_init,				\
			NULL,						\
			&flash_andes_qspi_data_##n,			\
			&flash_andes_qspi_config_##n,			\
			POST_KERNEL,					\
			CONFIG_FLASH_ANDES_QSPI_INIT_PRIORITY,		\
			&flash_andes_qspi_api);				\
									\
	static void flash_andes_qspi_configure_##n(void)		\
	{								\
		IRQ_CONNECT(DT_IRQN(DT_INST_BUS(n)),			\
			DT_IRQ(DT_INST_BUS(n), priority),		\
			qspi_andes_irq_handler,				\
			DEVICE_DT_INST_GET(n),				\
			0);						\
	}								\

DT_INST_FOREACH_STATUS_OKAY(FLASH_ANDES_QSPI_INIT)
