/*
 * Copyright (c) 2018 Savoir-Faire Linux.
 * Copyright (c) 2020 Peter Bigot Consulting, LLC
 * Copyright (c) 2023 Intercreate, Inc.
 *
 * This driver is heavily inspired from the spi_flash_w25qxxdv.c SPI NOR driver.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT jedec_spi_nor

#include <errno.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/init.h>
#include <string.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys_clock.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>

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

LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);

/* Device Power Management Notes
 *
 * These flash devices have several modes during operation:
 * * When CSn is asserted (during a SPI operation) the device is
 *   active.
 * * When CSn is deasserted the device enters a standby mode.
 * * Some devices support a Deep Power-Down mode which reduces current
 *   to as little as 0.1% of standby.
 *
 * When mapped to the Zephyr Device Power Management states:
 * * PM_DEVICE_STATE_ACTIVE covers both active and standby modes;
 * * PM_DEVICE_STATE_SUSPENDED corresponds to deep-power-down mode;
 * * PM_DEVICE_STATE_OFF covers the powered off state;
 */

#define SPI_NOR_MAX_ADDR_WIDTH 4
#define SPI_NOR_3B_ADDR_MAX    0xFFFFFF

#define ANY_INST_HAS_MXICY_MX25R_POWER_MODE DT_ANY_INST_HAS_PROP_STATUS_OKAY(mxicy_mx25r_power_mode)
#define ANY_INST_HAS_DPD DT_ANY_INST_HAS_BOOL_STATUS_OKAY(has_dpd)
#define ANY_INST_HAS_T_EXIT_DPD DT_ANY_INST_HAS_PROP_STATUS_OKAY(t_exit_dpd)
#define ANY_INST_HAS_DPD_WAKEUP_SEQUENCE DT_ANY_INST_HAS_PROP_STATUS_OKAY(dpd_wakeup_sequence)
#define ANY_INST_HAS_RESET_GPIOS DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
#define ANY_INST_HAS_WP_GPIOS DT_ANY_INST_HAS_PROP_STATUS_OKAY(wp_gpios)
#define ANY_INST_HAS_HOLD_GPIOS DT_ANY_INST_HAS_PROP_STATUS_OKAY(hold_gpios)
#define ANY_INST_USE_4B_ADDR_OPCODES DT_ANY_INST_HAS_BOOL_STATUS_OKAY(use_4b_addr_opcodes)
#define ANY_INST_HAS_FLSR \
	DT_ANY_INST_HAS_BOOL_STATUS_OKAY(use_flag_status_register)
#define ANY_INST_USE_FAST_READ DT_ANY_INST_HAS_BOOL_STATUS_OKAY(use_fast_read)
#define ANY_INST_REQUIRES_ULBPR DT_ANY_INST_HAS_BOOL_STATUS_OKAY(requires_ulbpr)

#ifdef CONFIG_SPI_NOR_ACTIVE_DWELL_MS
#define ACTIVE_DWELL_MS CONFIG_SPI_NOR_ACTIVE_DWELL_MS
#else
#define ACTIVE_DWELL_MS 0
#endif

#define DEV_CFG(_dev_) ((const struct spi_nor_config * const) (_dev_)->config)

/* MXICY Related defines*/
/* MXICY Low-power/high perf mode is second bit in configuration register 2 */
#define LH_SWITCH_BIT 9

#define JEDEC_MACRONIX_ID   0xc2
#define JEDEC_MX25R_TYPE_ID 0x28

/* Build-time data associated with the device. */
struct spi_nor_config {
	/* Devicetree SPI configuration */
	struct spi_dt_spec spi;

#if ANY_INST_HAS_RESET_GPIOS
	const struct gpio_dt_spec reset;
#endif

	/* Runtime SFDP stores no static configuration. */

#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME
	/* Size of device in bytes, from size property */
	uint32_t flash_size;

#ifdef CONFIG_FLASH_PAGE_LAYOUT
	/* Flash page layout can be determined from devicetree. */
	struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */

	/* Expected JEDEC ID, from jedec-id property */
	uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];

#if defined(CONFIG_SPI_NOR_SFDP_MINIMAL)
	/* Optional support for entering 32-bit address mode. */
	uint8_t enter_4byte_addr;
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */

#if defined(CONFIG_SPI_NOR_SFDP_DEVICETREE)
	/* Length of BFP structure, in 32-bit words. */
	uint8_t bfp_len;

	/* Pointer to the BFP table as read from the device
	 * (little-endian stored words), from sfdp-bfp property
	 */
	const struct jesd216_bfp *bfp;
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */

	/* Optional bits in SR to be cleared on startup.
	 *
	 * This information cannot be derived from SFDP.
	 */
	uint8_t has_lock;

#if ANY_INST_HAS_WP_GPIOS
	/* The write-protect GPIO (wp-gpios) */
	const struct gpio_dt_spec wp;
#endif

#if ANY_INST_HAS_HOLD_GPIOS
	/* The hold GPIO (hold-gpios) */
	const struct gpio_dt_spec hold;
#endif

#if ANY_INST_HAS_DPD
	uint16_t t_enter_dpd; /* in milliseconds */
	uint16_t t_dpdd_ms;   /* in milliseconds */
#if ANY_INST_HAS_T_EXIT_DPD
	uint16_t t_exit_dpd;  /* in milliseconds */
#endif
#endif

#if ANY_INST_HAS_DPD_WAKEUP_SEQUENCE
	uint16_t t_crdp_ms; /* in milliseconds */
	uint16_t t_rdp_ms;  /* in milliseconds */
#endif

#if ANY_INST_HAS_MXICY_MX25R_POWER_MODE
	bool mxicy_mx25r_power_mode;
#endif
	bool use_4b_addr_opcodes:1;

	/* exist flags for dts opt-ins */
	bool dpd_exist:1;
	bool dpd_wakeup_sequence_exist:1;
	bool mxicy_mx25r_power_mode_exist:1;
	bool reset_gpios_exist:1;
	bool requires_ulbpr_exist:1;
	bool wp_gpios_exist:1;
	bool hold_gpios_exist:1;
	bool has_flsr: 1;
	bool use_fast_read: 1;
};

/**
 * struct spi_nor_data - Structure for defining the SPI NOR access
 * @sem: The semaphore to access to the flash
 */
struct spi_nor_data {
	struct k_sem sem;
#if ANY_INST_HAS_DPD
	/* Low 32-bits of uptime counter at which device last entered
	 * deep power-down.
	 */
	uint32_t ts_enter_dpd;
#endif

	/* Miscellaneous flags */

	/* If set addressed operations should use 32-bit rather than
	 * 24-bit addresses.
	 *
	 * This is ignored if the access parameter to a command
	 * explicitly specifies 24-bit or 32-bit addressing.
	 */
	bool flag_access_32bit: 1;

	/* Minimal SFDP stores no dynamic configuration.  Runtime and
	 * devicetree store page size and erase_types; runtime also
	 * stores flash size and layout.
	 */
#ifndef CONFIG_SPI_NOR_SFDP_MINIMAL

	struct jesd216_erase_type erase_types[JESD216_NUM_ERASE_TYPES];

	/* Number of bytes per page */
	uint16_t page_size;

#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
	/* Size of flash, in bytes */
	uint32_t flash_size;

#ifdef CONFIG_FLASH_PAGE_LAYOUT
	struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
};

