/*
 * Copyright (c) 2023, Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#define DT_DRV_COMPAT cdns_nand

#include "socfpga_system_manager.h"

#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/kernel.h>

/* Check if reset property is defined */
#define CDNS_NAND_RESET_SUPPORT DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)

#if CDNS_NAND_RESET_SUPPORT
#include <zephyr/drivers/reset.h>
#endif

#include "flash_cadence_nand_ll.h"

#define DEV_CFG(_dev)  ((const struct flash_cadence_nand_config *)(_dev)->config)
#define DEV_DATA(_dev) ((struct flash_cadence_nand_data *const)(_dev)->data)

#define FLASH_WRITE_SIZE DT_PROP(DT_INST(0, DT_DRV_COMPAT), block_size)

#ifdef CONFIG_BOARD_INTEL_SOCFPGA_AGILEX5_SOCDK
#define DFI_CFG_OFFSET 0xFC
/* To check the DFI register setting for NAND in the System Manager */
#define DFI_SEL_CHK    (SOCFPGA_SYSMGR_REG_BASE + DFI_CFG_OFFSET)
#endif

LOG_MODULE_REGISTER(flash_cdns_nand, CONFIG_FLASH_LOG_LEVEL);

struct flash_cadence_nand_data {
	DEVICE_MMIO_NAMED_RAM(nand_reg);
	DEVICE_MMIO_NAMED_RAM(sdma);
	/* device info structure */
	struct cadence_nand_params params;
	/* Mutex to prevent multiple processes from accessing the same driver api */
	struct k_mutex nand_mutex;
#if CONFIG_CDNS_NAND_INTERRUPT_SUPPORT
	/* Semaphore to send a signal from an interrupt handler to a thread  */
	struct k_sem interrupt_sem;
#endif
};

struct flash_cadence_nand_config {
	DEVICE_MMIO_NAMED_ROM(nand_reg);
	DEVICE_MMIO_NAMED_ROM(sdma);
#if CDNS_NAND_RESET_SUPPORT
	/* Reset controller device configuration for NAND*/
	const struct reset_dt_spec reset;
	/* Reset controller device configuration for Combo Phy*/
	const struct reset_dt_spec combo_phy_reset;
#endif
#if CONFIG_CDNS_NAND_INTERRUPT_SUPPORT
	void (*irq_config)(void);
#endif
};

static const struct flash_parameters flash_cdns_parameters = {.write_block_size = FLASH_WRITE_SIZE,
							      .erase_value = 0xFF};

#if CONFIG_FLASH_PAGE_LAYOUT

struct flash_pages_layout flash_cdns_pages_layout;

void flash_cdns_page_layout(const struct device *nand_dev, const struct flash_pages_layout **layout,
			    size_t *layout_size)
{
	struct flash_cadence_nand_data *const nand_data = DEV_DATA(nand_dev);
	struct cadence_nand_params *nand_param = &nand_data->params;

	flash_cdns_pages_layout.pages_count = nand_param->page_count;
	flash_cdns_pages_layout.pages_size = nand_param->page_size;
	*layout = &flash_cdns_pages_layout;
	*layout_size = 1;
}

#endif

static int flash_cdns_nand_erase(const struct device *nand_dev, off_t offset, size_t len)
{
	struct flash_cadence_nand_data *const nand_data = DEV_DATA(nand_dev);
	struct cadence_nand_params *nand_param = &nand_data->params;
	int ret;

	k_mutex_lock(&nand_data->nand_mutex, K_FOREVER);

	ret = cdns_nand_erase(nand_param, offset, len);

	k_mutex_unlock(&nand_data->nand_mutex);

	return ret;
}

static int flash_cdns_nand_write(const struct device *nand_dev, off_t offset, const void *data,
				 size_t len)
{
	struct flash_cadence_nand_data *const nand_data = DEV_DATA(nand_dev);
	struct cadence_nand_params *nand_param = &nand_data->params;
	int ret;

	if (data == NULL) {
		LOG_ERR("Invalid input parameter for NAND Flash Write!");
		return -EINVAL;
	}

	k_mutex_lock(&nand_data->nand_mutex, K_FOREVER);

	ret = cdns_nand_write(nand_param, data, offset, len);

	k_mutex_unlock(&nand_data->nand_mutex);

	return ret;
}

static int flash_cdns_nand_read(const struct device *nand_dev, off_t offset, void *data, size_t len)
{
	struct flash_cadence_nand_data *const nand_data = DEV_DATA(nand_dev);
	struct cadence_nand_params *nand_param = &nand_data->params;
	int ret;

	if (data == NULL) {
		LOG_ERR("Invalid input parameter for NAND Flash Read!");
		return -EINVAL;
	}

	k_mutex_lock(&nand_data->nand_mutex, K_FOREVER);

	ret = cdns_nand_read(nand_param, data, offset, len);

	k_mutex_unlock(&nand_data->nand_mutex);

	return ret;
}

static const struct flash_parameters *flash_cdns_get_parameters(const struct device *nand_dev)
{
	ARG_UNUSED(nand_dev);

	return &flash_cdns_parameters;
}
static const struct flash_driver_api flash_cdns_nand_api = {
	.erase = flash_cdns_nand_erase,
	.write = flash_cdns_nand_write,
	.read = flash_cdns_nand_read,
	.get_parameters = flash_cdns_get_parameters,
#ifdef CONFIG_FLASH_PAGE_LAYOUT
	.page_layout = flash_cdns_page_layout,
#endif
};

