// SPDX-License-Identifier: GPL-2.0-only
/*
 * SPDX-FileCopyrightText: Copyright (c) 2019-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 */
#include <dce.h>
#include <dce-os-log.h>
#include <dce-os-utils.h>
#include <dce-linux-device.h>

#define MAX_NO_ASTS 2
#define MAX_AST_REGIONS 1
#define MAX_AST_STRMCTLS 2

#define AST_MASTER_ADDR_HI_BITS_SHIFT 32

/**
 * dce_get_phys_stream_id - Gets the physical stream ID to be programmed from
 * platform data.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : Stream ID Value
 */
static u8 dce_get_phys_stream_id(struct tegra_dce *d)
{
	return pdata_from_dce_linux_device(d)->phys_stream_id;
}


/**
 * dce_get_fw_vm_index - Gets the VMIndex for the fw region to be
 * programmed from platform data.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : VMIndex
 */
static u8 dce_get_fw_vm_index(struct tegra_dce *d)
{
	return pdata_from_dce_linux_device(d)->fw_vmindex;
}

/**
 * dce_get_fw_carveout_id- Gets the carveout ID for the fw region to be
 * programmed from platform data.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : Carveout Id
 */
static u8 dce_get_fw_carveout_id(struct tegra_dce *d)
{
	return pdata_from_dce_linux_device(d)->fw_carveout_id;
}

/**
 * dce_is_physical_id_valid - Checks if the DCE can use physical stream ID.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : True if SMMU is disabled.
 */
static bool dce_is_physical_id_valid(struct tegra_dce *d)
{
	return pdata_from_dce_linux_device(d)->use_physical_id;
}

/**
 * dce_get_fw_dce_addr - Gets the 32bit address to be used for
 *				loading	the fw before being converted
 *				by AST into a 40-bit address.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : 32bit address
 */
static u32 dce_get_fw_dce_addr(struct tegra_dce *d)
{
	return pdata_from_dce_linux_device(d)->fw_dce_addr;
}

/**
 * dce_config_ast0_control - programs the global
 *				ast control register for AST0
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_config_ast0_control(struct tegra_dce *d)
{
	u32 val;
	u32 def_physical;
	u32 phy_stream_id = dce_get_phys_stream_id(d) <<
				ast_ast0_control_physstreamid_shift_v();

	if (dce_is_physical_id_valid(d))
		def_physical = 1 <<
			ast_ast0_control_carveoutlock_defphysical_shift_v();
	else
		def_physical = 0 <<
			ast_ast0_control_carveoutlock_defphysical_shift_v();

	val = phy_stream_id | ast_ast0_control_carveoutlock_false_f()
		| def_physical | ast_ast0_control_matcherrctl_decerr_f()
		| ast_ast0_control_lock_false_f();

	dce_os_writel(d, ast_ast0_control_r(), val);
}

/**
 * dce_config_ast1_control - programs the global
 *				ast control register for AST1
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_config_ast1_control(struct tegra_dce *d)
{
	u32 val;
	u32 def_physical;
	u32 phy_stream_id = dce_get_phys_stream_id(d) <<
				ast_ast1_control_physstreamid_shift_v();

	if (dce_is_physical_id_valid(d))
		def_physical = 1 <<
			ast_ast1_control_carveoutlock_defphysical_shift_v();
	else
		def_physical = 0 <<
			ast_ast1_control_carveoutlock_defphysical_shift_v();

	val = phy_stream_id | ast_ast1_control_carveoutlock_false_f()
		| def_physical | ast_ast1_control_matcherrctl_decerr_f()
		| ast_ast1_control_lock_false_f();

	dce_os_writel(d, ast_ast1_control_r(), val);
}

/**
 * ast_ctl_fn is an array of read-only pointers
 * to a function returning void.
 *
 * Contains all the functions to program the global
 * ast control registers defined above.
 */
static void (*const ast_ctl_fn[MAX_NO_ASTS])(struct tegra_dce *d) = {

	dce_config_ast0_control,
	dce_config_ast1_control,
};