#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
/* The historically supported erase sizes. */
static const struct jesd216_erase_type minimal_erase_types[JESD216_NUM_ERASE_TYPES] = {
	{
		.cmd = SPI_NOR_CMD_BE,
		.exp = 16,
	},
	{
		.cmd = SPI_NOR_CMD_SE,
		.exp = 12,
	},
};
static const struct jesd216_erase_type minimal_erase_types_4b[JESD216_NUM_ERASE_TYPES] = {
	{
		.cmd = SPI_NOR_CMD_BE_4B,
		.exp = 16,
	},
	{
		.cmd = SPI_NOR_CMD_SE_4B,
		.exp = 12,
	},
};
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */


/* Register writes should be ready extremely quickly */
#define WAIT_READY_REGISTER K_NO_WAIT
/* Page writes range from sub-ms to 10ms */
#define WAIT_READY_WRITE K_TICKS(1)

#ifdef CONFIG_SPI_NOR_SLEEP_WHILE_WAITING_UNTIL_READY
#define WAIT_READY_ERASE K_MSEC(CONFIG_SPI_NOR_SLEEP_ERASE_MS)
#else
#define WAIT_READY_ERASE K_NO_WAIT
#endif

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

/* Get pointer to array of supported erase types.  Static const for
 * minimal, data for runtime and devicetree.
 */
static inline const struct jesd216_erase_type *
dev_erase_types(const struct device *dev)
{
#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
	if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) && DEV_CFG(dev)->use_4b_addr_opcodes) {
		return minimal_erase_types_4b;
	}
	return minimal_erase_types;
#else /* CONFIG_SPI_NOR_SFDP_MINIMAL */
	const struct spi_nor_data *data = dev->data;

	return data->erase_types;
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
}

/* Get the size of the flash device.  Data for runtime, constant for
 * minimal and devicetree.
 */
static inline uint32_t dev_flash_size(const struct device *dev)
{
#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
	const struct spi_nor_data *data = dev->data;

	return data->flash_size;
#else /* CONFIG_SPI_NOR_SFDP_RUNTIME */
	const struct spi_nor_config *cfg = dev->config;

	return cfg->flash_size;
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
}

/* Get the flash device page size.  Constant for minimal, data for
 * runtime and devicetree.
 */
static inline uint16_t dev_page_size(const struct device *dev)
{
#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
	return DT_INST_PROP_OR(0, page_size, 256);
#else /* CONFIG_SPI_NOR_SFDP_MINIMAL */
	const struct spi_nor_data *data = dev->data;

	return data->page_size;
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
}

static const struct flash_parameters flash_nor_parameters = {
	.write_block_size = 1,
	.erase_value = 0xff,
};

/* Capture the time at which the device entered deep power-down. */
static inline void record_entered_dpd(const struct device *const dev)
{
#if ANY_INST_HAS_DPD
	const struct spi_nor_config *const driver_config = dev->config;

	if (driver_config->dpd_exist) {
		struct spi_nor_data *const driver_data = dev->data;

		driver_data->ts_enter_dpd = k_uptime_get_32();
	}
#else
	ARG_UNUSED(dev);
#endif
}

#if ANY_INST_HAS_DPD
/* Check the current time against the time DPD was entered and delay
 * until it's ok to initiate the DPD exit process.
 */
static inline void delay_until_exit_dpd_ok(const struct device *const dev)
{
	const struct spi_nor_config *const driver_config = dev->config;

	if (driver_config->dpd_exist) {
		struct spi_nor_data *const driver_data = dev->data;
		int32_t since = (int32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd);

		/* If the time is negative the 32-bit counter has wrapped,
		 * which is certainly long enough no further delay is
		 * required.  Otherwise we have to check whether it's been
		 * long enough taking into account necessary delays for
		 * entering and exiting DPD.
		 */
		if (since >= 0) {
			/* Subtract time required for DPD to be reached */
			since -= driver_config->t_enter_dpd;

			/* Subtract time required in DPD before exit */
			since -= driver_config->t_dpdd_ms;

			/* If the adjusted time is negative we have to wait
			 * until it reaches zero before we can proceed.
			 */
			if (since < 0) {
				k_sleep(K_MSEC((uint32_t)-since));
			}
		}
	}
}
#endif /* ANY_INST_HAS_DPD */

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

/* Indicates that addressed access uses a 24-bit address regardless of
 * spi_nor_data::flag_32bit_addr.
 */
#define NOR_ACCESS_24BIT_ADDR BIT(1)

/* Indicates that addressed access uses a 32-bit address regardless of
 * spi_nor_data::flag_32bit_addr.
 */
#define NOR_ACCESS_32BIT_ADDR BIT(2)

/* Indicates that a dummy byte is to be sent following the address.
 */
#define NOR_ACCESS_DUMMY_BYTE BIT(3)

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

