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

 
#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

extern void wait_keyup();

int smbdev, smbfun;
unsigned short smbusbase;
unsigned char spd_raw[256];
char s[] = {'/', 0, '-', 0, '\\', 0, '|', 0};	

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 piix4_get_smb(void)
{
    unsigned long x;
    int result;
    
    result = 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();
    } 	
}

void sb800_get_smb(void)
{
    int lbyte, hbyte;
	
    __outb(AMD_SMBUS_BASE_REG + 1, AMD_INDEX_IO_PORT);	
    lbyte = __inb(AMD_DATA_IO_PORT);
    __outb(AMD_SMBUS_BASE_REG, AMD_INDEX_IO_PORT);	
    hbyte = __inb(AMD_DATA_IO_PORT);
		
    smbusbase = lbyte;
    smbusbase <<= 8;
    smbusbase += hbyte;
    smbusbase &= 0xFFE0;
		
    if (smbusbase == 0xFFE0)	{ smbusbase = 0; }	
}

unsigned char ich5_smb_read_byte(unsigned char adr, unsigned char cmd)
{
    int l1, h1, l2, h2;
    unsigned long long t;
    __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 = ((h2 - h1) * 0xffffffff + (l2 - l1)) / vv->clks_msec;
        if (t > 10) 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;
}

unsigned char us15w_smb_read_byte(unsigned char adr, unsigned char cmd)
{
    int l1, h1, l2, h2;
    unsigned long long t;
    //__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 til busy
    rdtsc(l1, h1);
    cprint(POP2_Y, POP2_X + 16, s + cmd % 8);	// progress bar
    while (((__inb(smbusbase + 1) & 0x01) == 0) ||
           ((__inb(smbusbase + 1) & 0x08) != 0)) {	// wait til command finished
	rdtsc(l2, h2);
	t = ((h2 - h1) * 0xffffffff + (l2 - l1)) / vv->clks_msec;
	if (t > 10) 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},

     // AMD SMBUS
     {0x1002, 0x4385, "AMD SB600/700",	piix4_get_smb, ich5_read_spd},
     {0x1022, 0x780B, "AMD SB800/900", sb800_get_smb, ich5_read_spd},
     {0, 0, "", NULL, NULL}
    };

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;
}

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;
    pop2up();
    wait_keyup();
    index = find_smb_controller();
    if (index == -1) {
	cprint(POP2_Y, POP2_X+1, "SMBus Controller not known");
	while (!get_key());
	wait_keyup();
	pop2down();
	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();
	}
    }
    pop2down();
}

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;
}


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;
		
}


struct ascii_map {
    unsigned hex_code;
    char *name;
};


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;
}
