// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright (c) 2022-2024, STMicroelectronics
 */

#include <arm.h>
#include <config.h>
#include <drivers/clk.h>
#include <drivers/clk_dt.h>
#include <drivers/stm32_rif.h>
#include <io.h>
#include <kernel/boot.h>
#include <kernel/delay.h>
#include <kernel/dt.h>
#include <kernel/dt_driver.h>
#include <kernel/panic.h>
#include <kernel/pm.h>
#include <libfdt.h>
#include <mm/core_memprot.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stm32_util.h>
#include <trace.h>

#define IPCC_C1SECCFGR			U(0x80)
#define IPCC_C1PRIVCFGR			U(0x84)
#define IPCC_C1CIDCFGR			U(0x88)
#define IPCC_C2SECCFGR			U(0x90)
#define IPCC_C2PRIVCFGR			U(0x94)
#define IPCC_C2CIDCFGR			U(0x98)
#define IPCC_HWCFGR			U(0x3F0)

/*
 * CIDCFGR register bitfields
 */
#define IPCC_CIDCFGR_CFEN		BIT(0)
#define IPCC_CIDCFGR_SCID_MASK		GENMASK_32(6, 4)
#define IPCC_CIDCFGR_CONF_MASK		(_CIDCFGR_CFEN |	 \
					 IPCC_CIDCFGR_SCID_MASK)

/*
 * PRIVCFGR register bitfields
 */
#define IPCC_PRIVCFGR_MASK		GENMASK_32(15, 0)

/*
 * SECCFGR register bitfields
 */
#define IPCC_SECCFGR_MASK		GENMASK_32(15, 0)

/*
 * IPCC_HWCFGR register bitfields
 */
#define IPCC_HWCFGR_CHAN_MASK		GENMASK_32(7, 0)

/*
 * Miscellaneous
 */
#define IPCC_NB_MAX_RIF_CHAN		U(16)

struct ipcc_pdata {
	/*
	 * An IPCC has nb_channels_cfg channel configuration for its
	 * (nb_channels_cfg / 2) bi-directionnal channels
	 */
	unsigned int nb_channels_cfg;
	struct clk *ipcc_clock;
	vaddr_t base;
	struct rif_conf_data *conf_data;
	bool is_tdcid;

	STAILQ_ENTRY(ipcc_pdata) link;
};

static STAILQ_HEAD(, ipcc_pdata) ipcc_list =
		STAILQ_HEAD_INITIALIZER(ipcc_list);