/*
 * @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.
 *        See NOR_ACCESS_*.
 * @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 spi_nor_access(const struct device *const dev,
			  uint8_t opcode, unsigned int access,
			  off_t addr, void *data, size_t length)
{
	const struct spi_nor_config *const driver_cfg = dev->config;
	struct spi_nor_data *const driver_data = dev->data;
	bool is_addressed = (access & NOR_ACCESS_ADDRESSED) != 0U;
	bool is_write = (access & NOR_ACCESS_WRITE) != 0U;
	bool has_dummy = (access & NOR_ACCESS_DUMMY_BYTE) != 0U;
	uint8_t buf[6] = {opcode};
	struct spi_buf spi_buf_tx[2] = {
		{
			.buf = buf,
			.len = 1,
		},
		{
			.buf = data,
			.len = length
		}
	};
	struct spi_buf spi_buf_rx[2] = {
		{
			.buf = NULL,
			.len = 1,
		},
		{
			.buf = data,
			.len = length
		}
	};

	if (is_addressed) {
		bool access_24bit = (access & NOR_ACCESS_24BIT_ADDR) != 0;
		bool access_32bit = (access & NOR_ACCESS_32BIT_ADDR) != 0;
		bool use_32bit = (access_32bit
				  || (!access_24bit
				      && driver_data->flag_access_32bit));
		union {
			uint32_t u32;
			uint8_t u8[4];
		} addr32 = {
			.u32 = sys_cpu_to_be32(addr),
		};

		if (use_32bit) {
			memcpy(&buf[1], &addr32.u8[0], 4);
			spi_buf_tx[0].len += 4;
			spi_buf_rx[0].len += 4;
		} else {
			memcpy(&buf[1], &addr32.u8[1], 3);
			spi_buf_tx[0].len += 3;
			spi_buf_rx[0].len += 3;
		}
	};
	if (has_dummy) {
		spi_buf_tx[0].len++;
		spi_buf_rx[0].len++;
	}

	const struct spi_buf_set tx_set = {
		.buffers = spi_buf_tx,
		.count = (is_write && length != 0) ? 2 : 1,
	};

	const struct spi_buf_set rx_set = {
		.buffers = spi_buf_rx,
		.count = 2,
	};

	if (is_write) {
		return spi_write_dt(&driver_cfg->spi, &tx_set);
	}

	return spi_transceive_dt(&driver_cfg->spi, &tx_set, &rx_set);
}

#define spi_nor_cmd_read(dev, opcode, dest, length) \
	spi_nor_access(dev, opcode, 0, 0, dest, length)
#define spi_nor_cmd_addr_read(dev, opcode, addr, dest, length) \
	spi_nor_access(dev, opcode, NOR_ACCESS_ADDRESSED, addr, dest, length)
#define spi_nor_cmd_addr_read_3b(dev, opcode, addr, dest, length)                                  \
	spi_nor_access(dev, opcode, NOR_ACCESS_24BIT_ADDR | NOR_ACCESS_ADDRESSED, addr, dest,      \
		       length)
#define spi_nor_cmd_addr_read_4b(dev, opcode, addr, dest, length)                                  \
	spi_nor_access(dev, opcode, NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_ADDRESSED, addr, dest,      \
		       length)
#define spi_nor_cmd_addr_fast_read(dev, opcode, addr, dest, length)                                \
	spi_nor_access(dev, opcode, NOR_ACCESS_ADDRESSED | NOR_ACCESS_DUMMY_BYTE, addr, dest,      \
		       length)
#define spi_nor_cmd_addr_fast_read_3b(dev, opcode, addr, dest, length)                             \
	spi_nor_access(dev, opcode,                                                                \
		       NOR_ACCESS_24BIT_ADDR | NOR_ACCESS_ADDRESSED | NOR_ACCESS_DUMMY_BYTE, addr, \
		       dest, length)
#define spi_nor_cmd_addr_fast_read_4b(dev, opcode, addr, dest, length)                             \
	spi_nor_access(dev, opcode,                                                                \
		       NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_ADDRESSED | NOR_ACCESS_DUMMY_BYTE, addr, \
		       dest, length)
#define spi_nor_cmd_write(dev, opcode) \
	spi_nor_access(dev, opcode, NOR_ACCESS_WRITE, 0, NULL, 0)
#define spi_nor_cmd_addr_write(dev, opcode, addr, src, length) \
	spi_nor_access(dev, opcode, NOR_ACCESS_WRITE | NOR_ACCESS_ADDRESSED, \
		       addr, (void *)src, length)
#define spi_nor_cmd_addr_write_3b(dev, opcode, addr, src, length)                                  \
	spi_nor_access(dev, opcode,                                                                \
		       NOR_ACCESS_24BIT_ADDR | NOR_ACCESS_WRITE | NOR_ACCESS_ADDRESSED, addr,      \
		       (void *)src, length)
#define spi_nor_cmd_addr_write_4b(dev, opcode, addr, src, length)                                  \
	spi_nor_access(dev, opcode,                                                                \
		       NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_WRITE | NOR_ACCESS_ADDRESSED, addr,      \
		       (void *)src, length)

/**
 * @brief Wait until the flash is ready
 *
 * @note The device must be externally acquired before invoking this
 * function.
 *
 * This function should be invoked after every ERASE, PROGRAM, or
 * WRITE_STATUS operation before continuing.  This allows us to assume
 * that the device is ready to accept new commands at any other point
 * in the code.
 *
 * @param dev The device structure
 * @param poll_delay Duration between polls of status register
 * @return 0 on success, negative errno code otherwise
 */
static int spi_nor_wait_until_ready(const struct device *dev, k_timeout_t poll_delay)
{
	const struct spi_nor_config *cfg = dev->config;
	int ret;
	uint8_t reg;

	ARG_UNUSED(poll_delay);

	while (true) {
		/* If flag status register is present, check it rather than the standard
		 * status register since it allows better error detection. Also, some devices
		 * that have it require it to be read after a program operation.
		 */
		if (IS_ENABLED(ANY_INST_HAS_FLSR) && cfg->has_flsr) {
			ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDFLSR, &reg, sizeof(reg));
			if (ret) {
				break;
			}
			if (reg & SPI_NOR_FLSR_READY) {
				if (reg & SPI_NOR_FLSR_ERASE_FAIL) {
					LOG_ERR("Erase failure");
					ret = -EIO;
				}
				if (reg & SPI_NOR_FLSR_PROGRAM_FAIL) {
					LOG_ERR("Program failure");
					ret = -EIO;
				}
				if (reg & SPI_NOR_FLSR_PROT_ERROR) {
					LOG_ERR("Protection violation");
					ret = -EIO;
				}

				if (ret) {
					/* Clear flag status register for next operation */
					int ret2 = spi_nor_cmd_write(dev, SPI_NOR_CMD_CLRFLSR);

					if (ret2) {
						LOG_ERR("Failed to clear flag status register: %d",
							ret2);
					}
				}
				break;
			}
		} else {
			ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
			/* Exit on error or no longer WIP */
			if (ret || !(reg & SPI_NOR_WIP_BIT)) {
				break;
			}
		}
#ifdef CONFIG_SPI_NOR_SLEEP_WHILE_WAITING_UNTIL_READY
		/* Don't monopolise the CPU while waiting for ready */
		k_sleep(poll_delay);
#endif /* CONFIG_SPI_NOR_SLEEP_WHILE_WAITING_UNTIL_READY */
	}
	return ret;
}

#if defined(CONFIG_SPI_NOR_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 spi_nor_access(dev, JESD216_CMD_READ_SFDP,
			      NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_ADDRESSED,
			      addr << 8, data, length);
}
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */

static int enter_dpd(const struct device *const dev)
{
	int ret = 0;
	const struct spi_nor_config *cfg = dev->config;

	if (cfg->dpd_exist) {
		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_DPD);
		if (ret == 0) {
			record_entered_dpd(dev);
		}
	}
	return ret;
}

static int exit_dpd(const struct device *const dev)
{
	int ret = 0;
#if ANY_INST_HAS_DPD
	const struct spi_nor_config *cfg = dev->config;

	if (cfg->dpd_exist) {
		delay_until_exit_dpd_ok(dev);

		if (cfg->dpd_wakeup_sequence_exist) {
#if ANY_INST_HAS_DPD_WAKEUP_SEQUENCE
			/* Assert CSn and wait for tCRDP.
			 *
			 * Unfortunately the SPI API doesn't allow us to
			 * control CSn so fake it by writing a known-supported
			 * single-byte command, hoping that'll hold the assert
			 * long enough.  This is highly likely, since the
			 * duration is usually less than two SPI clock cycles.
			 */
			ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID);

			/* Deassert CSn and wait for tRDP */
			k_sleep(K_MSEC(cfg->t_rdp_ms));
#endif /* ANY_INST_HAS_DPD_WAKEUP_SEQUENCE */
		} else {
			ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD);

#if ANY_INST_HAS_T_EXIT_DPD
			if (ret == 0) {
				if (cfg->dpd_exist) {
					k_sleep(K_MSEC(cfg->t_exit_dpd));
				}
			}
#endif /* T_EXIT_DPD */
		}
	}
#endif /* ANY_INST_HAS_DPD */
	return ret;
}

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

	if (IS_ENABLED(CONFIG_MULTITHREADING)) {
		struct spi_nor_data *const driver_data = dev->data;

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

	(void)pm_device_runtime_get(cfg->spi.bus);
}

/* Everything necessary to release access to the device. */
static void release_device(const struct device *dev)
{
	const struct spi_nor_config *cfg = dev->config;

	(void)pm_device_runtime_put(cfg->spi.bus);

	if (IS_ENABLED(CONFIG_MULTITHREADING)) {
		struct spi_nor_data *const driver_data = dev->data;

		k_sem_give(&driver_data->sem);
	}
}

