// SPDX-License-Identifier: GPL-2.0+
/*
 * Helpers that are commonly used with DW memory controller.
 *
 * (C) Copyright 2025 Jernej Skrabec <jernej.skrabec@gmail.com>
 *
 */

#include <init.h>
#include <asm/arch/dram_dw_helpers.h>

void mctl_auto_detect_rank_width(const struct dram_para *para,
				 struct dram_config *config)
{
	/* this is minimum size that it's supported */
	config->cols = 8;
	config->rows = 13;

	/*
	 * Strategy here is to test most demanding combination first and least
	 * demanding last, otherwise HW might not be fully utilized. For
	 * example, half bus width and rank = 1 combination would also work
	 * on HW with full bus width and rank = 2, but only 1/4 RAM would be
	 * visible.
	 */

	debug("testing 32-bit width, rank = 2\n");
	config->bus_full_width = 1;
	config->ranks = 2;
	if (mctl_core_init(para, config))
		return;

	debug("testing 32-bit width, rank = 1\n");
	config->bus_full_width = 1;
	config->ranks = 1;
	if (mctl_core_init(para, config))
		return;

	debug("testing 16-bit width, rank = 2\n");
	config->bus_full_width = 0;
	config->ranks = 2;
	if (mctl_core_init(para, config))
		return;

	debug("testing 16-bit width, rank = 1\n");
	config->bus_full_width = 0;
	config->ranks = 1;
	if (mctl_core_init(para, config))
		return;

	panic("This DRAM setup is currently not supported.\n");
}

static void mctl_write_pattern(void)
{
	unsigned int i;
	u32 *ptr, val;

	ptr = (u32 *)CFG_SYS_SDRAM_BASE;
	for (i = 0; i < 16; ptr++, i++) {
		if (i & 1)
			val = ~(ulong)ptr;
		else
			val = (ulong)ptr;
		writel(val, ptr);
	}
}

static bool mctl_check_pattern(ulong offset)
{
	unsigned int i;
	u32 *ptr, val;

	ptr = (u32 *)CFG_SYS_SDRAM_BASE;
	for (i = 0; i < 16; ptr++, i++) {
		if (i & 1)
			val = ~(ulong)ptr;
		else
			val = (ulong)ptr;
		if (val != *(ptr + offset / 4))
			return false;
	}

	return true;
}

void mctl_auto_detect_dram_size(const struct dram_para *para,
				struct dram_config *config)
{
	unsigned int shift, cols, rows;
	u32 buffer[16];

	/* max. config for columns, but not rows */
	config->cols = 11;
	config->rows = 13;
	mctl_core_init(para, config);

	/*
	 * Store content so it can be restored later. This is important
	 * if controller was already initialized and holds any data
	 * which is important for restoring system.
	 */
	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));

	mctl_write_pattern();

	shift = config->bus_full_width + 1;

	/* detect column address bits */
	for (cols = 8; cols < 11; cols++) {
		if (mctl_check_pattern(1ULL << (cols + shift)))
			break;
	}
	debug("detected %u columns\n", cols);

	/* restore data */
	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));

	/* reconfigure to make sure that all active rows are accessible */
	config->cols = 8;
	config->rows = 17;
	mctl_core_init(para, config);

	/* store data again as it might be moved */
	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));

	mctl_write_pattern();

	/* detect row address bits */
	shift = config->bus_full_width + 4 + config->cols;
	for (rows = 13; rows < 17; rows++) {
		if (mctl_check_pattern(1ULL << (rows + shift)))
			break;
	}
	debug("detected %u rows\n", rows);

	/* restore data again */
	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));

	config->cols = cols;
	config->rows = rows;
}

unsigned long mctl_calc_size(const struct dram_config *config)
{
	unsigned long size;
	u8 width = config->bus_full_width ? 4 : 2;

	/* 8 banks */
	size = (1ULL << (config->cols + config->rows + 3)) * width *
		config->ranks;

	/*
	 * There are boards with non-power-of-2 sized DRAM chips, like 1.5GB
	 * or 3GB. They are detected as the larger power-of-2 (2GB and 4GB),
	 * so test the last quarter for being able to store values.
	 */
	if (!mctl_check_memory(CFG_SYS_SDRAM_BASE + size / 4 * 3)) {
		if (mctl_check_memory(CFG_SYS_SDRAM_BASE + size / 4 * 3 - 64)) {
			size = (size / 4) * 3;
			debug("capping memory at %ld MB\n", size >> 20);
		} else {
			printf("DRAM test failure at address 0x%lx\n",
			       CFG_SYS_SDRAM_BASE + size / 4 * 3 - 64);
			return 0;
		}
	}

	return size;
}
