/* Copyright (c) 2025 Silicon Laboratories Inc.
 * SPDX-License-Identifier: Apache-2.0
 */
#define DT_DRV_COMPAT silabs_siwx91x_qspi_memory
#include <zephyr/logging/log.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/pinctrl.h>

#include <zephyr/sys/util.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>

#include "rsi_qspi_proto.h"
#include "sl_si91x_psram_handle.h"

LOG_MODULE_REGISTER(siwx91x_memc, CONFIG_MEMC_LOG_LEVEL);

struct siwx91x_memc_config {
	qspi_reg_t *reg;
	const struct device *clock_dev;
	clock_control_subsys_t clock_subsys;
	const struct pinctrl_dev_config *pincfg;
};

static int siwx91x_memc_init(const struct device *dev)
{
	const struct siwx91x_memc_config *config = dev->config;
	int ret;

	/* Memory controller is automatically setup by the siwx91x bootloader,
	 * so we have to uninitialize it before to change the configuration
	 */
	ret = sl_si91x_psram_uninit();
	if (ret) {
		return -EIO;
	}

	ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
	if (ret) {
		return -EIO;
	}
	if (config->clock_dev) {
		ret = device_is_ready(config->clock_dev);
		if (!ret) {
			return -EINVAL;
		}
		ret = clock_control_on(config->clock_dev, config->clock_subsys);
		if (ret && ret != -EALREADY && ret != -ENOSYS) {
			return ret;
		}
	}

	ret = sl_si91x_psram_init();
	if (ret) {
		LOG_ERR("sl_si91x_psram_init() returned %d", ret);
		return -EIO;
	}

	return 0;
}

PINCTRL_DT_INST_DEFINE(0);
static const struct siwx91x_memc_config siwx91x_memc_config = {
	.reg = (void *)DT_INST_REG_ADDR(0),
	.clock_dev = DEVICE_DT_GET_OR_NULL(DT_INST_CLOCKS_CTLR(0)),
	.clock_subsys = (void *)DT_INST_PHA_OR(0, clocks, clkid, NULL),
	.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
};
/* Required to properly initialize ,deviceID */
static const uint8_t devid[] = DT_INST_PROP(0, device_id);

/* PSRAM_Device is directly referenced by sl_si91x_psram_init() */
struct sl_psram_info_type_t PSRAM_Device = {
	.deviceID.MFID          = devid[0],
	.deviceID.KGD           = devid[1],
	.deviceID.EID           = { devid[2], devid[3], devid[4], devid[5], devid[6], devid[7] },
	/* FIXME: Currently, the Chip Select (.cs_no property) and the RAM start
	 * address are hard coded. The hardware also support Chip Select == 1,
	 * then RAM start address will be 0xb000000.
	 */
	.devDensity             = DT_REG_SIZE(DT_INST_CHILD(0, psram_a000000)),
	.normalReadMAXFrequency = DT_INST_PROP(0, normal_freq),
	.fastReadMAXFrequency   = DT_INST_PROP(0, fast_freq),
	.rwType                 = QUAD_RW,
	.defaultBurstWrapSize   = 1024,
	.toggleBurstWrapSize    = 0,
	.spi_config.spi_config_2.auto_mode         = 1,
	/* FIXME: user may want to customize these values */
	.spi_config.spi_config_1.read_cmd          = 0xEB,
	.spi_config.spi_config_3.wr_cmd            = 0x38,
	.spi_config.spi_config_1.extra_byte_mode   = QUAD_MODE,
	.spi_config.spi_config_1.dummy_mode        = QUAD_MODE,
	.spi_config.spi_config_1.addr_mode         = QUAD_MODE,
	.spi_config.spi_config_1.data_mode         = QUAD_MODE,
	.spi_config.spi_config_1.inst_mode         = QUAD_MODE,
	.spi_config.spi_config_3.wr_addr_mode      = QUAD_MODE,
	.spi_config.spi_config_3.wr_data_mode      = QUAD_MODE,
	.spi_config.spi_config_3.wr_inst_mode      = QUAD_MODE,
	.spi_config.spi_config_2.wrap_len_in_bytes = NO_WRAP,
	.spi_config.spi_config_2.swap_en           = 1,
	.spi_config.spi_config_2.addr_width        = 3, /* 24 bits */
	.spi_config.spi_config_2.cs_no             = 0,
	.spi_config.spi_config_4.secondary_csn     = 1,
	.spi_config.spi_config_2.neg_edge_sampling = 1,
	.spi_config.spi_config_1.no_of_dummy_bytes = 3,
	.spi_config.spi_config_1.dummy_W_or_R      = DUMMY_READS,
	.spi_config.spi_config_2.full_duplex       = IGNORE_FULL_DUPLEX,
	.spi_config.spi_config_2.qspi_clk_en       = QSPI_FULL_TIME_CLK,
	.spi_config.spi_config_1.flash_type        = 0xf,
	.spi_config.spi_config_3.dummys_4_jump     = 1,
	.spi_config.spi_config_4.valid_prot_bits   = 4,
	.spi_config.spi_config_1.d3d2_data         = 0x03,
	.spi_config.spi_config_5.d7_d4_data        = 0x0f,
};
/* PSRAMSecureSegments is directly referenced by sl_si91x_psram_init() */
struct PSRAMSecureSegmentType PSRAMSecureSegments[MAX_SEC_SEGMENTS] = {
	[0].segmentEnable = 1,
	[0].lowerBoundary = 0x00000,
	[0].higherBoundary = 0x0ffff,
};
DEVICE_DT_INST_DEFINE(0, siwx91x_memc_init, NULL, NULL, &siwx91x_memc_config,
		      PRE_KERNEL_1, CONFIG_MEMC_INIT_PRIORITY, NULL);