/**
 * @brief Read the status register.
 *
 * @note The device must be externally acquired before invoking this
 * function.
 *
 * @param dev Device struct
 *
 * @return the non-negative value of the status register, or an error code.
 */
static int spi_nor_rdsr(const struct device *dev)
{
	uint8_t reg;
	int ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));

	if (ret == 0) {
		ret = reg;
	}

	return ret;
}

/**
 * @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 spi_nor_wrsr(const struct device *dev,
			uint8_t sr)
{
	int ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);

	if (ret != 0) {
		return ret;
	}
	ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0, &sr,
					sizeof(sr));
	if (ret != 0) {
		return ret;
	}
	return spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER);
}

#if ANY_INST_HAS_MXICY_MX25R_POWER_MODE

/**
 * @brief Read the configuration register.
 *
 * @note The device must be externally acquired before invoking this
 * function.
 *
 * @param dev Device struct
 *
 * @return the non-negative value of the configuration register, or an error code.
 */
static int mxicy_rdcr(const struct device *dev)
{
	const struct spi_nor_config *cfg = dev->config;
	uint16_t cr = -ENOSYS;

	if (cfg->mxicy_mx25r_power_mode_exist) {
		int ret = spi_nor_cmd_read(dev, CMD_RDCR, &cr, sizeof(cr));

		if (ret < 0) {
			return ret;
		}
	}

	return cr;
}

/**
 * @brief Write the configuration register.
 *
 * @note The device must be externally acquired before invoking this
 * function.
 *
 * @param dev Device struct
 * @param cr  The new value of the configuration register
 *
 * @return 0 on success or a negative error code.
 */
static int mxicy_wrcr(const struct device *dev,
			uint16_t cr)
{
	const struct spi_nor_config *cfg = dev->config;
	int ret = -ENOSYS;
	/* The configuration register bytes on the Macronix MX25R devices are
	 * written using the Write Status Register command where the configuration
	 * register bytes are written as two extra bytes after the status register.
	 * First read out the current status register to preserve the value.
	 */

	if (cfg->mxicy_mx25r_power_mode_exist) {
		int sr = spi_nor_rdsr(dev);

		if (sr < 0) {
			LOG_ERR("Read status register failed: %d", sr);
			return sr;
		}

		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
		if (ret != 0) {
			return ret;
		}

		uint8_t data[] = {
			sr,
			cr & 0xFF,	/* Configuration register 1 */
			cr >> 8		/* Configuration register 2 */
		};

		ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0,
			data, sizeof(data));
		if (ret != 0) {
			return ret;
		}

		ret = spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER);
	}

	return ret;
}

static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id)
{
	const struct spi_nor_config *cfg = dev->config;
	int ret = -ENOSYS;

	if (cfg->mxicy_mx25r_power_mode_exist) {
		/* Low-power/high perf mode is second bit in configuration register 2 */
		int current_cr, new_cr;
		/* lh_switch enum index:
		 * 0: Ultra low power
		 * 1: High performance mode
		 */
		const bool use_high_perf = cfg->mxicy_mx25r_power_mode;

		/* Only supported on Macronix MX25R Ultra Low Power series. */
		if (jedec_id[0] != JEDEC_MACRONIX_ID || jedec_id[1] != JEDEC_MX25R_TYPE_ID) {
			LOG_WRN("L/H switch not supported for device id: %02x %02x %02x",
				jedec_id[0], jedec_id[1], jedec_id[2]);
			/* Do not return an error here because the flash still functions */
			return 0;
		}

		acquire_device(dev);

		/* Read current configuration register */

		ret = mxicy_rdcr(dev);
		if (ret < 0) {
			release_device(dev);
			return ret;
		}
		current_cr = ret;

		LOG_DBG("Use high performance mode? %d", use_high_perf);
		new_cr = current_cr;
		WRITE_BIT(new_cr, LH_SWITCH_BIT, use_high_perf);
		if (new_cr != current_cr) {
			ret = mxicy_wrcr(dev, new_cr);
		} else {
			ret = 0;
		}

		if (ret < 0) {
			LOG_ERR("Enable high performace mode failed: %d", ret);
		}

		release_device(dev);
	}

	return ret;
}

#endif /* ANY_INST_HAS_MXICY_MX25R_POWER_MODE */

static int spi_nor_read(const struct device *dev, off_t addr, void *dest,
			size_t size)
{
	const struct spi_nor_config *cfg = dev->config;
	const size_t flash_size = dev_flash_size(dev);
	int ret;

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

	/* Ensure flash is powered before read */
	if (pm_device_runtime_get(dev) < 0) {
		return -EIO;
	}

	acquire_device(dev);

	if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) && cfg->use_4b_addr_opcodes) {
		if (addr > SPI_NOR_3B_ADDR_MAX) {
			if (IS_ENABLED(ANY_INST_USE_FAST_READ) && cfg->use_fast_read) {
				ret = spi_nor_cmd_addr_fast_read_4b(dev, SPI_NOR_CMD_READ_FAST_4B,
								    addr, dest, size);
			} else {
				ret = spi_nor_cmd_addr_read_4b(dev, SPI_NOR_CMD_READ_4B, addr, dest,
							       size);
			}
		} else {
			if (IS_ENABLED(ANY_INST_USE_FAST_READ) && cfg->use_fast_read) {
				ret = spi_nor_cmd_addr_fast_read_3b(dev, SPI_NOR_CMD_READ_FAST,
								    addr, dest, size);
			} else {
				ret = spi_nor_cmd_addr_read_3b(dev, SPI_NOR_CMD_READ, addr, dest,
							       size);
			}
		}
	} else {
		if (IS_ENABLED(ANY_INST_USE_FAST_READ) && cfg->use_fast_read) {
			ret = spi_nor_cmd_addr_fast_read(dev, SPI_NOR_CMD_READ_FAST, addr, dest,
							 size);
		} else {
			ret = spi_nor_cmd_addr_read(dev, SPI_NOR_CMD_READ, addr, dest, size);
		}
	}

	release_device(dev);

	/* Release flash power requirement */
	(void)pm_device_runtime_put_async(dev, K_MSEC(ACTIVE_DWELL_MS));
	return ret;
}

#if defined(CONFIG_FLASH_EX_OP_ENABLED)
static int flash_spi_nor_ex_op(const struct device *dev, uint16_t code,
			const uintptr_t in, void *out)
{
	int ret;

	ARG_UNUSED(in);
	ARG_UNUSED(out);

	if (pm_device_runtime_get(dev) < 0) {
		return -EIO;
	}

	acquire_device(dev);

	switch (code) {
	case FLASH_EX_OP_RESET:
		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RESET_EN);
		if (ret == 0) {
			ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RESET_MEM);
		}
		break;
	default:
		ret = -ENOTSUP;
		break;
	}

	release_device(dev);
	(void)pm_device_runtime_put_async(dev, K_MSEC(ACTIVE_DWELL_MS));
	return ret;
}
#endif

