#include "defs.h"
#include "config.h"
#include "test.h"
#include "pci.h"
#include "controller.h"

/* controller ECC capabilities and mode */
#define __ECC_UNEXPECTED 1      /* Unknown ECC capability present */
#define __ECC_DETECT     2	/* Can detect ECC errors */
#define __ECC_CORRECT    4	/* Can correct some ECC errors */
#define __ECC_SCRUB      8	/* Can scrub corrected ECC errors */

#define ECC_UNKNOWN      (~0UL)    /* Unknown error correcting ability/status */
#define ECC_NONE         0       /* Doesnt support ECC (or is BIOS disabled) */
#define ECC_RESERVED     __ECC_UNEXPECTED  /* Reserved ECC type */
#define ECC_DETECT       __ECC_DETECT     
#define ECC_CORRECT      (__ECC_DETECT | __ECC_CORRECT)
#define ECC_SCRUB        (__ECC_DETECT | __ECC_CORRECT | __ECC_SCRUB)


static struct ecc_info {
	int index;
	int poll;
	unsigned bus;
	unsigned dev;
	unsigned fn;
	unsigned cap;
	unsigned mode;
} ctrl = 
{
	.index = 0,
	/* I know of no case where the memory controller is not on the
	 * host bridge, and the host bridge is not on bus 0  device 0
	 * fn 0.  But just in case leave these as variables. 
	 */
	.bus = 0,
	.dev = 0,
	.fn = 0,
	/* Properties of the current memory controller */
	.cap = ECC_UNKNOWN,
	.mode = ECC_UNKNOWN,
};


static void setup_nothing(void)
{
	ctrl.cap = ECC_NONE;
	ctrl.mode = ECC_NONE;
}

static void poll_nothing(void)
{
/* Code to run when we don't know how, or can't ask the memory
 * controller about memory errors.
 */ 
	return;
}


static void setup_amd751(void)
{
	unsigned long dram_status;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x5a, 2, &dram_status);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = (dram_status & (1 << 2))?ECC_CORRECT: ECC_NONE;
}

static void poll_amd751(void)
{
	unsigned long ecc_status;
	unsigned long bank_addr;
	unsigned long bank_info;
	unsigned long page;
	int bits;
	int i;

	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x58, 2, &ecc_status);
	if (ecc_status & (3 << 8)) {
		for(i = 0; i < 6; i++) {
			if (!(ecc_status & (1 << i))) {
				continue;
			}
			/* Find the bank the error occured on */
			bank_addr = 0x40 + (i << 1);
				
			/* Now get the information on the erroring bank */
			pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, bank_addr, 2, &bank_info);
				
			/* Parse the error location and error type */
			page = (bank_info & 0xFF80) << 4;
			bits = (((ecc_status >> 8) &3) == 2)?1:2;
				
			/* Report the error */
			print_ecc_err(page, 0, bits==1?1:0, 0, 0);
			
		}
	
		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0x58, 2, 0);
	}
}


static void setup_amd76x(void)
{
	static const int ddim[] = { ECC_NONE, ECC_DETECT, ECC_CORRECT, ECC_CORRECT };
	unsigned long ecc_mode_status;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x48, 4, &ecc_mode_status);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(ecc_mode_status >> 10)&3]; 
}

static void poll_amd76x(void)
{
	unsigned long ecc_mode_status;
	unsigned long bank_addr;
	unsigned long bank_info;
	unsigned long page;

	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x48, 4, &ecc_mode_status);
	/* Multibit error */
	if (ecc_mode_status & (1 << 9)) {
		/* Find the bank the error occured on */
		bank_addr = 0xC0 + (((ecc_mode_status >> 4) & 0xf) << 2);

		/* Now get the information on the erroring bank */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, bank_addr, 4, &bank_info);

		/* Parse the error location and error type */
		page = (bank_info & 0xFF800000) >> 12;

		/* Report the error */
		print_ecc_err(page, 0, 1, 0, 0);

	}
	/* Singlebit error */
	if (ecc_mode_status & (1 << 8)) {
		/* Find the bank the error occured on */
		bank_addr = 0xC0 + (((ecc_mode_status >> 0) & 0xf) << 2);

		/* Now get the information on the erroring bank */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, bank_addr, 4, &bank_info);

		/* Parse the error location and error type */
		page = (bank_info & 0xFF800000) >> 12;

		/* Report the error */
		print_ecc_err(page, 0, 0, 0, 0);

	}
	/* Clear the error status */
	if (ecc_mode_status & (3 << 8)) {
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0x48, 4, ecc_mode_status);
	}
}