/**
 * dce_cfg_ast0_streamid_ctl_0 - programs the ast streamid
 *				control register for AST0 and Control0
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_cfg_ast0_streamid_ctl_0(struct tegra_dce *d)
{
	u32 stream_id_en;
	u32 dce_stream_id = dce_os_get_dce_stream_id(d);

	if (dce_is_physical_id_valid(d))
		stream_id_en = ast_ast0_streamid_ctl_0_enable_disable_f();
	else
		stream_id_en = ast_ast0_streamid_ctl_0_enable_enable_f();

	dce_os_writel(d, ast_ast0_streamid_ctl_0_r(),
		(dce_stream_id << ast_ast0_streamid_ctl_0_streamid_shift_v()) |
		stream_id_en);
}

/**
 * dce_cfg_ast0_streamid_ctl_1 - programs the ast streamid
 *				control register for AST0 and Control1
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_cfg_ast0_streamid_ctl_1(struct tegra_dce *d)
{
	u32 stream_id_en;
	u32 dce_stream_id = dce_os_get_dce_stream_id(d);

	if (dce_is_physical_id_valid(d))
		stream_id_en = ast_ast0_streamid_ctl_1_enable_disable_f();
	else
		stream_id_en = ast_ast0_streamid_ctl_1_enable_enable_f();

	dce_os_writel(d, ast_ast0_streamid_ctl_1_r(), (dce_stream_id <<
			ast_ast0_streamid_ctl_1_streamid_shift_v()) |
			stream_id_en);
}

/**
 * dce_cfg_ast1_streamid_ctl_0 - programs the ast streamid
 *				control register for AST1 and Control0
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_cfg_ast1_streamid_ctl_0(struct tegra_dce *d)
{
	u32 stream_id_en;
	u32 dce_stream_id = dce_os_get_dce_stream_id(d);

	if (dce_is_physical_id_valid(d))
		stream_id_en = ast_ast1_streamid_ctl_0_enable_disable_f();
	else
		stream_id_en = ast_ast1_streamid_ctl_0_enable_enable_f();

	dce_os_writel(d, ast_ast1_streamid_ctl_0_r(),
		(dce_stream_id << ast_ast1_streamid_ctl_0_streamid_shift_v()) |
		stream_id_en);
}

/**
 * dce_cfg_ast1_streamid_ctl_1 - programs the ast streamid
 *				control register for AST1 and Control1
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_cfg_ast1_streamid_ctl_1(struct tegra_dce *d)
{
	u32 stream_id_en;
	u32 dce_stream_id = dce_os_get_dce_stream_id(d);

	if (dce_is_physical_id_valid(d))
		stream_id_en = ast_ast1_streamid_ctl_1_enable_disable_f();
	else
		stream_id_en = ast_ast1_streamid_ctl_1_enable_enable_f();

	dce_os_writel(d, ast_ast1_streamid_ctl_1_r(),
		(dce_stream_id << ast_ast1_streamid_ctl_1_streamid_shift_v()) |
		stream_id_en);
}

/**
 * ast_strmidctl_fn is a 2D array of read-only pointers
 * to a function returning void.
 *
 * Contains all the functions to program the streamId
 * controls registers defined above for a given AST and Control ID
 */
static void (*const ast_strmidctl_fn[MAX_NO_ASTS][MAX_AST_STRMCTLS])
						(struct tegra_dce *d) = {

	{
		dce_cfg_ast0_streamid_ctl_0,
		dce_cfg_ast0_streamid_ctl_1,
	},
	{
		dce_cfg_ast1_streamid_ctl_0,
		dce_cfg_ast1_streamid_ctl_1,
	},
};


/**
 * dce_set_ast0_slave_addr_32_reg0 - programs the ast slave address
 *						for AST0 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static void dce_set_ast0_slave_addr_32_reg0(struct tegra_dce *d, u32 addr)
{
	dce_os_writel(d, ast_ast0_region_0_slave_base_lo_r(),
		   (addr | ast_ast0_region_0_slave_base_lo_enable_true_f()) &
		   ast_ast1_region_0_slave_base_lo_write_mask_v());
}

/**
 * dce_set_ast1_slave_addr_32_reg0- programs the ast slave address
 *						for AST1 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static void dce_set_ast1_slave_addr_32_reg0(struct tegra_dce *d, u32 addr)
{
	dce_os_writel(d, ast_ast1_region_0_slave_base_lo_r(),
		   (addr | ast_ast1_region_0_slave_base_lo_enable_true_f()) &
		   ast_ast1_region_0_slave_base_lo_write_mask_v());
}

/**
 * ast_slave_addr_fn is a 2D array of read-only pointers
 * to a function returning void.
 *
 * Contains all the functions to program the slave address and
 * other bits in salve address registers defined above for a
 * given AST and region.
 */
static void (*const ast_slave_addr_fn[MAX_NO_ASTS][MAX_AST_REGIONS])
	     (struct tegra_dce *d, u32 addr) = {

	{
		dce_set_ast0_slave_addr_32_reg0,
	},
	{
		dce_set_ast1_slave_addr_32_reg0,
	},
};