static int spi_nor_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);
	int ret;

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

	/* Ensure flash is powered before write */
	if (pm_device_runtime_get(dev) < 0) {
		return -EIO;
	}

	acquire_device(dev);
	ret = spi_nor_write_protection_set(dev, false);
	if (ret == 0) {
		while (size > 0) {
			size_t to_write = size;

			/* Don't write more than a page. */
			if (to_write >= page_size) {
				to_write = page_size;
			}

			/* Don't write across a page boundary */
			if (((addr + to_write - 1U) / page_size)
			!= (addr / page_size)) {
				to_write = page_size - (addr % page_size);
			}

			ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);

			if (ret != 0) {
				break;
			}

			if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) &&
			    DEV_CFG(dev)->use_4b_addr_opcodes) {
				if (addr > SPI_NOR_3B_ADDR_MAX) {
					ret = spi_nor_cmd_addr_write_4b(dev, SPI_NOR_CMD_PP_4B,
									addr, src, to_write);
				} else {
					ret = spi_nor_cmd_addr_write_3b(dev, SPI_NOR_CMD_PP, addr,
									src, to_write);
				}
			} else {
				ret = spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_PP, addr, src,
							     to_write);
			}

			if (ret != 0) {
				break;
			}

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

			ret = spi_nor_wait_until_ready(dev, WAIT_READY_WRITE);
			if (ret != 0) {
				break;
			}
		}
	}

	int ret2 = spi_nor_write_protection_set(dev, true);

	if (!ret) {
		ret = ret2;
	}

	release_device(dev);

	/* Release flash power requirement */
	(void)pm_device_runtime_put_async(dev, K_MSEC(ACTIVE_DWELL_MS));
	return ret;
}

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

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

	/* 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;
	}

	/* Ensure flash is powered before erase */
	if (pm_device_runtime_get(dev) < 0) {
		return -EIO;
	}

	acquire_device(dev);
	ret = spi_nor_write_protection_set(dev, false);

	while ((size > 0) && (ret == 0)) {
		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
		if (ret) {
			break;
		}

		if (size == flash_size) {
			/* chip erase */
			ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_CE);
			size -= flash_size;
		} else {
			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)
				    && (size >= BIT(etp->exp))
				    && ((bet == NULL)
					|| (etp->exp > bet->exp))) {
					bet = etp;
				}
			}
			if (bet != NULL) {
				if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) &&
				    DEV_CFG(dev)->use_4b_addr_opcodes) {
					ret = spi_nor_cmd_addr_write_4b(dev, bet->cmd, addr, NULL,
									0);
				} else {
					ret = spi_nor_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;
			}
		}
		if (ret != 0) {
			break;
		}

		ret = spi_nor_wait_until_ready(dev, WAIT_READY_ERASE);
	}

	int ret2 = spi_nor_write_protection_set(dev, true);

	if (!ret) {
		ret = ret2;
	}

	release_device(dev);

	/* Release flash power requirement */
	(void)pm_device_runtime_put_async(dev, K_MSEC(ACTIVE_DWELL_MS));
	return ret;
}

/* @note The device must be externally acquired before invoking this
 * function.
 */
static int spi_nor_write_protection_set(const struct device *dev,
					bool write_protect)
{
	int ret = 0;

#if ANY_INST_HAS_WP_GPIOS
	if (DEV_CFG(dev)->wp_gpios_exist && !write_protect) {
		gpio_pin_set_dt(&(DEV_CFG(dev)->wp), 0);
	}
#endif

	if (IS_ENABLED(ANY_INST_REQUIRES_ULBPR)
	    && DEV_CFG(dev)->requires_ulbpr_exist
	    && !write_protect) {
		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_ULBPR);
	}

#if ANY_INST_HAS_WP_GPIOS
	if (DEV_CFG(dev)->wp_gpios_exist && write_protect) {
		gpio_pin_set_dt(&(DEV_CFG(dev)->wp), 1);
	}
#endif

	return ret;
}

#if defined(CONFIG_FLASH_JESD216_API) || defined(CONFIG_SPI_NOR_SFDP_RUNTIME)

static int spi_nor_sfdp_read(const struct device *dev, off_t addr,
			     void *dest, size_t size)
{
	if (pm_device_runtime_get(dev) < 0) {
		return -EIO;
	}

	acquire_device(dev);

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

	release_device(dev);

	(void)pm_device_runtime_put_async(dev, K_MSEC(ACTIVE_DWELL_MS));

	return ret;
}

#endif /* CONFIG_FLASH_JESD216_API || CONFIG_SPI_NOR_SFDP_RUNTIME */

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

	if (pm_device_runtime_get(dev) < 0) {
		return -EIO;
	}

	acquire_device(dev);

	int ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDID, id, SPI_NOR_MAX_ID_LEN);

	release_device(dev);

	(void)pm_device_runtime_put_async(dev, K_MSEC(ACTIVE_DWELL_MS));

	return ret;
}

/* Put the device into the appropriate address mode, if supported.
 *
 * On successful return spi_nor_data::flag_access_32bit has been set
 * (cleared) if the device is configured for 4-byte (3-byte) addresses
 * for read, write, and erase commands.
 *
 * @param dev the device
 *
 * @param enter_4byte_addr the Enter 4-Byte Addressing bit set from
 * DW16 of SFDP BFP.  A value of all zeros or all ones is interpreted
 * as "not supported".
 *
 * @retval -ENOTSUP if 4-byte addressing is supported but not in a way
 * that the driver can handle.
 * @retval negative codes if the attempt was made and failed
 * @retval 0 if the device is successfully left in 24-bit mode or
 *         reconfigured to 32-bit mode.
 */
static int spi_nor_set_address_mode(const struct device *dev,
				    uint8_t enter_4byte_addr)
{
	int ret = 0;

	LOG_DBG("Checking enter-4byte-addr %02x", enter_4byte_addr);

	/* Do nothing if not provided (either no bits or all bits
	 * set).
	 */
	if ((enter_4byte_addr == 0)
	    || (enter_4byte_addr == 0xff)) {
		return 0;
	}

	/* This currently only supports command 0xB7 (Enter 4-Byte
	 * Address Mode), with or without preceding WREN.
	 * Or when BIT(3) is set where the 4-byte address mode can be entered
	 * by setting BIT(7) in a register via a 0x17 write
	 * instruction. See JEDEC 216F 16th DWORD.
	 */
	if ((enter_4byte_addr & 0x0b) == 0) {
		return -ENOTSUP;
	}

	acquire_device(dev);

	if ((enter_4byte_addr & 0x08) != 0) {
		/* Enter 4-byte address mode by setting BIT(7) in a register
		 * via a 0x17 write instruction.
		 */
		uint8_t sr = BIT(7);

		ret = spi_nor_access(dev, 0x17, NOR_ACCESS_WRITE, 0, &sr, sizeof(sr));
		goto done;
	}

	if ((enter_4byte_addr & 0x02) != 0) {
		/* Enter after WREN. */
		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
	}

	if (ret == 0) {
		ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_4BA);
	}

done:
	if (ret == 0) {
		struct spi_nor_data *data = dev->data;

		data->flag_access_32bit = true;
	}

	release_device(dev);

	return ret;
}

