[LinuxBIOS] i440bx RAM initialization code (SPD)

Carl-Daniel Hailfinger c-d.hailfinger.devel.2006 at gmx.net
Thu Jun 14 19:08:22 CEST 2007


What's the status of the code below?

Carl-Daniel

On 30.05.2007 04:36, Alfred Wanga wrote:
> Apologies everyone... I meant to tweak it after the feedback, but never
> could seem to get around to it. Here is the original patch again.
> 
> Signed-off-by: Alfred Wanga <awanga at gmail.com>
> 
> 
> Uwe Hermann wrote:
>> Hi,
>>
>> thanks for your patch. However, please sign-off all patches you submit,
>> otherwise we cannot commit them.
>>
>> http://linuxbios.org/Development_Guidelines#Sign-off_Procedure
>>
>> Please re-send this patch with a sign-off.
>>
>>
>> On Mon, Apr 30, 2007 at 10:47:42AM -0400, Alfred Wanga wrote:
>>> * PMCR - When should it be updated?
>>> Looking at the assembly, it seems as though it's ok to just set the
>>> final value before the RAM refresh code instead of waiting until
>>> afterwards, but I don't know for sure, so I left the original code
>>> alone.
>>
>> Yeah, I'm not sure either. Will look into that later...
>>
>>
>>> * sdram_enable delays
>>> I changed all the RAM timing delays (tRP, tRC, tMRD) to 1usec, since
>>> the timings are on the order of hundreds of nanoseconds (according to
>>> SPD values) and the smallest resolution timer available seems to be
>>> udelay() anyway. It should work for any SDRAM, and shaves a few
>>> milliseconds off previous code.
>>
>> Yep, when everything is working fine (or maybe even before) we'll lower
>> the delays. I set them quite high to make sure that it's definately not
>> a too short delay which is causing problems...
>>
>> If you want you can submit an extra patch with just the delay-fixes. I'll
>> test that on my hardware and commit if it still works with shorter
>> delays.
>>
>>
>>> Anyway, enough verbosity... take a look and see what you think.
>>> Unfortunately, besides testing the SPD code on memory dumps, I haven't
>>> tested the image.
>>
>> It doesn't compile, but that's not a problem in your code, but rather a
>> limitation of romcc ("too few registers").
>>
>> I'll try to move the 440BX code over to Cache as RAM, that'll be
>> needed for
>> LinuxBIOSv3 anyways. Expect a patch soonish...
>>
>>
>> After a few quick tests (after ripping out lots of code and debug
>> statements so that romcc compiles the code) it didn't seem to work on
>> my board. That may have lots of reasons (after all, I had to remove lots
>> of code and replace it with hardcoded values), but I'll look into it
>> in more detail.
>>
>>
>> Anyway, on the long run I don't want to merge this code as is (not a
>> pure translation of the v1 assembler code), but rather create a generic
>> framework for the RAM init on 440BX-ish chipsets.
>>
>> It should be possible to share most of the code for, e.g.
>>  - 440BX/ZX/FX/...
>>  - 430TX/...
>>  - and maybe more such (probably quite similar) chipsets.
>>
>> Thus, no spd_set_pgpol() etc., but more generic code (as far as
>> possible).
>>
>> But please re-send your patch with a sign-off, I think we can merge at
>> least some parts of it.
>>
>>
>>> Index: src/northbridge/intel/i440bx/raminit.c
>>> ===================================================================
>>> --- src/northbridge/intel/i440bx/raminit.c    (revision 2621)
>>> +++ src/northbridge/intel/i440bx/raminit.c    (working copy)
>>> @@ -377,7 +377,348 @@
>>>  DIMM-independant configuration functions.
>>>  -----------------------------------------------------------------------------*/
>>>
>>>  
>>> +/* Performs Bit Scan Right (MSB) Function (for SPD code) */
>>> +static inline uint8_t bsr(uint16_t val)
>>> +{
>>> +#if ASSEMBLY
>>> +    __asm__ __volatile__ ("bsr %1, %%ax;" : "=a"(val) : "a"(val));
>>
>> No assembler, please. Apart from the very early stuff which _has_ to be
>> written in assembler, LinuxBIOS should be written completely in C.
>>
>>
>>>      /* 2. Precharge all. Wait tRP. */
>>>      PRINT_DEBUG("RAM Enable 2: Precharge all\r\n");
>>> -    do_ram_command(ctrl, RAM_COMMAND_PRECHARGE, 0);
>>> -    mdelay(10);
>>> +    do_ram_command(ctrl, RAM_COMMAND_PRECHARGE, 0x2000);
>>
>> Why 0x2000 here?
>>
>>
>> Uwe.
> 
> 
> ------------------------------------------------------------------------
> 
> Index: src/northbridge/intel/i440bx/raminit.c
> ===================================================================
> --- src/northbridge/intel/i440bx/raminit.c	(revision 2621)
> +++ src/northbridge/intel/i440bx/raminit.c	(working copy)
> @@ -377,7 +377,348 @@
>  DIMM-independant configuration functions.
>  -----------------------------------------------------------------------------*/
>  
> +/* Performs Bit Scan Right (MSB) Function (for SPD code) */
> +static inline uint8_t bsr(uint16_t val)
> +{
> +#if ASSEMBLY
> +	__asm__ __volatile__ ("bsr %1, %%ax;" : "=a"(val) : "a"(val));
> +
> +	return (uint8_t)val;
> +#else
> +	uint8_t i;
> +
> +	for (i = 15; i > 0; i--)
> +	{
> +		if (val & 0x8000) break;
> +		val <<= 1;
> +	}
> +	return i;
> +#endif
> +}
> +
> +
>  /**
> + * Set the DRAM Row Boundary Registers for all DIMMs.
> + *
> + * @param Memory controller
> + */
> +static void spd_set_drb(const struct mem_controller *ctrl)
> +{
> +	uint8_t i, size = 0, addr = 0;
> +	uint16_t tmp;
> +
> +	for( i = 0; i < DIMM_SOCKETS; i++ )
> +	{
> +		size = (smbus_read_byte(ctrl->channel0[i], SPD_NUM_ROWS) & 0xf);
> +		size += (smbus_read_byte(ctrl->channel0[i], 
> +			SPD_NUM_COLUMNS) & 0xf);
> +
> +		/* Skip calculations if SPD returns undefined data */
> +		if (size)
> +		{
> +			tmp = smbus_read_byte(ctrl->channel0[i], 
> +				SPD_NUM_BANKS_PER_SDRAM) & 0xff;
> +			size += bsr(tmp);
> +
> +			/* Get the module data width and convert it 
> +			 * to a power of two */
> +			tmp = (smbus_read_byte(ctrl->channel0[i], 
> +				SPD_MODULE_DATA_WIDTH_MSB) << 8) | 
> +				(smbus_read_byte(ctrl->channel0[i], 
> +				 SPD_MODULE_DATA_WIDTH_LSB) & 0xff);
> +			size += bsr(tmp);
> +
> +			/* Now we have ram size as a power of two (less 1) */
> +			/* 2^23 = 8MBit: seems like it should be MBits not MB */
> +			size -= (23 - 1); /* Make it multiples of 8MBits */
> +		}
> +		addr += (size << 1);
> +
> +		PRINT_DEBUG("DRB");
> +		PRINT_DEBUG_HEX8(i<<1);
> +		PRINT_DEBUG(": ");
> +		PRINT_DEBUG_HEX8(addr);
> +		PRINT_DEBUG("\r\n");
> +
> +		/* Ignore the dimm if it is over 2GB */
> +		if (size >= 8)
> +			pci_write_config8(ctrl->d0, DRB+(i<<1), 0);
> +		else
> +			pci_write_config8(ctrl->d0, DRB+(i<<1), addr);
> +
> +
> +		/* side two */
> +		if (smbus_read_byte(ctrl->channel0[i], SPD_NUM_DIMM_BANKS) > 1)
> +		{
> +
> +#if 0			/* Asymmetric code doesn't seem to work */
> +			size = (smbus_read_byte(ctrl->channel0[i], 
> +				SPD_NUM_ROWS) & 0xf0) >> 4;
> +			size += (smbus_read_byte(ctrl->channel0[i], 
> +				SPD_NUM_COLUMNS) & 0xf0) >> 4;
> +
> +			if (size)
> +			{
> +				tmp = smbus_read_byte(ctrl->channel0[i], 
> +					SPD_NUM_BANKS_PER_SDRAM) & 0xff;
> +				size += bsr(tmp);
> +
> +				/* Get the module data width and convert it 
> +				 * to a power of two */
> +				tmp = (smbus_read_byte(ctrl->channel0[i], 
> +					SPD_MODULE_DATA_WIDTH_MSB) << 8) |
> +					(smbus_read_byte(ctrl->channel0[i], 
> +					 SPD_MODULE_DATA_WIDTH_LSB) & 0x1f);
> +				size += bsr(tmp);
> +
> +				/* Make it multiples of 8MBits */
> +				size -= (23 - 1);
> +			}
> +#endif
> +			addr += (size << 1);
> +
> +			PRINT_DEBUG("DRB");
> +			PRINT_DEBUG_HEX8((i<<1)+1);
> +			PRINT_DEBUG(": ");
> +			PRINT_DEBUG_HEX8(addr);
> +			PRINT_DEBUG("\r\n");
> +
> +			/* Ignore the dimm if it is over 2GB (possible?) */
> +			if (size >= 8)
> +				pci_write_config8(ctrl->d0, DRB+(i<<1)+1, 0);
> +			else
> +				pci_write_config8(ctrl->d0, DRB+(i<<1)+1, addr);
> +		}
> +
> +	}
> +}
> +
> +/**
> + * Set the DRAM Control Register.
> + *
> + * @param Memory controller
> + */
> +static void spd_set_dramc(const struct mem_controller *ctrl)
> +{
> +	uint8_t i, memid;
> +	uint8_t dram_reg = 0;
> +
> +	/* Auto detect if RAM is registered or not. */
> +	/* The DRAMC register also controls the refresh rate but we can't
> +	 * set that here because we must leave refresh disabled.
> +	 */
> +
> +	/* Find the first dimm and assume the rest are the same (dangerous?) */
> +	for (i = 0; i < DIMM_SOCKETS; i++) 
> +	{
> +		/* This was changed from SPD_MODULE_ATTRIBUTES because
> +		 * it can be equal to zero for an existing DIMM */
> +		memid = smbus_read_byte(ctrl->channel0[i], SPD_MEMORY_TYPE);
> +
> +		if (memid == SPD_MEMORY_TYPE_SDRAM) break;
> +	}
> +
> +	if (memid != SPD_MEMORY_TYPE_SDRAM)
> +	{
> +		/* no SDRAM memory! */
> +		die("No memory found!");
> +	}
> +
> +	memid = smbus_read_byte(ctrl->channel0[i], SPD_MODULE_ATTRIBUTES);
> +
> +	if (memid & 0x12)
> +		dram_reg |= 0x10; /* registered DRAM */
> +	else
> +		dram_reg |= 0x08; /* unregistered DRAM */
> +
> +	/* Write DRAM control register (without refresh) */
> +	PRINT_DEBUG("DRAMC: ");
> +	PRINT_DEBUG_HEX8(dram_reg);
> +	PRINT_DEBUG("\r\n");
> +	pci_write_config8(ctrl->d0, DRAMC, dram_reg);
> +}
> +
> +/**
> + * Set the SDRAM Row Page Size Register.
> + *
> + * @param Memory controller
> + */
> +static void spd_set_rps(const struct mem_controller *ctrl)
> +{
> +	uint16_t page, reg = 0;
> +	uint8_t i, cnt = 0;
> +
> +	/* The RPS register holds the size of a "page" of DRAM on each DIMM */
> +	/* default all page sizes to 2KB */
> +	for (i = 0; i < DIMM_SOCKETS; i++ )
> +	{
> +		page = (smbus_read_byte(ctrl->channel0[i], 
> +			SPD_NUM_COLUMNS) & 0xf);
> +
> +		/* FIXME: do something with page sizes greater than 8KB!! */
> +		/* This workaround seems to generate valid values */
> +		if (page > 8) page = 8;
> +
> +		if (page <= 8)
> +		{
> +			page <<= cnt;
> +			reg |= page & 0xf;
> +
> +			/* Only handling the symmetric case */
> +			if ( smbus_read_byte(ctrl->channel0[i], 
> +				SPD_NUM_DIMM_BANKS) > 1)
> +				reg |= ((page & 0xf) << 4);
> +		}
> +		cnt += 4;
> +	}
> +
> +	/* Next block is for Ron's attempt to get registered to work.
> +	 * We have just verified that we have to have this code. It appears that
> +	 * the registered SDRAMs do indeed set the RPS wrong. Sheesh.
> +	 *
> +	 * We have verified that for registered DRAM the values are
> +	 * 1/2 the size they should be. So we test for registered
> +	 * and then double the sizes if needed.
> +	 */
> +
> +	if (pci_read_config8(ctrl->d0, DRAMC) & 0x10)
> +	{
> +		/* BIOS makes weird page size for registered!
> +		 * what we have found is you need to set the EVEN banks to
> +		 * twice the size. Fortunately there is a very easy way to
> +		 * do this. First, read the WORD value of the RPS register
> +		 * and double the size of the EVEN banks we only need to add 1
> +		 * because the size is log2
> +        	 */
> +		reg += 0x1111;
> +	}
> +
> +	/* now write that final value into RPS register */
> +	PRINT_DEBUG("RPS: ");
> +	PRINT_DEBUG_HEX16(reg);
> +	PRINT_DEBUG("\r\n");
> +	pci_write_config16(ctrl->d0, RPS, reg);
> +}
> +
> +/**
> + * Set the Paging Policy Register.
> + *
> + * @param Memory controller
> + */
> +static void spd_set_pgpol(const struct mem_controller *ctrl)
> +{
> +	uint16_t policy = 0;
> +	uint8_t i, cnt = 0;
> +
> +	/* The PGPOL register stores the number of logical banks per DIMM,
> +	 * and number of clocks the DRAM controller waits in the idle
> +	 * state.
> +	 */
> +	for (i = 0; i < DIMM_SOCKETS; i++ )
> +	{
> +		if (smbus_read_byte(ctrl->channel0[i], 
> +			SPD_NUM_BANKS_PER_SDRAM) >= 4)
> +		{
> +			policy |= (0x1 << cnt);
> +
> +			/* for now only handle the symmtrical case */
> +			if (smbus_read_byte(ctrl->channel0[i], 
> +				SPD_NUM_DIMM_BANKS) > 1)
> +				policy |= (0x2 << cnt);
> +		}
> +		cnt += 2;
> +	}
> +
> +	/* 32 clocks DRAM idle time (change back to 0 after MRS?) */
> +	policy = (policy << 8) | 0x7;
> +
> +	PRINT_DEBUG("PGPOL: ");
> +	PRINT_DEBUG_HEX16(policy);
> +	PRINT_DEBUG("\r\n");
> +	pci_write_config16(ctrl->d0, PGPOL, policy);
> +}
> +
> +/**
> + * Set the SDRAM Control Register.
> + *
> + * @param Memory controller
> + */
> +static void spd_set_sdramc(const struct mem_controller *ctrl)
> +{
> +	uint8_t i;
> +	uint16_t reg = 0x0100;
> +
> +	for (i = 0; i < DIMM_SOCKETS; i++ )
> +	{
> +		/* Default settings are OK if only CAS=3 is supported */
> +		if (smbus_read_byte(ctrl->channel0[i], 
> +			SPD_ACCEPTABLE_CAS_LATENCIES) & 0x2)
> +		{
> +			PRINT_DEBUG("DIMM bank ");
> +			PRINT_DEBUG_HEX8(i);
> +			PRINT_DEBUG(" supports CAS=2\r\n");
> +			pci_write_config8(ctrl->d0, SDRAMC, reg | 0x4);
> +			break;
> +		}
> +	}
> +}
> +
> +/**
> + * Set the NBX Configuration Register.
> + *
> + * @param Memory controller
> + */
> +static inline void spd_set_nbxcfg(const struct mem_controller *ctrl)
> +{
> +	uint8_t i, cnt = 24;
> +	uint32_t reg;
> +
> +	/* Say all dimms have no ECC support */
> +	reg = pci_read_config32(ctrl->d0, NBXCFG) | 0xff000000;
> +
> +	for (i = 0; i < DIMM_SOCKETS; i++)
> +	{
> +		/* Module error correction type: 
> +		 * 0 == None, 1 == Parity, 2 == ECC */
> +		if (smbus_read_byte(ctrl->channel0[i], 
> +			SPD_DIMM_CONFIG_TYPE) & 0x2)
> +		{
> +			reg ^= (0x1 << cnt);
> +
> +			/* TODO: Set Data Integrity Mode for ECC Mode */
> +			/* Is this right? What about other EC modes? */
> +			/* reg |= 0x10; */
> +
> +			if (smbus_read_byte(ctrl->channel0[i], 
> +				SPD_NUM_DIMM_BANKS) > 1)
> +			{
> +				/* only the symmetric case is possible */
> +				reg ^= (0x2 << cnt);
> +			}
> +		}
> +		cnt += 2;
> +	}
> +
> +	/* set correct DRAM bus speed */
> +	for (i = 0; i < DIMM_SOCKETS; i++)
> +	{
> +		/* set to 66MHz if at least one DIMM is 66MHz only */
> +		if (smbus_read_byte(ctrl->channel0[i], 
> +			SPD_INTEL_SPEC_FOR_FREQUENCY) == 0x66)
> +		{
> +			/* TODO: need to handle?: 66Mhz->CL2 / 100MHz->CL3 */
> +			reg |= 0x2000;
> +			break;
> +		}
> +	}
> +
> +	PRINT_DEBUG("NBXCFG: ");
> +	PRINT_DEBUG_HEX32(reg);
> +	PRINT_DEBUG("\r\n");
> +	pci_write_config32(ctrl->d0, NBXCFG, reg);
> +}
> +
> +/**
>   * TODO.
>   *
>   * @param Memory controller
> @@ -387,6 +728,9 @@
>  	int i, value;
>  	uint8_t reg;
>  
> +	/* Set DRAM idle time to 0 after MRS (needed?) */
> +	pci_write_config8(ctrl->d0, PGPOL, 0);
> +
>  	reg = pci_read_config8(ctrl->d0, DRAMC);
>  
>  	for (i = 0; i < DIMM_SOCKETS; i++) {
> @@ -446,32 +790,25 @@
>   */
>  static void sdram_set_spd_registers(const struct mem_controller *ctrl)
>  {
> -	/* TODO: Don't hardcode the values here, get info via SPD. */
> +	PRINT_DEBUG("Reading SPD Registers...\r\n");
>  
> -	/* TODO: Set DRB0-DRB7. */
> -	pci_write_config8(ctrl->d0, DRB0, 0x08);
> -	pci_write_config8(ctrl->d0, DRB1, 0x08);
> -	pci_write_config8(ctrl->d0, DRB2, 0x08);
> -	pci_write_config8(ctrl->d0, DRB3, 0x08);
> -	pci_write_config8(ctrl->d0, DRB4, 0x08);
> -	pci_write_config8(ctrl->d0, DRB5, 0x08);
> -	pci_write_config8(ctrl->d0, DRB6, 0x08);
> -	pci_write_config8(ctrl->d0, DRB7, 0x08);
> +	/* Set DRB0-DRB7. */
> +	spd_set_drb(ctrl);
>  
> -	/* TODO: Set DRAMC. Don't enable refresh for now. */
> -	pci_write_config8(ctrl->d0, DRAMC, 0x08);
> +	/* Set DRAMC. Don't enable refresh for now. */
> +	spd_set_dramc(ctrl);
>  
> -	/* TODO: Set RPS. */
> -	pci_write_config16(ctrl->d0, RPS, 0x0001);
> +	/* Set RPS. */
> +	spd_set_rps(ctrl);
>  
> -	/* TODO: Set SDRAMC. */
> -	// pci_write_config16(ctrl->d0, SDRAMC, 0x0000);
> +	/* Set SDRAMC. */
> +	spd_set_sdramc(ctrl);
>  
> -	/* TODO: Set PGPOL. */
> -	pci_write_config16(ctrl->d0, PGPOL, 0x0107);
> +	/* Set PGPOL. */
> +	spd_set_pgpol(ctrl);
>  
> -	/* TODO: Set NBXCFG. */
> -	// pci_write_config32(ctrl->d0, NBXCFG, 0x0100220c);
> +	/* Set NBXCFG. */
> +	spd_set_nbxcfg(ctrl);
>  
>  	/* TODO: Set PMCR? */
>  	// pci_write_config8(ctrl->d0, PMCR, 0x14);
> @@ -495,45 +832,51 @@
>  	int i;
>  
>  	/* TODO: Use a delay here? Needed? */
> -	mdelay(200);
> +	/* Wait at least 1msec after device deselect (when is that?) */
> +	mdelay(1);
>  
> -	/* TODO: How long should the delays be? Fix later. */
> +	/* RAM Timings (tRP, tRC, tMRD) from SPD are on the order or 
> +	 * nanoseconds (max=255ns) so 1usec should be more than enough 
> +	 * time for any given SDRAM. */
>  
>  	/* 1. Apply NOP. */
>  	PRINT_DEBUG("RAM Enable 1: Apply NOP\r\n");
>  	do_ram_command(ctrl, RAM_COMMAND_NOP, 0);
> -	mdelay(10);
> +	udelay(200); /* minimum pause of 200usec after the NOP */
>  
>  	/* 2. Precharge all. Wait tRP. */
>  	PRINT_DEBUG("RAM Enable 2: Precharge all\r\n");
> -	do_ram_command(ctrl, RAM_COMMAND_PRECHARGE, 0);
> -	mdelay(10);
> +	do_ram_command(ctrl, RAM_COMMAND_PRECHARGE, 0x2000);
> +	udelay(1);
>  
>  	/* 3. Perform 8 refresh cycles. Wait tRC each time. */
>  	PRINT_DEBUG("RAM Enable 3: CBR\r\n");
>  	for (i = 0; i < 8; i++) {
>  		do_ram_command(ctrl, RAM_COMMAND_CBR, 0);
> -		mdelay(10);
> +		udelay(1);
>  	}
>  
>  	/* 4. Mode register set. Wait two memory cycles. */
>  	PRINT_DEBUG("RAM Enable 4: Mode register set\r\n");
> -	do_ram_command(ctrl, RAM_COMMAND_MRS, 0x1d0);
> -	// TODO: Is 0x1d0 correct?
> -	// do_ram_command(ctrl, RAM_COMMAND_MRS, 0x1d0000);
> -	mdelay(10);
> -	mdelay(10);
> +	/* Compute MRS Opcode (Burst length = 4, Interleaved) */
> +	i = 0x2a;
> +	/* If CAS = 3, modify opcode */
> +	if (pci_read_config8(ctrl->d0, SDRAMC) & 0x4 == 0) 
> +		i |= 0x10;
> +	i <<= 3;
> +	do_ram_command(ctrl, RAM_COMMAND_MRS, i); //0x1d0
> +	udelay(1);
>  
>  	/* 5. Normal operation. */
>  	PRINT_DEBUG("RAM Enable 5: Normal operation\r\n");
>  	do_ram_command(ctrl, RAM_COMMAND_NORMAL, 0);
> -	mdelay(10);
> +	udelay(1);
>  
>  	/* 6. Finally enable refresh. */
>  	PRINT_DEBUG("RAM Enable 6: Enable refresh\r\n");
>  	// pci_write_config8(ctrl->d0, PMCR, 0x10);
>  	spd_enable_refresh(ctrl);
> -	mdelay(10);
> +	udelay(1);
>  
>  	PRINT_DEBUG("Northbridge following SDRAM init:\r\n");
>  	DUMPNORTH();
> 


-- 
http://www.hailfinger.org/





More information about the coreboot mailing list