/* * This file is part of the coreboot project. * * Copyright (C) 2007-2008 Uwe Hermann * Copyright (C) 2009 Maciej Pijanka * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "i440lx.h" /*----------------------------------------------------------------------------- Macros and definitions. -----------------------------------------------------------------------------*/ /* Uncomment this to enable debugging output. */ /* Debugging macros. */ #if CONFIG_DEBUG_RAM_SETUP #define PRINT_DEBUG(x) print_debug(x) #define PRINT_DEBUG_HEX8(x) print_debug_hex8(x) #define PRINT_DEBUG_HEX16(x) print_debug_hex16(x) #define PRINT_DEBUG_HEX32(x) print_debug_hex32(x) #define DUMPNORTH() dump_pci_device(PCI_DEV(0, 0, 0)) #else #define PRINT_DEBUG(x) #define PRINT_DEBUG_HEX8(x) #define PRINT_DEBUG_HEX16(x) #define PRINT_DEBUG_HEX32(x) #define DUMPNORTH() #endif #define NB PCI_DEV(0, 0, 0) /* DRAMXC[7:5] - DRAM extended control register (SMS). */ #define RAM_COMMAND_NORMAL 0x0 #define RAM_COMMAND_NOP 0x1 // (NOPCE) #define RAM_COMMAND_PRECHARGE 0x2 // ABPCE #define RAM_COMMAND_MRS 0x3 // MRSCE #define RAM_COMMAND_CBR 0x4 // CBRC // rest are reserved /* Table format: register, bitmask, value. */ static const long register_values[] = { // ~0x02 == bit 9 // 0x04 == bit 10 // BASE is 0x8A but we dont want bit 9 or 10 have ENABLED so 0x8C PACCFG + 1, 0x38, 0x8c, DBC, 0x00, 0xC3, DRT, 0x00, 0xFF, DRT+1, 0x00, 0xFF, DRAMC, 0x00, 0x00, /* disable refresh for now. */ DRAMT, 0x00, 0x00, PAM0, 0x00, 0x30, // everything is a mem PAM1, 0x00, 0x33, PAM2, 0x00, 0x33, PAM3, 0x00, 0x33, PAM4, 0x00, 0x33, PAM5, 0x00, 0x33, PAM6, 0x00, 0x33, /* Set the DRBs to zero for now, this will be fixed later. */ DRB0, 0x00, 0x00, DRB1, 0x00, 0x00, DRB2, 0x00, 0x00, DRB3, 0x00, 0x00, DRB4, 0x00, 0x00, DRB5, 0x00, 0x00, DRB6, 0x00, 0x00, DRB7, 0x00, 0x00, /* No memory holes. */ FDHC, 0x00, 0x00, }; /*----------------------------------------------------------------------------- SDRAM configuration functions. -----------------------------------------------------------------------------*/ /** * Send the specified RAM command to all DIMMs. * * @param command The RAM command to send to the DIMM(s). */ static void do_ram_command(u32 command) { int i, caslatency; u8 dimm_start, dimm_end; u16 reg16; u32 addr, addr_offset; /* Configure the RAM command. */ reg16 = pci_read_config16(NB, DRAMXC); reg16 &= 0xff1f; /* Clear bits 7-5. */ reg16 |= (u16) (command << 5); /* Write command into bits 7-5. */ pci_write_config16(NB, DRAMXC, reg16); /* * RAM_COMMAND_NORMAL affects only the memory controller and * doesn't need to be "sent" to the DIMMs. */ if (command == RAM_COMMAND_NORMAL) return; /* Send the RAM command to each row of memory. */ dimm_start = 0; for (i = 0; i < (DIMM_SOCKETS * 2); i++) { addr_offset = 0; caslatency = 3; /* TODO: Dynamically get CAS latency later. */ /* before translation it is * * M[02:00] Burst Length * M[03:03] Burst Type * M[06:04] Cas Latency * 000 - Reserved * 001 - Reserved * 010 - CAS 2 * 011 - CAS 3 * 100 - Reserved * 101 - Reserved * 110 - Reserved * 111 - Reserved * M[08:07] Op Mode * Must Be 00b (Defined mode) * M[09:09] Write Burst Mode * 0 - Programmed burst length * 1 - Single location access * M[11:10] Reserved * write 0 to ensure compatibility with.... */ /* seems constructed value will be right shifted by 3 bit, thus constructed value * must be left shifted by 3 * so possible formula is (caslatency <<4)|(burst_type << 1)|(burst length) * then << 3 shift to compensate shift in Memory Controller */ if (command == RAM_COMMAND_MRS) { if (caslatency == 3) addr_offset = 0x1d0; if (caslatency == 2) addr_offset = 0x150; } dimm_end = pci_read_config8(NB, DRB + i); addr = (dimm_start * 8 * 1024 * 1024) + addr_offset; if (dimm_end > dimm_start) { #if 0 PRINT_DEBUG(" Sending RAM command 0x"); PRINT_DEBUG_HEX16(reg16); PRINT_DEBUG(" to 0x"); PRINT_DEBUG_HEX32(addr); PRINT_DEBUG("\n"); #endif read32(addr); } /* Set the start of the next DIMM. */ dimm_start = dimm_end; } } /*----------------------------------------------------------------------------- DIMM-independant configuration functions. -----------------------------------------------------------------------------*/ static void spd_enable_refresh(void) { uint8_t reg; reg = pci_read_config8(NB, DRAMC); /* this chipset offer only two choices regarding refresh * refresh disabled, or refresh normal */ pci_write_config8(NB, DRAMC, reg | 0x01); reg = pci_read_config8(NB, DRAMC); PRINT_DEBUG("spd_enable_refresh: dramc = 0x"); PRINT_DEBUG_HEX8(reg); PRINT_DEBUG("\n"); } /*----------------------------------------------------------------------------- Public interface. -----------------------------------------------------------------------------*/ static void northbridge_init(void) { uint32_t reg32; reg32 = pci_read_config32(NB, APBASE); reg32 &= 0xe8000000U; pci_write_config32(NB, APBASE, reg32); #if CONFIG_DEBUG_RAM_SETUP /* * apbase dont get set still, no idea what i have doing wrong yet, * i am almost sure that somehow i set it by mistake once, but can't * repeat that. */ reg32 = pci_read_config32(NB, APBASE); PRINT_DEBUG("APBASE "); PRINT_DEBUG_HEX32(reg32); PRINT_DEBUG("\n"); #endif } /** * This routine sets RAM controller inside northbridge to known state * */ static void sdram_set_registers(void) { int i, max; /* nice banner with FSB shown? do we have * any standart policy about such things? */ #if 0 uint16_t reg16; reg16 = pci_read_config16(NB, PACCFG); printk(BIOS_DEBUG, "i82443LX Host Freq: 6%C MHz\n", (reg16 & 0x4000) ? '0' : '6'); #endif PRINT_DEBUG("Northbridge prior to SDRAM init:\n"); DUMPNORTH(); northbridge_init(); max = ARRAY_SIZE(register_values); /* Set registers as specified in the register_values[] array. */ for (i = 0; i < max; i += 3) { uint8_t reg,tmp; reg = pci_read_config8(NB, register_values[i]); reg &= register_values[i + 1]; reg |= register_values[i + 2] & ~(register_values[i + 1]); pci_write_config8(NB, register_values[i], reg); /* * i am not sure if that is needed, but was usefull * for me to confirm what got written */ #if CONFIG_DEBUG_RAM_SETUP PRINT_DEBUG(" Set register 0x"); PRINT_DEBUG_HEX8(register_values[i]); PRINT_DEBUG(" to 0x"); PRINT_DEBUG_HEX8(reg); tmp = pci_read_config8(NB, register_values[i]); PRINT_DEBUG(" readed 0x"); PRINT_DEBUG_HEX8(tmp); if (tmp == reg) { PRINT_DEBUG(" OK "); } else { PRINT_DEBUG(" FAIL "); } PRINT_DEBUG("\n"); #endif } PRINT_DEBUG("Northbridge atexit sdram set registers\n"); DUMPNORTH(); } static void sdram_set_spd_registers(void) { int i; u16 memsize = 0; for (i = 0; i < DIMM_SOCKETS; i++) { uint16_t ds = 0; // dimm size int j; /* this code skips second bank on each socket (no idea how to fix it now) */ PRINT_DEBUG("DIMM"); PRINT_DEBUG_HEX8(i); PRINT_DEBUG(" rows: "); PRINT_DEBUG_HEX8(spd_read_byte(DIMM_SPD_BASE + i, SPD_NUM_DIMM_BANKS) & 0xFF); PRINT_DEBUG(" rowsize: "); PRINT_DEBUG_HEX8(spd_read_byte(DIMM_SPD_BASE + i, SPD_DENSITY_OF_EACH_ROW_ON_MODULE) & 0xFF); PRINT_DEBUG(" modulesize: "); j = spd_read_byte(DIMM_SPD_BASE + i, SPD_NUM_DIMM_BANKS); if (j < 0) j = 0; else ds = j; j = spd_read_byte(DIMM_SPD_BASE + i, SPD_DENSITY_OF_EACH_ROW_ON_MODULE); if (j < 0) j = 0; else ds = ds * (j >> 1); // convert from 4MB to 8MB units in place /* This is more or less crude hack * allowing to run this target under qemu (even if that is not really * same hardware emulated), * probably some kconfig expert option should be added to enable/disable * this nicelly */ #if 0 if (ds == 0 && memsize == 0) ds = 0x8; #endif // todo: support for bank with not equal sizes as per jedec standart? /* * because density is reported in units of 4Mbyte * and rows in device are just value, * and for setting registers we need value in 8Mbyte units */ PRINT_DEBUG_HEX16(ds); PRINT_DEBUG("\n"); memsize += ds; pci_write_config8(NB, DRB + (2*i), memsize); pci_write_config8(NB, DRB + (2*i) + 1, memsize); if (ds > 0) { /* i have no idea why pci_read_config16 not work for * me correctly here */ ds = pci_read_config8(NB, DRT+1); ds <<=8; ds |= pci_read_config8(NB, DRT); PRINT_DEBUG("DRT "); PRINT_DEBUG_HEX16(ds); ds &= ~(0x01 << (4 * i)); PRINT_DEBUG(" "); PRINT_DEBUG_HEX16(ds); PRINT_DEBUG("\n"); /* * modify DRT register if current row isn't empty * code assume its SDRAM plugged (should check if its sdram or EDO, * edo would have 0x00 as constand instead 0x10 for SDRAM * also this code is buggy because ignores second row of each dimm socket */ /* and as above write_config16 not work here too) * pci_write_config16(NB, DRT, j); */ pci_write_config8(NB, DRT+1, ds >> 8); pci_write_config8(NB, DRT, ds & 0xFF); } } #if 0 PRINT_DEBUG("Mem: 0x"); PRINT_DEBUG_HEX16(memsize * 8); PRINT_DEBUG(" MB\n"); if (memsize == 0) { /* maybe we should use some nice die/hlt sequence with printing on console * that no memory found, get lost, i can't run? * maybe such event_handler can be commonly defined routine to decrease * code duplication? */ PRINT_DEBUG("No memory detected via SPD\n"); PRINT_DEBUG("Reverting to hardcoded 64M single side dimm in first bank\n"); } #endif /* Set DRAMC. Don't enable refresh for now. */ pci_write_config8(NB, DRAMC, 0x00); /* Cas latency 3, and other shouldbe properly from spd too */ pci_write_config8(NB, DRAMT, 0xAC); /* TODO? */ pci_write_config8(NB, PCI_LATENCY_TIMER, 0x40); // set drive strength pci_write_config32(NB, MBSC, 0x00000000); } static void sdram_enable(void) { int i; /* 0. Wait until power/voltages and clocks are stable (200us). */ udelay(200); /* 1. Apply NOP. Wait 200 clock cycles (clock might be 60 or 66 Mhz). */ PRINT_DEBUG("RAM Enable 1: Apply NOP\n"); do_ram_command(RAM_COMMAND_NOP); udelay(200); /* 2. Precharge all. Wait tRP. */ PRINT_DEBUG("RAM Enable 2: Precharge all\n"); do_ram_command(RAM_COMMAND_PRECHARGE); udelay(1); /* 3. Perform 8 refresh cycles. Wait tRC each time. */ PRINT_DEBUG("RAM Enable 3: CBR\n"); for (i = 0; i < 8; i++) { do_ram_command(RAM_COMMAND_CBR); udelay(1); } /* 4. Mode register set. Wait two memory cycles. */ PRINT_DEBUG("RAM Enable 4: Mode register set\n"); do_ram_command(RAM_COMMAND_MRS); udelay(2); /* 5. Normal operation. */ PRINT_DEBUG("RAM Enable 5: Normal operation\n"); do_ram_command(RAM_COMMAND_NORMAL); udelay(1); /* 6. Finally enable refresh. */ PRINT_DEBUG("RAM Enable 6: Enable refresh\n"); pci_write_config8(NB, DRAMC, 0x01); spd_enable_refresh(); udelay(1); PRINT_DEBUG("Northbridge following SDRAM init:\n"); }