/* This function expects IPCC bus clock is enabled */
static void apply_rif_config(struct ipcc_pdata *ipcc_d)
{
	bool proc1_cidfilt = false;
	bool proc2_cidfilt = false;
	uint32_t priv_proc_1 = 0;
	uint32_t priv_proc_2 = 0;
	uint32_t sec_proc_1 = 0;
	uint32_t sec_proc_2 = 0;
	unsigned int cid1 = 0;
	unsigned int cid2 = 0;
	unsigned int i = 0;
	bool is_cid_configured = false;

	if (!ipcc_d->conf_data)
		return;

	/*
	 * Check that the number of channel supported by hardware
	 * is coherent with the config
	 */
	assert((io_read32(ipcc_d->base + IPCC_HWCFGR) &
			  IPCC_HWCFGR_CHAN_MASK) >=
	       ipcc_d->nb_channels_cfg / 2);

	/*
	 * When TDCID, OP-TEE should be the one to set the CID filtering
	 * configuration. Clearing previous configuration prevents
	 * undesired events during the only legitimate configuration.
	 */
	if (ipcc_d->is_tdcid) {
		/* IPCC Processor 1 */
		io_clrbits32(ipcc_d->base + IPCC_C1CIDCFGR,
			     IPCC_CIDCFGR_CONF_MASK);

		/* IPCC Processor 2 */
		io_clrbits32(ipcc_d->base + IPCC_C2CIDCFGR,
			     IPCC_CIDCFGR_CONF_MASK);
	}

	cid1 = io_read32(ipcc_d->base + IPCC_C1CIDCFGR) &
	       IPCC_CIDCFGR_SCID_MASK;
	cid2 = io_read32(ipcc_d->base + IPCC_C2CIDCFGR) &
	       IPCC_CIDCFGR_SCID_MASK;

	proc1_cidfilt = io_read32(ipcc_d->base + IPCC_C1CIDCFGR) &
		       IPCC_CIDCFGR_CFEN;
	proc2_cidfilt = io_read32(ipcc_d->base + IPCC_C2CIDCFGR) &
		       IPCC_CIDCFGR_CFEN;

	/* Split the sec and priv configuration for IPCC processor 1 and 2 */
	sec_proc_1 = ipcc_d->conf_data->sec_conf[0] &
		     GENMASK_32(IPCC_NB_MAX_RIF_CHAN - 1, 0);
	priv_proc_1 = ipcc_d->conf_data->priv_conf[0] &
		      GENMASK_32(IPCC_NB_MAX_RIF_CHAN - 1, 0);

	sec_proc_2 = (ipcc_d->conf_data->sec_conf[0] &
		      GENMASK_32((IPCC_NB_MAX_RIF_CHAN * 2) - 1,
				 IPCC_NB_MAX_RIF_CHAN)) >>
		     IPCC_NB_MAX_RIF_CHAN;
	priv_proc_2 = (ipcc_d->conf_data->priv_conf[0] &
		       GENMASK_32((IPCC_NB_MAX_RIF_CHAN * 2) - 1,
				  IPCC_NB_MAX_RIF_CHAN)) >>
		      IPCC_NB_MAX_RIF_CHAN;

	/* Security and privilege RIF configuration */
	if (!proc1_cidfilt || cid1 == RIF_CID1) {
		io_clrsetbits32(ipcc_d->base + IPCC_C1PRIVCFGR,
				IPCC_PRIVCFGR_MASK, priv_proc_1);
		io_clrsetbits32(ipcc_d->base + IPCC_C1SECCFGR,
				IPCC_SECCFGR_MASK, sec_proc_1);
	}
	if (!proc2_cidfilt || cid2 == RIF_CID1) {
		io_clrsetbits32(ipcc_d->base + IPCC_C2PRIVCFGR,
				IPCC_PRIVCFGR_MASK, priv_proc_2);
		io_clrsetbits32(ipcc_d->base + IPCC_C2SECCFGR,
				IPCC_SECCFGR_MASK, sec_proc_2);
	}

	/*
	 * Evaluate RIF CID filtering configuration before setting it.
	 * Parsed configuration must have consistency. If CID filtering
	 * is enabled for an IPCC channel, then it must be the case for all
	 * channels of this processor. This is a configuration check.
	 */
	for (i = 0; i < IPCC_NB_MAX_RIF_CHAN; i++) {
		if (!(BIT(i) & ipcc_d->conf_data->access_mask[0]))
			continue;

		if (!is_cid_configured &&
		    (BIT(0) & ipcc_d->conf_data->cid_confs[i])) {
			is_cid_configured = true;
			if (i > 0)
				panic("Inconsistent IPCC CID filtering RIF configuration");
		}

		if (is_cid_configured &&
		    !(BIT(0) & ipcc_d->conf_data->cid_confs[i]))
			panic("Inconsistent IPCC CID filtering RIF configuration");
	}

	/* IPCC processor 1 CID filtering configuration */
	if (!ipcc_d->is_tdcid)
		return;

	io_clrsetbits32(ipcc_d->base + IPCC_C1CIDCFGR,
			IPCC_CIDCFGR_CONF_MASK,
			ipcc_d->conf_data->cid_confs[0]);

	/*
	 * Reset this field to evaluate CID filtering configuration
	 * for processor 2
	 */
	is_cid_configured = false;

	for (i = IPCC_NB_MAX_RIF_CHAN; i < IPCC_NB_MAX_RIF_CHAN * 2; i++) {
		if (!(BIT(i) & ipcc_d->conf_data->access_mask[0]))
			continue;

		if (!is_cid_configured &&
		    (BIT(0) & ipcc_d->conf_data->cid_confs[i])) {
			is_cid_configured = true;
			if (i > IPCC_NB_MAX_RIF_CHAN)
				panic("Inconsistent IPCC CID filtering RIF configuration");
		}

		if (is_cid_configured &&
		    !(BIT(0) & ipcc_d->conf_data->cid_confs[i]))
			panic("Inconsistent IPCC CID filtering RIF configuration");
	}

	/* IPCC Processor 2 CID filtering configuration */
	io_clrsetbits32(ipcc_d->base + IPCC_C2CIDCFGR,
			IPCC_CIDCFGR_CONF_MASK,
			ipcc_d->conf_data->cid_confs[IPCC_NB_MAX_RIF_CHAN]);
}

static void stm32_ipcc_pm_resume(struct ipcc_pdata *ipcc)
{
	apply_rif_config(ipcc);
}

static void stm32_ipcc_pm_suspend(struct ipcc_pdata *ipcc __unused)
{
	/*
	 * Do nothing because IPCC forbids RIF configuration read if CID
	 * filtering is enabled. We'll simply restore the device tree RIF
	 * configuration.
	 */
}

