/*
 * MemTest86+ V5 Specific code (GPL V2.0)
 * By Samuel DEMEULEMEESTER, sdemeule@memtest.org
 * http://www.canardpc.com - http://www.memtest.org
 */

#include "stdint.h"
#include "test.h"
#include "io.h"
#include "pci.h"
#include "msr.h"
#include "spd.h"
#include "screen_buffer.h"
#include "jedec_id.h"

#define NULL 0

#define SMBHSTSTS smbusbase
#define SMBHSTCNT (smbusbase + 2)
#define SMBHSTCMD (smbusbase + 3)
#define SMBHSTADD (smbusbase + 4)
#define SMBHSTDAT (smbusbase + 5)

#define AMD_INDEX_IO_PORT	0xCD6
#define AMD_DATA_IO_PORT	0xCD7
#define AMD_SMBUS_BASE_REG	0x2C
#define AMD_PM_DECODE_EN_REG	0x00

static int smbdev, smbfun;
static unsigned short smbusbase;
static unsigned char spd_raw[256];

static char *convert_hex_to_char(unsigned hex_org)
{
	static char buf[2] = " ";
	if (hex_org >= 0x20 && hex_org < 0x80) {
		buf[0] = hex_org;
	} else {
		//buf[0] = '\0';
		buf[0] = ' ';
	}

	return buf;
}

static void ich5_get_smb(void)
{
	unsigned long x;
	int result;
	result = pci_conf_read(0, smbdev, smbfun, 0x20, 2, &x);
	if (result == 0)
		smbusbase = (unsigned short)x & 0xFFFE;
}

static void sb800_get_smb(void)
{
	int lbyte, hbyte;

	__outb(AMD_SMBUS_BASE_REG + 1, AMD_INDEX_IO_PORT);
	hbyte = __inb(AMD_DATA_IO_PORT);
	__outb(AMD_SMBUS_BASE_REG, AMD_INDEX_IO_PORT);
	lbyte = __inb(AMD_DATA_IO_PORT);

	smbusbase = hbyte;
	smbusbase <<= 8;
	smbusbase += lbyte;
	smbusbase &= 0xFFE0;

	if (smbusbase == 0xFFE0) {
		smbusbase = 0;
	}
}

static void sb600_get_smb(void)
{
	unsigned long x;
	int result;

	(void)pci_conf_read(0, smbdev, smbfun, 0x08, 1, &x);

	if (x < 0x40) {
		// SB600/700
		result = pci_conf_read(0, smbdev, smbfun, 0x90, 2, &x);
		if (result == 0)
			smbusbase = (unsigned short)x & 0xFFFE;
	} else {
		// SB800
		sb800_get_smb();
	}
}

static void fch_get_smb(void)
{
	int lbyte;
	unsigned long x;

	pci_conf_read(0, smbdev, smbfun, 0x08, 1, &x);

	/* if processor revision is ML_A0 or ML_A1 use different way for SMBus
	 * IO base calculation */
	if (x == 0x42 || x == 0x41) {
		/* read PMx00+1 to get SmbusAsfIoBase */
		__outb(AMD_PM_DECODE_EN_REG + 1, AMD_INDEX_IO_PORT);
		lbyte = __inb(AMD_DATA_IO_PORT);

		/* SMBus IO base is defined as {Smbus0AsfIoBase[7:0], 0x00} */
		smbusbase = (lbyte & 0xFF) << 8;
	} else {
		sb800_get_smb();
	}
}

static unsigned char ich5_smb_read_byte(unsigned char adr, unsigned char cmd)
{
	int l1, h1, l2, h2;
	uint64_t t;
	uint64_t toval = 10 * v->clks_msec;

	__outb(0x1f, SMBHSTSTS); // reset SMBus Controller
	__outb(0xff, SMBHSTDAT);
	while (__inb(SMBHSTSTS) & 0x01)
		; // wait until ready
	__outb(cmd, SMBHSTCMD);
	__outb((adr << 1) | 0x01, SMBHSTADD);
	__outb(0x48, SMBHSTCNT);
	rdtsc(l1, h1);
	//cprint(POP2_Y, POP2_X + 16, s + cmd % 8); // progress bar
	while (!(__inb(SMBHSTSTS) & 0x02)) { // wait til command finished
		rdtsc(l2, h2);
		t = ((uint64_t) (h2 - h1) * 0xffffffff + (l2 - l1));
		if (t > toval)
			break; // break after 10ms
	}
	return __inb(SMBHSTDAT);
}

