2008-10-29 05:51:07 +01:00
|
|
|
/*
|
|
|
|
* This file is part of the coreboot project.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2007-2008 coresystems GmbH
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2017-01-10 14:26:23 +01:00
|
|
|
#include <console/console.h>
|
2019-03-03 07:01:05 +01:00
|
|
|
#include <device/mmio.h>
|
2008-10-29 05:51:07 +01:00
|
|
|
#include "raminit.h"
|
2017-01-10 14:26:23 +01:00
|
|
|
#include "i945.h"
|
2008-10-29 05:51:07 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* sample the strobes signal
|
|
|
|
*/
|
|
|
|
static u32 sample_strobes(int channel_offset, struct sys_info *sysinfo)
|
|
|
|
{
|
|
|
|
u32 reg32, addr;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
MCHBAR32(C0DRC1 + channel_offset) |= (1 << 6);
|
2009-07-21 23:44:24 +02:00
|
|
|
|
2008-10-29 05:51:07 +01:00
|
|
|
MCHBAR32(C0DRC1 + channel_offset) &= ~(1 << 6);
|
|
|
|
|
|
|
|
addr = 0;
|
|
|
|
|
|
|
|
if (channel_offset != 0) { /* must be dual channel */
|
2017-03-09 11:30:23 +01:00
|
|
|
if (sysinfo->interleaved == 1)
|
2008-10-29 05:51:07 +01:00
|
|
|
addr |= (1 << 6);
|
2017-03-09 11:30:23 +01:00
|
|
|
else
|
2008-10-29 05:51:07 +01:00
|
|
|
addr = ((u32)MCHBAR8(C0DRB3)) << 25;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 28; i++) {
|
2014-12-25 03:43:20 +01:00
|
|
|
read32((void *)addr);
|
|
|
|
read32((void *)(addr + 0x80));
|
2008-10-29 05:51:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
reg32 = MCHBAR32(RCVENMT);
|
2017-03-09 11:30:23 +01:00
|
|
|
if (channel_offset == 0)
|
2008-10-29 05:51:07 +01:00
|
|
|
reg32 = reg32 << 2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* [19] = 1: all bits are high
|
|
|
|
* [18] = 1: all bits are low
|
|
|
|
* [19:18] = 00: bits are mixed high, low
|
|
|
|
*/
|
|
|
|
return reg32;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function sets receive enable coarse and medium timing parameters
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void set_receive_enable(int channel_offset, u8 medium, u8 coarse)
|
|
|
|
{
|
|
|
|
u32 reg32;
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, " set_receive_enable() medium=0x%x, coarse=0x%x\n", medium, coarse);
|
2009-07-21 23:44:24 +02:00
|
|
|
|
2008-10-29 05:51:07 +01:00
|
|
|
reg32 = MCHBAR32(C0DRT1 + channel_offset);
|
|
|
|
reg32 &= 0xf0ffffff;
|
|
|
|
reg32 |= ((u32)coarse & 0x0f) << 24;
|
|
|
|
MCHBAR32(C0DRT1 + channel_offset) = reg32;
|
2009-07-21 23:44:24 +02:00
|
|
|
|
|
|
|
/* This should never happen: */
|
2008-10-29 05:51:07 +01:00
|
|
|
if (coarse > 0x0f)
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "set_receive_enable: coarse overflow: 0x%02x.\n", coarse);
|
2008-10-29 05:51:07 +01:00
|
|
|
|
|
|
|
/* medium control
|
|
|
|
*
|
|
|
|
* 00 - 1/4 clock
|
|
|
|
* 01 - 1/2 clock
|
|
|
|
* 10 - 3/4 clock
|
|
|
|
* 11 - 1 clock
|
|
|
|
*/
|
|
|
|
|
|
|
|
reg32 = MCHBAR32(RCVENMT);
|
|
|
|
if (!channel_offset) {
|
2009-07-21 23:44:24 +02:00
|
|
|
/* Channel 0 */
|
2008-10-29 05:51:07 +01:00
|
|
|
reg32 &= ~(3 << 2);
|
|
|
|
reg32 |= medium << 2;
|
|
|
|
} else {
|
2009-07-21 23:44:24 +02:00
|
|
|
/* Channel 1 */
|
2008-10-29 05:51:07 +01:00
|
|
|
reg32 &= ~(3 << 0);
|
|
|
|
reg32 |= medium;
|
|
|
|
}
|
|
|
|
MCHBAR32(RCVENMT) = reg32;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-03-09 11:30:23 +01:00
|
|
|
static int normalize(int channel_offset, u8 *mediumcoarse, u8 *fine)
|
2008-10-29 05:51:07 +01:00
|
|
|
{
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, " normalize()\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
|
|
|
|
if (*fine < 0x80)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*fine -= 0x80;
|
|
|
|
*mediumcoarse += 1;
|
|
|
|
|
|
|
|
if (*mediumcoarse >= 0x40) {
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "Normalize Error\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
|
|
|
|
MCHBAR8(C0WL0REOST + channel_offset) = *fine;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-09 11:30:23 +01:00
|
|
|
static int find_preamble(int channel_offset, u8 *mediumcoarse,
|
2008-10-29 05:51:07 +01:00
|
|
|
struct sys_info *sysinfo)
|
|
|
|
{
|
|
|
|
/* find start of the data phase */
|
|
|
|
u32 reg32;
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, " find_preamble()\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
|
|
|
|
do {
|
|
|
|
if (*mediumcoarse < 4) {
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "No Preamble found.\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*mediumcoarse -= 4;
|
|
|
|
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
|
|
|
|
reg32 = sample_strobes(channel_offset, sysinfo);
|
|
|
|
|
2009-07-21 23:44:24 +02:00
|
|
|
} while (reg32 & (1 << 19));
|
2008-10-29 05:51:07 +01:00
|
|
|
|
2009-07-21 23:44:24 +02:00
|
|
|
if (!(reg32 & (1 << 18))) {
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "No Preamble found (neither high nor low).\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* add a quarter clock to the current receive enable settings
|
|
|
|
*/
|
|
|
|
|
2017-03-09 11:30:23 +01:00
|
|
|
static int add_quarter_clock(int channel_offset, u8 *mediumcoarse, u8 *fine)
|
2008-10-29 05:51:07 +01:00
|
|
|
{
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, " add_quarter_clock() mediumcoarse=%02x fine=%02x\n",
|
2008-10-29 05:51:07 +01:00
|
|
|
*mediumcoarse, *fine);
|
|
|
|
if (*fine >= 0x80) {
|
|
|
|
*fine -= 0x80;
|
|
|
|
|
|
|
|
*mediumcoarse += 2;
|
|
|
|
if (*mediumcoarse >= 0x40) {
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "clocks at max.\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
} else {
|
|
|
|
*fine += 0x80;
|
|
|
|
}
|
|
|
|
|
|
|
|
MCHBAR8(C0WL0REOST + channel_offset) = *fine;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-09 11:30:23 +01:00
|
|
|
static int find_strobes_low(int channel_offset, u8 *mediumcoarse, u8 *fine,
|
2008-10-29 05:51:07 +01:00
|
|
|
struct sys_info *sysinfo)
|
|
|
|
{
|
|
|
|
u32 rcvenmt;
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, " find_strobes_low()\n");
|
2009-07-21 23:44:24 +02:00
|
|
|
|
2008-10-29 05:51:07 +01:00
|
|
|
for (;;) {
|
|
|
|
MCHBAR8(C0WL0REOST + channel_offset) = *fine;
|
|
|
|
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
|
|
|
|
rcvenmt = sample_strobes(channel_offset, sysinfo);
|
|
|
|
|
|
|
|
if (((rcvenmt & (1 << 18)) != 0))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*fine -= 0x80;
|
|
|
|
if (*fine == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*mediumcoarse -= 2;
|
2009-07-21 23:44:24 +02:00
|
|
|
if (*mediumcoarse < 0xfe)
|
2008-10-29 05:51:07 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "Could not find low strobe\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-09 11:30:23 +01:00
|
|
|
static int find_strobes_edge(int channel_offset, u8 *mediumcoarse, u8 *fine,
|
2008-10-29 05:51:07 +01:00
|
|
|
struct sys_info *sysinfo)
|
|
|
|
{
|
2009-07-21 23:44:24 +02:00
|
|
|
|
2008-10-29 05:51:07 +01:00
|
|
|
int counter;
|
|
|
|
u32 rcvenmt;
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, " find_strobes_edge()\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
|
|
|
|
counter = 8;
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
MCHBAR8(C0WL0REOST + channel_offset) = *fine;
|
|
|
|
rcvenmt = sample_strobes(channel_offset, sysinfo);
|
|
|
|
|
|
|
|
if ((rcvenmt & (1 << 19)) == 0) {
|
|
|
|
counter = 8;
|
|
|
|
} else {
|
|
|
|
counter--;
|
|
|
|
if (!counter)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*fine = *fine + 1;
|
|
|
|
if (*fine < 0xf8) {
|
|
|
|
if (*fine & (1 << 3)) {
|
|
|
|
*fine &= ~(1 << 3);
|
|
|
|
*fine += 0x10;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2009-07-21 23:44:24 +02:00
|
|
|
|
2008-10-29 05:51:07 +01:00
|
|
|
*fine = 0;
|
|
|
|
*mediumcoarse += 2;
|
|
|
|
if (*mediumcoarse <= 0x40) {
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "Could not find rising edge.\n");
|
2008-10-29 05:51:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2009-07-21 23:44:24 +02:00
|
|
|
|
2008-10-29 05:51:07 +01:00
|
|
|
*fine -= 7;
|
2009-07-21 23:44:24 +02:00
|
|
|
if (*fine >= 0xf9) {
|
2008-10-29 05:51:07 +01:00
|
|
|
*mediumcoarse -= 2;
|
|
|
|
set_receive_enable(channel_offset, *mediumcoarse & 3,
|
|
|
|
*mediumcoarse >> 2);
|
|
|
|
}
|
|
|
|
|
2009-07-21 23:44:24 +02:00
|
|
|
*fine &= ~(1 << 3);
|
2008-10-29 05:51:07 +01:00
|
|
|
MCHBAR8(C0WL0REOST + channel_offset) = *fine;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-07-21 23:44:24 +02:00
|
|
|
* Here we use a trick. The RCVEN channel 0 registers are all at an
|
2008-10-29 05:51:07 +01:00
|
|
|
* offset of 0x80 to the channel 0 registers. We don't want to waste
|
2016-08-23 21:29:48 +02:00
|
|
|
* a lot of if ()s so let's just pass 0 or 0x80 for the channel offset.
|
2008-10-29 05:51:07 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
static int receive_enable_autoconfig(int channel_offset,
|
|
|
|
struct sys_info *sysinfo)
|
|
|
|
{
|
|
|
|
u8 mediumcoarse;
|
|
|
|
u8 fine;
|
|
|
|
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_SPEW, "receive_enable_autoconfig() for channel %d\n",
|
2008-10-29 05:51:07 +01:00
|
|
|
channel_offset ? 1 : 0);
|
|
|
|
|
|
|
|
/* Set initial values */
|
2009-07-21 23:44:24 +02:00
|
|
|
mediumcoarse = (sysinfo->cas << 2) | 3;
|
2008-10-29 05:51:07 +01:00
|
|
|
fine = 0;
|
|
|
|
|
|
|
|
if (find_strobes_low(channel_offset, &mediumcoarse, &fine, sysinfo))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (find_strobes_edge(channel_offset, &mediumcoarse, &fine, sysinfo))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (add_quarter_clock(channel_offset, &mediumcoarse, &fine))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (find_preamble(channel_offset, &mediumcoarse, sysinfo))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (add_quarter_clock(channel_offset, &mediumcoarse, &fine))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (normalize(channel_offset, &mediumcoarse, &fine))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* This is a debug check to see if the rcven code is fully working.
|
|
|
|
* It can be removed when the output message is not printed anymore
|
|
|
|
*/
|
2017-03-09 11:30:23 +01:00
|
|
|
if (MCHBAR8(C0WL0REOST + channel_offset) == 0)
|
2010-03-22 12:42:32 +01:00
|
|
|
printk(BIOS_DEBUG, "Weird. No C%sWL0REOST\n", channel_offset?"1":"0");
|
2008-10-29 05:51:07 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void receive_enable_adjust(struct sys_info *sysinfo)
|
|
|
|
{
|
|
|
|
/* Is channel 0 populated? */
|
|
|
|
if (sysinfo->dimm[0] != SYSINFO_DIMM_NOT_POPULATED
|
|
|
|
|| sysinfo->dimm[1] != SYSINFO_DIMM_NOT_POPULATED)
|
|
|
|
if (receive_enable_autoconfig(0, sysinfo))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Is channel 1 populated? */
|
|
|
|
if (sysinfo->dimm[2] != SYSINFO_DIMM_NOT_POPULATED
|
|
|
|
|| sysinfo->dimm[3] != SYSINFO_DIMM_NOT_POPULATED)
|
|
|
|
if (receive_enable_autoconfig(0x80, sysinfo))
|
|
|
|
return;
|
|
|
|
}
|