/*
 * Copyright 2020 NXP
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <errno.h>
#include <stdbool.h>

#include <platform_def.h>

#include <common/debug.h>
#include <lib/mmio.h>
#include <plat/common/platform.h>
#include "xrdc_config.h"

#define XRDC_ADDR	0x292f0000
#define MRC_OFFSET	0x2000
#define MRC_STEP	0x200

#define XRDC_MGR_PAC_ID 0
#define XRDC_MGR_PAC_SLOT 47

enum xrdc_comp_type{
	MDA_TYPE = (1 << 16),
	MRC_TYPE = (2 << 16),
	PAC_TYPE = (3 << 16),
	MSC_TYPE = (4 << 16),
};

enum xrdc_pd_type{
	XRDC_AD_PD,
	XRDC_HIFI_PD,
	XRDC_AV_PD,
};

#define XRDC_TYPE_MASK (0x7 << 16)
#define XRDC_ID_MASK 0xFFFF
#define XRDC_ID(id) (id & XRDC_ID_MASK)

typedef bool (*xrdc_check_func)(enum xrdc_comp_type type, uint16_t id);

/* Access below XRDC needs enable PS 8
 * and HIFI clocks and release HIFI firstly
 */
uint32_t hifi_xrdc_list[] = {
	(MDA_TYPE 	| XRDC_ID(9)),
	(MRC_TYPE 	| XRDC_ID(7)),
	(MRC_TYPE 	| XRDC_ID(9)),
	(MRC_TYPE 	| XRDC_ID(11)),
};

/* Access below XRDC needs enable PS 16 firstly */
uint32_t av_periph_xrdc_list[] = {
	(MDA_TYPE 	| XRDC_ID(10)),
	(MDA_TYPE 	| XRDC_ID(11)),
	(MDA_TYPE 	| XRDC_ID(12)),
	(MDA_TYPE 	| XRDC_ID(13)),
	(MDA_TYPE 	| XRDC_ID(14)),
	(MDA_TYPE 	| XRDC_ID(15)),
	(MDA_TYPE 	| XRDC_ID(16)),

	(PAC_TYPE 	| XRDC_ID(2)),

	(MRC_TYPE 	| XRDC_ID(6)),
	(MRC_TYPE 	| XRDC_ID(8)),
	(MRC_TYPE 	| XRDC_ID(10)),

	(MSC_TYPE 	| XRDC_ID(1)),
	(MSC_TYPE 	| XRDC_ID(2)),
};

uint32_t imx8ulp_pac_slots[]= {
	61, 23, 53
};

uint32_t imx8ulp_msc_slots[]= {
	2, 1, 7
};

static int xrdc_config_mrc_w0_w1(uint32_t mrc_con, uint32_t region, uint32_t w0, uint32_t size)
{

	uint32_t w0_addr, w1_addr;

	w0_addr = XRDC_ADDR + MRC_OFFSET + mrc_con * 0x200 + region * 0x20;
	w1_addr = w0_addr + 4;

	if ((size % 32) != 0)
		return -EINVAL;

	mmio_write_32(w0_addr, w0 & ~0x1f);
	mmio_write_32(w1_addr, w0 + size - 1);

	return 0;
}

static int xrdc_config_mrc_w2(uint32_t mrc_con, uint32_t region, uint32_t dxsel_all)
{
	uint32_t w2_addr;

	w2_addr = XRDC_ADDR + MRC_OFFSET + mrc_con * 0x200 + region * 0x20 + 0x8;

	mmio_write_32(w2_addr, dxsel_all);

	return 0;
}

static int xrdc_config_mrc_w3_w4(uint32_t mrc_con, uint32_t region, uint32_t w3, uint32_t w4)
{
	uint32_t w3_addr = XRDC_ADDR + MRC_OFFSET + mrc_con * 0x200 + region * 0x20 + 0xC;
	uint32_t w4_addr = w3_addr + 4;

	mmio_write_32(w3_addr, w3);
	mmio_write_32(w4_addr, w4);

	return 0;
}

static int xrdc_config_pac(uint32_t pac, uint32_t index, uint32_t dxacp)
{
	uint32_t w0_addr;
	uint32_t val;

	if (pac > 2)
		return -EINVAL;

	/* Skip the PAC slot for XRDC MGR, use Sentinel configuration */
	if (pac == XRDC_MGR_PAC_ID && index == XRDC_MGR_PAC_SLOT)
		return 0;

	w0_addr = XRDC_ADDR + 0x1000 + 0x400 * pac + 0x8 * index;

	mmio_write_32(w0_addr, dxacp);

	val = mmio_read_32(w0_addr + 4);
	mmio_write_32(w0_addr + 4, val | BIT_32(31));

	return 0;
}

static int xrdc_config_msc(uint32_t msc, uint32_t index, uint32_t dxacp)
{
	uint32_t w0_addr;
	uint32_t val;

	if (msc > 2)
		return -EINVAL;

	w0_addr = XRDC_ADDR + 0x4000 + 0x400 * msc + 0x8 * index;

	mmio_write_32(w0_addr, dxacp);

	val = mmio_read_32(w0_addr + 4);
	mmio_write_32(w0_addr + 4, val | BIT_32(31));

	return 0;
}

static int xrdc_config_mda(uint32_t mda_con, uint32_t dom, enum xrdc_mda_sa sa)
{
	uint32_t w0_addr;
	uint32_t val;

	w0_addr = XRDC_ADDR + 0x800 + mda_con * 0x20;

	val = mmio_read_32(w0_addr);

	if (val & BIT_32(29)) {
		mmio_write_32(w0_addr, (val & (~0xFF)) | dom | BIT_32(31) | 0x20 | ((sa & 0x3) << 6));
	} else {
		mmio_write_32(w0_addr, dom | BIT_32(31));
		mmio_write_32(w0_addr + 0x4, dom | BIT_32(31));
	}

	return 0;
}

