/* SPDX-License-Identifier: GPL-2.0-only */

#include <stdint.h>
#include <delay.h>
#include <console/console.h>
#include "gm45.h"

static const int ddr3_lookup_schedule[6][2] = {
	{ 0, 1 }, { 2, 3 }, { 4, 5 }, { 4, 5 }, { 6, 7 }, { 6, 7 }
};
/* Look-up table:
 *   a1step X idx X (group || pull-up/-down)
 */
static const u8 ddr3_lut[2][64][8] = {
	{	/* Stepping B3 and below. */
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   8,  8,  3,  3,  3,  3,  5,  7  },
		{   9,  9,  3,  3,  3,  3,  6,  7  },
		{   9,  9,  3,  3,  3,  3,  6,  7  },
		{  10, 10,  3,  3,  3,  3,  7,  8  },
		{  11, 10,  3,  3,  3,  3,  7,  8  },
		{  12, 11,  3,  3,  3,  3,  8,  9  },
		{  13, 11,  3,  3,  3,  3,  9,  9  },
		{  14, 12,  3,  3,  3,  3,  9, 10  },
		{  15, 13,  3,  3,  3,  3,  9, 10  },
		{  16, 14,  3,  3,  3,  3,  9, 11  },
		{  18, 16,  3,  3,  3,  3, 10, 12  },
		{  20, 18,  4,  3,  4,  4, 10, 12  },
		{  22, 22,  4,  4,  4,  4, 11, 12  },
		{  24, 24,  4,  4,  4,  4, 11, 12  },
		{  28, 26,  4,  4,  4,  4, 12, 12  },
		{  32, 28,  5,  4,  5,  5, 12, 12  },
		{  36, 32,  5,  5,  5,  5, 13, 13  },
		{  40, 36,  5,  5,  5,  5, 14, 13  },
		{  43, 40,  5,  5,  5,  5, 15, 14  },
		{  43, 43,  5,  5,  6,  5, 15, 14  },
		{  43, 43,  6,  5,  6,  5, 15, 15  },
		{  43, 43,  6,  5,  6,  6, 15, 15  },
		{  43, 43,  6,  6,  6,  6, 15, 15  },
		{  43, 43,  6,  6,  7,  6, 15, 15  },
		{  43, 43,  7,  6,  7,  6, 15, 15  },
		{  43, 43,  7,  7,  7,  7, 15, 15  },
		{  43, 43,  7,  7,  7,  7, 15, 15  },
		{  43, 43,  7,  7,  7,  7, 15, 15  },
		{  43, 43,  8,  7,  8,  7, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
	},
	{	/* Stepping A1 and above. */
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   8,  8,  3,  3,  3,  3,  5,  5  },
		{   9,  9,  3,  3,  3,  3,  6,  6  },
		{   9,  9,  3,  3,  3,  3,  6,  6  },
		{  10, 10,  3,  3,  3,  3,  7,  7  },
		{  10, 10,  3,  3,  3,  3,  7,  7  },
		{  12, 11,  3,  3,  3,  3,  8,  8  },
		{  13, 11,  3,  3,  3,  3,  9,  9  },
		{  14, 12,  3,  3,  3,  3,  9,  9  },
		{  15, 13,  3,  3,  3,  3,  9,  9  },
		{  16, 14,  3,  3,  3,  3,  9,  9  },
		{  18, 16,  3,  3,  3,  3, 10, 10  },
		{  20, 18,  4,  3,  4,  4, 10, 10  },
		{  22, 22,  4,  4,  4,  4, 11, 11  },
		{  24, 24,  4,  4,  4,  4, 11, 11  },
		{  28, 26,  4,  4,  4,  4, 12, 12  },
		{  32, 28,  5,  4,  5,  5, 12, 12  },
		{  36, 32,  5,  5,  5,  5, 13, 13  },
		{  40, 36,  5,  5,  5,  5, 14, 14  },
		{  43, 40,  5,  5,  5,  5, 15, 15  },
		{  43, 43,  5,  5,  6,  5, 15, 15  },
		{  43, 43,  6,  5,  6,  5, 15, 15  },
		{  43, 43,  6,  5,  6,  6, 15, 15  },
		{  43, 43,  6,  6,  6,  6, 15, 15  },
		{  43, 43,  6,  6,  7,  6, 15, 15  },
		{  43, 43,  7,  6,  7,  6, 15, 15  },
		{  43, 43,  7,  7,  7,  7, 15, 15  },
		{  43, 43,  7,  7,  7,  7, 15, 15  },
		{  43, 43,  7,  7,  7,  7, 15, 15  },
		{  43, 43,  8,  7,  8,  7, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
		{  43, 43,  8,  8,  8,  8, 15, 15  },
	}
};
static void lookup_and_write(const int a1step,
				    const int row, const int col,
				    unsigned int mchbar)
{
	int i;

	/* Write 4 32-bit registers, 4 values each. */
	for (i = row; i < row + 16; i += 4) {
		mchbar_write32(mchbar,
			(ddr3_lut[a1step][i + 0][col] & 0x3f) <<  0 |
			(ddr3_lut[a1step][i + 1][col] & 0x3f) <<  8 |
			(ddr3_lut[a1step][i + 2][col] & 0x3f) << 16 |
			(ddr3_lut[a1step][i + 3][col] & 0x3f) << 24);
		mchbar += 4;
	}
}
void raminit_rcomp_calibration(const stepping_t stepping) {
	const int a1step = stepping >= STEPPING_CONVERSION_A1;

	int i;

	enum {
		PULL_UP = 0,
		PULL_DOWN = 1,
	};
	/* channel X group X pull-up/-down */
	char lut_idx[2][6][2];
	for (i = 0; i < 2 * 6 * 2; ++i)
		((char *)lut_idx)[i] = -1;

	mchbar_setbits32(0x400, 1 << 2);
	mchbar_setbits32(0x418, 1 << 17);
	mchbar_clrbits32(0x40c, 1 << 23);
	mchbar_clrbits32(0x41c, 1 << 7 | 1 << 3);
	mchbar_setbits32(0x400, 1 << 0);

	/* Read lookup indices. */
	for (i = 0; i < 12; ++i) {
		do {
			mchbar_setbits32(0x400, 1 << 3);
			udelay(10);
			mchbar_clrbits32(0x400, 1 << 3);
		} while ((mchbar_read32(0x530) & 0x7) != 0x4);
		u32 reg = mchbar_read32(0x400);
		const unsigned int group = (reg >> 13) & 0x7;
		const unsigned int channel = (reg >> 12) & 0x1;
		if (group > 5)
			break;
		reg = mchbar_read32(0x518);
		lut_idx[channel][group][PULL_UP] = (reg >> 24) & 0x7f;
		lut_idx[channel][group][PULL_DOWN] = (reg >> 16) & 0x7f;
	}
	/* Cleanup? */
	mchbar_setbits32(0x400, 1 << 3);
	udelay(10);
	mchbar_clrbits32(0x400, 1 << 3);
	mchbar_clrbits32(0x400, 1 << 2);

	/* Check for consistency. */
	for (i = 0; i < 2 * 6 * 2; ++i) {
		const char idx = ((char *)lut_idx)[i];
		if ((idx < 7) || (idx > 55))
			die("Bad RCOMP calibration lookup index.\n");
	}

	/* Lookup values and fill registers. */
	int channel, group, pu_pd;
	unsigned int mchbar = 0x0680;
	for (channel = 0; channel < 2; ++channel) {
		for (group = 0; group < 6; ++group) {
			for (pu_pd = PULL_DOWN; pu_pd >= PULL_UP; --pu_pd) {
				lookup_and_write(
					a1step,
					lut_idx[channel][group][pu_pd] - 7,
					ddr3_lookup_schedule[group][pu_pd],
					mchbar);
				mchbar += 0x0018;
			}
			mchbar += 0x0010;
			/* Channel B knows only the first two groups. */
			if ((1 == channel) && (1 == group))
				break;
		}
		mchbar += 0x0040;
	}
}
