/*
 * Copyright (c) 2021 Antmicro <www.antmicro.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/fpga.h>
#include "fpga_eos_s3.h"

void eos_s3_fpga_enable_clk(void)
{
	CRU->C16_CLK_GATE = C16_CLK_GATE_PATH_0_ON;
	CRU->C21_CLK_GATE = C21_CLK_GATE_PATH_0_ON;
	CRU->C09_CLK_GATE = C09_CLK_GATE_PATH_1_ON | C09_CLK_GATE_PATH_2_ON;
	CRU->C02_CLK_GATE = C02_CLK_GATE_PATH_1_ON;
}

void eos_s3_fpga_disable_clk(void)
{
	CRU->C16_CLK_GATE = C16_CLK_GATE_PATH_0_OFF;
	CRU->C21_CLK_GATE = C21_CLK_GATE_PATH_0_OFF;
	CRU->C09_CLK_GATE = C09_CLK_GATE_PATH_1_OFF | C09_CLK_GATE_PATH_2_OFF;
	CRU->C02_CLK_GATE = C02_CLK_GATE_PATH_1_OFF;
}

struct quickfeather_fpga_data {
	char *FPGA_info;
};

static enum FPGA_status eos_s3_fpga_get_status(const struct device *dev)
{
	ARG_UNUSED(dev);

	if (PMU->FB_STATUS == FPGA_STATUS_ACTIVE) {
		return FPGA_STATUS_ACTIVE;
	} else {
		return FPGA_STATUS_INACTIVE;
	}
}

static const char *eos_s3_fpga_get_info(const struct device *dev)
{
	struct quickfeather_fpga_data *data = dev->data;

	return data->FPGA_info;
}

static int eos_s3_fpga_on(const struct device *dev)
{
	if (eos_s3_fpga_get_status(dev) == FPGA_STATUS_ACTIVE) {
		return 0;
	}

	/* wake up the FPGA power domain */
	PMU->FFE_FB_PF_SW_WU = PMU_FFE_FB_PF_SW_WU_FB_WU;
	while (PMU->FFE_FB_PF_SW_WU == PMU_FFE_FB_PF_SW_WU_FB_WU) {
		/* The register will clear itself if the FPGA starts */
	};

	eos_s3_fpga_enable_clk();

	/* enable FPGA programming */
	PMU->GEN_PURPOSE_0 = FB_CFG_ENABLE;
	PIF->CFG_CTL = CFG_CTL_LOAD_ENABLE;

	return 0;
}

static int eos_s3_fpga_off(const struct device *dev)
{
	if (eos_s3_fpga_get_status(dev) == FPGA_STATUS_INACTIVE) {
		return 0;
	}

	PMU->FB_PWR_MODE_CFG = PMU_FB_PWR_MODE_CFG_FB_SD;
	PMU->FFE_FB_PF_SW_PD = PMU_FFE_FB_PF_SW_PD_FB_PD;

	eos_s3_fpga_disable_clk();

	return 0;
}

static int eos_s3_fpga_reset(const struct device *dev)
{
	if (eos_s3_fpga_get_status(dev) == FPGA_STATUS_ACTIVE) {
		eos_s3_fpga_off(dev);
	}

	eos_s3_fpga_on(dev);

	if (eos_s3_fpga_get_status(dev) == FPGA_STATUS_INACTIVE) {
		return -EAGAIN;
	}

	return 0;
}

static int eos_s3_fpga_load(const struct device *dev, uint32_t *image_ptr, uint32_t img_size)
{
	if (eos_s3_fpga_get_status(dev) == FPGA_STATUS_INACTIVE) {
		return -EINVAL;
	}

	volatile uint32_t *bitstream = (volatile uint32_t *)image_ptr;

	for (uint32_t chunk_cnt = 0; chunk_cnt < (img_size / 4); chunk_cnt++) {
		PIF->CFG_DATA = *bitstream;
		bitstream++;
	}

	/* disable FPGA programming */
	PMU->GEN_PURPOSE_0 = FB_CFG_DISABLE;
	PIF->CFG_CTL = CFG_CTL_LOAD_DISABLE;
	PMU->FB_ISOLATION = FB_ISOLATION_DISABLE;

	/* disable software resets */
	CRU->FB_SW_RESET &= ~(FB_C21_DOMAIN_SW_RESET
			    | FB_C16_DOMAIN_SW_RESET
			    | FB_C02_DOMAIN_SW_RESET
			    | FB_C09_DOMAIN_SW_RESET);

	return 0;
}

static int eos_s3_fpga_init(const struct device *dev)
{
	IO_MUX->PAD_19_CTRL = PAD_ENABLE;

	struct quickfeather_fpga_data *data = dev->data;

	data->FPGA_info = FPGA_INFO;

	eos_s3_fpga_reset(dev);

	return 0;
}

static struct quickfeather_fpga_data fpga_data;

static DEVICE_API(fpga, eos_s3_api) = {
	.reset = eos_s3_fpga_reset,
	.load = eos_s3_fpga_load,
	.get_status = eos_s3_fpga_get_status,
	.on = eos_s3_fpga_on,
	.off = eos_s3_fpga_off,
	.get_info = eos_s3_fpga_get_info
};

DEVICE_DT_DEFINE(DT_NODELABEL(fpga0), &eos_s3_fpga_init, NULL, &fpga_data, NULL, POST_KERNEL,
	      CONFIG_FPGA_INIT_PRIORITY, &eos_s3_api);
