/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2009 One Laptop per Child, Association, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/* FIXME this should go away */
static const struct mem_controller ctrl = {
	.channel0 = {0x50, 0x51},
};

#define SMBUS_ADDR_CH_A_1       0xA0	/* Dimmx */
#define SMBUS_ADDR_CH_A_2       0xA2	/* Dimmx */
#define SMBUS_ADDR_CH_B_1       0xA4	/* Dimmx */
#define SMBUS_ADDR_CH_B_2       0xA6	/* Dimmx */

/* read data */
CB_STATUS GetSPDData(u8 Slot, u8 Length, u8 *Buf);
void DRAMCmdRate(DRAM_SYS_ATTR *DramAttr);
CB_STATUS GetInfoFromSPD(DRAM_SYS_ATTR *DramAttr);

CB_STATUS GetSPDData(u8 Slot, u8 Length, u8 *Buf)
{
	// CB_STATUS Status = CB_NOT_READY;
	u8 Val, i;

	if (1 > Length || NULL == Buf)
		return CB_INVALID_PARAMETER;

	for (i = 0; i < Length; i++) {
		Val = get_spd_data(ctrl.channel0[Slot], i);
		*(Buf + i) = Val;
	}
	return CB_SUCCESS;
}

CB_STATUS DRAMDetect(DRAM_SYS_ATTR *DramAttr)
{
	CB_STATUS Status = CB_SUCCESS;

	PRINT_DEBUG_MEM("DRAM detection \r");

	/* Read D0F3Rx6C, detect memory type DDR1 or DDR2. */
	/* 353 supports DDR2 only */
	DramAttr->DramType = RAMTYPE_SDRAMDDR2;
	/* Get information for SPD. */
	Status = GetInfoFromSPD(DramAttr);
	if (CB_SUCCESS == Status) {
		/* 64bit or 128Bit */

		// if (RAMTYPE_SDRAMDDR == DramAttr->DramType)

		/* Select command rate. */
		DRAMCmdRate(DramAttr);
	}
	return Status;
}

/*
 * Determine 1T or 2T command rate.
 *
 * To enable 1T command rate, the system will satisfy the following
 * three conditions:
 *
 *   1. Each DRAM channel may have 1 or 2 ranks of DIMM. 3/4 ranks can not
 *      support 1T command rate. It's for loading issue. 1T can supports
 *       (a) only one socket with two ranks, OR
 *       (b) two sockets each with 1 rank.
 *   2. User wishes to enable 1T command rate mode and turn on by setup menu.
 *   3. If 1T command rate can be enabled, just set EBP bit here.
 */
void DRAMCmdRate(DRAM_SYS_ATTR *DramAttr)
{
	u8 Data;

	// 5.1t/2t command rate, use the stable set
	//offset50
	DramAttr->CmdRate = 2;
	Data = pci_read_config8(MEMCTRL, 0x50);
	Data = (u8) (Data & 0xEE);
	pci_write_config8(MEMCTRL, 0x50, Data);
}

/*
 * Get SPD data and set RANK presence map.
 *
 * Sockets0,1 is Channel A / Sockets2,3 is Channel B.
 *
 * Socket0 SPD device address 0x50 / socket1 SPD device address 0x51
 * Socket2 SPD device address 0x52 / socket3 SPD device address 0x53
 */