static void setup_cnb20(void)
{
	/* Fill in the correct memory capabilites */
	ctrl.cap = ECC_CORRECT;

	/* FIXME add ECC error polling.  I don't have the documentation
	 * do it right now.
	 */
}


static void setup_i440gx(void)
{
	static const int ddim[] = { ECC_NONE, ECC_DETECT, ECC_CORRECT, ECC_CORRECT };
	unsigned long nbxcfg;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x50, 4, &nbxcfg);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(nbxcfg >> 7)&3];
}

static void poll_i440gx(void)
{
	unsigned long errsts;
	unsigned long page;
	int bits;
	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x91, 2, &errsts);
	if (errsts & 0x11) {
		unsigned long eap;
		/* Read the error location */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x80, 4, &eap);

		/* Parse the error location and error type */
		page = (eap & 0xFFFFF000) >> 12;
		bits = 0;
		if (eap &3) {
			bits = ((eap & 3) == 1)?1:2;
		}

		if (bits) {
			/* Report the error */
			print_ecc_err(page, 0, bits==1?1:0, 0, 0);
		}

		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0x91, 2, 0x11);
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0x80, 4, 3);
	}
	
}
static void setup_i840(void)
{
	static const int ddim[] = { ECC_NONE, ECC_RESERVED, ECC_CORRECT, ECC_CORRECT };
	unsigned long mchcfg;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x50, 2, &mchcfg);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(mchcfg >> 7)&3]; 
}

static void poll_i840(void)
{
	unsigned long errsts;
	unsigned long page;
	unsigned long syndrome;
	int channel;
	int bits;
	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, &errsts);
	if (errsts & 3) {
		unsigned long eap;
		unsigned long derrctl_sts;
		/* Read the error location */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xE4, 4, &eap);
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xE2, 2, &derrctl_sts);

		/* Parse the error location and error type */
		page = (eap & 0xFFFFF800) >> 11;
		channel = eap & 1;
		syndrome = derrctl_sts & 0xFF;
		bits = ((errsts & 3) == 1)?1:2;

		/* Report the error */
		print_ecc_err(page, 0, bits==1?1:0, syndrome, channel);

		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xE2, 2, 3 << 10);
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, 3);
	}
}

static void setup_i845(void)
{
	static const int ddim[] = { ECC_NONE, ECC_RESERVED, ECC_CORRECT, ECC_RESERVED };
	unsigned long drc;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x7C, 4, &drc);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(drc >> 20)&3];
}

static void poll_i845(void)
{
	unsigned long errsts;
	unsigned long page, offset;
	unsigned long syndrome;
	int bits;
	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, &errsts);
	if (errsts & 3) {
		unsigned long eap;
		unsigned long derrsyn;
		/* Read the error location */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x8C, 4, &eap);
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x86, 1, &derrsyn);

		/* Parse the error location and error type */
		offset = (eap & 0xFE) << 4;
		page = (eap & 0x3FFFFFFE) >> 8;
		syndrome = derrsyn;
		bits = ((errsts & 3) == 1)?1:2;

		/* Report the error */
		print_ecc_err(page, offset, bits==1?1:0, syndrome, 0);

		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, 3);
	}
}
static void setup_i820(void)
{
	static const int ddim[] = { ECC_NONE, ECC_RESERVED, ECC_CORRECT, ECC_CORRECT };
	unsigned long mchcfg;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xbe, 2, &mchcfg);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(mchcfg >> 7)&3];
}

static void poll_i820(void)
{
	unsigned long errsts;
	unsigned long page;
	unsigned long syndrome;
	int bits;
	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, &errsts);
	if (errsts & 3) {
		unsigned long eap;
		/* Read the error location */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xc4, 4, &eap);

		/* Parse the error location and error type */
		page = (eap & 0xFFFFF000) >> 4;
		syndrome = eap & 0xFF;
		bits = ((errsts & 3) == 1)?1:2;

		/* Report the error */
		print_ecc_err(page, 0, bits==1?1:0, syndrome, 0);

		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, 3);
	}
}

static void setup_i850(void)
{
	static const int ddim[] = { ECC_NONE, ECC_RESERVED, ECC_CORRECT, ECC_RESERVED };
	unsigned long mchcfg;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x50, 2, &mchcfg);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(mchcfg >> 7)&3]; 
}