static int ich5_read_spd(int dimmadr)
{
	int x;
	spd_raw[0] = ich5_smb_read_byte(0x50 + dimmadr, 0);
	if (spd_raw[0] == 0xff)
		return -1;	// no spd here
	for (x = 1; x < 256; x++) {
		spd_raw[x] =
		    ich5_smb_read_byte(0x50 + dimmadr, (unsigned char)x);
	}
	return 0;
}

static void us15w_get_smb(void)
{
	unsigned long x;
	int result;
	result = pci_conf_read(0, 0x1f, 0, 0x40, 2, &x);
	if (result == 0)
		smbusbase = (unsigned short)x & 0xFFC0;
}

static unsigned char us15w_smb_read_byte(unsigned char adr, unsigned char cmd)
{
	int l1, h1, l2, h2;
	uint64_t t;
	uint64_t toval = 10 * v->clks_msec;

	//__outb(0x00, smbusbase + 1);		// reset SMBus Controller
	//__outb(0x00, smbusbase + 6);
	//while((__inb(smbusbase + 1) & 0x08) != 0)
	//	; // wait until ready
	__outb(0x02, smbusbase + 0);			// Byte read
	__outb(cmd, smbusbase + 5);			// Command
	__outb(0x07, smbusbase + 1);			// Clear status
	__outb((adr << 1) | 0x01, smbusbase + 4);	// DIMM address
	__outb(0x12, smbusbase + 0);			// Start
	//while (((__inb(smbusbase + 1) & 0x08) == 0))
	//	; // wait until busy
	rdtsc(l1, h1);
	static const char s[] = { '/', 0, '-', 0, '\\', 0, '|', 0 };
	cprint(POP2_Y, POP2_X + 16, s + cmd % 8); // progress bar

	// wait til command finished
	while (((__inb(smbusbase + 1) & 0x01) == 0) || ((__inb(smbusbase + 1) & 0x08) != 0)) {
		rdtsc(l2, h2);
		t = ((uint64_t) (h2 - h1) * 0xffffffff + (l2 - l1));
		if (t > toval)
			break; // break after 10ms
	}
	return __inb(smbusbase + 6);
}

static int us15w_read_spd(int dimmadr)
{
	int x;
	spd_raw[0] = us15w_smb_read_byte(0x50 + dimmadr, 0);
	if (spd_raw[0] == 0xff)
		return -1;	// no spd here
	for (x = 1; x < 256; x++) {
		spd_raw[x] =
		    us15w_smb_read_byte(0x50 + dimmadr, (unsigned char)x);
	}
	return 0;
}

struct pci_smbus_controller {
	unsigned vendor;
	unsigned device;
	char *name;
	void (*get_adr) (void);
	int (*read_spd) (int dimmadr);
};

static struct pci_smbus_controller smbcontrollers[] = {
	// Intel SMBUS
	{0x8086, 0x9C22, "Intel HSW-ULT", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x8C22, "Intel HSW", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x1E22, "Intel Z77", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x1C22, "Intel P67", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x3B30, "Intel P55", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x3A60, "Intel ICH10B", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x3A30, "Intel ICH10R", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x2930, "Intel ICH9", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x283E, "Intel ICH8", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x27DA, "Intel ICH7", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x266A, "Intel ICH6", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x24D3, "Intel ICH5", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x24C3, "Intel ICH4", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x25A4, "Intel 6300ESB", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x269B, "Intel ESB2", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x8119, "Intel US15W", us15w_get_smb, us15w_read_spd},
	{0x8086, 0x5032, "Intel EP80579", ich5_get_smb, ich5_read_spd},
	{0x8086, 0x0f12, "Intel E3800", ich5_get_smb, ich5_read_spd},

	// AMD SMBUS
	{0x1002, 0x4385, "AMD SBx00", sb600_get_smb, ich5_read_spd},
	{0x1022, 0x780B, "AMD FCH", fch_get_smb, ich5_read_spd},
	{0, 0, "", NULL, NULL}
};

static int find_smb_controller(void)
{
	int i = 0;
	unsigned long valuev, valued;

	for (smbdev = 0; smbdev < 32; smbdev++) {
		for (smbfun = 0; smbfun < 8; smbfun++) {
			pci_conf_read(0, smbdev, smbfun, 0, 2, &valuev);
			if (valuev != 0xFFFF) {	// if there is something look what's it..
				for (i = 0; smbcontrollers[i].vendor > 0; i++) { // check if this is a known smbus controller
					if (valuev == smbcontrollers[i].vendor) {
						pci_conf_read(0, smbdev, smbfun, 2, 2, &valued); // read the device id
						if (valued == smbcontrollers[i].device) {
							return i;
						}
					}
				}
			}
		}
	}
	return -1;
}