#ifndef CONFIG_SPI_NOR_SFDP_MINIMAL

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

	LOG_INF("%s: %u %ciBy flash", dev->name,
		(flash_size < (1024U * 1024U)) ? (uint32_t)(flash_size >> 10)
					       : (uint32_t)(flash_size >> 20),
		(flash_size < (1024U * 1024U)) ? 'k' : 'M');

	/* Copy over the erase types, preserving their order.  (The
	 * Sector Map Parameter table references them by index.)
	 */
	memset(data->erase_types, 0, sizeof(data->erase_types));
	for (uint8_t ti = 1; ti <= ARRAY_SIZE(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;
	}

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

	LOG_DBG("Page size %u bytes", data->page_size);

	/* If 4-byte addressing is supported, switch to it. */
	if (jesd216_bfp_addrbytes(bfp) != JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_3B) {
		struct jesd216_bfp_dw16 dw16;
		int rc = 0;

		if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) && DEV_CFG(dev)->use_4b_addr_opcodes) {
			LOG_DBG("4-byte addressing supported, using it via specific opcodes");
			return 0;
		}

		if (jesd216_bfp_decode_dw16(php, bfp, &dw16) == 0) {
			rc = spi_nor_set_address_mode(dev, dw16.enter_4ba);
		}

		if (rc != 0) {
			LOG_ERR("Unable to enter 4-byte mode: %d\n", rc);
			return rc;
		}
	}
	return 0;
}

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

#if defined(CONFIG_SPI_NOR_SFDP_RUNTIME)
	struct spi_nor_data *dev_data = dev->data;
	/* For runtime we need to read the SFDP table, identify the
	 * BFP block, and process it.
	 */
	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;

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

	uint32_t magic = jesd216_sfdp_magic(hp);

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

	LOG_INF("%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_INF("PH%u: %04x rev %u.%u: %u DW @ %x",
			(int)(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[20];
				struct jesd216_bfp bfp;
			} u_param;
			const struct jesd216_bfp *bfp = &u_param.bfp;

			rc = spi_nor_sfdp_read(dev, jesd216_param_addr(php),
				u_param.dw, sizeof(u_param.dw));
			if (rc == 0) {
				rc = spi_nor_process_bfp(dev, php, bfp);
			}

			if (rc != 0) {
				LOG_INF("SFDP BFP failed: %d", rc);
				break;
			}
		}
		if (id == JESD216_SFDP_PARAM_ID_4B_ADDR_INSTR) {
			if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) &&
			    DEV_CFG(dev)->use_4b_addr_opcodes) {
				/*
				 * Check table 4 byte address instruction table to get supported
				 * erase opcodes when running in 4 byte address mode
				 */
				union {
					uint32_t dw[2];
					struct {
						uint32_t dummy;
						uint8_t type[4];
					} types;
				} u2;
				rc = spi_nor_sfdp_read(
					dev, jesd216_param_addr(php), (uint8_t *)u2.dw,
					MIN(sizeof(uint32_t) * php->len_dw, sizeof(u2.dw)));
				if (rc != 0) {
					break;
				}
				for (uint8_t ei = 0; ei < JESD216_NUM_ERASE_TYPES; ++ei) {
					struct jesd216_erase_type *etp = &dev_data->erase_types[ei];
					const uint8_t cmd = u2.types.type[ei];
					/* 0xff means not supported */
					if (cmd == 0xff) {
						etp->exp = 0;
						etp->cmd = 0;
					} else {
						etp->cmd = cmd;
					};
				}

				if (!((sys_le32_to_cpu(u2.dw[0]) &
				       JESD216_SFDP_4B_ADDR_DW1_1S_1S_1S_READ_13_SUP) &&
				      (sys_le32_to_cpu(u2.dw[0]) &
				       JESD216_SFDP_4B_ADDR_DW1_1S_1S_1S_PP_12_SUP))) {
					LOG_ERR("4-byte addressing not supported");
					return -ENOTSUP;
				}
			}
		}
		++php;
	}
#elif defined(CONFIG_SPI_NOR_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 spi_nor_config *cfg = dev->config;
	struct jesd216_param_header bfp_hdr = {
		.len_dw = cfg->bfp_len,
	};

	rc = spi_nor_process_bfp(dev, &bfp_hdr, cfg->bfp);
#else
#error Unhandled SFDP choice
#endif

	return rc;
}

#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static int setup_pages_layout(const struct device *dev)
{
#if defined(CONFIG_SPI_NOR_SFDP_RUNTIME)
	struct spi_nor_data *data = dev->data;
	const size_t flash_size = dev_flash_size(dev);
	const uint32_t layout_page_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE;
	uint8_t exp = 0;

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

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

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

	uint32_t erase_size = BIT(exp);

	/* 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_INF("layout page %u wastes space with device size %zu",
			layout_page_size, flash_size);
	}

	data->layout.pages_size = layout_page_size;
	data->layout.pages_count = flash_size / layout_page_size;
	LOG_DBG("layout %zu x %zu By pages", data->layout.pages_count, data->layout.pages_size);
#elif defined(CONFIG_SPI_NOR_SFDP_DEVICETREE)
	const struct spi_nor_config *cfg = dev->config;
	const struct flash_pages_layout *layout = &cfg->layout;
	const size_t flash_size = dev_flash_size(dev);
	size_t layout_size = layout->pages_size * layout->pages_count;

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

	return 0;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */

/**
 * @brief Configure the flash
 *
 * @param dev The flash device structure
 * @param info The flash info structure
 * @return 0 on success, negative errno code otherwise
 */
static int spi_nor_configure(const struct device *dev)
{
	const struct spi_nor_config *cfg = dev->config;
	uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];
	int rc;

	/* Validate bus and CS is ready */
	if (!spi_is_ready_dt(&cfg->spi)) {
		return -ENODEV;
	}

#if ANY_INST_HAS_RESET_GPIOS

	if (cfg->reset_gpios_exist) {
		if (!gpio_is_ready_dt(&cfg->reset)) {
			LOG_ERR("Reset pin not ready");
			return -ENODEV;
		}
		if (gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_ACTIVE)) {
			LOG_ERR("Couldn't configure reset pin");
			return -ENODEV;
		}
		rc = gpio_pin_set_dt(&cfg->reset, 0);
		if (rc) {
			return rc;
		}
	}
#endif

	/* After a soft-reset the flash might be in DPD or busy writing/erasing.
	 * Exit DPD and wait until flash is ready.
	 */
	acquire_device(dev);

	rc = exit_dpd(dev);
	if (rc < 0) {
		LOG_ERR("Failed to exit DPD (%d)", rc);
		release_device(dev);
		return -ENODEV;
	}

	rc = spi_nor_rdsr(dev);
	if (rc > 0 && (rc & SPI_NOR_WIP_BIT)) {
		LOG_WRN("Waiting until flash is ready");
		rc = spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER);
	}
	release_device(dev);
	if (rc < 0) {
		LOG_ERR("Failed to wait until flash is ready (%d)", rc);
		return -ENODEV;
	}

	/* now the spi bus is configured, we can verify SPI
	 * connectivity by reading the JEDEC ID.
	 */

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

#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME
	/* For minimal and devicetree we need to check the JEDEC ID
	 * against the one from devicetree, to ensure we didn't find a
	 * device that has different parameters.
	 */

	if (memcmp(jedec_id, cfg->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],
			cfg->jedec_id[0], cfg->jedec_id[1], cfg->jedec_id[2]);
		return -EINVAL;
	}