static void poll_i850(void)
{
	unsigned long errsts;
	unsigned long page;
	unsigned long syndrome;
	int channel;
	int bits;
	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, &errsts);
	if (errsts & 3) {
		unsigned long eap;
		unsigned long derrctl_sts;
		/* Read the error location */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xE4, 4, &eap);
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xE2, 2, &derrctl_sts);

		/* Parse the error location and error type */
		page = (eap & 0xFFFFF800) >> 11;
		channel = eap & 1;
		syndrome = derrctl_sts & 0xFF;
		bits = ((errsts & 3) == 1)?1:2;

		/* Report the error */
		print_ecc_err(page, 0, bits==1?1:0, syndrome, channel);

		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, errsts & 3);
	}
}

static void setup_i860(void)
{
	static const int ddim[] = { ECC_NONE, ECC_RESERVED, ECC_CORRECT, ECC_RESERVED };
	unsigned long mchcfg;
	unsigned long errsts;

	/* Fill in the correct memory capabilites */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0x50, 2, &mchcfg);
	ctrl.cap = ECC_CORRECT;
	ctrl.mode = ddim[(mchcfg >> 7)&3]; 

	/* Clear any prexisting error reports */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, &errsts);	
	pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, errsts & 3);
}

static void poll_i860(void)
{
	unsigned long errsts;
	unsigned long page;
	unsigned char syndrome;
	int channel;
	int bits;
	/* Read the error status */
	pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, &errsts);
	if (errsts & 3) {
		unsigned long eap;
		unsigned long derrctl_sts;
		/* Read the error location */
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xE4, 4, &eap);
		pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, 0xE2, 2, &derrctl_sts);

		/* Parse the error location and error type */
		page = (eap & 0xFFFFFE00) >> 9;
		channel = eap & 1;
		syndrome = derrctl_sts & 0xFF;
		bits = ((errsts & 3) == 1)?1:2;

		/* Report the error */
		print_ecc_err(page, 0, bits==1?1:0, syndrome, channel);

		/* Clear the error status */
		pci_conf_write(ctrl.bus, ctrl.dev, ctrl.fn, 0xC8, 2, errsts & 3);
	}
}

struct pci_memory_controller {
	unsigned vendor;
	unsigned device;
	char *name;
	int tested;
	void (*setup_ecc)(void);
	void (*poll_errors)(void);
};

static struct pci_memory_controller controllers[] = {
	/* Default unknown chipset */
	{ 0, 0, "",                    0, setup_nothing, poll_nothing },
	/* AMD */
	{ 0x1022, 0x7006, "AMD 751",   0, setup_amd751, poll_amd751 },
	{ 0x1022, 0x700c, "AMD 762",   0, setup_amd76x, poll_amd76x },
	{ 0x1022, 0x700e, "AMD 761",   0, setup_amd76x, poll_amd76x },
	/* Motorola */
	{ 0x1057, 0x4802, "Falcon",    0, setup_nothing, poll_nothing },
	/* Apple */
	{ 0x106b, 0x0001, "Bandit",    0, setup_nothing, poll_nothing },
	/* SiS */
	{ 0x1039, 0x0600, "SiS 600",   0, setup_nothing, poll_nothing },
	{ 0x1039, 0x0620, "SiS 620",   0, setup_nothing, poll_nothing },
	{ 0x1039, 0x5600, "SiS 5600",  0, setup_nothing, poll_nothing },
	/* ALi */
	{ 0x10b9, 0x1531, "Aladdin 4", 0,setup_nothing, poll_nothing },
	{ 0x10b9, 0x1541, "Aladdin 5", 0,setup_nothing, poll_nothing },
	/* VIA */
	{ 0x1106, 0x0305, "vt8363",    0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0391, "vt8371",    0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0501, "vt8501",    0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0585, "vt82c585",  0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0595, "vt82c595",  0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0597, "vt82c597",  0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0598, "vt82c598",  0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0691, "vt82c691",  0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0693, "vt82c693",  0, setup_nothing, poll_nothing },
	{ 0x1106, 0x0694, "vt82c694",  0, setup_nothing, poll_nothing },

	/* Serverworks */
	{ 0x1166, 0x0008, "CNB20HE",   0, setup_cnb20, poll_nothing },
	{ 0x1166, 0x0009, "CNB20LE",   0, setup_cnb20, poll_nothing },