/**
 * dce_set_ast0_master_addr_lo_reg0 - programs the lower 32 bits of ast
 *					master address for AST0 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static inline void
dce_set_ast0_master_addr_lo_reg0(struct tegra_dce *d, u32 addr)
{
	dce_os_writel(d, ast_ast0_region_0_master_base_lo_r(), addr);
}

/**
 * dce_set_ast1_master_addr_lo_reg0 - programs the lower 32 bits of ast
 *					master address for AST1 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static inline void
dce_set_ast1_master_addr_lo_reg0(struct tegra_dce *d, u32 addr)
{
	dce_os_writel(d, ast_ast1_region_0_master_base_lo_r(), addr);
}

/**
 * dce_set_ast1_master_addr_hi_reg0 - programs the high 32 bits of ast
 *					master address for AST1 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static inline void
dce_set_ast1_master_addr_hi_reg0(struct tegra_dce *d, u32 addr)
{
	dce_os_writel(d, ast_ast1_region_0_master_base_hi_r(), addr);
}

/**
 * dce_set_ast0_master_addr_hi_reg0 - programs the high 32 bits of ast
 *					master address for AST0 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static inline void
dce_set_ast0_master_addr_hi_reg0(struct tegra_dce *d, u32 addr)
{
	dce_os_writel(d, ast_ast0_region_0_master_base_hi_r(), addr);
}

/**
 * dce_set_ast0_master_addr_reg0 - programs the ast master address
 *						for AST0 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static void dce_set_ast0_master_addr_reg0(struct tegra_dce *d, u64 addr)
{
	u32 ast_master_hi;
	u32 ast_master_lo;

	ast_master_lo = addr & ast_ast0_region_0_master_base_lo_write_mask_v();
	dce_set_ast0_master_addr_lo_reg0(d, ast_master_lo);

	ast_master_hi = addr >> AST_MASTER_ADDR_HI_BITS_SHIFT;
	dce_set_ast0_master_addr_hi_reg0(d, ast_master_hi);
}

/**
 * dce_set_ast1_master_addr_reg0 - programs the ast master address
 *						for AST0 and Region0
 *
 * @d : Pointer to tegra_dce sturct.
 * @addr : Address to be programmed.
 *
 * Return : Void
 */
static void dce_set_ast1_master_addr_reg0(struct tegra_dce *d, u64 addr)
{
	u32 ast_master_hi;
	u32 ast_master_lo;

	ast_master_lo = addr & ast_ast0_region_0_master_base_lo_write_mask_v();
	dce_set_ast1_master_addr_lo_reg0(d, ast_master_lo);

	ast_master_hi = addr >> AST_MASTER_ADDR_HI_BITS_SHIFT;
	dce_set_ast1_master_addr_hi_reg0(d, ast_master_hi);
}

/**
 * ast_master_addr_fn is a 2D array of read-only pointers
 * to a function returning void.
 *
 * Contains all the functions to program the master address registers
 * defined above for a given AST and region.
 */
static void (*const ast_master_addr_fn[MAX_NO_ASTS][MAX_AST_REGIONS])
	     (struct tegra_dce *d, u64 addr) = {

	{
		dce_set_ast0_master_addr_reg0,
	},
	{
		dce_set_ast1_master_addr_reg0,
	},
};

/**
 * dce_get_fw_ast_reg_mask - Returns the size mask based on the fw size
 *				for configuring AST region
 *
 * @d : Pointer to tegra_dce struct
 *
 * If size is 64K(0x10000), mask is 0xffff. If size is 2MB(0x200000), mask is
 * 0x1FFFFF.
 *
 * Ruturns 64 bit mask.
 */
static u64 dce_get_fw_ast_reg_mask(struct tegra_dce *d)
{
	struct dce_firmware *fw = d->fw_data;

	return fw->size - 1UL;
}

/**
 * dce_ast_cfg_reg_mask_ast0_reg0 - sets the region mask based on the size
 *					of the DRAM memory for AST0 and Region0.
 *
 * @d : pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_ast_cfg_reg_mask_ast0_reg0(struct tegra_dce *d)
{
	u64 size_mask = dce_get_fw_ast_reg_mask(d);
	u32 val = size_mask & ast_ast0_region_0_mask_lo_write_mask_v();

	dce_os_writel(d, ast_ast0_region_0_mask_lo_r(), val);
}

/**
 * dce_ast_cfg_reg_mask_ast1_reg0 - sets the region mask based on the size
 *					of the DRAM memory for AST1 and Region0.
 *
 * @d : pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_ast_cfg_reg_mask_ast1_reg0(struct tegra_dce *d)
{
	u64 size_mask = dce_get_fw_ast_reg_mask(d);
	u32 val = size_mask & ast_ast1_region_0_mask_lo_write_mask_v();

	dce_os_writel(d, ast_ast1_region_0_mask_lo_r(), val);
}

/**
 * ast_mask_fn is a 2D array of read-only pointers
 * to a function returning void.
 *
 * Contains all the functions to program the mask
 * register bits for a given AST and region.
 */