CB_STATUS GetInfoFromSPD(DRAM_SYS_ATTR *DramAttr)
{
	CB_STATUS Status;
	u8 *pSPDDataBuf;
	u8 ModuleDataWidth, ChipWidth, RankNum, LoadNum, Sockets, i;
	BOOLEAN bFind;  /* FIXME: We don't have/want BOOLEAN. */

	bFind = FALSE;  /* FIXME: We don't have/want FALSE. */
	Status = CB_DEVICE_ERROR;

	for (Sockets = 0; Sockets < MAX_SOCKETS; Sockets++) {
		pSPDDataBuf = DramAttr->DimmInfo[Sockets].SPDDataBuf;
		pSPDDataBuf[SPD_MEMORY_TYPE] =
		    get_spd_data(ctrl.channel0[Sockets], SPD_MEMORY_TYPE);
		if (pSPDDataBuf[SPD_MEMORY_TYPE] == 0) {
			Status = CB_NOT_READY;
		} else {
			Status =
			    GetSPDData(Sockets, SPD_DATA_SIZE, pSPDDataBuf);
			PRINT_DEBUG_MEM("SPD : \r");
			for (i = 0; i < SPD_DATA_SIZE; i++) {
				PRINT_DEBUG_MEM(" ");
				PRINT_DEBUG_MEM_HEX8(pSPDDataBuf[i]);
			}
		}
		if (CB_SUCCESS == Status) {
			/*
			 * If DRAM controller detected type not same as the
			 * type got from SPD, there are ERROR.
			 */
			if (pSPDDataBuf[SPD_MEMORY_TYPE] != DramAttr->DramType) {
				Status = CB_DEVICE_ERROR; /* memory int error */
				PRINT_DEBUG_MEM("Memory Device ERROR: DRAM "
                                                "controller detected type != "
                                                "type got from SPD\r");
				break;
			}
			DramAttr->DimmInfo[Sockets].bPresence = TRUE;

			/* Calculate load number (chips number). */
			ModuleDataWidth = (u8) (DramAttr->
				  DimmInfo[Sockets].SPDDataBuf
				  [SPD_SDRAM_MOD_DATA_WIDTH + 1]);
			ModuleDataWidth = (u8) (ModuleDataWidth << 8);
			ModuleDataWidth |= (u8) (DramAttr->
				  DimmInfo[Sockets].SPDDataBuf
				  [SPD_SDRAM_MOD_DATA_WIDTH]);
			ChipWidth = (u8) ((DramAttr->
				   DimmInfo[Sockets].SPDDataBuf
				   [SPD_SDRAM_WIDTH]) & 0x7F);
			LoadNum = (u8) (ModuleDataWidth / ChipWidth);

			/* Set the RANK map. */
                        /* Get bit0,1, the most number of supported RANK is 2. */
			RankNum = (u8) (pSPDDataBuf[SPD_SDRAM_DIMM_RANKS] & 0x3);
			if (RAMTYPE_SDRAMDDR2 == DramAttr->DramType)
                                /*
                                 * For DDR bit[0,1]: 01->1 RANK, 10->2 RANK
                                 * For DDR2 bit[0,1]: 00->1 RANK, 01->2 RANK
                                 */
				RankNum++;

                        /* Every DIMM have 1 or 2 ranks. */
			if (RankNum != 2 && RankNum != 1) {
				Status = CB_DEVICE_ERROR;
				PRINT_DEBUG_MEM("Memory Device ERROR: Number "
                                                "of RANK not supported!\r");
				break;
			}

			if (Sockets < 2) { /* Sockets0,1 is channel A */
				DramAttr->RankNumChA =
				    (u8) (DramAttr->RankNumChA + RankNum);
				DramAttr->DimmNumChA++;
				DramAttr->LoadNumChA =
				    (u8) (DramAttr->LoadNumChA * LoadNum *
					  RankNum);
			} else { /* Sockets2,3 is channel B */
				DramAttr->RankNumChB =
				    (u8) (DramAttr->RankNumChB + RankNum);
				DramAttr->DimmNumChB++;
				DramAttr->LoadNumChB =
				    (u8) (DramAttr->LoadNumChB * LoadNum *
					  RankNum);;
			}
			RankNum |= 1; /* Set rank map. */
			DramAttr->RankPresentMap |= (RankNum << (Sockets * 2));
			bFind = TRUE;
		}
	}

	PRINT_DEBUG_MEM("Rank Present Map:");
	PRINT_DEBUG_MEM_HEX8(DramAttr->RankPresentMap);
	PRINT_DEBUG_MEM("\r");

	if (bFind)
		Status = CB_SUCCESS;

	return Status;
}
