/*
 * (C) Copyright 2001
 * Kenneth Johansson ,Ericsson Business Innovation.
 * kenneth.johansson@inn.ericsson.se
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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; either version 2 of
 * the License, or (at your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <ppcboot.h>
#include <asm/processor.h>
#include <405gp_i2c.h>

#ifdef CONFIG_DIMM_EEPROM

#define  SDRAM0_CFG_DCE_MASK     0x80000000
#define  SDRAM0_CFG_SRE_MASK     0x40000000
#define  SDRAM0_CFG_PME_MASK     0x20000000
#define  SDRAM0_CFG_MEMCHK_MASK  0x10000000
#define  SDRAM0_CFG_REGEN_MASK   0x08000000
#define  SDRAM0_CFG_DRW_MASK     0x06000000
#define  SDRAM0_CFG_BRPF_MASK    0x01800000
#define  SDRAM0_CFG_ECCDD_MASK   0x00400000
#define  SDRAM0_CFG_EMDULR_MASK  0x00200000

#define  SDRAM0_CFG_DCE_SHIFT    (31-0) 
#define  SDRAM0_CFG_SRE_SHIFT    (31-1) 
#define  SDRAM0_CFG_PME_SHIFT    (31-2) 
#define  SDRAM0_CFG_MEMCHK_SHIFT (31-3) 
#define  SDRAM0_CFG_REGEN_SHIFT  (31-4) 
#define  SDRAM0_CFG_DRW_SHIFT    (31-6) 
#define  SDRAM0_CFG_BRPF_SHIFT   (31-8) 
#define  SDRAM0_CFG_ECCDD_SHIFT  (31-9) 
#define  SDRAM0_CFG_EMDULR_SHIFT (31-10)

#define  SDRAM0_TR_CASL_SHIFT (31-8)
#define  SDRAM0_TR_PTA_SHIFT  (31-13)
#define  SDRAM0_TR_CTP_SHIFT  (31-15)
#define  SDRAM0_TR_LDF_SHIFT  (31-17)
#define  SDRAM0_TR_RFTA_SHIFT (31-29)
#define  SDRAM0_TR_RCD_SHIFT  (31-31)

#define  SDRAM0_RTR_SHIFT     (31-12)

/* SDRAM0_CFG enable macro  */
#define SDRAM0_CFG_DCE(x)  ( (( x << SDRAM0_CFG_DCE_SHIFT) & SDRAM0_CFG_DCE_MASK) )   
#define SDRAM0_CFG_BRPF(x) ( (( x << SDRAM0_CFG_BRPF_SHIFT) & SDRAM0_CFG_BRPF_MASK) )   


#define SDRAM0_BXCR_SZ_MASK  0x000e0000
#define SDRAM0_BXCR_AM_MASK  0x0000e000

#define SDRAM0_BXCR_SZ_SHIFT (31-14)
#define SDRAM0_BXCR_AM_SHIFT (31-18)

#define SDRAM0_BXCR_SZ(x)  ( (( x << SDRAM0_BXCR_SZ_SHIFT) & SDRAM0_BXCR_SZ_MASK) )   
#define SDRAM0_BXCR_AM(x)  ( (( x << SDRAM0_BXCR_AM_SHIFT) & SDRAM0_BXCR_AM_MASK) )   


/*
 * what we really want is 
 * (1/hertz) but we don't want to use floats so multiply with 10E9
 *
 * The error needs to be on the safe side so we want the floor function.
 * This means we get an exact value or we calculate that our bus frequency is
 * a bit faster than it really is and thus we don't progam the sdram controller
 * to run to fast
 */
#define sdram_HZ_to_ns(hertz) (1000000000/(hertz))


/*
 * This function is reading data from the DIMM module EEPROM over the SPD bus
 * and uses that to program the sdram controller.
 * 
 * This works on boards that has the same schematics that the IBM walnut has.
 *
 * BUG: Don't handle ECC memory
 * BUG: A few values in the TR register is currently hardcoded
 */