#endif

	/* Check for block protect bits that need to be cleared.  This
	 * information cannot be determined from SFDP content, so the
	 * devicetree node property must be set correctly for any device
	 * that powers up with block protect enabled.
	 */
	if (cfg->has_lock != 0) {
		acquire_device(dev);

		rc = spi_nor_rdsr(dev);

		/* Only clear if RDSR worked and something's set. */
		if (rc > 0) {
			rc = spi_nor_wrsr(dev, rc & ~cfg->has_lock);
		}

		release_device(dev);

		if (rc != 0) {
			LOG_ERR("BP clear failed: %d\n", rc);
			return -ENODEV;
		}
	}

#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
	/* For minimal we support some overrides from specific
	 * devicertee properties.
	 */
	if (cfg->enter_4byte_addr != 0) {
		rc = spi_nor_set_address_mode(dev, cfg->enter_4byte_addr);

		if (rc != 0) {
			LOG_ERR("Unable to enter 4-byte mode: %d\n", rc);
			return -ENODEV;
		}
	}

#else /* CONFIG_SPI_NOR_SFDP_MINIMAL */
	/* For devicetree and runtime we need to process BFP data and
	 * set up or validate page layout.
	 */
	rc = spi_nor_process_sfdp(dev);
	if (rc != 0) {
		LOG_ERR("SFDP read failed: %d", rc);
		return -ENODEV;
	}

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

#if ANY_INST_HAS_MXICY_MX25R_POWER_MODE
	if (cfg->mxicy_mx25r_power_mode_exist) {
	/* Do not fail init if setting configuration register fails */
		(void)mxicy_configure(dev, jedec_id);
	}
#endif /* ANY_INST_HAS_MXICY_MX25R_POWER_MODE */

	return 0;
}

static int spi_nor_pm_control(const struct device *dev, enum pm_device_action action)
{
	int rc = 0;

	switch (action) {
	case PM_DEVICE_ACTION_SUSPEND:
		acquire_device(dev);
		rc = enter_dpd(dev);
		release_device(dev);
		break;
	case PM_DEVICE_ACTION_RESUME:
		acquire_device(dev);
		rc = exit_dpd(dev);
		release_device(dev);
		break;
	case PM_DEVICE_ACTION_TURN_ON:
		/* Coming out of power off */
		rc = spi_nor_configure(dev);
		if (rc == 0) {
			/* Move to DPD, the correct device state
			 * for PM_DEVICE_STATE_SUSPENDED
			 */
			acquire_device(dev);
			rc = enter_dpd(dev);
			release_device(dev);
		}
		break;
	case PM_DEVICE_ACTION_TURN_OFF:
		break;
	default:
		rc = -ENOSYS;
	}

	return rc;
}

/**
 * @brief Initialize and configure the flash
 *
 * @param name The flash name
 * @return 0 on success, negative errno code otherwise
 */
static int spi_nor_init(const struct device *dev)
{
	if (IS_ENABLED(CONFIG_MULTITHREADING)) {
		struct spi_nor_data *const driver_data = dev->data;

		k_sem_init(&driver_data->sem, 1, K_SEM_MAX_LIMIT);
	}

#if ANY_INST_HAS_WP_GPIOS
	if (DEV_CFG(dev)->wp_gpios_exist) {
		if (!device_is_ready(DEV_CFG(dev)->wp.port)) {
			LOG_ERR("Write-protect pin not ready");
			return -ENODEV;
		}
		if (gpio_pin_configure_dt(&(DEV_CFG(dev)->wp), GPIO_OUTPUT_ACTIVE)) {
			LOG_ERR("Write-protect pin failed to set active");
			return -ENODEV;
		}
	}
#endif /* ANY_INST_HAS_WP_GPIOS */
#if ANY_INST_HAS_HOLD_GPIOS
	if (DEV_CFG(dev)->hold_gpios_exist) {
		if (!device_is_ready(DEV_CFG(dev)->hold.port)) {
			LOG_ERR("Hold pin not ready");
			return -ENODEV;
		}
		if (gpio_pin_configure_dt(&(DEV_CFG(dev)->hold), GPIO_OUTPUT_INACTIVE)) {
			LOG_ERR("Hold pin failed to set inactive");
			return -ENODEV;
		}
	}
#endif /* ANY_INST_HAS_HOLD_GPIOS */

	return pm_device_driver_init(dev, spi_nor_pm_control);
}

#if defined(CONFIG_FLASH_PAGE_LAYOUT)

static void spi_nor_pages_layout(const struct device *dev,
				 const struct flash_pages_layout **layout,
				 size_t *layout_size)
{
	/* Data for runtime, const for devicetree and minimal. */
#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
	const struct spi_nor_data *data = dev->data;

	*layout = &data->layout;
#else /* CONFIG_SPI_NOR_SFDP_RUNTIME */
	const struct spi_nor_config *cfg = dev->config;

	*layout = &cfg->layout;
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */

	*layout_size = 1;
}

#endif /* CONFIG_FLASH_PAGE_LAYOUT */

static const struct flash_parameters *
flash_nor_get_parameters(const struct device *dev)
{
	ARG_UNUSED(dev);

	return &flash_nor_parameters;
}

static int flash_nor_get_size(const struct device *dev, uint64_t *size)
{
	*size = (uint64_t)dev_flash_size(dev);

	return 0;
}

static DEVICE_API(flash, spi_nor_api) = {
	.read = spi_nor_read,
	.write = spi_nor_write,
	.erase = spi_nor_erase,
	.get_parameters = flash_nor_get_parameters,
	.get_size = flash_nor_get_size,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
	.page_layout = spi_nor_pages_layout,
#endif
#if defined(CONFIG_FLASH_JESD216_API)
	.sfdp_read = spi_nor_sfdp_read,
	.read_jedec_id = spi_nor_read_jedec_id,
#endif
#if defined(CONFIG_FLASH_EX_OP_ENABLED)
	.ex_op = flash_spi_nor_ex_op,
#endif
};

#define PAGE_LAYOUT_GEN(idx)								\
	BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, size),					\
		"jedec,spi-nor size required for non-runtime SFDP page layout");	\
	enum {										\
		INST_##idx##_BYTES = (DT_INST_PROP(idx, size) / 8)			\
	};										\
	BUILD_ASSERT(SPI_NOR_IS_SECTOR_ALIGNED(CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE),	\
		"SPI_NOR_FLASH_LAYOUT_PAGE_SIZE must be multiple of 4096");		\
	enum {										\
		LAYOUT_PAGES_##idx##_COUNT =						\
			(INST_##idx##_BYTES / CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE)	\
	};										\
	BUILD_ASSERT((CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE * LAYOUT_PAGES_##idx##_COUNT) ==	\
			INST_##idx##_BYTES,						\
	     "SPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size");

#define SFDP_BFP_ATTR_GEN(idx)							\
	BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, sfdp_bfp),			\
		"jedec,spi-nor sfdp-bfp required for devicetree SFDP");		\
	static const __aligned(4) uint8_t bfp_##idx##_data[] = DT_INST_PROP(idx, sfdp_bfp);

#define INST_ATTR_GEN(idx)								\
	BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, jedec_id),				\
		"jedec,spi-nor jedec-id required for non-runtime SFDP");		\
	IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, (PAGE_LAYOUT_GEN(idx)))			\
	IF_ENABLED(CONFIG_SPI_NOR_SFDP_DEVICETREE, (SFDP_BFP_ATTR_GEN(idx)))