static bool xrdc_check_pd(enum xrdc_comp_type type, uint16_t id, enum xrdc_pd_type pd)
{
	int i, size;
	uint32_t item = type | XRDC_ID(id);
	uint32_t *list;

	if (pd == XRDC_HIFI_PD) {
		size = ARRAY_SIZE(hifi_xrdc_list);
		list = hifi_xrdc_list;
	} else if (pd == XRDC_AV_PD) {
		size = ARRAY_SIZE(av_periph_xrdc_list);
		list = av_periph_xrdc_list;
	} else {
		return false;
	}

	for (i = 0; i < size; i++) {
		if (item == list[i])
			return true;
	}

	return false;
}

static bool xrdc_check_lpav(enum xrdc_comp_type type, uint16_t id)
{
	return xrdc_check_pd(type, id, XRDC_AV_PD);
}

static bool xrdc_check_hifi(enum xrdc_comp_type type, uint16_t id)
{
	return xrdc_check_pd(type, id, XRDC_HIFI_PD);
}

static bool xrdc_check_ad(enum xrdc_comp_type type, uint16_t id)
{
	return (!xrdc_check_pd(type, id, XRDC_HIFI_PD) &&
			!xrdc_check_pd(type, id, XRDC_AV_PD));
}

static int xrdc_apply_config(xrdc_check_func check_func)
{
	int i, j;
	uint32_t val;

	for (i = 0; i < ARRAY_SIZE(imx8ulp_mda); i++){
		if (check_func(MDA_TYPE, imx8ulp_mda[i].mda_id)) {
			xrdc_config_mda(imx8ulp_mda[i].mda_id, imx8ulp_mda[i].did, imx8ulp_mda[i].sa);
		}
	}

	for (i = 0; i < ARRAY_SIZE(imx8ulp_mrc); i++){
		if (check_func(MRC_TYPE, imx8ulp_mrc[i].mrc_id)) {
			xrdc_config_mrc_w0_w1(imx8ulp_mrc[i].mrc_id, imx8ulp_mrc[i].region_id,
				imx8ulp_mrc[i].region_start, imx8ulp_mrc[i].region_size);

			val = 0;
			for (j = 0; j < DID_MAX; j++)
				val |= imx8ulp_mrc[i].dsel[j] << (3 * j);

			xrdc_config_mrc_w2(imx8ulp_mrc[i].mrc_id, imx8ulp_mrc[i].region_id, val);
			xrdc_config_mrc_w3_w4(imx8ulp_mrc[i].mrc_id,imx8ulp_mrc[i].region_id,
				0, imx8ulp_mrc[i].accset[0] | (imx8ulp_mrc[i].accset[1] << 16) | BIT_32(31));
		}
	}

	for (i = 0; i < ARRAY_SIZE(imx8ulp_pdac); i++){
		if (check_func(PAC_TYPE, imx8ulp_pdac[i].pac_msc_id)) {
			val = 0;
			for (j = 0; j < DID_MAX; j++)
				val |= imx8ulp_pdac[i].dsel[j] << (3 * j);

			if (imx8ulp_pdac[i].slot_id == PAC_SLOT_ALL) {
				/* Apply to all slots*/
				for (j = 0; j < imx8ulp_pac_slots[imx8ulp_pdac[i].pac_msc_id]; j++)
					xrdc_config_pac(imx8ulp_pdac[i].pac_msc_id, j, val);
			} else {
				if (imx8ulp_pdac[i].slot_id >= imx8ulp_pac_slots[imx8ulp_pdac[i].pac_msc_id])
					return -EINVAL;

				xrdc_config_pac(imx8ulp_pdac[i].pac_msc_id, imx8ulp_pdac[i].slot_id, val);
			}
		}
	}

	for (i = 0; i < ARRAY_SIZE(imx8ulp_msc); i++){
		if (check_func(MSC_TYPE, imx8ulp_msc[i].pac_msc_id)) {
			val = 0;
			for (j = 0; j < DID_MAX; j++)
				val |= imx8ulp_msc[i].dsel[j] << (3 * j);

			if (imx8ulp_msc[i].slot_id == MSC_SLOT_ALL) {
				/* Apply to all slots*/
				for (j = 0; j < imx8ulp_msc_slots[imx8ulp_msc[i].pac_msc_id]; j++)
					xrdc_config_msc(imx8ulp_msc[i].pac_msc_id, j, val);
			} else {
				if (imx8ulp_msc[i].slot_id >= imx8ulp_msc_slots[imx8ulp_msc[i].pac_msc_id])
					return -EINVAL;

				xrdc_config_msc(imx8ulp_msc[i].pac_msc_id, imx8ulp_msc[i].slot_id, val);
			}
		}
	}

	return 0;
}

int xrdc_apply_lpav_config(void)
{
	/* Configure PAC2 to allow to access PCC5 */
	xrdc_config_pac(2, 39, 0xe00000);

	/* Enable the eDMA2 MP clock for MDA16 access */
	mmio_write_32(0x2da70000, 0xc0000000);
	return xrdc_apply_config(xrdc_check_lpav);
}

int xrdc_apply_hifi_config(void)
{
	return xrdc_apply_config(xrdc_check_hifi);
}

int xrdc_apply_apd_config(void)
{
	return xrdc_apply_config(xrdc_check_ad);
}

void xrdc_enable(void)
{
	mmio_write_32(XRDC_ADDR, BIT(14) | BIT(15) | BIT(0));
}