static int get_ddr3_module_size(int sdram_capacity, int prim_bus_width,
				int sdram_width, int ranks)
{
	int module_size;

	switch (sdram_capacity) {
	case 0:
		module_size = 256;
		break;
	case 1:
		module_size = 512;
		break;
	default:
	case 2:
		module_size = 1024;
		break;
	case 3:
		module_size = 2048;
		break;
	case 4:
		module_size = 4096;
		break;
	case 5:
		module_size = 8192;
		break;
	case 6:
		module_size = 16384;
		break;
	}

	module_size /= 8;

	switch (prim_bus_width) {
	case 0:
		module_size *= 8;
		break;
	case 1:
		module_size *= 16;
		break;
	case 2:
		module_size *= 32;
		break;
	case 3:
		module_size *= 64;
		break;
	}

	switch (sdram_width) {
	case 0:
		module_size /= 4;
		break;
	case 1:
		module_size /= 8;
		break;
	case 2:
		module_size /= 16;
		break;
	case 3:
		module_size /= 32;
		break;
	}

	module_size *= (ranks + 1);

	return module_size;
}

static int get_ddr2_module_size(int rank_density_byte, int rank_num_byte)
{
	int module_size;

	switch (rank_density_byte) {
	case 1:
		module_size = 1024;
		break;
	case 2:
		module_size = 2048;
		break;
	case 4:
		module_size = 4096;
		break;
	case 8:
		module_size = 8192;
		break;
	case 16:
		module_size = 16384;
		break;
	case 32:
		module_size = 128;
		break;
	case 64:
		module_size = 256;
		break;
	default:
	case 128:
		module_size = 512;
		break;
	}

	module_size *= (rank_num_byte & 7) + 1;

	return module_size;

}