static void (*const ast_mask_fn[MAX_NO_ASTS][MAX_AST_REGIONS])
						(struct tegra_dce *d) = {

	{
		dce_ast_cfg_reg_mask_ast0_reg0,
	},
	{
		dce_ast_cfg_reg_mask_ast1_reg0,
	},
};

/**
 * dce_ast_cfg_reg_control_ast0_reg0 - Configures the ast region control
 *						register for AST0 and Region0
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_ast_cfg_reg_control_ast0_reg0(struct tegra_dce *d)
{
	u32 vm_index;
	u32 carveout_id;
	u32 use_physical_id;

	if (dce_is_physical_id_valid(d)) {
		use_physical_id = 1 <<
			ast_ast0_region_0_control_physical_shift_v();
		vm_index = 0 <<
			ast_ast0_region_0_control_vmindex_shift_v();
	} else {
		use_physical_id = 0 <<
			ast_ast0_region_0_control_physical_shift_v();
		vm_index = dce_get_fw_vm_index(d) <<
			ast_ast0_region_0_control_vmindex_shift_v();
	}
	carveout_id = dce_get_fw_carveout_id(d) <<
		ast_ast0_region_0_control_carveoutid_shift_v();

	dce_os_writel(d, ast_ast0_region_0_control_r(),
		   use_physical_id | vm_index | carveout_id |
		   ast_ast0_region_0_control_snoop_enable_f());
}

/**
 * dce_ast_cfg_reg_control_ast1_reg0 - Configures the ast region control
 *						register for AST1 and Region0
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_ast_cfg_reg_control_ast1_reg0(struct tegra_dce *d)
{
	u32 vm_index;
	u32 carveout_id;
	u32 use_physical_id;

	if (dce_is_physical_id_valid(d)) {
		use_physical_id = 1 <<
			ast_ast1_region_0_control_physical_shift_v();
		vm_index = 0 <<
			ast_ast1_region_0_control_vmindex_shift_v();
	} else {
		use_physical_id = 0 <<
			ast_ast1_region_0_control_physical_shift_v();
		vm_index = dce_get_fw_vm_index(d) <<
			ast_ast1_region_0_control_vmindex_shift_v();
	}

	carveout_id = dce_get_fw_carveout_id(d) <<
		ast_ast1_region_0_control_carveoutid_shift_v();


	dce_os_writel(d, ast_ast1_region_0_control_r(),
		   use_physical_id | vm_index | carveout_id |
		   ast_ast1_region_0_control_snoop_enable_f());
}

/**
 * ast_reg_control_fn is a 2D array of read-only pointers
 * to a function returning void.
 *
 * Contains all the functions to program the region control
 * register bits given AST and region.
 */
static void (*const ast_reg_control_fn[MAX_NO_ASTS][MAX_AST_REGIONS])
						(struct tegra_dce *d) = {

	{
		dce_ast_cfg_reg_control_ast0_reg0,
	},
	{
		dce_ast_cfg_reg_control_ast1_reg0,
	},
};

/**
 * dce_config_ast - Configures the a AST region for initial loading of fw
 * platform data.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : void
 */
void dce_config_ast(struct tegra_dce *d)
{
	u8 i;
	u8 j;
	u32 slave_addr;
	u64 master_addr;

	d->boot_status |= DCE_AST_CONFIG_START;
	slave_addr = dce_get_fw_dce_addr(d);

	if (!d->fw_data) {
		dce_os_err(d, "DCE_BOOT_FAILED: No fw_data present");
		d->boot_status |= DCE_AST_CONFIG_FAILED;
		return;
	}

	master_addr = d->fw_data->dma_handle;

	for (i = 0; i < MAX_NO_ASTS; i++) {
		ast_ctl_fn[i](d);

		for (j = 0; j < MAX_AST_STRMCTLS; j++)
			ast_strmidctl_fn[i][j](d);

		for (j = 0; j < MAX_AST_REGIONS; j++) {
			ast_mask_fn[i][j](d);
			ast_reg_control_fn[i][j](d);
			ast_master_addr_fn[i][j](d, master_addr);
			ast_slave_addr_fn[i][j](d, slave_addr);
		}
	}
	d->boot_status |= DCE_AST_CONFIG_DONE;
}
