// SPDX-License-Identifier: GPL-2.0-only
/**
 * Copyright (c) 2016-2023, NVIDIA CORPORATION. All rights reserved.
 */

#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include "dev.h"
#include "dev-t18x.h"

#define AST_CONTROL					0x000
#define AST_STREAMID_CTL_0				0x020
#define AST_STREAMID_CTL_1				0x024
#define AST_RGN_SLAVE_BASE_LO				0x100
#define AST_RGN_SLAVE_BASE_HI				0x104
#define AST_RGN_MASK_BASE_LO				0x108
#define AST_RGN_MASK_BASE_HI				0x10c
#define AST_RGN_MASTER_BASE_LO				0x110
#define AST_RGN_MASTER_BASE_HI				0x114
#define AST_RGN_CONTROL					0x118

#define AST_PAGE_MASK					(~0xFFF)
#define AST_LO_SHIFT					32
#define AST_LO_MASK					0xFFFFFFFF
#define AST_PHY_SID_IDX					0
#define AST_APE_SID_IDX					1
#define AST_NS						(1 << 3)
#define AST_CARVEOUTID(ID)				(ID << 5)
#define AST_VMINDEX(IDX)				(IDX << 15)
#define AST_PHSICAL(PHY)				(PHY << 19)
#define AST_STREAMID(ID)				(ID << 8)
#define AST_VMINDEX_ENABLE				(1 << 0)
#define AST_RGN_ENABLE					(1 << 0)
#define AST_RGN_OFFSET					0x20

struct acast_region {
	u32	rgn;
	u32	rgn_ctrl;
	u32	strmid_reg;
	u32	strmid_ctrl;
	u64	slave;
	u64	size;
	u64	master;
};

#define NUM_MAX_ACAST			2

#define ACAST_RGN_PHY			0x0
#define ACAST_RGN_CTL_PHY		(AST_PHSICAL(1) | AST_CARVEOUTID(0x7))

#define ACAST_RGN_VM			0x2
#define ACAST_VMINDEX			1
#define ACAST_RGN_CTL_VM(IDX)		AST_VMINDEX(IDX)
#define ACAST_SID_REG_EVAL(IDX)		AST_STREAMID_CTL_##IDX
#define ACAST_STRMID_REG(IDX)		ACAST_SID_REG_EVAL(IDX)

static inline void acast_write(void __iomem *acast, u32 reg, u32 val)
{
	writel(val, acast + reg);
}

static inline u32 acast_read(void __iomem *acast, u32 reg)
{
	return readl(acast + reg);
}

static inline u32 acast_rgn_reg(u32 rgn, u32 reg)
{
	return rgn * AST_RGN_OFFSET + reg;
}

static void tegra18x_acast_map(struct device *dev,
			       void __iomem *acast, u32 rgn, u32 rgn_ctrl,
			       u32 strmid_reg, u32 strmid_ctrl,
			       u64 slave, u64 size, u64 master)
{
	u32 val;

	val = acast_read(acast, acast_rgn_reg(rgn, AST_RGN_SLAVE_BASE_LO));
	if (val & AST_RGN_ENABLE) {
		dev_warn(dev, "ACAST rgn %u already mapped...skipping\n", rgn);
		return;
	}

	val = master & AST_LO_MASK;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_MASTER_BASE_LO), val);
	val = master >> AST_LO_SHIFT;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_MASTER_BASE_HI), val);

	val = ((size - 1) & AST_PAGE_MASK) & AST_LO_MASK;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_MASK_BASE_LO), val);
	val = (size - 1) >> AST_LO_SHIFT;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_MASK_BASE_HI), val);

	val = acast_read(acast, acast_rgn_reg(rgn, AST_RGN_CONTROL));
	val |= rgn_ctrl;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_CONTROL), val);

	if (strmid_reg)
		acast_write(acast, strmid_reg, strmid_ctrl);

	val = slave >> AST_LO_SHIFT;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_SLAVE_BASE_HI), val);
	val = (slave & AST_LO_MASK) | AST_RGN_ENABLE;
	acast_write(acast,
		    acast_rgn_reg(rgn, AST_RGN_SLAVE_BASE_LO), val);
}