long int walnut_dimm(void)
{
	unsigned char dataout[1];
	unsigned char datain[128];
	int i,bus_period,tmp;
	int TotalSize;
   
	int bank_size,mode;    
 
	int sdram0_pmit=0x07c00000;
	int sdram0_besr0=-1;
	int sdram0_besr1=-1;
	int sdram0_eccesr=-1;
	int sdram0_ecccfg=0;

	int sdram0_rtr=0;
	int sdram0_tr=0;

	int sdram0_b0cr;
	int sdram0_b1cr;
	int sdram0_b2cr;
	int sdram0_b3cr;

	int sdram0_cfg=0;

	PPC405_SYS_INFO sys_info;

	
	get_sys_info(&sys_info);
	bus_period = sdram_HZ_to_ns(sys_info.freqPLB );

	/* Read Serial Presence Detect Information */
	dataout[0] = 0;
	for (i=0; i<128;i++)
		datain[i] = 127;
	i2c_send(EEPROM_WRITE_ADDRESS,1,dataout);
	i2c_receive(EEPROM_READ_ADDRESS,128,datain);
	printf("\nReading DIMM...\n");    

#if 0
	for (i=0;i<128;i++)
		printf("%d=0x%x \n", i, datain[i]);
	printf("\n");
#endif

	/* Make shure we are using SDRAM */
	if(datain[2] != 0x4){
		printf("Detected memory type %d\n",datain[2]);
		printf("Memory error: We only suport SDRAM\n");
		hang();
	}


/*------------------------------------------------------------------
  configure memory timing register

  data from DIMM:
  27	IN Row Precharge Time ( t RP)
  29	MIN RAS to CAS Delay ( t RCD)
  127   Component and Clock Detail ,clk0-clk3, junction temp, CAS
  -------------------------------------------------------------------*/    

	/*
	 * set CASL
	 * datain[127] bit1 = cas2 bit2=cas3
	 */

	if(datain[127] & 0x02)
		sdram0_tr = 1 << SDRAM0_TR_CASL_SHIFT;	
	else if (datain[127] & 0x04)
		sdram0_tr = 2 << SDRAM0_TR_CASL_SHIFT;	
	else {
		printf("Found unsupported CAS value\n");
		hang();
	}

	/*
	 * set PTA
	 * datain[27] time in ns
	 */
	tmp = datain[27]/bus_period;
	if( tmp*bus_period < datain[27])
		tmp+=1;
	tmp--; /* the value is coded in the reg */
	sdram0_tr |= tmp << SDRAM0_TR_PTA_SHIFT ;
	
	/*
	 * set CTP
	 * 2 => 3clk
	 */
	sdram0_tr |= 2 << SDRAM0_TR_CTP_SHIFT ;

	/*
	 * set LDF
	 * 1 => 2 clk
	*/
	sdram0_tr |= 1 << SDRAM0_TR_LDF_SHIFT ;

	/*
	 * set RFTA
	 * 3 => 7clk
	 */
	sdram0_tr |= 3 << SDRAM0_TR_RFTA_SHIFT ;

	/*
	  set RCD
	  datain[29] Ras to cas delay
	 */
	tmp = datain[29]/bus_period;

	if( tmp*bus_period < datain[29])
		tmp+=1;
	tmp--; /* the value is coded in the reg */
	sdram0_tr |= tmp << SDRAM0_TR_RCD_SHIFT ;

/*	sdram0_tr=0x0106400d; */

/*------------------------------------------------------------------
  configure RTR register
  -------------------------------------------------------------------*/    

	tmp = datain[12] & 0x7f ; /* Mask away self refresh bit */

	switch(tmp){
	case 0x00:
		tmp=15625;
		break;
	case 0x01:
		tmp=15625/4;
		break;
	case 0x02:
		tmp=15625/2;
		break;
	case 0x03:
		tmp=15625*2;
		break;
	case 0x04:
		tmp=15625*4;
		break;
	case 0x05:
		tmp=15625*8;
		break;
	default:
		printf("DRAM Refresh rate outside spec\n");
		hang();
	}
	tmp = tmp/bus_period;
	sdram0_rtr = tmp <<  SDRAM0_RTR_SHIFT;

/*------------------------------------------------------------------
  configure memory bank register
  -------------------------------------------------------------------*/    

	/* byte 31 (sdram) module bank density */
	switch( datain[31] ){
	case 0x1:
		TotalSize = 4;
		bank_size = 0;
		break;
	case 0x2:
		TotalSize = 8;
		bank_size = 0;
		break;
	case 0x4:
		TotalSize = 16;
		bank_size = 1;
		break;
	case 0x8:
		TotalSize = 32;
		bank_size = 2;
		break;
	case 0x10:
		TotalSize = 64;
		bank_size = 3;
		break;
	case 0x20:
		TotalSize = 128;
		bank_size = 4;
		break;
	case 0x40:
		TotalSize = 256;
		bank_size = 5;
		break;
	case 0x80:
		TotalSize = 512;
		bank_size = 6;
		break;
	default:      
		printf("Invalid module size\n");
		TotalSize = 128;
		bank_size = 2;
	}

	/* Need this in bytes */
	TotalSize*= 1024*1024;

	/* map  rows * cols * banks to a mode 
	 * datain[  3] number of row
	 * datain[  4] number of column 
	 * datain[ 17] number of internal banks 
	 */ 
	switch( datain[3] )
	{
	case 11:
		switch ( datain[4] )
		{
		case 8:
			mode=4; /* mode 5 */
			break;
		case 9:
		case 10:
			mode=0; /* mode 1 */
			break;		    
		default:
			printf("DRAM column number not supported");
		}
		break;
	case 12:
		switch ( datain[4] )
		{
		case 8:
			mode=3; /* mode 4 */
			break;
		case 9:
		case 10:
			mode=1; /* mode 2 */
			break;
		default:
			printf("DRAM column number not supported");
		}	    
		break;
	case 13:
		switch ( datain[4] )
		{
		case 8:
			mode=5; /* mode 6 */
			break;
		case 9:
		case 10:
			if (datain[17] ==2 )
				mode=6; /* mode 7 */
			else
				mode=2; /* mode 3 */
			break;
		case 11:
			mode=2; /* mode 3 */
			break;
		default:
			printf("DRAM column number not supported");
		}	    
		break;
	default:
		printf("DRAM row number not supported\n");
	}

	/* double-sided DIMM ? */
	if (datain[5] == 2){
		printf("double-sided DIMM\n");
		TotalSize *= 2;

		sdram0_b0cr = (TotalSize>>2) * 0 | SDRAM0_BXCR_SZ(bank_size) | SDRAM0_BXCR_AM(mode) | 1;
		sdram0_b1cr = (TotalSize>>2) * 1 | SDRAM0_BXCR_SZ(bank_size) | SDRAM0_BXCR_AM(mode) | 1;
		sdram0_b2cr = (TotalSize>>2) * 2 | SDRAM0_BXCR_SZ(bank_size) | SDRAM0_BXCR_AM(mode) | 1;
		sdram0_b3cr = (TotalSize>>2) * 3 | SDRAM0_BXCR_SZ(bank_size) | SDRAM0_BXCR_AM(mode) | 1;
	}
	else{ /* else single-sided DIMM => SDRAM bank 0 and bank 2 are valid */
		printf("single-sided DIMM\n");
		sdram0_b0cr = (TotalSize>>1) * 0 | SDRAM0_BXCR_SZ(bank_size) | SDRAM0_BXCR_AM(mode) | 1;
		sdram0_b2cr = (TotalSize>>1) * 1 | SDRAM0_BXCR_SZ(bank_size) | SDRAM0_BXCR_AM(mode) | 1;

		sdram0_b1cr = 0;
		sdram0_b3cr = 0;
	}
    
	/* 
	 *  enable sdram controller DCE=1
	 *  enable burst read prefetch to 16 bytes BRPF=1
	 *  leave other functions off
	 */
	sdram0_cfg = SDRAM0_CFG_DCE(1) | SDRAM0_CFG_BRPF(1);



#ifdef VERBOSE_SDRAM_REPORT
	printf("sdram0_pmit %x\n",sdram0_pmit);

	printf("sdram0_besr0 %x\n",sdram0_besr0);
	printf("sdram0_besr1 %x\n",sdram0_besr1);
	printf("sdram0_ecccfg %x\n",sdram0_ecccfg);
	printf("sdram0_eccesr %x\n",sdram0_eccesr);

	printf("sdram0_rtr %x\n",sdram0_rtr);
	printf("sdram0_tr %x\n",sdram0_tr);

	printf("sdram0_b0cr %x\n",sdram0_b0cr);
	printf("sdram0_b1cr %x\n",sdram0_b1cr);
	printf("sdram0_b2cr %x\n",sdram0_b2cr);
	printf("sdram0_b3cr %x\n",sdram0_b3cr);

	printf("sdram0_cfg %x\n",sdram0_cfg);
#endif


#define mtsdram0(reg, data)  mtdcr(memcfga,reg);mtdcr(memcfgd,data)

	mtsdram0( mem_besra , sdram0_besr0 );
	mtsdram0( mem_besrb , sdram0_besr1 );
	mtsdram0( mem_rtr   , sdram0_rtr );
	mtsdram0( mem_pmit  , sdram0_pmit );
	mtsdram0( mem_mb0cf , sdram0_b0cr );
	mtsdram0( mem_mb1cf , sdram0_b1cr );
	mtsdram0( mem_mb2cf , sdram0_b2cr );
	mtsdram0( mem_mb3cf , sdram0_b3cr );
	mtsdram0( mem_sdtr1 , sdram0_tr );
	mtsdram0( mem_ecccf , sdram0_ecccfg );
	mtsdram0( mem_eccerr, sdram0_eccesr );

	/* SDRAM have a power on delay,  500 micro should do */
	udelay(500);
	mtsdram0( mem_mcopt1, sdram0_cfg );


	/* kernel 2.4.2 from mvista has a bug with memory over 128MB */
#ifdef MVISTA_MEM_BUG
	if (TotalSize > 128*1024*1024 )
		TotalSize=128*1024*1024;
#endif	
	return (TotalSize);
}

#endif /* CONFIG_DIMM_EEPROM */