	/* Intel */
	{ 0x8086, 0x1130, "i815",      0, setup_nothing, poll_nothing },
	{ 0x8086, 0x122d, "i430fx",    0, setup_nothing, poll_nothing },
	{ 0x8086, 0x1237, "i440fx",    0, setup_nothing, poll_nothing },
	{ 0x8086, 0x1250, "i430hx",    0, setup_nothing, poll_nothing },
	{ 0x8086, 0x1A21, "i840",      0,setup_i840, poll_i840 },
	{ 0x8086, 0x1A30, "i845",      0,setup_i845, poll_i845 },
	{ 0x8086, 0x2500, "i820",      0,setup_i820, poll_i820 },
	{ 0x8086, 0x2530, "i850",      0,setup_i850, poll_i850 },
	{ 0x8086, 0x2531, "i860",      1, setup_i860, poll_i860 },
	{ 0x8086, 0x7030, "i430vx",    0, setup_nothing, poll_nothing },
	{ 0x8086, 0x7120, "i810",      0, setup_nothing, poll_nothing },
	{ 0x8086, 0x7122, "i810",      0, setup_nothing, poll_nothing },
	{ 0x8086, 0x7124, "i810e",     0, setup_nothing, poll_nothing },
	{ 0x8086, 0x7180, "i440[le]x", 0, setup_nothing, poll_nothing },
	{ 0x8086, 0x7190, "i440[bz]x", 0, setup_nothing, poll_nothing },
	{ 0x8086, 0x7192, "i440[bz]x", 0, setup_nothing, poll_nothing },
	{ 0x8086, 0x71A0, "i440gx",    0, setup_i440gx, poll_i440gx },
	{ 0x8086, 0x71A2, "i440gx",    0, setup_i440gx, poll_i440gx },
	{ 0x8086, 0x84C5, "i450gx",    0, setup_nothing, poll_nothing },
};

static void print_memory_controller(void)
{
	/* Print memory controller info */
	int col;
	char *name;
	if (ctrl.index == 0) {
		return;
	}
	
	/* Print the controller name */
	name = controllers[ctrl.index].name;
	col = 11;
	cprint(LINE_CPU+4, col, name);
	/* Now figure out how much I just printed */
	while(name[col - 11] != '\0') {
		col++;
	} 
	/* Now print the memory controller capabilities */
	cprint(LINE_CPU+4, col, " "); col++;
	if (ctrl.cap == ECC_UNKNOWN) {
		return;
	}
	if (ctrl.cap & __ECC_DETECT) {
		int on;
		on = ctrl.mode & __ECC_DETECT;
		cprint(LINE_CPU+4, col, "Detect");
		cprint(LINE_CPU+4, col +6, on?"+ ":"- ");
		col += 8;
	}
	if (ctrl.cap & __ECC_CORRECT) {
		int on;
		on = ctrl.mode & __ECC_CORRECT;
		cprint(LINE_CPU+4, col, "Correct");
		cprint(LINE_CPU+4, col +7, on?"+ ":"- ");
		col += 9;
	}
	if (ctrl.cap & __ECC_SCRUB) {
		int on;
		on = ctrl.mode & __ECC_SCRUB;
		cprint(LINE_CPU+4, col, "Scrub");
		cprint(LINE_CPU+4, col +5, on?"+ ":"- ");
		col += 7;
	}
	if (ctrl.cap & __ECC_UNEXPECTED) {
		int on;
		on = ctrl.mode & __ECC_UNEXPECTED;
		cprint(LINE_CPU+4, col, "Unknown");
		cprint(LINE_CPU+4, col +7, on?"+ ":"- ");
		col += 9;
	}
}


void find_controller(void)
{
	unsigned long vendor;
	unsigned long device;
	int i;
	int result;
	result = pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, PCI_VENDOR_ID, 2, &vendor);
	result = pci_conf_read(ctrl.bus, ctrl.dev, ctrl.fn, PCI_DEVICE_ID, 2, &device);
	ctrl.index = 0;
	if (result == 0) {
		for(i = 1; i < sizeof(controllers)/sizeof(controllers[0]); i++) {
			if ((controllers[i].vendor == vendor) &&
				(controllers[i].device == device)) {
				ctrl.index = i;
				break;
			}
		}
	}
	controllers[ctrl.index].setup_ecc();
	/* Don't enable ECC polling by default unless it has
	 * been well tested.
	 */
	set_ecc_polling(-1);
	print_memory_controller();
}

void poll_errors(void)
{
	if (ctrl.poll) {
		controllers[ctrl.index].poll_errors();
	}
}

void set_ecc_polling(int val)
{
	int tested = controllers[ctrl.index].tested;
	if (val == -1) {
		val = tested;
	}
	if (val) {
		ctrl.poll = 1;
		cprint(LINE_INFO, COL_ECC, tested? " on": " ON");
	} else {
		ctrl.poll = 0;
		cprint(LINE_INFO, COL_ECC, tested? "OFF": "off");
	}
}