static TEE_Result
stm32_ipcc_pm(enum pm_op op, unsigned int pm_hint,
	      const struct pm_callback_handle *pm_handle)
{
	struct ipcc_pdata *ipcc = pm_handle->handle;
	TEE_Result res = TEE_ERROR_GENERIC;

	if (!PM_HINT_IS_STATE(pm_hint, CONTEXT) || !ipcc->is_tdcid)
		return TEE_SUCCESS;

	res = clk_enable(ipcc->ipcc_clock);
	if (res)
		return res;

	if (op == PM_OP_RESUME)
		stm32_ipcc_pm_resume(ipcc);
	else
		stm32_ipcc_pm_suspend(ipcc);

	clk_disable(ipcc->ipcc_clock);

	return TEE_SUCCESS;
}

static TEE_Result parse_dt(const void *fdt, int node, struct ipcc_pdata *ipcc_d)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	struct dt_node_info info = { };
	const fdt32_t *cuint = NULL;
	struct io_pa_va addr = { };
	unsigned int i = 0;
	int lenp = 0;

	fdt_fill_device_info(fdt, &info, node);
	assert(info.reg != DT_INFO_INVALID_REG &&
	       info.reg_size != DT_INFO_INVALID_REG_SIZE);

	addr.pa = info.reg;
	ipcc_d->base = io_pa_or_va_secure(&addr, info.reg_size);
	assert(ipcc_d->base);

	/* Gate the IP */
	res = clk_dt_get_by_index(fdt, node, 0, &ipcc_d->ipcc_clock);
	if (res)
		return res;

	cuint = fdt_getprop(fdt, node, "st,protreg", &lenp);
	if (!cuint) {
		DMSG("No RIF configuration available");
		return TEE_SUCCESS;
	}

	ipcc_d->conf_data = calloc(1, sizeof(*ipcc_d->conf_data));
	if (!ipcc_d->conf_data)
		panic();

	ipcc_d->nb_channels_cfg = (unsigned int)(lenp / sizeof(uint32_t));
	assert(ipcc_d->nb_channels_cfg <= (IPCC_NB_MAX_RIF_CHAN * 2));

	ipcc_d->conf_data->cid_confs = calloc(IPCC_NB_MAX_RIF_CHAN * 2,
					      sizeof(uint32_t));
	ipcc_d->conf_data->sec_conf = calloc(1, sizeof(uint32_t));
	ipcc_d->conf_data->priv_conf = calloc(1, sizeof(uint32_t));
	ipcc_d->conf_data->access_mask = calloc(1, sizeof(uint32_t));
	if (!ipcc_d->conf_data->cid_confs || !ipcc_d->conf_data->sec_conf ||
	    !ipcc_d->conf_data->priv_conf || !ipcc_d->conf_data->access_mask)
		panic("Missing memory capacity for ipcc RIF configuration");

	for (i = 0; i < ipcc_d->nb_channels_cfg; i++)
		stm32_rif_parse_cfg(fdt32_to_cpu(cuint[i]), ipcc_d->conf_data,
				    IPCC_NB_MAX_RIF_CHAN * 2);

	return TEE_SUCCESS;
}

static TEE_Result stm32_ipcc_probe(const void *fdt, int node,
				   const void *compat_data __unused)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	struct ipcc_pdata *ipcc_d = NULL;

	ipcc_d = calloc(1, sizeof(*ipcc_d));
	if (!ipcc_d)
		return TEE_ERROR_OUT_OF_MEMORY;

	res = stm32_rifsc_check_tdcid(&ipcc_d->is_tdcid);
	if (res)
		goto err;

	res = parse_dt(fdt, node, ipcc_d);
	if (res)
		goto err;

	res = clk_enable(ipcc_d->ipcc_clock);
	if (res)
		panic("Cannot access IPCC clock");

	apply_rif_config(ipcc_d);

	clk_disable(ipcc_d->ipcc_clock);

	STAILQ_INSERT_TAIL(&ipcc_list, ipcc_d, link);

	register_pm_core_service_cb(stm32_ipcc_pm, ipcc_d, "stm32-ipcc");

	return TEE_SUCCESS;

err:
	/* Free all allocated resources */
	if (ipcc_d->conf_data) {
		free(ipcc_d->conf_data->access_mask);
		free(ipcc_d->conf_data->cid_confs);
		free(ipcc_d->conf_data->priv_conf);
		free(ipcc_d->conf_data->sec_conf);
	}
	free(ipcc_d->conf_data);
	free(ipcc_d);

	return res;
}

static const struct dt_device_match stm32_ipcc_match_table[] = {
	{ .compatible = "st,stm32mp25-ipcc" },
	{ }
};

DEFINE_DT_DRIVER(stm32_ipcc_dt_driver) = {
	.name = "st,stm32mp-ipcc",
	.match_table = stm32_ipcc_match_table,
	.probe = stm32_ipcc_probe,
};