#define ATTRIBUTES_DEFINE(idx) COND_CODE_1(CONFIG_SPI_NOR_SFDP_RUNTIME, EMPTY(),	\
	(INST_ATTR_GEN(idx)))

#define DEFINE_PAGE_LAYOUT(idx)								\
	IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT,						\
		   (.layout = {								\
			.pages_count = LAYOUT_PAGES_##idx##_COUNT,			\
			.pages_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE,		\
		    },))

#define INST_HAS_LOCK(idx) DT_INST_NODE_HAS_PROP(idx, has_lock)

#define LOCK_DEFINE(idx)								\
	IF_ENABLED(INST_HAS_LOCK(idx), (BUILD_ASSERT(DT_INST_PROP(idx, has_lock) ==	\
					(DT_INST_PROP(idx, has_lock) & 0xFF),		\
					"Need support for lock clear beyond SR1");))

#define CONFIGURE_4BYTE_ADDR(idx) .enter_4byte_addr = DT_INST_PROP_OR(idx, enter_4byte_addr, 0),

#define INIT_T_ENTER_DPD(idx)								\
	COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, t_enter_dpd),				\
		(.t_enter_dpd =								\
			DIV_ROUND_UP(DT_INST_PROP(idx, t_enter_dpd), NSEC_PER_MSEC)),\
		(.t_enter_dpd = 0))

#if ANY_INST_HAS_T_EXIT_DPD
#define INIT_T_EXIT_DPD(idx)								\
	COND_CODE_1(									\
		DT_INST_NODE_HAS_PROP(idx, t_exit_dpd),					\
		(.t_exit_dpd = DIV_ROUND_UP(DT_INST_PROP(idx, t_exit_dpd), NSEC_PER_MSEC)),\
		(.t_exit_dpd = 0))
#endif

#define INIT_WP_GPIOS(idx) .wp = GPIO_DT_SPEC_INST_GET_OR(idx, wp_gpios, {0})

#define INIT_HOLD_GPIOS(idx) .hold = GPIO_DT_SPEC_INST_GET_OR(idx, hold_gpios, {0})

#define INIT_WAKEUP_SEQ_PARAMS(idx)							\
	COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, dpd_wakeup_sequence),			\
		(.t_dpdd_ms = DIV_ROUND_UP(						\
			DT_INST_PROP_BY_IDX(idx, dpd_wakeup_sequence, 0), NSEC_PER_MSEC),\
		.t_crdp_ms = DIV_ROUND_UP(						\
			DT_INST_PROP_BY_IDX(idx, dpd_wakeup_sequence, 1), NSEC_PER_MSEC),\
		.t_rdp_ms = DIV_ROUND_UP(						\
			DT_INST_PROP_BY_IDX(idx, dpd_wakeup_sequence, 2), NSEC_PER_MSEC)),\
		(.t_dpdd_ms = 0, .t_crdp_ms = 0, .t_rdp_ms = 0))

#define INIT_MXICY_MX25R_POWER_MODE(idx)							\
	.mxicy_mx25r_power_mode = DT_INST_ENUM_IDX_OR(idx, mxicy_mx25r_power_mode, 0)

#define INIT_RESET_GPIOS(idx) .reset = GPIO_DT_SPEC_INST_GET_OR(idx, reset_gpios, {0})

#define INST_CONFIG_STRUCT_GEN(idx)								\
	DEFINE_PAGE_LAYOUT(idx)									\
	.flash_size = DT_INST_PROP(idx, size) / 8,						\
	.jedec_id = DT_INST_PROP(idx, jedec_id),						\
	IF_ENABLED(CONFIG_SPI_NOR_SFDP_MINIMAL, (CONFIGURE_4BYTE_ADDR(idx)))			\
	IF_ENABLED(CONFIG_SPI_NOR_SFDP_DEVICETREE,						\
		(.bfp_len = sizeof(bfp_##idx##_data) / 4,					\
		 .bfp = (const struct jesd216_bfp *)bfp_##idx##_data,))

#define GENERATE_CONFIG_STRUCT(idx)								\
	static const struct spi_nor_config spi_nor_##idx##_config = {				\
		.spi = SPI_DT_SPEC_INST_GET(idx, SPI_WORD_SET(8)),				\
		.dpd_exist = DT_INST_PROP(idx, has_dpd),					\
		.dpd_wakeup_sequence_exist = DT_INST_NODE_HAS_PROP(idx, dpd_wakeup_sequence),	\
		.mxicy_mx25r_power_mode_exist =							\
			DT_INST_NODE_HAS_PROP(idx, mxicy_mx25r_power_mode),			\
		.reset_gpios_exist = DT_INST_NODE_HAS_PROP(idx, reset_gpios),			\
		.requires_ulbpr_exist = DT_INST_PROP(idx, requires_ulbpr),			\
		.wp_gpios_exist = DT_INST_NODE_HAS_PROP(idx, wp_gpios),				\
		.hold_gpios_exist = DT_INST_NODE_HAS_PROP(idx, hold_gpios),			\
		.use_4b_addr_opcodes = DT_INST_PROP(idx, use_4b_addr_opcodes),			\
		.has_flsr = DT_INST_PROP(idx, use_flag_status_register),			\
		.use_fast_read = DT_INST_PROP(idx, use_fast_read),				\
		IF_ENABLED(INST_HAS_LOCK(idx), (.has_lock = DT_INST_PROP(idx, has_lock),))	\
		IF_ENABLED(ANY_INST_HAS_DPD, (INIT_T_ENTER_DPD(idx),))				\
		IF_ENABLED(UTIL_AND(ANY_INST_HAS_DPD, ANY_INST_HAS_T_EXIT_DPD),			\
			(INIT_T_EXIT_DPD(idx),))						\
		IF_ENABLED(ANY_INST_HAS_DPD_WAKEUP_SEQUENCE, (INIT_WAKEUP_SEQ_PARAMS(idx),))	\
		IF_ENABLED(ANY_INST_HAS_MXICY_MX25R_POWER_MODE,					\
			(INIT_MXICY_MX25R_POWER_MODE(idx),))					\
		IF_ENABLED(ANY_INST_HAS_RESET_GPIOS, (INIT_RESET_GPIOS(idx),))			\
		IF_ENABLED(ANY_INST_HAS_WP_GPIOS, (INIT_WP_GPIOS(idx),))			\
		IF_ENABLED(ANY_INST_HAS_HOLD_GPIOS, (INIT_HOLD_GPIOS(idx),))			\
		IF_DISABLED(CONFIG_SPI_NOR_SFDP_RUNTIME, (INST_CONFIG_STRUCT_GEN(idx)))};

#define ASSIGN_PM(idx)							\
		PM_DEVICE_DT_INST_DEFINE(idx, spi_nor_pm_control);

#define SPI_NOR_INST(idx)							\
	ASSIGN_PM(idx)								\
	ATTRIBUTES_DEFINE(idx)							\
	LOCK_DEFINE(idx)							\
	GENERATE_CONFIG_STRUCT(idx)						\
	static struct spi_nor_data spi_nor_##idx##_data;			\
	DEVICE_DT_INST_DEFINE(idx, &spi_nor_init, PM_DEVICE_DT_INST_GET(idx),	\
			&spi_nor_##idx##_data, &spi_nor_##idx##_config,		\
			POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY, &spi_nor_api);

DT_INST_FOREACH_STATUS_OKAY(SPI_NOR_INST)
