/*
 * Copyright (c) 2022-2023, Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT intel_socfpga_reset

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

/** regwidth 4 for 32 bit register */
#define RESET_REG_WIDTH 4

struct reset_intel_config {
	DEVICE_MMIO_ROM;
	/* check peripheral active low / active high */
	bool active_low;
};

struct reset_intel_soc_data {
	DEVICE_MMIO_RAM;
};

static int32_t reset_intel_soc_status(const struct device *dev, uint32_t id, uint8_t *status)
{
	const struct reset_intel_config *config = (const struct reset_intel_config *)dev->config;
	uintptr_t base_address = DEVICE_MMIO_GET(dev);
	uint32_t value;
	uint16_t offset;
	uint8_t regbit;

	regbit = (id & ((RESET_REG_WIDTH << (RESET_REG_WIDTH - 1)) - 1));
	offset = (id >> (RESET_REG_WIDTH + 1)) << (RESET_REG_WIDTH >> 1);
	value = sys_read32(base_address + offset);
	*status = !(value & BIT(regbit)) ^ config->active_low;
	return 0;
}

static void reset_intel_soc_update(const struct device *dev, uint32_t id, bool assert)
{
	const struct reset_intel_config *config = (const struct reset_intel_config *)dev->config;
	uintptr_t base_address = DEVICE_MMIO_GET(dev);
	uint16_t offset;
	uint8_t regbit;

	regbit = (id & ((RESET_REG_WIDTH << (RESET_REG_WIDTH - 1)) - 1));
	offset = (id >> (RESET_REG_WIDTH + 1)) << (RESET_REG_WIDTH >> 1);

	if (assert ^ !config->active_low) {
		if (sys_test_bit(base_address + offset, regbit) == 0) {
			sys_set_bit(base_address + offset, regbit);
		}
	} else {
		if (sys_test_bit(base_address + offset, regbit) != 0) {
			sys_clear_bit(base_address + offset, regbit);
		}
	}
}

static int32_t reset_intel_soc_line_assert(const struct device *dev, uint32_t id)
{
	reset_intel_soc_update(dev, id, true);

	return 0;
}

static int32_t reset_intel_soc_line_deassert(const struct device *dev, uint32_t id)
{
	reset_intel_soc_update(dev, id, false);

	return 0;
}

static int32_t reset_intel_soc_line_toggle(const struct device *dev, uint32_t id)
{
	(void)reset_intel_soc_line_assert(dev, id);
	/* TODO: Add required delay once tested on Emulator/Hardware platform. */
	(void)reset_intel_soc_line_deassert(dev, id);

	return 0;
}

static int32_t reset_intel_soc_init(const struct device *dev)
{
	DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);

	return 0;
}

static const struct reset_driver_api reset_intel_soc_driver_api = {
	.status = reset_intel_soc_status,
	.line_assert = reset_intel_soc_line_assert,
	.line_deassert = reset_intel_soc_line_deassert,
	.line_toggle = reset_intel_soc_line_toggle,
};

#define INTEL_SOC_RESET_INIT(_inst)						\
	static struct reset_intel_soc_data reset_intel_soc_data_##_inst;	\
	static const struct reset_intel_config reset_intel_config_##_inst = {	\
		DEVICE_MMIO_ROM_INIT(DT_DRV_INST(_inst)),			\
		.active_low = DT_INST_PROP(_inst, active_low),			\
	};									\
										\
	DEVICE_DT_INST_DEFINE(_inst,						\
			reset_intel_soc_init,					\
			NULL,							\
			&reset_intel_soc_data_##_inst,				\
			&reset_intel_config_##_inst,				\
			PRE_KERNEL_1,						\
			CONFIG_RESET_INIT_PRIORITY,				\
			&reset_intel_soc_driver_api);

DT_INST_FOREACH_STATUS_OKAY(INTEL_SOC_RESET_INIT);