static int tegra18x_acast_init(struct device *dev,
		uint32_t acast_addr, uint32_t acast_size,
		struct acast_region *acast_regions, uint32_t num_regions)
{
	void __iomem *acast_base;
	int i;

	acast_base = devm_ioremap(dev, acast_addr, acast_size);
	if (IS_ERR_OR_NULL(acast_base)) {
		dev_err(dev, "failed to map ACAST 0x%x\n", acast_addr);
		return PTR_ERR(acast_base);
	}

	for (i = 0; i < num_regions; i++) {
		tegra18x_acast_map(dev, acast_base,
				   acast_regions[i].rgn,
				   acast_regions[i].rgn_ctrl,
				   acast_regions[i].strmid_reg,
				   acast_regions[i].strmid_ctrl,
				   acast_regions[i].slave,
				   acast_regions[i].size,
				   acast_regions[i].master);

		dev_dbg(dev, "i:%d rgn:0x%x rgn_ctrl:0x%x ",
			i, acast_regions[i].rgn, acast_regions[i].rgn_ctrl);
		dev_dbg(dev, "strmid_reg:0x%x strmid_ctrl:0x%x ",
			acast_regions[i].strmid_reg,
			acast_regions[i].strmid_ctrl);
		dev_dbg(dev, "slave:0x%llx size:0x%llx master:0x%llx\n",
			acast_regions[i].slave, acast_regions[i].size,
			acast_regions[i].master);
	}

	return 0;
}

int nvadsp_acast_t18x_init(struct platform_device *pdev)
{
	struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);
	struct device *dev = &pdev->dev;
	struct resource *co_mem = &drv_data->co_mem;
	uint32_t acast_addr, acast_size;
	int iter, num_acast = 0, ret = 0;
	struct acast_region acast_config;

	if (co_mem->start) {
		acast_config.rgn         = ACAST_RGN_PHY;
		acast_config.rgn_ctrl    = ACAST_RGN_CTL_PHY;
		acast_config.strmid_reg  = 0;
		acast_config.strmid_ctrl = 0;
		acast_config.slave   = drv_data->adsp_mem[ADSP_OS_ADDR];
		acast_config.size    = drv_data->adsp_mem[ADSP_OS_SIZE];
		acast_config.master  = co_mem->start;
	} else {
		uint32_t stream_id;
		uint64_t iommu_addr_start, iommu_addr_end;

		if (of_property_read_u32_index(dev->of_node,
				"iommus", 1, &stream_id)) {
			dev_warn(dev, "no SMMU stream ID found\n");
			goto exit;
		}
		if (of_property_read_u64_index(dev->of_node,
				"iommu-resv-regions", 1, &iommu_addr_start)) {
			dev_warn(dev, "no IOMMU reserved region\n");
			goto exit;
		}
		if (of_property_read_u64_index(dev->of_node,
				"iommu-resv-regions", 2, &iommu_addr_end)) {
			dev_warn(dev, "no IOMMU reserved region\n");
			goto exit;
		}

		acast_config.rgn         = ACAST_RGN_VM;
		acast_config.rgn_ctrl    = ACAST_RGN_CTL_VM(ACAST_VMINDEX);
		acast_config.strmid_reg  = ACAST_STRMID_REG(ACAST_VMINDEX);
		acast_config.strmid_ctrl = AST_STREAMID(stream_id) |
						AST_VMINDEX_ENABLE;
		acast_config.slave   = iommu_addr_start;
		acast_config.size    = (iommu_addr_end - acast_config.slave);
		acast_config.master  = iommu_addr_start;
	}

	for (iter = 0; iter < (NUM_MAX_ACAST * 2); iter += 2) {
		if (of_property_read_u32_index(dev->of_node,
			"nvidia,acast_config", iter, &acast_addr))
			continue;
		if (of_property_read_u32_index(dev->of_node,
			"nvidia,acast_config", (iter + 1), &acast_size))
			continue;

		ret = tegra18x_acast_init(dev, acast_addr, acast_size,
					&acast_config, 1);
		if (ret)
			goto exit;

		num_acast++;
	}

	if (num_acast == 0)
		dev_warn(dev, "no ACAST configurations found\n");

exit:
	return ret;
}