#if CONFIG_CDNS_NAND_INTERRUPT_SUPPORT

static void cdns_nand_irq_handler(const struct device *nand_dev)
{
	struct flash_cadence_nand_data *const nand_data = DEV_DATA(nand_dev);
	struct cadence_nand_params *nand_param = &nand_data->params;

	cdns_nand_irq_handler_ll(nand_param);
	k_sem_give(&nand_param->interrupt_sem_t);
}

#endif

static int flash_cdns_nand_init(const struct device *nand_dev)
{
	DEVICE_MMIO_NAMED_MAP(nand_dev, nand_reg, K_MEM_CACHE_NONE);
	DEVICE_MMIO_NAMED_MAP(nand_dev, sdma, K_MEM_CACHE_NONE);
	const struct flash_cadence_nand_config *nand_config = DEV_CFG(nand_dev);
	struct flash_cadence_nand_data *const nand_data = DEV_DATA(nand_dev);
	struct cadence_nand_params *nand_param = &nand_data->params;
	int ret;

#ifdef CONFIG_BOARD_INTEL_SOCFPGA_AGILEX5_SOCDK
	uint32_t status;

	status = sys_read32(DFI_SEL_CHK);
	if ((status & 1) != 0) {
		LOG_ERR("DFI not configured for NAND Flash controller!!!");
		return -ENODEV;
	}
#endif

#if CDNS_NAND_RESET_SUPPORT
	/* Reset Combo phy and NAND only if reset controller driver is supported */
	if ((nand_config->combo_phy_reset.dev != NULL) && (nand_config->reset.dev != NULL)) {
		if (!device_is_ready(nand_config->reset.dev)) {
			LOG_ERR("Reset controller device not ready");
			return -ENODEV;
		}

		ret = reset_line_toggle(nand_config->combo_phy_reset.dev,
					nand_config->combo_phy_reset.id);
		if (ret != 0) {
			LOG_ERR("Combo phy reset failed");
			return ret;
		}

		ret = reset_line_toggle(nand_config->reset.dev, nand_config->reset.id);
		if (ret != 0) {
			LOG_ERR("NAND reset failed");
			return ret;
		}
	}
#endif
	nand_param->nand_base = DEVICE_MMIO_NAMED_GET(nand_dev, nand_reg);
	nand_param->sdma_base = DEVICE_MMIO_NAMED_GET(nand_dev, sdma);
	ret = k_mutex_init(&nand_data->nand_mutex);
	if (ret != 0) {
		LOG_ERR("Mutex creation Failed");
		return ret;
	}

#if CONFIG_CDNS_NAND_INTERRUPT_SUPPORT

	if (nand_config->irq_config == NULL) {
		LOG_ERR("Interrupt function not initialized!!");
		return -EINVAL;
	}
	nand_config->irq_config();
	ret = k_sem_init(&nand_param->interrupt_sem_t, 0, 1);
	if (ret != 0) {
		LOG_ERR("Semaphore creation Failed");
		return ret;
	}
#endif
	nand_param->page_count =
		(nand_param->npages_per_block * nand_param->nblocks_per_lun * nand_param->nluns);
	/* NAND Memory Controller init */
	ret = cdns_nand_init(nand_param);
	if (ret != 0) {
		LOG_ERR("NAND initialization Failed");
		return ret;
	}
	return 0;
}

#define CDNS_NAND_RESET_SPEC_INIT(inst)                                                            \
	.reset = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 0),                                           \
	.combo_phy_reset = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 1),

#define CREATE_FLASH_CADENCE_NAND_DEVICE(inst)                                                     \
	IF_ENABLED(CONFIG_CDNS_NAND_INTERRUPT_SUPPORT,                                             \
		   (static void cdns_nand_irq_config_##inst(void);))                               \
	struct flash_cadence_nand_data flash_cadence_nand_data_##inst = {                          \
		.params = {                                                                        \
			.datarate_mode = DT_INST_PROP(inst, data_rate_mode),                       \
		}};                                                                                \
	const struct flash_cadence_nand_config flash_cadence_nand_config_##inst = {                \
		DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(nand_reg, DT_DRV_INST(inst)),                   \
		DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(sdma, DT_DRV_INST(inst)),                       \
		IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, resets), (CDNS_NAND_RESET_SPEC_INIT(inst))) \
			IF_ENABLED(CONFIG_CDNS_NAND_INTERRUPT_SUPPORT,                             \
				   (.irq_config = cdns_nand_irq_config_##inst,))};                 \
	DEVICE_DT_INST_DEFINE(inst, flash_cdns_nand_init, NULL, &flash_cadence_nand_data_##inst,   \
			      &flash_cadence_nand_config_##inst, POST_KERNEL,                      \
			      CONFIG_FLASH_INIT_PRIORITY, &flash_cdns_nand_api);                   \
	IF_ENABLED(CONFIG_CDNS_NAND_INTERRUPT_SUPPORT,                                             \
		   (static void cdns_nand_irq_config_##inst(void)                                  \
		   {										   \
			   IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority),            \
				       cdns_nand_irq_handler, DEVICE_DT_INST_GET(inst), 0);        \
			   irq_enable(DT_INST_IRQN(inst));                                         \
		   }))

DT_INST_FOREACH_STATUS_OKAY(CREATE_FLASH_CADENCE_NAND_DEVICE)