void get_spd_spec(void)
{
	int index;
	int h, i, j, z;
	int k = 0;
	int module_size;
	int curcol;
	int temp_nbd;
	int tck;

	index = find_smb_controller();

	if (index == -1) {
		// Unknown SMBUS Controller, exit
		return;
	}

	smbcontrollers[index].get_adr();
	cprint(LINE_SPD - 2, 0, "Memory SPD Informations");
	cprint(LINE_SPD - 1, 0, "--------------------------");

	for (j = 0; j < 8; j++) {
		if (smbcontrollers[index].read_spd(j) == 0) {
			curcol = 1;
			if (spd_raw[2] == 0x0b) {
				// We are here if DDR3 present

				// First print slot#, module capacity
				cprint(LINE_SPD + k, curcol, " - Slot   :");
				dprint(LINE_SPD + k, curcol + 8, k, 1, 0);

				module_size =
				    get_ddr3_module_size(spd_raw[4] & 0xF,
							 spd_raw[8] & 0x7,
							 spd_raw[7] & 0x7,
							 spd_raw[7] >> 3);
				temp_nbd = getnum(module_size);
				curcol += 12;
				dprint(LINE_SPD + k, curcol, module_size,
				       temp_nbd, 0);
				curcol += temp_nbd;
				cprint(LINE_SPD + k, curcol, " MB");
				curcol += 4;

				// If XMP is supported, check Tck in XMP reg
				if (spd_raw[176] == 0x0C && spd_raw[177] == 0x4A
				    && spd_raw[12]) {
					tck = spd_raw[186];
				} else {
					tck = spd_raw[12];
				}

				// Then module jedec speed
				switch (tck) {
				default:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-????");
					break;
				case 20:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-800");
					curcol--;
					break;
				case 15:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-1066");
					break;
				case 12:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-1333");
					break;
				case 10:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-1600");
					break;
				case 9:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-1866");
					break;
				case 8:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-2133");
					break;
				case 7:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-2400");
					break;
				case 6:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-2533");
					break;
				case 5:
					cprint(LINE_SPD + k, curcol,
					       "DDR3-2666");
					break;
				}

				curcol += 10;

				if ((spd_raw[8] >> 3) == 1) {
					cprint(LINE_SPD + k, curcol, "ECC");
					curcol += 4;
				}
				// Then print module infos (manufacturer & part number)
				spd_raw[117] &= 0x0F;	// Parity odd or even
				for (i = 0; jep106[i].cont_code < 9; i++) {
					if (spd_raw[117] == jep106[i].cont_code &&
					    spd_raw[118] == jep106[i].hex_byte) {
						// We are here if a Jedec manufacturer is detected
						cprint(LINE_SPD + k,
						       curcol,
						       "-");
						curcol += 2;
						cprint(LINE_SPD + k,
						       curcol,
						       jep106[i].name);
						for (z = 0; jep106[i].name[z] != '\0'; z++) {
							curcol++;
						}
						curcol++;
						// Display module serial number
						for (h = 128; h < 146; h++) {
							cprint(LINE_SPD + k,
							       curcol,
							       convert_hex_to_char
							       (spd_raw[h]));
							curcol++;
						}

						// Detect Week and Year of Manufacturing (Think to upgrade after 2015 !!!)
						if (curcol <= 72
						    && spd_raw[120] > 3
						    && spd_raw[120] < 16
						    && spd_raw[121] < 55) {
							cprint(LINE_SPD + k,
							       curcol, "(W");
							dprint(LINE_SPD + k,
							       curcol + 2,
							       spd_raw[121],
							       2,
							       0);
							if (spd_raw[121] < 10) {
								cprint(LINE_SPD + k,
								       curcol + 2,
								       "0");
							}
							cprint(LINE_SPD + k,
							       curcol + 4,
							       "'");
							dprint(LINE_SPD + k,
							       curcol + 5,
							       spd_raw[120],
							       2,
							       0);
							if (spd_raw[120] < 10) {
								cprint(LINE_SPD
								       + k,
								       curcol +
								       5, "0");
							}
							cprint(LINE_SPD + k,
							       curcol + 7, ")");
							curcol += 9;
						}
						// Detect XMP Memory
						if (spd_raw[176] == 0x0C
						    && spd_raw[177] == 0x4A) {
							cprint(LINE_SPD + k,
							       curcol, "*XMP*");
						}
					}
				}
			}
			// We enter this function if DDR2 is detected
			if (spd_raw[2] == 0x08) {
				// First print slot#, module capacity
				cprint(LINE_SPD + k, curcol, " - Slot   :");
				dprint(LINE_SPD + k, curcol + 8, k, 1, 0);

				module_size = get_ddr2_module_size(spd_raw[31],
					      spd_raw[5]);
				temp_nbd = getnum(module_size);
				curcol += 12;
				dprint(LINE_SPD + k, curcol, module_size,
				       temp_nbd, 0);
				curcol += temp_nbd;
				cprint(LINE_SPD + k, curcol, " MB");
				curcol += 4;

				// Then module jedec speed
				float ddr2_speed, byte1, byte2;

				byte1 = (spd_raw[9] >> 4) * 10;
				byte2 = spd_raw[9] & 0xF;

				ddr2_speed = 1 / (byte1 + byte2) * 10000 * 2;

				temp_nbd = getnum(ddr2_speed);
				cprint(LINE_SPD + k, curcol, "DDR2-");
				curcol += 5;
				dprint(LINE_SPD + k, curcol, ddr2_speed,
				       temp_nbd, 0);
				curcol += temp_nbd;

				if ((spd_raw[11] >> 1) == 1) {
					cprint(LINE_SPD + k, curcol + 1, "ECC");
					curcol += 4;
				}
				// Then print module infos (manufacturer & part number)
				int ccode = 0;

				for (i = 64; i < 72; i++) {
					if (spd_raw[i] == 0x7F) {
						ccode++;
					}
				}

				curcol++;

				for (i = 0; jep106[i].cont_code < 9; i++) {
					if (ccode == jep106[i].cont_code
					    && spd_raw[64 + ccode] ==
					    jep106[i].hex_byte) {
						// We are here if a Jedec manufacturer is detected
						cprint(LINE_SPD + k, curcol,
						       "-");
						curcol += 2;
						cprint(LINE_SPD + k, curcol,
						       jep106[i].name);
						for (z = 0;
						     jep106[i].name[z] != '\0';
						     z++) {
							curcol++;
						}
						curcol++;
						// Display module serial number
						for (h = 73; h < 91; h++) {
							cprint(LINE_SPD + k,
							       curcol,
							       convert_hex_to_char
							       (spd_raw[h]));
							curcol++;
						}
					}
				}
			}
			k++;
		}
	}
}

void show_spd(void)
{
	int index;
	int i, j;
	int flag = 0;
	wait_keyup();
	index = find_smb_controller();
	if (index == -1) {
		cprint(POP2_Y, POP2_X + 1, "SMBus Controller not known");
		while (!get_key()) ;
		wait_keyup();
		return;
	} else {
		cprint(POP2_Y, POP2_X + 1, "SPD Data: Slot");
	}
	smbcontrollers[index].get_adr();
	for (j = 0; j < 16; j++) {
		if (smbcontrollers[index].read_spd(j) == 0) {
			dprint(POP2_Y, POP2_X + 15, j, 2, 0);
			for (i = 0; i < 256; i++) {
				hprint2(2 + POP2_Y + i / 16,
					3 + POP2_X + (i % 16) * 3, spd_raw[i],
					2);
			}
			flag = 0;
			while (!flag) {
				if (get_key())
					flag++;
			}
			wait_keyup();
		}
	}
}
