/*
 * Copyright (c) 2025 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef __FLASH_MSPI_NOR_QUIRKS_H__
#define __FLASH_MSPI_NOR_QUIRKS_H__

/* Flash chip specific quirks */
struct flash_mspi_nor_quirks {
	/* Called after switching to default IO mode. */
	int (*post_switch_mode)(const struct device *dev);
};

/* Extend this macro when adding new flash chip with quirks */
#define FLASH_MSPI_QUIRKS_GET(node)						\
	COND_CODE_1(DT_NODE_HAS_COMPAT_STATUS(node, mxicy_mx25r, okay),		\
		    (&flash_quirks_mxicy_mx25r),				\
	(COND_CODE_1(DT_NODE_HAS_COMPAT_STATUS(node, mxicy_mx25u, okay),	\
		    (&flash_quirks_mxicy_mx25u),				\
		    (NULL))))

#if DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25r)

#define MXICY_MX25R_LH_MASK BIT(1)
#define MXICY_MX25R_QE_MASK BIT(6)
#define MXICY_MX25R_REGS_LEN 3

static uint8_t mxicy_mx25r_hp_payload[MXICY_MX25R_REGS_LEN] = {
	MXICY_MX25R_QE_MASK, 0x0, MXICY_MX25R_LH_MASK
};

/* For quad io mode above 8 MHz and single io mode above 33 MHz,
 * high performance mode needs to be enabled.
 */
static inline bool needs_hp(enum mspi_io_mode io_mode, uint32_t freq)
{
	if ((io_mode == MSPI_IO_MODE_QUAD_1_1_4) || (io_mode == MSPI_IO_MODE_QUAD_1_4_4)) {
		if (freq > MHZ(8)) {
			return true;
		}
	} else if (io_mode == MSPI_IO_MODE_SINGLE) {
		if (freq > MHZ(33)) {
			return true;
		}
	}

	return false;
}

static inline int mxicy_mx25r_post_switch_mode(const struct device *dev)
{
	const struct flash_mspi_nor_config *dev_config = dev->config;
	struct flash_mspi_nor_data *dev_data = dev->data;
	enum mspi_io_mode io_mode = dev_config->mspi_nor_cfg.io_mode;
	uint32_t freq = dev_config->mspi_nor_cfg.freq;
	int rc;
	uint8_t status;
	uint8_t config[MXICY_MX25R_REGS_LEN - 1];

	if (!needs_hp(io_mode, freq)) {
		return 0;
	}

	/* Wait for previous write to finish */
	do {
		flash_mspi_command_set(dev, &dev_config->jedec_cmds->status);
		dev_data->packet.data_buf  = &status;
		dev_data->packet.num_bytes = sizeof(status);
		rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
		if (rc < 0) {
			return rc;
		}
	} while (status & SPI_NOR_WIP_BIT);

	/* Write enable */
	flash_mspi_command_set(dev, &commands_single.write_en);
	rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
			     &dev_data->xfer);
	if (rc < 0) {
		return rc;
	}

	/* Write status and config registers */
	const struct flash_mspi_nor_cmd cmd_status = {
		.dir = MSPI_TX,
		.cmd = SPI_NOR_CMD_WRSR,
		.cmd_length = 1,
	};

	flash_mspi_command_set(dev, &cmd_status);
	dev_data->packet.data_buf  = mxicy_mx25r_hp_payload;
	dev_data->packet.num_bytes = sizeof(mxicy_mx25r_hp_payload);
	rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
	if (rc < 0) {
		return rc;
	}

	/* Wait for write to end and verify status register */
	do {
		flash_mspi_command_set(dev, &dev_config->jedec_cmds->status);
		dev_data->packet.data_buf  = &status;
		dev_data->packet.num_bytes = sizeof(status);
		rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
		if (rc < 0) {
			return rc;
		}
	} while (status & SPI_NOR_WIP_BIT);

	if (status != mxicy_mx25r_hp_payload[0]) {
		return -EIO;
	}

	/* Verify configuration registers */
	flash_mspi_command_set(dev, &dev_config->jedec_cmds->config);
	dev_data->packet.data_buf  = config;
	dev_data->packet.num_bytes = sizeof(config);
	rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
	if (rc < 0) {
		return rc;
	}

	for (uint8_t i = 0; i < MXICY_MX25R_REGS_LEN - 1; i++) {
		if (config[i] != mxicy_mx25r_hp_payload[i + 1]) {
			return -EIO;
		}
	}

	return 0;
}

struct flash_mspi_nor_quirks flash_quirks_mxicy_mx25r = {
	.post_switch_mode = mxicy_mx25r_post_switch_mode,
};

#endif /* DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25r) */

#if DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25u)

#define MXICY_MX25R_OE_MASK BIT(0)

static uint8_t mxicy_mx25u_oe_payload = MXICY_MX25R_OE_MASK;

static inline int mxicy_mx25u_post_switch_mode(const struct device *dev)
{
	const struct flash_mspi_nor_config *dev_config = dev->config;
	struct flash_mspi_nor_data *dev_data = dev->data;
	enum mspi_io_mode io_mode = dev_config->mspi_nor_cfg.io_mode;
	int rc;

	if (io_mode != MSPI_IO_MODE_OCTAL) {
		return 0;
	}

	/* Write enable */
	flash_mspi_command_set(dev, &commands_single.write_en);
	rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
			     &dev_data->xfer);
	if (rc < 0) {
		return rc;
	}

	/* Write config register 2 */
	const struct flash_mspi_nor_cmd cmd_status = {
		.dir = MSPI_TX,
		.cmd = SPI_NOR_CMD_WR_CFGREG2,
		.cmd_length = 1,
		.addr_length = 4,
	};

	flash_mspi_command_set(dev, &cmd_status);
	dev_data->packet.data_buf  = &mxicy_mx25u_oe_payload;
	dev_data->packet.num_bytes = sizeof(mxicy_mx25u_oe_payload);
	rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
	return rc;
}

struct flash_mspi_nor_quirks flash_quirks_mxicy_mx25u = {
	.post_switch_mode = mxicy_mx25u_post_switch_mode,
};

#endif /* DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25u) */

#endif /*__FLASH_MSPI_NOR_QUIRKS_H__*/
