Alexandru Gagniuc (mr.nuke.me@gmail.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/171
-gerrit
commit c28dcca158728bcb540f2f0acf62a78bb1e2925a Author: Alexandru Gagniuc mr.nuke.me@gmail.com Date: Wed Aug 24 23:11:44 2011 -0500
NOTFORMERGE: vx900 (not-yet-functional) raminit
Change-Id: I38193572bf0416fd642002dba94c19257f0f6f5b Signed-off-by: Alexandru Gagniuc mr.nuke.me@gmail.com --- src/devices/dram/dram.h | 149 +++++ src/devices/dram/dram_util.c | 297 +++++++++ src/devices/smbus/early_smbus.c | 141 +++++ src/devices/smbus/smbus.h | 99 +++ src/mainboard/via/epia-m850/.directory | 4 + src/mainboard/via/epia-m850/Kconfig | 47 ++ src/mainboard/via/epia-m850/Makefile.inc | 21 + src/mainboard/via/epia-m850/chip.h | 21 + src/mainboard/via/epia-m850/devicetree.cb | 62 ++ src/mainboard/via/epia-m850/mainboard.c | 25 + src/mainboard/via/epia-m850/romstage.c | 77 +++ src/northbridge/amd/amdfam10/.directory | 7 + src/northbridge/amd/amdmct/mct_ddr3/.directory | 7 + src/northbridge/via/vx900/Kconfig | 23 + src/northbridge/via/vx900/Makefile.inc | 33 + src/northbridge/via/vx900/early_smbus.c | 191 ++++++ src/northbridge/via/vx900/early_vx900.h | 35 + src/northbridge/via/vx900/forgotten.c | 180 ++++++ src/northbridge/via/vx900/forgotten.h | 78 +++ src/northbridge/via/vx900/raminit.h | 56 ++ src/northbridge/via/vx900/raminit_ddr3.c | 804 ++++++++++++++++++++++++ src/northbridge/via/vx900/romstrap.inc | 50 ++ src/northbridge/via/vx900/romstrap.lds | 27 + src/northbridge/via/vx900/vx900.h | 26 + 24 files changed, 2460 insertions(+), 0 deletions(-)
diff --git a/src/devices/dram/dram.h b/src/devices/dram/dram.h new file mode 100644 index 0000000..7543d94 --- /dev/null +++ b/src/devices/dram/dram.h @@ -0,0 +1,149 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +#ifndef DRAM_H +#define DRAM_H + +#include <stdint.h> + +/* DRAM type, byte 2 of spd */ +#define DRAM_TYPE_UNDEFINED 0x00 +#define DRAM_TYPE_FPM_DRAM 0x01 +#define DRAM_TYPE_EDO 0x02 +#define DRAM_TYPE_PIPELINED_NIBBLE 0x03 +#define DRAM_TYPE_SDRAM 0x04 +#define DRAM_TYPE_ROM 0x05 +#define DRAM_TYPE_DDR_SGRAM 0x06 +#define DRAM_TYPE_DDR 0x07 +#define DRAM_TYPE_DDR2 0x08 +#define DRAM_TYPE_DDR2_FBDIMM 0x09 +#define DRAM_TYPE_DDR2_FB_PROBE 0x0a +#define DRAM_TYPE_DDR3 0x0b + +/* Module type (byte 3, bits 3:0) of SPD */ +#define DIMM_TYPE_UNDEFINED 0 +#define DIMM_TYPE_RDIMM 1 +#define DIMM_TYPE_UDIMM 2 +#define DIMM_TYPE_SO_DIMM 3 +#define DIMM_TYPE_MICRO_DIMM 4 +#define DIMM_TYPE_MINI_RDIMM 5 +#define DIMM_TYPE_MINI_UDIMM 6 +#define DIMM_TYPE_MINI_CDIMM 7 +#define DIMM_TYPE_72B_SO_UDIMM 8 +#define DIMM_TYPE_72B_SO_RDIMM 9 +#define DIMM_TYPE_72B_SO_CDIMM 10 + +#if defined(CONFIG_DEBUG_RAM_SETUP) && (CONFIG_DEBUG_RAM_SETUP) +#define printram(x, ...) printk(BIOS_DEBUG, x, ##__VA_ARGS__) +#else +#define printram(x, ...) +#endif + +/* Different values for tCK, representing standard DDR3 frequencies + * As always, these values are in 1/256 ns units */ +#define TCK_1066MHZ 240 +#define TCK_800MHZ 320 +#define TCK_666MHZ 384 +#define TCK_533MHZ 480 +#define TCK_400MHZ 640 +#define TCK_333MHZ 768 +#define TCK_266MHZ 960 +#define TCK_200MHZ 1280 + +#define RAM_UNIT (1<<24) + +#include <stdint.h> + +/** + *\brief DIMM characteristics + * + * The characteristics of each DIMM, as presented by the SPD + */ +typedef struct dimm_attr_st { + u8 dram_type; + u16 cas; + /* Number of ranks */ + u8 ranks; + /* Number or row address bits */ + u8 row_bits; + /* Number or column address bits */ + u8 col_bits; + /* Size of module in (1 << 24) bytes (16MB) */ + u16 size; + /* Latencies are in units of ns, scaled by x256 */ + u32 tCK; + u32 tAA; + u32 tWR; + u32 tRCD; + u32 tRRD; + u32 tRP; + u32 tRAS; + u32 tRC; + u32 tRFC; + u32 tWTR; + u32 tRTP; + u32 tFAW; + +} dimm_attr; + +typedef struct ramctr_timing_st { + u8 dram_type; + u16 cas; + /* Latencies are in units of ns, scaled by x256 */ + u32 tCK; + u32 tAA; + u32 tWR; + u32 tRCD; + u32 tRRD; + u32 tRP; + u32 tRAS; + u32 tRC; + u32 tRFC; + u32 tWTR; + u32 tRTP; + u32 tFAW; + +} ramctr_timing; + + +typedef u8 spd_raw_data[256]; + +/** + * \brief Decode the raw spd data + */ +void spd_decode_ddr3(dimm_attr *dimm, spd_raw_data spd_data); + +/** + * \brief Checks if the DIMM is Registered based on byte[3] of the spd + */ +int dimm_is_registered(u8 spd_byte3); + +/** + * \brief Print the info in dimm + */ +void dram_print_spd_ddr3(const dimm_attr *dimm); + +/** + * \brief Read double word from specified address + * + * Should be useful when doing an MRS to the DIMM + */ +u32 volatile_read(u32 addr); + +#endif /* DRAM_H */ diff --git a/src/devices/dram/dram_util.c b/src/devices/dram/dram_util.c new file mode 100644 index 0000000..ca652ce --- /dev/null +++ b/src/devices/dram/dram_util.c @@ -0,0 +1,297 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +#include "dram.h" +#include <console/console.h> +#include <device/device.h> + +u32 volatile_read(volatile u32 addr) +{ + volatile u32 result; + result = *(volatile u32*)addr; + return result; +} + +int dimm_is_registered(u8 spd_byte3) +{ + spd_byte3 &= 0x0f; + if( (spd_byte3 == DIMM_TYPE_RDIMM) + | (spd_byte3 == DIMM_TYPE_MINI_RDIMM) + | (spd_byte3 == DIMM_TYPE_72B_SO_RDIMM) ) + return 1; + + return 0; +} + +void spd_decode_ddr3(dimm_attr *dimm, spd_raw_data spd) +{ + int nCRC, i; + u16 crc, spd_crc; + u8 * ptr = spd; + u8 ftb_divisor; + u8 ftb_dividend; + u8 capacity_shift, bus_width, sdram_width; + u32 mtb; /* medium time base */ + /* Make sure that the spd dump is indeed from a DDR3 module */ + if(spd[2] != DRAM_TYPE_DDR3) + { + printram("Not a DDR3 SPD!\n"); + dimm->dram_type = DRAM_TYPE_UNDEFINED; + return; + } + dimm->dram_type = DRAM_TYPE_DDR3; + + /* Find the number of bytes covered by CRC */ + if(spd[0] & 0x80) { + nCRC = 117; + } else { + nCRC =126; + } + + /* Compute the CRC */ + crc = 0; + while (--nCRC >= 0) { + crc = crc ^ (int)*ptr++ << 8; + for (i = 0; i < 8; ++i) + if (crc & 0x8000) { + crc = crc << 1 ^ 0x1021; + } else { + crc = crc << 1; + } + } + /* Compare with the CRC in the SPD */ + spd_crc = (spd[127] << 8) + spd[126]; + /* Verify the CRC is correct */ + if(crc != spd_crc) + printram("SPD CRC failed!!!"); + + + unsigned int reg, val, param; + printram(" Revision: %x \n", spd[1] ); + printram(" Type : %x \n", spd[2] ); + printram(" Key : %x \n", spd[3] ); + + reg = spd[4]; + /* Number of memory banks */ + val = (reg >> 4) & 0x07; + if (val > 0x03) printram(" Invalid number of memory banks\n"); + param = 1 << (val + 3); + printram(" Banks : %u \n", param ); + /* SDRAM capacity */ + capacity_shift = reg & 0x0f; + if (capacity_shift > 0x06) printram(" Invalid module capacity\n"); + if (capacity_shift < 0x02) { + printram(" Capacity: %u Mb\n", 256 << capacity_shift); + } else { + printram(" Capacity: %u Gb\n", 1 << (capacity_shift - 2)); + } + + reg = spd[5]; + /* Row address bits */ + val = (reg >> 4) & 0x07; + if(val > 0x04) printram(" Invalid row address bits\n"); + dimm->row_bits = val + 12; + /* Column address bits */ + val = reg & 0x07; + if(val > 0x03) printram(" Invalid column address bits\n"); + dimm->col_bits = val + 9; + + /* Module nominal voltage */ + reg = spd[6]; + print_debug(" Supported voltages: "); + if(reg & (1<<2) ) print_debug("1.2V "); + if(reg & (1<<1) ) print_debug("1.35V "); + if( !(reg & (1<<0)) ) print_debug("1.5V "); + print_debug("\n"); + + /* Module organization */ + reg = spd[7]; + /* Number of ranks*/ + val = (reg >> 3) & 0x07; + if(val > 3) printram(" Invalid number of ranks\n"); + dimm->ranks = val + 1; + /* SDRAM device width */ + val = (reg & 0x07); + if(val > 3) printram(" Invalid SDRAM width\n"); + sdram_width = (4 << val); + printram(" SDRAM width : %u \n", sdram_width); + + /* Memory bus width */ + reg = spd[8]; + /* Bus extension */ + val = (reg >> 3) & 0x03; + if(val > 1) printram(" Invalid bus extension\n"); + printram(" Bus extension : %u bits\n", val?8:0); + /* Bus width */ + val = reg & 0x07; + if(val > 3) printram(" Invalid bus width\n"); + bus_width = 8 << val; + printram(" Bus width : %u \n", bus_width); + + /* We have all the info we need to compute the dimm size */ + /* Capacity is 256Mbit multiplied by the power of 2 specified in + * capacity_shift + * The rest is the JEDEC formula */ + /* I am certain it will fit in 16 bits + * Remember, It's in units of 2^24 bytes*/ + dimm->size = ( (1 << (capacity_shift + (25-24)) ) * bus_width + * dimm->ranks ) / sdram_width; + + /* Fine Timebase (FTB) Dividend/Divisor */ + /* Dividend */ + ftb_dividend = (spd[9] >> 4) & 0x0f; + /* Divisor */ + ftb_divisor = spd[9] & 0x0f; + + /* Medium Timebase = + * Medium Timebase (MTB) Dividend / + * Medium Timebase (MTB) Divisor */ + mtb = (((u32)spd[10]) << 8) / spd [11]; + + /* SDRAM Minimum Cycle Time (tCKmin) */ + dimm->tCK = spd[12] * mtb; + + /* CAS Latencies Supported */ + dimm->cas = (spd[15] << 8) + spd[14]; + + /* Minimum CAS Latency Time (tAAmin) */ + dimm->tAA = spd[16] * mtb; + + /* Minimum Write Recovery Time (tWRmin) */ + dimm->tWR = spd[17] * mtb; + + /* Minimum RAS# to CAS# Delay Time (tRCDmin) */ + dimm->tRCD = spd[18] * mtb; + + /* Minimum Row Active to Row Active Delay Time (tRRDmin) */ + dimm->tRRD = spd[19] * mtb; + + /* Minimum Row Precharge Delay Time (tRPmin)*/ + dimm->tRP = spd[20] * mtb; + + /* Minimum Active to Precharge Delay Time (tRASmin) */ + dimm->tRAS = (((spd[21] & 0x0f) << 8) + spd[22]) * mtb; + + /* Minimum Active to Active/Refresh Delay Time (tRCmin) */ + dimm->tRC = (((spd[21] & 0xf0) << 4) + spd[23]) * mtb; + + /* Minimum Refresh Recovery Delay Time (tRFCmin) */ + dimm->tRFC = ((spd[25] << 8) + spd[24]) * mtb; + + /* Minimum Internal Write to Read Command Delay Time (tWTRmin) */ + dimm->tWTR = spd[26] * mtb; + + /* Minimum Internal Read to Precharge Command Delay Time (tRTPmin) */ + dimm->tRTP = spd[27] * mtb; + + /* Minimum Four Activate Window Delay Time (tFAWmin) */ + dimm->tFAW = (((spd[28] & 0x0f) << 8) + spd[29]) * mtb; + + /* SDRAM Optional Features */ + reg = spd[30]; + print_debug(" Optional features :"); + if(reg & 0x80) print_debug(" DLL-Off_mode"); + if(reg & 0x02) print_debug(" RZQ/7"); + if(reg & 0x01) print_debug(" RZQ/6"); + print_debug("\n"); + + /* SDRAM Thermal and Refresh Options */ + reg = spd[31]; + print_debug(" Thermal features :"); + if(reg & 0x80) print_debug(" PASR"); + if(reg & 0x08) print_debug(" ODTS"); + if(reg & 0x04) print_debug(" ASR"); + if(reg & 0x02) print_debug(" ext_temp_refresh"); + if(reg & 0x01) print_debug(" ext_temp_range"); + print_debug("\n"); + + /* Module Thermal Sensor */ + reg = spd[32]; + print_debug(" Thermal sensor : "); + if(reg & 0x80) print_debug("yes"); + else print_debug("no"); + print_debug("\n"); + + /* SDRAM Device Type */ + reg = spd[33]; + print_debug(" Standard SDRAM : "); + if(reg & 0x80) print_debug("no"); + else print_debug("yes"); + print_debug("\n"); + + /* Fine Offset for SDRAM Minimum Cycle Time (tCKmin) */ + //printram(" tCKmin FTB : %i \n", spd[34]); + /* Fine Offset for Minimum CAS Latency Time (tAAmin) */ + //printram(" tAAmin FTB : %i \n", spd[35]); + /* Fine Offset for Minimum RAS# to CAS# Delay Time (tRCDmin) */ + //printram(" tRCDmin FTB : %i \n", spd[36]); + /* Fine Offset for Minimum Row Precharge Delay Time (tRPmin) */ + //printram(" tRPmin FTB : %i \n", spd[37]); + /* Fine Offset for Minimum Active to Active/Refresh Delay Time (tRCmin) */ + //printram(" tRCmin FTB : %i \n", spd[38]); + + if(spd[60] & 0x01) + printram(" DIMM Address bits mirrorred!!!\n"); + +} + +static void print_ns(const char * msg, u32 val) +{ + u32 mant, fp; + mant = val/256; + fp = (val % 256) * 1000/256; + + printram("%s%3u.%.3u ns\n", msg, mant, fp); +} + +void dram_print_spd_ddr3(const dimm_attr *dimm) +{ + u16 val16; + int i; + + printram(" Row addr bits : %u \n", dimm->row_bits); + printram(" Column addr bits : %u \n", dimm->col_bits); + printram(" Number of ranks : %u \n", dimm->ranks); + printram(" DIMM Capacity : %u MB\n", dimm->size << 4); + + /* CAS Latencies Supported */ + val16 = dimm->cas; + print_debug(" CAS latencies :"); + i = 0; + do{ + if(val16 & 1) printram(" %u", i + 4); + i++; + val16 >>= 1; + } while(val16); + print_debug("\n"); + + print_ns(" tCKmin : ", dimm->tCK); + print_ns(" tAAmin : ", dimm->tAA); + print_ns(" tWRmin : ", dimm->tWR); + print_ns(" tRCDmin : ", dimm->tRCD); + print_ns(" tRRDmin : ", dimm->tRRD); + print_ns(" tRPmin : ", dimm->tRP); + print_ns(" tRASmin : ", dimm->tRAS); + print_ns(" tRCmin : ", dimm->tRC); + print_ns(" tRFCmin : ", dimm->tRFC); + print_ns(" tWTRmin : ", dimm->tWTR); + print_ns(" tRTPmin : ", dimm->tRTP); + print_ns(" tFAWmin : ", dimm->tFAW); + +} diff --git a/src/devices/smbus/early_smbus.c b/src/devices/smbus/early_smbus.c new file mode 100644 index 0000000..4583aca --- /dev/null +++ b/src/devices/smbus/early_smbus.c @@ -0,0 +1,141 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ +/** + * @file post_codes.h + * + * This file defines the implementations for the functions defined in smbus.h + * These are a generic SMBUS implementation, which should work with a majority + * of chipsets. + * They are marked weak so that they can be overridden by the chipset code if + * necessary. + */ + +#include "smbus.h" + +/** + * \brief Brief delay for SMBUS transactions + */ +__attribute__((weak)) +void __smbus_delay(void) +{ + inb(0x80); +} + +/** + * \brief Clear the SMBUS host status register + */ +__attribute__((weak)) +void __smbus_reset(u16 __smbus_io_base) +{ + outb(0xdf, SMBHSTSTAT); +} + +/** + * \brief Print an error, should it occur. If no error, just exit. + * + * @param host_status The data returned on the host status register after + * a transaction is processed. + * @param loops The number of times a transaction was attempted. + * @return 0 if no error occurred + * 1 if an error was detected + */ +__attribute__((weak)) +int __smbus_print_error(u8 host_status, int loops, u16 __smbus_io_base) +{ + /* Check if there actually was an error. */ + if ((host_status == 0x00 || host_status == 0x40 || + host_status == 0x42) && (loops < SMBUS_TIMEOUT)) + return 0; + + if (loops >= SMBUS_TIMEOUT) + printsmbus("SMBus timeout\n"); + if (host_status & (1 << 4)) + printsmbus("Interrupt/SMI# was Failed Bus Transaction\n"); + if (host_status & (1 << 3)) + printsmbus("Bus error\n"); + if (host_status & (1 << 2)) + printsmbus("Device error\n"); + if (host_status & (1 << 1)) + printsmbus("Interrupt/SMI# completed successfully\n"); + if (host_status & (1 << 0)) + printsmbus("Host busy\n"); + return 1; +} + +/** + * \brief Checks if the SMBUS is currently busy with a transaction + */ +__attribute__((weak)) +int __smbus_is_busy(u16 __smbus_io_base) +{ + /* Check if bit 0 of the status register is 1 (busy) or 0 (ready) */ + return ( (inb(SMBHSTSTAT) & (1 << 0)) == 1); +} + +/** + * \brief Wait for the SMBUS to become ready to process a new transaction. + */ +__attribute__((weak)) +int __smbus_wait_until_ready(u16 __smbus_io_base) +{ + int loops; + + printsmbus("Waiting until SMBus ready\n"); + + /* Loop up to SMBUS_TIMEOUT times, waiting for bit 0 of the + * SMBus Host Status register to go to 0, indicating the operation + * was completed successfully. I don't remember why I did it this way, + * but I think it was because ROMCC was running low on registers */ + loops = 0; + while (__smbus_is_busy(__smbus_io_base) && loops < SMBUS_TIMEOUT) + ++loops; + + return __smbus_print_error(inb(SMBHSTSTAT), loops, __smbus_io_base); +} + +/** + * \brief Read a byte from the SMBUS. + * + * @param dimm The address location of the DIMM on the SMBus. + * @param offset The offset the data is located at. + */ +__attribute__((weak)) +u8 __smbus_read_byte(u8 dimm, u8 offset, u16 __smbus_io_base) +{ + u8 val; + + /* Initialize SMBUS sequence */ + __smbus_reset(__smbus_io_base); + /* Clear host data port. */ + outb(0x00, SMBHSTDAT0); + + __smbus_wait_until_ready(__smbus_io_base); + + /* Actual addr to reg format. */ + dimm = (dimm << 1); + dimm |= 1; /* read command */ + outb(dimm, SMBXMITADD); + outb(offset, SMBHSTCMD); + /* Start transaction, byte data read. */ + outb(0x48, SMBHSTCTL); + __smbus_wait_until_ready(__smbus_io_base); + + val = inb(SMBHSTDAT0); + return val; +} \ No newline at end of file diff --git a/src/devices/smbus/smbus.h b/src/devices/smbus/smbus.h new file mode 100644 index 0000000..820a0dd --- /dev/null +++ b/src/devices/smbus/smbus.h @@ -0,0 +1,99 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +/** + * @file post_codes.h + * + * This file defines the prototypes for several common SMBUS functions + * These functions are prefixed with __smbus_ so that they do not conflict with + * the dozens of similar (duplicated) implementations in many southbridges. + * + * As a last parameter, the SMBUS functions take a u16 value __smbus_io_base, + * which represents the base IO port for smbus transactions + */ + +#include <arch/io.h> + +/** + * \brief SMBUS IO ports in relation to the base IO port + */ +#define SMBHSTSTAT __smbus_io_base + 0x0 +#define SMBSLVSTAT __smbus_io_base + 0x1 +#define SMBHSTCTL __smbus_io_base + 0x2 +#define SMBHSTCMD __smbus_io_base + 0x3 +#define SMBXMITADD __smbus_io_base + 0x4 +#define SMBHSTDAT0 __smbus_io_base + 0x5 +#define SMBHSTDAT1 __smbus_io_base + 0x6 +#define SMBBLKDAT __smbus_io_base + 0x7 +#define SMBSLVCTL __smbus_io_base + 0x8 +#define SMBTRNSADD __smbus_io_base + 0x9 +#define SMBSLVDATA __smbus_io_base + 0xa + +#define SMBUS_TIMEOUT (100*1000*10) + +/** + * \brief printk macro for SMBUS debugging + */ +#if defined(CONFIG_DEBUG_SMBUS_SETUP) && (CONFIG_DEBUG_SMBUS_SETUP) +#define printsmbus(x, ...) printk(BIOS_DEBUG, x, ##__VA_ARGS__) +#else +#define printsmbus(x, ...) +#endif + +void __smbus_reset(u16 __smbus_io_base); +int __smbus_print_error(u8 host_status, int loops, u16 __smbus_io_base); +int __smbus_is_busy(u16 __smbus_io_base); +int __smbus_wait_until_ready(u16 __smbus_io_base); +u8 __smbus_read_byte(u8 dimm, u8 offset, u16 __smbus_io_base); + +void __smbus_delay(void); + +#if defined(SMBUS_IO_BASE) && (SMBUS_IO_BASE != 0) + +__attribute__((always_inline, unused)) +static void smbus_reset(void) +{ + __smbus_reset(SMBUS_IO_BASE); +} + +__attribute__((always_inline, unused)) +static int smbus_is_busy(void) +{ + return __smbus_is_busy(SMBUS_IO_BASE); +} + +__attribute__((always_inline, unused)) +static int smbus_wait_until_ready(void) +{ + return __smbus_wait_until_ready(SMBUS_IO_BASE); +} + +__attribute__((always_inline, unused)) +static int smbus_print_error(u8 host_status, int loops) +{ + return __smbus_print_error(host_status, loops, SMBUS_IO_BASE); +} + +__attribute__((always_inline, unused)) +static u8 smbus_read_byte(u8 dimm, u8 offset) +{ + return __smbus_read_byte(dimm, offset, SMBUS_IO_BASE); +} + +#endif \ No newline at end of file diff --git a/src/mainboard/via/epia-m850/.directory b/src/mainboard/via/epia-m850/.directory new file mode 100644 index 0000000..8a30135 --- /dev/null +++ b/src/mainboard/via/epia-m850/.directory @@ -0,0 +1,4 @@ +[Dolphin] +ShowPreview=true +Timestamp=2011,8,1,4,33,6 +Version=2 diff --git a/src/mainboard/via/epia-m850/Kconfig b/src/mainboard/via/epia-m850/Kconfig new file mode 100644 index 0000000..ae46646 --- /dev/null +++ b/src/mainboard/via/epia-m850/Kconfig @@ -0,0 +1,47 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com +## +## 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, see http://www.gnu.org/licenses/. +## + +if BOARD_VIA_EPIA_M850 + +config BOARD_SPECIFIC_OPTIONS # dummy + def_bool y + select ARCH_X86 + select CPU_VIA_C7 + select NORTHBRIDGE_VIA_VX900 + select SUPERIO_FINTEK_F81865F + #select BOARD_HAS_FADT + #select HAVE_PIRQ_TABLE + #select HAVE_ACPI_TABLES + #select HAVE_OPTION_TABLE + select BOARD_ROMSIZE_KB_512 + #select RAMINIT_SYSINFO + +config MAINBOARD_DIR + string + default via/epia-m850 + +config MAINBOARD_PART_NUMBER + string + default "EPIA-M850" + +config IRQ_SLOT_COUNT + int + default 13 + +endif # BOARD_VIA_EPIA_M850 diff --git a/src/mainboard/via/epia-m850/Makefile.inc b/src/mainboard/via/epia-m850/Makefile.inc new file mode 100644 index 0000000..9dffc79 --- /dev/null +++ b/src/mainboard/via/epia-m850/Makefile.inc @@ -0,0 +1,21 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com +## +## 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 3 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, see http://www.gnu.org/licenses/. +## + +#romstage-y += ./../../../superio/fintek/f81865f/f81865f_early_serial.c + diff --git a/src/mainboard/via/epia-m850/chip.h b/src/mainboard/via/epia-m850/chip.h new file mode 100644 index 0000000..2ac4f12 --- /dev/null +++ b/src/mainboard/via/epia-m850/chip.h @@ -0,0 +1,21 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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 3 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, see http://www.gnu.org/licenses/. + */ +extern struct chip_operations mainboard_ops; + +struct mainboard_config {}; diff --git a/src/mainboard/via/epia-m850/devicetree.cb b/src/mainboard/via/epia-m850/devicetree.cb new file mode 100644 index 0000000..34f2848 --- /dev/null +++ b/src/mainboard/via/epia-m850/devicetree.cb @@ -0,0 +1,62 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com +## +## 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 3 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, see http://www.gnu.org/licenses/. +## + +chip northbridge/via/vx900 # Northbridge + device lapic_cluster 0 on # APIC cluster + chip cpu/via/model_c7 # CPU ATOM (same as VIA C7) + device lapic 0 on end # APIC + end + end + device pci_domain 0 on + device pci 0.0 on end # AGP Bridge + device pci 0.1 on end # Error Reporting + device pci 0.2 on end # Host Bus Control + device pci 0.3 on end # Memory Controller + device pci 0.4 on end # Power Management + device pci 0.5 on end # Power Management + device pci 0.6 on end # Power Management + device pci 0.7 on end # V-Link Controller + device pci 1.0 on end # PCI Bridge + device pci 1.1 on end # Audio + device pci 3.0 on end # PCIE control + device pci 3.1 on end # PEX1 + device pci 3.2 on end # PEX2 + device pci 3.3 on end # PEX3 + device pci 3.4 on end # PEX4 + device pci f.0 on end # IDE/SATA + device pci 10.0 on end # USB 1.1 + device pci 10.1 on end # USB 1.1 + device pci 10.2 on end # USB 1.1 + device pci 10.3 on end # USB 1.1 + device pci 10.4 on end # USB 2.0 + device pci 11.0 on end # LPC + chip drivers/generic/generic # DIMM 0-0-0 + device i2c 50 on end + end + chip drivers/generic/generic # DIMM 0-0-1 + device i2c 51 on end + end + chip superio/fintek/f81865f # Super duper IO + end + device pci 11.7 on end # NB/SB control + device pci 13.0 on end # PCI Bridge + device pci 14.0 on end # HD Audio + end + +end diff --git a/src/mainboard/via/epia-m850/mainboard.c b/src/mainboard/via/epia-m850/mainboard.c new file mode 100644 index 0000000..572f7da --- /dev/null +++ b/src/mainboard/via/epia-m850/mainboard.c @@ -0,0 +1,25 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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 3 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, see http://www.gnu.org/licenses/. + */ + +#include <device/device.h> +#include "chip.h" + +struct chip_operations mainboard_ops = { + CHIP_NAME("VIA EPIA-M850 Mainboard") +}; diff --git a/src/mainboard/via/epia-m850/romstage.c b/src/mainboard/via/epia-m850/romstage.c new file mode 100644 index 0000000..f458554 --- /dev/null +++ b/src/mainboard/via/epia-m850/romstage.c @@ -0,0 +1,77 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +/* + * Inspired from the EPIA-M700 + */ + +#include <stdint.h> +#include <device/pci_def.h> +#include <device/pci_ids.h> +#include <arch/io.h> +#include <device/pnp_def.h> +#include <arch/romcc_io.h> +#include <arch/hlt.h> +#include <console/console.h> +#include <lib.h> +#include "cpu/x86/bist.h" +#include <string.h> + +#include "northbridge/via/vx900/early_vx900.h" +#include "northbridge/via/vx900/raminit.h" +#include "superio/fintek/f81865f/f81865f_early_serial.c" + +#define SERIAL_DEV PNP_DEV(0x2e, 0) + +/* cache_as_ram.inc jumps to here. */ +void main(unsigned long bist) +{ + /* Enable multifunction bit for northbridge. + * This enables the PCI configuration spaces of D0F1 to D0F7 to be + * accessed */ + pci_write_config8(PCI_DEV(0, 0, 0), 0x4f, 0x01); + + f81865f_enable_serial(SERIAL_DEV, CONFIG_TTYS0_BASE); + console_init(); + print_debug("Console initialized. \n"); + + /* Halt if there was a built-in self test failure. */ + report_bist_failure(bist); + + /* x86 cold boot I/O cmd. */ + enable_smbus(); + + /* If this works, then SMBUS is up and running */ + //dump_spd_data(); + + /* Now we can worry about raminit. + * This board only has DDR3, so no need to worry about which DRAM type + * to use */ + dimm_layout dimms = {{0x50, 0x51, SPD_END_LIST}}; + vx900_init_dram_ddr3(&dimms); + //ram_check(0, 640 * 1024); + //ram_check(1<<26, (1<<26) + 0x20); + ram_check(0x04321000, 0x04321000 + 0x80); + ram_check(0, 0x80); + + print_debug("We passed RAM verify\n"); + + return; + +} diff --git a/src/northbridge/amd/amdfam10/.directory b/src/northbridge/amd/amdfam10/.directory new file mode 100644 index 0000000..24fd569 --- /dev/null +++ b/src/northbridge/amd/amdfam10/.directory @@ -0,0 +1,7 @@ +[Dolphin] +ShowPreview=true +SortOrder=1 +Sorting=1 +Timestamp=2011,8,2,17,56,9 +Version=2 +ViewMode=1 diff --git a/src/northbridge/amd/amdmct/mct_ddr3/.directory b/src/northbridge/amd/amdmct/mct_ddr3/.directory new file mode 100644 index 0000000..601cad6 --- /dev/null +++ b/src/northbridge/amd/amdmct/mct_ddr3/.directory @@ -0,0 +1,7 @@ +[Dolphin] +ShowPreview=true +SortOrder=1 +Sorting=1 +Timestamp=2011,8,2,17,57,51 +Version=2 +ViewMode=1 diff --git a/src/northbridge/via/vx900/Kconfig b/src/northbridge/via/vx900/Kconfig new file mode 100644 index 0000000..60e7993 --- /dev/null +++ b/src/northbridge/via/vx900/Kconfig @@ -0,0 +1,23 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com +## +## 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 3 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, see http://www.gnu.org/licenses/. +## + +config NORTHBRIDGE_VIA_VX900 + bool + select HAVE_DEBUG_RAM_SETUP + select HAVE_DEBUG_SMBUS \ No newline at end of file diff --git a/src/northbridge/via/vx900/Makefile.inc b/src/northbridge/via/vx900/Makefile.inc new file mode 100644 index 0000000..5450b1d --- /dev/null +++ b/src/northbridge/via/vx900/Makefile.inc @@ -0,0 +1,33 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com +## +## 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 3 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, see http://www.gnu.org/licenses/. +## + +romstage-y += early_smbus.c +romstage-y += raminit_ddr3.c +romstage-y += ./../../../devices/dram/dram_util.c +romstage-y += ./../../../devices/smbus/early_smbus.c + +# Drivers for these devices already exist with the vx800 +# Use those instead of duplicating code + +driver-y += ./../vx800/northbridge.c +driver-y += ./../vx800/vga.c +driver-y += ./../vx800/lpc.c + +chipset_bootblock_inc += $(src)/northbridge/via/vx900/romstrap.inc +chipset_bootblock_lds += $(src)/northbridge/via/vx900/romstrap.lds \ No newline at end of file diff --git a/src/northbridge/via/vx900/early_smbus.c b/src/northbridge/via/vx900/early_smbus.c new file mode 100644 index 0000000..aa6cec0 --- /dev/null +++ b/src/northbridge/via/vx900/early_smbus.c @@ -0,0 +1,191 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +#include <device/pci_ids.h> +#include "early_vx900.h" + +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <console/console.h> +#include <devices/dram/dram.h> + +__attribute__((unused)) +static void smbus_delays(int delays) +{ + while(delays--) __smbus_delay(); +} + + +/** + * Read a byte from the SMBus. + * + * @param dimm The address location of the DIMM on the SMBus. + * @param offset The offset the data is located at. + */ +u8 __smbus_read_byte(u8 dimm, u8 offset, u16 __smbus_io_base) +{ + u8 val; + + /* Initialize SMBUS sequence */ + smbus_reset(); + /* Clear host data port. */ + outb(0x00, SMBHSTDAT0); + + smbus_wait_until_ready(); + smbus_delays(50); + + /* Actual addr to reg format. */ + dimm = (dimm << 1); + dimm |= 1; /* read command */ + outb(dimm, SMBXMITADD); + outb(offset, SMBHSTCMD); + /* Start transaction, byte data read. */ + outb(0x48, SMBHSTCTL); + smbus_wait_until_ready(); + + val = inb(SMBHSTDAT0); + return val; +} + +void enable_smbus(void) +{ + device_t dev; + u8 reg8; + u16 __smbus_io_base = SMBUS_IO_BASE; + + /* Locate the Power Management control */ + dev = pci_locate_device(PCI_ID(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_VX900_LPC), 0); + + if (dev == PCI_DEV_INVALID) { + die("Power Managment Controller not found\n"); + } + + /* + * To use SMBus to manage devices on the system board, it is a must to + * enable SMBus function by setting + * PMU_RXD2[0] (SMBus Controller Enable) to 1. + * And set PMU_RXD0 and PMU_RXD1 (SMBus I/O Base) to an appropriate + * I/O port address, so that all registers in SMBus I/O port can be + * accessed. + */ + + reg8 = pci_read_config8(dev, 0xd2); + /* Enable SMBus controller */ + reg8 |= 1; + /* Set SMBUS clock from 128k source */ + reg8 |= 1<<2; + pci_write_config8(dev, 0xd2, reg8); + + reg8 = pci_read_config8(dev, 0x94); + /* SMBUS clock from divider of 14.318 MHz */ + reg8 &= ~(1<<7); + pci_write_config8(dev, 0x94, reg8); + + /* Set SMBus IO base */ + pci_write_config16(dev, 0xd0, SMBUS_IO_BASE); + + /* + * Initialize the SMBus sequence: + */ + /* Clear SMBus host status register */ + smbus_reset(); + /* Clear SMBus host data 0 register */ + outb(0x00, SMBHSTDAT0); + + /* Wait for SMBUS */ + smbus_wait_until_ready(); + +} + +void spd_read(u8 addr, spd_raw_data spd) +{ + u8 reg; + int i, regs; + reg = smbus_read_byte(addr, 2); + if(reg != 0x0b) + { + printk(BIOS_DEBUG, "SMBUS device %x not a DDR3 module\n", addr); + spd[2] = 0; + return; + } + + reg = smbus_read_byte(addr, 0); + reg &= 0xf; + if (reg == 0x3) { + regs = 256; + } else if (reg == 0x2) { + regs = 176; + } else if (reg == 0x1) { + regs = 128; + } else { + printk(BIOS_INFO, "No DIMM present at %x\n", addr); + spd[2] = 0; + return; + } + printk(BIOS_DEBUG, "SPD Data for DIMM %x \n", addr); + for (i = 0; i < regs; i++) { + reg = smbus_read_byte(addr, i); + //printk(BIOS_DEBUG, " Offset %u = 0x%x \n", i, reg ); + spd[i] = reg; + } +} + +void dump_spd_data(void) +{ + int dimm, offset, regs; + unsigned int reg; + spd_raw_data spd; + dimm_attr dimmx; + + for (dimm = 0x50; dimm < 0x52; dimm++) { + reg = smbus_read_byte(dimm, 2); + if(reg != 0x0b) + { + printk(BIOS_DEBUG, + "SMBUS device %x not a DDR3 module\n", dimm); + continue; + } + + reg = smbus_read_byte(dimm, 0); + reg &= 0xf; + if (reg == 0x3) { + regs = 256; + } else if (reg == 0x2) { + regs = 176; + } else if (reg == 0x1) { + regs = 128; + } else { + printk(BIOS_INFO, "No DIMM present at %x\n", dimm); + regs = 0; + continue; + } + printk(BIOS_DEBUG, "SPD Data for DIMM %x \n", dimm); + for (offset = 0; offset < regs; offset++) { + reg = smbus_read_byte(dimm, offset); + //printk(BIOS_DEBUG, " Offset %u = 0x%x \n", offset, reg ); + spd[offset] = reg; + } + + spd_decode_ddr3(&dimmx, spd); + dram_print_spd_ddr3(&dimmx); + + } +} + diff --git a/src/northbridge/via/vx900/early_vx900.h b/src/northbridge/via/vx900/early_vx900.h new file mode 100644 index 0000000..6b33077 --- /dev/null +++ b/src/northbridge/via/vx900/early_vx900.h @@ -0,0 +1,35 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +#ifndef EARLY_VX900_H +#define EARLY_VX900_H + +#include <stdint.h> + +#define SMBUS_IO_BASE 0x500 +#include <devices/smbus/smbus.h> + + +#include "raminit.h" + +void enable_smbus(void); +void dump_spd_data(void); +void spd_read(u8 addr, spd_raw_data spd); + +#endif /* EARLY_VX900_H */ diff --git a/src/northbridge/via/vx900/forgotten.c b/src/northbridge/via/vx900/forgotten.c new file mode 100644 index 0000000..c3d3ed8 --- /dev/null +++ b/src/northbridge/via/vx900/forgotten.c @@ -0,0 +1,180 @@ +#include "forgotten.h" + +u16 ddr3_get_mr0( + char precharge_pd, + u8 write_recovery, + char dll_reset, + char mode, + u8 cas, + char interleaved_burst, + u8 burst_lenght +){ + u32 cmd = 0; + if(precharge_pd) cmd |= (1 << 12); + /* Write recovery */ + cmd |= ( ((write_recovery - 4) & 0x7) << 9); + if(dll_reset) cmd |= (1 << 8); + if(mode) cmd |= (1<<7); + /* CAS latency) */ + cmd |= ( ((cas - 4) & 0x7) << 4); + if(interleaved_burst) cmd |= (1 << 3); + /* Burst lenght */ + cmd |= (burst_lenght & 0x3); + return cmd; +} + +u16 ddr3_get_mr1( + char q_off_disable, + char tdqs, + u8 rtt_nom, + char write_leveling, + u8 output_drive_strenght, + u8 additive_latency, + u8 dll_disable +){ + u32 cmd = 0; + if(q_off_disable) cmd |= (1 << 12); + if(tdqs) cmd |= (1 << 11); + /* rtt_nom */ + cmd |= ( ((rtt_nom & 4) << 9) | ((rtt_nom & 2) << 6) + | ((rtt_nom & 1) << 2)); + if(write_leveling) cmd |= (1 << 7); + /* output drive strenght */ + cmd |= ( ((output_drive_strenght & 2) << 5) + | ((output_drive_strenght & 1) << 1) ); + /* Additive latency */ + cmd |= ((additive_latency & 0x3) << 3); + if(dll_disable) cmd |= (1 << 0); + return cmd; +} + +u16 ddr3_get_mr2( + u8 rtt_wr, + char extended_temp, + char auto_self_refresh, + u8 cas_write +){ + u32 cmd = 0; + /* Rtt_wr */ + cmd |= ( (rtt_wr & 0x3) << 9); + if(extended_temp) cmd |= (1 << 7); + if(auto_self_refresh) cmd |= (1 << 6); + /* CAS write latency */ + cmd |= ( ((cas_write - 5) & 0x7) << 3); + return cmd; +} + +u16 ddr3_get_mr3(char dataflow_from_mpr) +{ + u32 cmd = 0; + if(dataflow_from_mpr) cmd |= (1<<2); + return cmd; +} + +/* + * Translate the MRS command into the memory address corresponding to the + * command. This is based on the CPU address to memory address mapping described + * by the initial values of registers 0x52 and 0x53, so do not fuck with them + * until after the MRS commands have been sent to all ranks + */ +static u32 vx900_get_mrs_addr(u8 mrs_type, u16 cmd) +{ + u32 addr = 0; + /* A3 <-> MA0, A4 <-> MA1, ... A12 <-> MA9 */ + addr |= ((cmd &0x3ff)<< 3); + /* A20 <-> MA10 */ + addr |= (((cmd >> 10) & 0x1) << 20); + /* A13 <-> MA11, A14 <-> MA12 */ + addr |= (((cmd >> 11) & 0x3) << 13); + + /* Do not fuck with registers 0x52 and 0x53 if you want the following + * mappings to work for you: + * A17 <-> BA0, A18 <-> BA1, A19 <-> BA2 */ + addr |= ((mrs_type & 0x7) << 17); + return addr; +} +void vx900_dram_ddr3_init_rank(device_t mcu, int rank); +void vx900_dram_ddr3_init_rank(device_t mcu, int rank) +{ + u8 reg8; + u16 reg16; + u32 reg32, cmd, res, addr; + + printram("Initializing rank %u\n", rank); + + reg16 = 0xbbbb; + reg16 &= ~(0xf << (4 * rank)); + reg16 |= (0x8 << (4 * rank)); + pci_write_config16(mcu, 0x54, reg16); + + /* Step 06 - Set Fun3_RX6B[2:0] to 001b (NOP Command Enable). */ + reg8 = pci_read_config8(mcu, 0x6b); + reg8 &= ~(0x03); + reg8 |= (1<<0); + pci_write_config8(mcu, 0x6b, reg8); + /* Step 07 - Read a double word from any address of the DIMM. */ + reg32 = volatile_read(0x0); + printram("We just read 0x%x\n", reg32); + /* Step 08 - Set Fun3_RX6B[2:0] to 011b (MSR Enable). */ + reg8 = pci_read_config8(mcu, 0x6b); + reg8 &= ~(0x03); + reg8 |= (3<<0); + pci_write_config8(mcu, 0x6b, reg8); + + /* Step 09 – Issue MR2 cycle. Read a double word from the address depended + * on DRAM’s Rtt_WR and CWL settings. + * (Check the byte 63 of SPD. If memory address lines of target physical + * rank is mirrored, MA3~MA8 and BA0~BA1 should be swapped.) */ + cmd = ddr3_get_mr2(2,0,0,7); + addr = vx900_get_mrs_addr(2, cmd); + res = volatile_read(addr); + printram("MR2 cycle 0x%.4x @ addr 0x%.8x read 0x%.8x\n", cmd, addr, res); + + /* Step 10 – Issue MR3 cycle. Read a double word from the address 60000h to + * set DRAM to normal operation mode. + * (Check the byte 63 of SPD. If memory address lines of target physical + * rank is mirrored, MA3~MA8 and BA0~BA1 should be swapped.) */ + cmd = ddr3_get_mr3(0); + addr = vx900_get_mrs_addr(3, cmd); + res = volatile_read(addr); + printram("MR3 cycle 0x%.4x @ addr 0x%.8x read 0x%.8x\n", cmd, addr, res); + + /* Step 11 –Issue MR1 cycle. Read a double word from the address depended + * on DRAM’s output driver impedance and Rtt_Nom settings. + * The DLL enable field, TDQS field, write leveling enable field, + * additive latency field, and Qoff field should be set to 0. + * (Check the byte 63 of SPD. If memory address lines of target physical + * rank is mirrored, MA3~MA8 and BA0~BA1 should be swapped.) */ + cmd = ddr3_get_mr1(DDR3_MR1_QOFF_ENABLE, DDR3_MR1_TQDS_DISABLE, 3, + DDR3_MR1_WRITE_LEVELING_DISABLE, 1, + DDR3_MR1_AL_DISABLE, DDR3_MR1_DLL_ENABLE); + addr = vx900_get_mrs_addr(1, cmd); + res = volatile_read(addr); + printram("MR1 cycle 0x%.4x @ addr 0x%.8x read 0x%.8x\n", cmd, addr, res); + + /* Step 12 - Issue MR0 cycle. Read a double word from the address depended + * on DRAM’s burst length, CAS latency and write recovery time settings. + * The read burst type field should be set to interleave. + * The mode field should be set to normal mode. + * The DLL reset field should be set to No. + * The DLL control for precharge PD field should be set to Fast exit. + * (Check the byte 63 of SPD. If memory address lines of target + * physical rank is mirrored, MA3~MA8 and BA0~BA1 should be swapped.) */ + cmd = ddr3_get_mr0(DDR3_MR0_PRECHARGE_FAST, 7, DDR3_MR0_MODE_NORMAL, + DDR3_MR0_DLL_RESET_NO, 7, + DDR3_MR0_BURST_TYPE_INTERLEAVED, + 1); + addr = vx900_get_mrs_addr(0, cmd); + res = volatile_read(addr); + printram("MR0 cycle 0x%.4x @ addr 0x%.8x read 0x%.8x\n", cmd, addr, res); + + /* Step 13 - Set Fun3_RX6B[2:0] to 110b (Long ZQ calibration command). */ + reg8 = pci_read_config8(mcu, 0x6b); + reg8 &= ~(0x03); + reg8 |= (3<<1); + pci_write_config8(mcu, 0x6b, reg8); + + /* Step 14 - Read a double word from any address of the DIMM. */ + reg32 = volatile_read(0x0); + printram("We just read 0x%x\n", reg32); +} \ No newline at end of file diff --git a/src/northbridge/via/vx900/forgotten.h b/src/northbridge/via/vx900/forgotten.h new file mode 100644 index 0000000..43641f7 --- /dev/null +++ b/src/northbridge/via/vx900/forgotten.h @@ -0,0 +1,78 @@ +#ifndef REDUNDANT_H +#define REDUNDANT_H + +#define DDR3_MR0_PRECHARGE_SLOW 0 +#define DDR3_MR0_PRECHARGE_FAST 1 +#define DDR3_MR0_MODE_NORMAL 0 +#define DDR3_MR0_MODE_TEST 1 +#define DDR3_MR0_DLL_RESET_NO 0 +#define DDR3_MR0_DLL_RESET_YES 1 +#define DDR3_MR0_BURST_TYPE_SEQUENTIAL 0 +#define DDR3_MR0_BURST_TYPE_INTERLEAVED 1 +#define DDR3_MR0_BURST_LENGTH_FIXED_8 0 +#define DDR3_MR0_BURST_LENGTH_CHOP 1 +#define DDR3_MR0_BURST_LENGTH_FIXED_4 2 +/** + * \brief Get command address for a DDR3 MR0 command + */ +u16 ddr3_get_mr0( + char precharge_pd, + u8 write_recovery, + char dll_reset, + char mode, + u8 cas, + char interleaved_burst, + u8 burst_lenght +); + +#define DDR3_MR1_TQDS_DISABLE 0 +#define DDR3_MR1_TQDS_ENABLE 1 +#define DDR3_MR1_QOFF_ENABLE 0 +#define DDR3_MR1_QOFF_DISABLE 1 +#define DDR3_MR1_WRITE_LEVELING_DISABLE 0 +#define DDR3_MR1_WRITE_LEVELING_ENABLE 1 +#define DDR3_MR1_RTT_NOM_OFF 0 +#define DDR3_MR1_RTT_NOM_RZQ4 1 +#define DDR3_MR1_RTT_NOM_RZQ2 2 +#define DDR3_MR1_RTT_NOM_RZQ6 3 +#define DDR3_MR1_RTT_NOM_RZQ12 4 +#define DDR3_MR1_RTT_NOM_RZQ8 5 +#define DDR3_MR1_AL_DISABLE 0 +#define DDR3_MR1_AL_CL_MINUS_1 1 +#define DDR3_MR1_AL_CL_MINUS_2 2 +#define DDR3_MR1_ODS_RZQ6 0 +#define DDR3_MR1_ODS_RZQ7 1 +#define DDR3_MR1_DLL_ENABLE 0 +#define DDR3_MR1_DLL_DISABLE 1 +/** + * \brief Get command address for a DDR3 MR1 command + */ +u16 ddr3_get_mr1( + char q_off, + char tdqs, + u8 rtt_nom, + char write_leveling, + u8 output_drive_strenght, + u8 additive_latency, + u8 dll_disable +); + +#define DDR3_MR2_RTT_WR_OFF 0 +#define DDR3_MR2_RTT_WR_RZQ4 1 +#define DDR3_MR2_RTT_WR_RZQ2 2 +/** + * \brief Get command address for a DDR3 MR2 command + */ +u16 ddr3_get_mr2( + u8 rtt_wr, + char extended_temp, + char auto_self_refresh, + u8 cas_write +); + +/** + * \brief Get command address for a DDR3 MR3 command + */ +u16 ddr3_get_mr3(char dataflow_from_mpr); + +#endif /* REDUNDANT_H */ \ No newline at end of file diff --git a/src/northbridge/via/vx900/raminit.h b/src/northbridge/via/vx900/raminit.h new file mode 100644 index 0000000..f90cb60 --- /dev/null +++ b/src/northbridge/via/vx900/raminit.h @@ -0,0 +1,56 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +#ifndef RAMINIT_VX900_H +#define RAMINIT_VX900_H + +#include <devices/dram/dram.h> + +/* The maximum number of DIMM slots that the VX900 supports */ +#define VX900_MAX_DIMM_SLOTS 2 + +#define VX900_MAX_MEM_RANKS 4 + + +#define SPD_END_LIST 0xff + +typedef struct dimm_layout_st +{ + /* The address of the DIMM on the SMBUS * + * 0xFF to terminate the array*/ + u8 spd_addr[VX900_MAX_DIMM_SLOTS + 1]; +} dimm_layout; + +typedef struct dimm_info_st +{ + dimm_attr dimm[VX900_MAX_DIMM_SLOTS]; +} dimm_info; + +typedef struct mem_rank_st { + u16 start_addr; + u16 end_addr; +}mem_rank; + +typedef struct rank_layout_st { + u32 phys_rank_size[VX900_MAX_MEM_RANKS]; + mem_rank virt[VX900_MAX_MEM_RANKS]; +} rank_layout; +void vx900_init_dram_ddr3(const dimm_layout *dimms); + +#endif /* RAMINIT_VX900_H */ diff --git a/src/northbridge/via/vx900/raminit_ddr3.c b/src/northbridge/via/vx900/raminit_ddr3.c new file mode 100644 index 0000000..2b1d9eb --- /dev/null +++ b/src/northbridge/via/vx900/raminit_ddr3.c @@ -0,0 +1,804 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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, see http://www.gnu.org/licenses/. + */ + +#include <arch/io.h> +#include <arch/romcc_io.h> + +#include "early_vx900.h" +#include "raminit.h" + +#include <string.h> +#include <console/console.h> +#include <device/pci_ids.h> + +#define min(a,b) a<b?a:b +#define max(a,b) a>b?a:b + +#define MCU PCI_DEV(0, 0, 3) + +typedef struct pci_reg8_st { + u8 addr; + u8 val; +} pci_reg8; + +/* These are some "safe" values that can be used for memory initialization. + * Some will stay untouched, and others will be overwritten later on */ +static pci_reg8 mcu_init_config[] = { + {0x40, 0x01}, /* Virtual rank 0 ending address = 64M - 1 */ + {0x48, 0x00}, /* Virtual rank 0 starting address = 0 */ + {0x50, 0xd8}, /* Set ranks 0-3 to 11 col bits, 16 row bits */ + {0x52, 0x33}, /* Map BA0 to A15, BA1 to A18 */ + {0x53, 0x0b}, /* Map BA2 to A19 */ + /* Disable rank interkeaving in ranks 0-3 */ + {0x58, 0x00}, {0x59, 0x00}, {0x5a, 0x00}, {0x5b, 0x00}, + {0x6c, 0xA0}, /* Memory type: DDR3, VDIMM: 1.5V, 64-bit DRAM */ + {0x6e, 0x38}, /* Burst lenght: 8, burst-chop: enable */ + {0xc6, 0x80}, /* Minimum latency from self-refresh. Bit [7] must be 1 */ + {0xc8, 0x80}, /* Enable automatic triggering of short ZQ calibration */ + /* Enable differential DQS; MODT assertion values suggested in DS */ + {0x9e, 0xa1}, {0x9f, 0x50} +}; + +/* FIXME: DO NOT ACCEPT THIS PATCH IF YOU SEE THIS ARRAY + * This is just a cheat sheet to get the config up and running, and is specific + * to my hardware setup. */ +static pci_reg8 mcu_cheat_sheet[] = { + /* Number of row, col and bank bits */ + {0x50, 0xa0}, + /* Rank interleave address select */ + {0x52, 0x11}, {0x53, 0x59}, + /* DRAM Pipeline control */ + {0x60, 0xf4}, {0x61, 0x2e}, + /* DRAM arbitration */ + {0x65, 0x49}, {0x66, 0x80}, + /* Bank interleave control; request reorder control */ + {0x69, 0xe7}, {0x6a, 0xfc}, + + /* very finetuned timing; do not touch; leave commented out */ + // {0x72, 0x0f}, {0x73, 0x04}, + // {0x76, 0x68}, {0x77, 0x10}, + + /* Top mem; need to calculate this dynamically */ + {0x84, 0x01}, {0x85, 0x40}, + + /* DRAM clocking control; mostly finetuned delays ; leave out */ + /*{0x91, 0x08}, {0x92, 0x08}, {0x93, 0x16}, + {0x94, 0x00}, {0x95, 0x16}, {0x96, 0x00}, {0x97, 0xa4}, + {0x98, 0xba}, {0x99, 0xff}, {0x9a, 0x80}, + {0x9c, 0xe4}, + */ + +}; +/* FIXME: Cheat sheet number two + * We need to set driving strength before trying to operate the controller + * It's like putting it in first gear before pushing the accelerator */ +static pci_reg8 memdrv_cheat_sheet[] = { + /* Enable memory clock output */ + //{0x9b, 0x3f}, // on by default + + {0xd3, 0x01}, /* Enable controller ODT auto-compensation */ + + /* Memory driving controls */ + /* ?? Internal ODT control ?? */ + //{0xd4, 0x80}, + /* ?? ODT Range selection ?? */ + {0xd5, 0x04}, + /* ?? MCLK/MA/MCS driving selection ?? */ + //{0xd6, 0x20}, + /* Auto compensation mode for _everything_ */ + {0xe1, 0x7e}, + +}; + +static void vx900_dram_write_init_config(void) +{ + size_t i; + for(i = 0; i < (sizeof(mcu_init_config)/sizeof(pci_reg8)); i++) + pci_write_config8(MCU, mcu_init_config[i].addr, + mcu_init_config[i].val); +} + +static void dram_find_spds_ddr3(const dimm_layout *addr, dimm_info *dimm) +{ + size_t i = 0; + int dimms = 0; + do { + spd_raw_data spd; + spd_read(addr->spd_addr[i], spd); + spd_decode_ddr3(&dimm->dimm[i], spd); + if(dimm->dimm[i].dram_type != DRAM_TYPE_DDR3) continue; + dimms++; + dram_print_spd_ddr3(&dimm->dimm[i]); + } while(addr->spd_addr[++i] != SPD_END_LIST + && i < VX900_MAX_DIMM_SLOTS); + + if(!dimms) + die("No DIMMs were found"); +} + +static void dram_find_common_params(const dimm_info *dimms, ramctr_timing *ctrl) +{ + size_t i, valid_dimms; + memset(ctrl, 0, sizeof(ramctr_timing)); + ctrl->cas = 0xff; + valid_dimms = 0; + for(i = 0; i < VX900_MAX_DIMM_SLOTS; i++) + { + const dimm_attr *dimm = &dimms->dimm[i]; + if(dimm->dram_type == DRAM_TYPE_UNDEFINED) continue; + valid_dimms++; + + if(valid_dimms == 1) { + /* First DIMM defines the type of DIMM */ + ctrl->dram_type = dimm->dram_type; + } else { + /* Check if we have mismatched DIMMs */ + if(ctrl->dram_type != dimm->dram_type) + die("Mismatched DIMM Types"); + } + /* Find all possible CAS combinations */ + ctrl->cas &= dimm->cas; + + /* Find the smallest common latencies supported by all DIMMs */ + ctrl->tCK = max(ctrl->tCK, dimm->tCK ); + ctrl->tAA = max(ctrl->tAA, dimm->tAA ); + ctrl->tWR = max(ctrl->tWR, dimm->tWR ); + ctrl->tRCD = max(ctrl->tRCD, dimm->tRCD); + ctrl->tRRD = max(ctrl->tRRD, dimm->tRRD); + ctrl->tRP = max(ctrl->tRP, dimm->tRP ); + ctrl->tRAS = max(ctrl->tRAS, dimm->tRAS); + ctrl->tRC = max(ctrl->tRC, dimm->tRC ); + ctrl->tRFC = max(ctrl->tRFC, dimm->tRFC); + ctrl->tWTR = max(ctrl->tWTR, dimm->tWTR); + ctrl->tRTP = max(ctrl->tRTP, dimm->tRTP); + ctrl->tFAW = max(ctrl->tFAW, dimm->tFAW); + + } + + if(!ctrl->cas) die("Unsupported DIMM combination. " + "DIMMS do not support common CAS latency"); + if(!valid_dimms) die("No valid DIMMs found"); +} + +static void vx900_dram_phys_bank_range(const dimm_info *dimms, + rank_layout *ranks) +{ + size_t i; + u8 reg8; + for(i = 0; i < VX900_MAX_DIMM_SLOTS; i ++) + { + if(dimms->dimm[i].dram_type == DRAM_TYPE_UNDEFINED) + continue; + u8 nranks = dimms->dimm[i].ranks; + if(nranks > 2) + die("Found DIMM with more than two ranks, which is not" + "supported by this chipset"); + u32 size = dimms->dimm[i].size; + if(nranks == 2) { + /* Each rank holds half the capacity of the DIMM */ + size >>= 1; + ranks->phys_rank_size[i<<1] = size; + ranks->phys_rank_size[(i<<1) | 1] = size; + } else { + /* Otherwise, everything is held in the first bank */ + ranks->phys_rank_size[i<<1] = size; + ranks->phys_rank_size[(i<<1) | 1] = 0;; + } + } + /* There's a tiny thing we need to do: + * set the column and row address bits + * FIXME: Really bad programming here */ + reg8 = 0;// ((( (1<<2) | (dimms->dimm[0].col_bits - 9) ) & 0x7) << 2); + reg8 |= ((( (1<<2) | (dimms->dimm[1].col_bits - 9) ) & 0x7) << 5); + pci_write_config8(PCI_DEV(0,0,3), 0x50, reg8); + +} + +static void vx900_dram_driving_ctrl(void) +{ + /* My memory drive strength cheat sheet */ + size_t i; + for(i = 0; i < (sizeof(memdrv_cheat_sheet)/sizeof(pci_reg8)); i++) + pci_write_config8(MCU, memdrv_cheat_sheet[i].addr, + memdrv_cheat_sheet[i].val); +} + +static void vx900_dram_range(ramctr_timing *ctrl, rank_layout *ranks) +{ + size_t i, vrank = 0; + u8 reg8; + u32 reg32 = 0, ramsize = 0; + for(i = 0; i < VX900_MAX_MEM_RANKS; i++) + { + u32 rank_size = ranks->phys_rank_size[i]; + if(!rank_size) continue; + ranks->virt[vrank].start_addr = ramsize; + ramsize += rank_size; + ranks->virt[vrank].end_addr = ramsize; + + /* Physical to virtual rank mapping */ + /* The position of the bit that enables physical rank [i] */ + const char rank_en_bit[] = {3, 7, 11, 15}; + /* The position of the LSB of the VR mapping of rank [i] */ + const char rank_map_pos[] = {0, 4, 8, 12}; + /* Enable the physical rank */ + reg32 |= ( 1 << rank_en_bit[i]); + /* Map the physical rank to the first unused virtual rank */ + reg32 |= (vrank << rank_map_pos[i]); + /* Rank memory range */ + reg8 = (ranks->virt[vrank].start_addr >> 2); + pci_write_config8(MCU, 0x48 + vrank, reg8); + reg8 = (ranks->virt[vrank].end_addr >> 2); + pci_write_config8(MCU, 0x40 + vrank, reg8); + + printram("Mapped Physical rank %u, to virtual rank %u\n" + " Start address: 0x%.10x000000\n" + " End address: 0x%.10x000000\n", + (int) i, (int) vrank, + ranks->virt[vrank].start_addr, + ranks->virt[vrank].end_addr); + + /* Move on to next virtual rank */ + vrank++; + } + + /* Finally, write the mapping configuration to the MCU */ + pci_write_config32(MCU, 0x54, reg32); + + printram("Initialized %u virtual ranks, with a total size of %u MB\n", + (int) vrank, ramsize << 4); +} + +static void vx900_dram_freq_latency(ramctr_timing *ctrl) +{ + u8 reg8, val; + u32 val32; + + /* Maximum supported DDR3 frequency is 533MHz (DDR3 1066) + * so make sure we cap it if we have faster DIMMs */ + if(ctrl->tCK < TCK_533MHZ) ctrl->tCK = TCK_533MHZ; + val32 = (1000 << 8) / ctrl->tCK; + printram("Selected DRAM frequency: %u MHz\n", val32); + + /* To change the DRAM frequency, it is required to set this bits to + * 1111b to reset the PLL first and set to the target value then. + * Minimum assertion time for the PLL Reset is 10 ns. + * Datasheet also states that the DRAM frequency must be set first */ + /* Reset the PLL */ + pci_write_config8(MCU, 0x90, 0x0f); + /* Wait at least 10 ns; in this case, we will wait about 1 us */ + inb(0x80); + /* Now find the right DRAM frequency setting, + * and bring it to the closest JEDEC standard frequency */ + if(ctrl->tCK <= TCK_533MHZ) {val = 0x07; ctrl->tCK = TCK_533MHZ;} + else if(ctrl->tCK <= TCK_400MHZ) {val = 0x06; ctrl->tCK = TCK_400MHZ;} + else if(ctrl->tCK <= TCK_333MHZ) {val = 0x05; ctrl->tCK = TCK_333MHZ;} + else if(ctrl->tCK <= TCK_266MHZ) {val = 0x04; ctrl->tCK = TCK_266MHZ;} + + reg8 = val & 0x0f; + /* Restart the PLL with the correct frequency */ + pci_write_config8(MCU, 0x90, reg8); + + /* If we have registered DIMMs, we need to set bit[0] */ + if(dimm_is_registered(ctrl->dram_type)){ + printram("Enabling RDIMM support in memory controller\n"); + reg8 = pci_read_config8(MCU, 0x6c); + reg8 |= 1; + pci_write_config8(MCU, 0x6c, reg8); + } + + /* Here we are calculating latencies, and writing them to the appropiate + * registers. Some registers do not take latencies from 0T, for example: + * CAS: 000 = 4T, 001 = 5T, 010 = 6T, etc + * In this example we subtract 4T from the result for CAS: (val - 4) + * The & 0x07 after (val - T0) just makes sure that, no matter what + * crazy thing may happen, we do not write outside the bits allocated + * in the register */ + + /* Find CAS latency */ + val = (ctrl->tAA + ctrl->tCK -1) / ctrl->tCK; + printram("Minimum CAS latency : %uT\n", val); + /* Find lowest supported CAS latency that satisfies the minimum value */ + while( !((ctrl->cas >> (val-4))&1) && (ctrl->cas >> (val-4))) val++; + /* Is CAS supported */ + if(!(ctrl->cas & (1 << (val-4))) ) printram("CAS not supported\n"); + printram("Selected CAS latency : %uT\n", val); + /* Write CAS latency */ + val -= 4; /* 000 means 4T */ + reg8 = ((val &0x07) << 4 ) | (val & 0x07); + pci_write_config8(MCU, 0xc0, reg8); + + /* Find tRCD */ + val = (ctrl->tRCD + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tRCD : %uT\n", val); + reg8 = ((val-4) & 0x7) << 4; + /* Find tRP */ + val = (ctrl->tRP + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tRP : %uT\n", val); + reg8 |= ((val-4) & 0x7); + pci_write_config8(MCU, 0xc1, reg8); + + /* Find tRAS */ + val = (ctrl->tRAS + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tRAS : %uT\n", val); + reg8 = ((val-15) & 0x7) << 4; + /* Find tWR */ + val = (ctrl->tWR + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tWR : %uT\n", val); + reg8 |= ((val-4) & 0x7); + pci_write_config8(MCU, 0xc2, reg8); + + /* Find tFAW */ + val = (ctrl->tFAW + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tFAW : %uT\n", val); + reg8 = ((val-0) & 0x7) << 4; + /* Find tRRD */ + val = (ctrl->tRRD + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tRRD : %uT\n", val); + reg8 |= ((val-2) & 0x7); + pci_write_config8(MCU, 0xc3, reg8); + + /* This register needs a little more attention, as bit 7 controls some + * other stuff: + * [7] 8-Bank Device Timing Constraint (See datasheet) + * Since all DDR3 modules are minimum 8-banks, there's no need to + * worry about this bit, and we can just set it; + * [6] Reserved + * [5:4] tRTP with 00 = 3T for DDR3 + * [3] Reserved + * [2-0] tWTR with 000 = 2T + */ + reg8 = 0x80; + /* Find tRTP */ + val = (ctrl->tRTP + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tRTP : %uT\n", val); + reg8 |= ((val & 0x3) << 4); + /* Find tWTR */ + val = (ctrl->tWTR + ctrl->tCK -1) / ctrl->tCK; + printram("Selected tWTR : %uT\n", val); + reg8 |= ((val - 2) & 0x7); + pci_write_config8(MCU, 0xc4, reg8); + + /* DRAM Timing for All Ranks - VI + * [7:6] CKE Assertion Minimum Pulse Width + * We probably don't want to mess with this just yet. + * [5:0] Refresh-to-Active or Refresh-to-Refresh (tRFC) + * RxC4[7] = 0: + * tRFC = (10 + [5:0])T + * RxC4[7] = 1: + * tRFC = (30 + 2 * [5:0])T + * Since we previously set RxC4[7], we're going to use this formula + */ + reg8 = pci_read_config8(MCU, 0xc5); + val = (ctrl->tRFC + ctrl->tCK -1) / ctrl->tCK; + printram("Minimum tRFC : %uT\n", val); + if(ctrl->tRFC < 30) { + val = 0; + } else { + val = (val -30 + 1 ) / 2; + } + ; + printram("Selected tRFC : %uT\n", 30 + 2 * val); + reg8 |= (val & 0x1f); + pci_write_config8(MCU, 0xc5, reg8); + + /* Where does this go??? */ + val = (ctrl->tRC + ctrl->tCK -1) / ctrl->tCK; + printram("Required tRC : %uT\n", val); +} + +/* The VX900 can send the MRS commands directly through hardware */ +static void vx900_dram_ddr3_do_hw_mrs(u8 ma_swap, u8 rtt_nom, + u8 ods, u8 rtt_wr, u8 srt, u8 asr) +{ + u8 reg8 = 0; + if(asr) reg8 |= (1 << 0); + if(srt) reg8 |= (1 << 1); + reg8 |= ((rtt_wr & 0x03) << 4); + pci_write_config8(MCU, 0xcd, reg8); + reg8 = 1; + if(ma_swap) reg8 |= (1 << 1); + reg8 |= ((ods & 0x03) << 2); + reg8 |= ((rtt_nom & 0x7) << 4); + pci_write_config8(MCU, 0xcc, reg8); + while(pci_read_config8(MCU, 0xcc) & 1); +} +#include "forgotten.h" +#include "forgotten.c" +static void vx900_dump_0x78_0x7f(void) +{ + u8 i; + for(i = 0x78; i < 0x80; i ++) + { + printram(" %.2x", pci_read_config8(MCU, i)); + } + printram("\n"); +} + +static void vx900_dump_calib(const u8 what) +{ + u8 reg8; + /* Dump lower bound */ + reg8 = ((what & 0x3) << 2) | 0x1; + pci_write_config8(MCU, 0x70, reg8); + printram("Lower bound : "); + vx900_dump_0x78_0x7f(); + + /* Dump upper bound */ + reg8 = ((what & 0x3) << 2) | 0x2; + pci_write_config8(MCU, 0x70, reg8); + printram("Upper bound : "); + vx900_dump_0x78_0x7f(); + + /* Dump average values */ + reg8 = ((what & 0x3) << 2); + pci_write_config8(MCU, 0x70, reg8); + printram("Average : "); + vx900_dump_0x78_0x7f(); +} + +static void vx900_dram_send_soft_mrs(u8 type, u16 cmd) +{ + u8 reg8; + u32 addr; + /* Set Fun3_RX6B[2:0] to 011b (MSR Enable). */ + reg8 = pci_read_config8(MCU, 0x6b); + reg8 &= ~(0x03); + reg8 |= (3<<0); + pci_write_config8(MCU, 0x6b, reg8); + /* Find the address corresponding to the MRS */ + addr = vx900_get_mrs_addr(type, cmd); + /* Execute the MRS */ + volatile_read(addr); + /* Set Fun3_Rx6B[2:0] to 000b (Normal SDRAM Mode). */ + reg8 = pci_read_config8(MCU, 0x6b); + reg8 &= ~(7<<0); + pci_write_config8(MCU, 0x6b, reg8); +} +static void vx900_rx_capture_range_calib(u8 pattern); +static void vx900_rx_dqs_delay_calib(u8 pattern); +static void vx900_tx_dq_delay_calib(void); +static void vx900_tx_dqs_delay_calib(const u8 rank); + +static void vx900_delay_calib_rank(const u8 rank) +{ + u8 reg8, val; + u16 reg16; + printram("Starting delay calibration for rank %u\n", rank); + + reg16 = 0xbbbb; + reg16 &= ~(0xf << (4 * rank)); + reg16 |= (0x8 << (4 * rank)); + pci_write_config16(MCU, 0x54, reg16); + + /* MD Input Data Push Timing Control; + * use values recommended in datasheet */ + reg8 = pci_read_config8(MCU, 0x74); + val = (TCK_533MHZ <= TCK_400MHZ)?2:1; /* FIXME*/ + reg8 &= ~(0x03 << 1); + reg8 |= ((val & 0x03) << 1); + pci_write_config8(MCU, 0x74, reg8); + + /* Disable additional delays */ + pci_write_config8(MCU, 0xec, 0x00); + pci_write_config8(MCU, 0xed, 0x00); + pci_write_config8(MCU, 0xee, 0x00); + + pci_write_config8(MCU, 0x77, 0x10);/*FIXME*/ + const u8 pattern = 0x40; /*FIXME*/ + //const u8 pattern = 0x00; /*FIXME*/ + /* Run calibrations */ + vx900_rx_capture_range_calib(pattern); + vx900_rx_dqs_delay_calib(pattern); + vx900_tx_dq_delay_calib(); + vx900_tx_dqs_delay_calib(rank); +} + +static void vx900_rx_capture_range_calib(u8 pattern) +{ + u8 reg8; + u16 cmd; + /* + * Calibration of RX Capture Range can be triggered by setting + * Rx71[5] to 1. + * The process started by sending a series of read commands on the DRAM + * bus with different setting for the RX Capture Range. + * + * After a MRS (Mode Register Set) command to put DDR3 in a mode called + * “Read Leveling Mode”, + */ + /* Put DRAM in read leveling mode */ + cmd = ddr3_get_mr3(1); + vx900_dram_send_soft_mrs(3, cmd); + + /* DDR3 returned the read command with a predefined + * data pattern either “00h-01h-00h-01h” or “00h-FFh-00h-FFh”. There is + * no specific definition in the current DDR3 specification for this + * pattern, so, the working software might need to do the calibration + * twice. It could triggered the scanning with + * Rx71[6] set to 1 first. + * If nothing can be working right, the software trigger the scanning + * again with + * Rx71[6] set to 0. + * The controller thus can check those pattern to verify if the data + * from DDR3 DIMM is correct or not. The calibration will work from the + * lowest limit of the setting for the RX Capture Range, it marked the + * setting for its lower bound when checking is correct. It increased + * the setting and issued read cycle again and again until checking of + * the data is incorrect. It then marked the last working setting as + * the upper bound. + * Controller also calculated the average of the lower bound and upper + * bound to have the center of the setting, which presumably the most + * stable one. Besides this capture range setting, there is another set + * of delay control for internal MD paths at RxEF[5:4]. + * However, that one is independent of the calibration mechanism here.*/ + /* Data pattern must be 0x00 for this calibration */ + pci_write_config8(MCU, 0x8e, 0x00); + /* Trigger calibration */ + reg8 = 0xa0 | (pattern & 0x40); + pci_write_config8(MCU, 0x71, reg8); + + printram("RX capture range calibration results:\n"); + vx900_dump_calib(3); + + /* Disable read leveling, and put dram in normal operation mode */ + cmd = ddr3_get_mr3(0); + vx900_dram_send_soft_mrs(3, cmd); +} + +static void vx900_rx_dqs_delay_calib(u8 pattern) +{ + u8 reg8; + /* The same process is done by setting + * Rx71[1] to 1 + * to start the calibration for RX DQS Delay. Besides the manual + * setting, there is also a setting which is calculated by using + * internal DCLK DLL to have a 1/4T delay for DQS. + * Noted that when this option is set + * (Rx71[2] =1) + * the DQS of all the bytes (MDQS[7:0]P/N) will use this self-calculated + * 1/4 delay setting. */ + + /* Data pattern must be 0x00 for this calibration */ + pci_write_config8(MCU, 0x8e, 0x00); + /* Trigger calibration */ + reg8 = 0x82 | (pattern & 0x40); + pci_write_config8(MCU, 0x71, reg8); + + printram("RX DQS delay calibration results:\n"); + vx900_dump_calib(2); +} +static void vx900_tx_dq_delay_calib() +{ + /* The calibration for TX DQ Delay is started once + * Rx75[1] is set to 1. + * That process is to issue a write with + * data defined by Rx8E[7:0] + * and to issue a read command right after it to do the checking. + * Before issuing the write command, the calibration controller will set + * the setting of TX DQ Delay. It started with the smallest setting, and + * it will mark the one as lower bound when the corresponding checking + * is right. It continue with the write + read + checking processes and + * see if it started to fail. The last working setting is the upper + * bound. This process will not stop until all the upper bound are found + * for all the bytes. */ + /* Data pattern for calibration */ + pci_write_config8(MCU, 0x8e, 0x5a); + /* Trigger calibration */ + pci_write_config8(MCU, 0x75, 0x02); + + printram("TX DQ delay calibration results:\n"); + vx900_dump_calib(1); +} +static void vx900_tx_dqs_delay_calib(const u8 rank) +{ + u8 reg8; + u16 cmd; + /* Noted that there are two types of calibration for the TX DQS Delay. + * Setting Rx75[5] to 1, + * the calibration process will go as that of TX DQ Delay. Only that the + * adjustment made is on the TX DQS output path not the TX DQ output + * path. There is another way of calibration worked with DDR3 DRAM + * called “Write Leveling”. The procedure would be + * 1) Set Rx9E[3:2] to select which Rank to do write leveling. + * 2) Issue MRS commands to each Rank of DRAM to let them know which one + * is enabled for Write leveling. + * 3) Set Rx75[2] to 1 to trigger the operation. + * After step 3, the controller will adjust the TX DQS delay by one step + * and send a pulse of DQS out to DRAM. The bit0 of MD of a byte + * (MD0, MD8, MD16, .. MD57) will return the status (“high” or “low”) of + * DCLK the selected Rank sensed. The controller will continue adjusting + * the delay and sending a pulse of MDQS out. This scheme is to detect + * the MDQS delay against the DCLK’s rising and falling edge. Thus we + * can know where the lower bound of the TX DQS setting is when the + * falling edge of DCLK is found, and where the upper bound of the + * TX DQS setting is when the rising edge of DCLK is found by the DRAM. + * With the upper bound and lower bound, we can choose the middle point + * as the setting for the calibration (scan) result. */ + + reg8 = pci_read_config8(MCU, 0x9e); + reg8 &= ~(3 << 2); + reg8 |= ((rank & 0x3) << 2); + pci_write_config8(MCU, 0x9e, reg8); + cmd = ddr3_get_mr1(0, 0, 0, 1, 0, 0, 0); + vx900_dram_send_soft_mrs(1, cmd); + /* Data pattern for calibration */ + pci_write_config8(MCU, 0x8e, 0x5a); + /* Trigger calibration */ + pci_write_config8(MCU, 0x75, 0x04); + printram("TX DQS delay calibration results:\n"); + vx900_dump_calib(0); + cmd = ddr3_get_mr1(0, 0, 0, 0, 0, 0, 0); + vx900_dram_send_soft_mrs(1, cmd); + +} + +static void vx900_dram_ddr3_dimm_init(const ramctr_timing *ctrl, + const rank_layout *ranks) +{ + u8 reg8; + size_t i; + + /* Step 01 - Set Fun3_Rx6E[5] to 1b to support burst length. */ + /* This is set at init time + reg8 = pci_read_config8(MCU, 0x6e); + reg8 |= (1<<5); + pci_write_config8(MCU, 0x6e, reg8);*/ + /* Step 02 - Set Fun3_RX69[0] to 0b (Disable Multiple Page Mode). */ + reg8 = pci_read_config8(MCU, 0x69); + reg8 &= ~(1<<0); + pci_write_config8(MCU, 0x69, reg8); + + /* Step 03 - Set the target physical rank to virtual rank0 and other + * ranks to virtual rank3. If physical rank0 init is desired, + * set Fun3_Rx54 to 08Bh and Fun3_Rx55=0BBh. */ + pci_write_config16(MCU, 0x54, 0xbbb8); + + /* Step 04 - Set Fun3_Rx50 to D8h. */ + pci_write_config8(MCU, 0x50, 0xd8); + /* Step 05 - Set Fun3_RX6B[5] to 1b to de-assert RESET# and wait for at + * least 500 us. */ + reg8 = pci_read_config8(MCU, 0x6b); + reg8 |= (1<<5); + pci_write_config8(MCU, 0x6b, reg8); + for(i = 0; i < 500; i++) inb(0x80); + + for(i = 0; i < VX900_MAX_MEM_RANKS; i++) + { + u16 reg16; + if(ranks->phys_rank_size[i] == 0) continue; + printram("Initializing rank %lu\n", i); + + const u8 rtt_nom = 2; + const u8 ods = 0; + const u8 rtt_wr = 1; + + reg16 = 0xbbbb; + reg16 &= ~(0xf << (4 * i)); + reg16 |= (0x8 << (4 * i)); + pci_write_config16(MCU, 0x54, reg16); + vx900_dram_ddr3_do_hw_mrs(0, rtt_nom, ods, rtt_wr, 0, 0); + vx900_delay_calib_rank(i); + } + /* Step 15 – Set the next target physical rank to virtual rank 0 and + * other ranks to virtual rank 3. Repeat Step 6 to 14, then jump to + * Step 16. */ + + /* Step 16 – Set Fun3_Rx6B[2:0] to 000b (Normal SDRAM Mode). */ + reg8 = pci_read_config8(MCU, 0x6b); + reg8 &= ~(7<<0); + pci_write_config8(MCU, 0x6b, reg8); + + /* Step 17 – Set Fun3_Rx69[0] to 1b (Enable Multiple Page Mode). */ + reg8 = pci_read_config8(MCU, 0x69); + reg8 |= (1<<0); + pci_write_config8(MCU, 0x69, reg8); + + printram("DRAM initialization sequence complete\n"); +} + +static void print_debug_pci_dev(device_t dev) +{ + print_debug("PCI: "); + print_debug_hex8((dev >> 20) & 0xff); + print_debug_char(':'); + print_debug_hex8((dev >> 15) & 0x1f); + print_debug_char('.'); + print_debug_hex8((dev >> 12) & 7); +} + +static void dump_pci_device(device_t dev) +{ + int i; + print_debug_pci_dev(dev); + print_debug("\n"); + + for (i = 0; i <= 255; i++) { + unsigned char val; + if ((i & 0x0f) == 0) { + print_debug_hex8(i); + print_debug_char(':'); + } + + if ((i & 0x0f) == 0x08) { + print_debug(" |"); + } + + val = pci_read_config8(dev, i); + print_debug_char(' '); + print_debug_hex8(val); + + if ((i & 0x0f) == 0x0f) { + print_debug("\n"); + } + } +} + +static void vx900_dram_write_final_config(ramctr_timing *ctrl) +{ + u8 reg8; + /* Set DRAM refresh counter + * Based on a refresh counter of 0x61 at 400MHz */ + reg8 = (TCK_400MHZ * 0x61) / ctrl->tCK; + pci_write_config8(MCU, 0xc7, reg8); + + /* My cheat sheet */ + size_t i; + for(i = 0; i < (sizeof(mcu_cheat_sheet)/sizeof(pci_reg8)); i++) + pci_write_config8(MCU, mcu_cheat_sheet[i].addr, + mcu_cheat_sheet[i].val); +} + +void vx900_init_dram_ddr3(const dimm_layout *dimm_addr) +{ + dimm_info dimm_prop; + ramctr_timing ctrl_prop; + rank_layout ranks; + device_t mcu; + /* Locate the Memory controller */ + mcu = pci_locate_device(PCI_ID(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_VX900_DRAM_CTR), 0); + + if (mcu == PCI_DEV_INVALID) { + die("Memory Controller not found\n"); + } + + memset(&ranks, 0, sizeof(ranks)); + /* 1) Write some initial "safe" parameters */ + vx900_dram_write_init_config(); + /* 2) Get timing information from SPDs */ + dram_find_spds_ddr3(dimm_addr, &dimm_prop); + /* 3) Find lowest common denominator for all modules */ + dram_find_common_params(&dimm_prop, &ctrl_prop); + /* 4) Find the size of each memory rank */ + vx900_dram_phys_bank_range(&dimm_prop, &ranks); + /* 5) Set DRAM driving strength */ + vx900_dram_driving_ctrl(); + /* 6) Set DRAM frequency and latencies */ + vx900_dram_freq_latency(&ctrl_prop); + /* 7) Initialize the modules themselves */ + vx900_dram_ddr3_dimm_init(&ctrl_prop, &ranks); + /* 8) Enable Physical to Virtual Rank mapping */ + vx900_dram_range(&ctrl_prop, &ranks); + /* 99) Some final adjustments */ + vx900_dram_write_final_config(&ctrl_prop); + /* Take a dump */ + dump_pci_device(mcu); + +} diff --git a/src/northbridge/via/vx900/romstrap.inc b/src/northbridge/via/vx900/romstrap.inc new file mode 100644 index 0000000..8e54627 --- /dev/null +++ b/src/northbridge/via/vx900/romstrap.inc @@ -0,0 +1,50 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2004 Tyan Computer + * (Written by Yinghai Lu yhlu@tyan.com for Tyan Computer) + * Copyright (C) 2007 Rudolf Marek r.marek@assembler.cz + * Copyright (C) 2009 One Laptop per Child, Association, Inc. + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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 + */ + +/* This file constructs the ROM strap table for VX900 */ + + .section ".romstrap", "a", @progbits + + .globl __romstrap_start +__romstrap_start: +tblpointer: + .long 0x77886047 + .long 0x00777777 + .long 0x00000000 + .long 0x00000000 + .long 0x00888888 + .long 0x00AA1111 + .long 0x00000000 + .long 0x00000000 + +/* + * The pointer to above table should be at 0xffffffd0, + * the table itself MUST be aligned to 128B it seems! + */ +rspointers: + .long tblpointer // It will be 0xffffffd0 + + .globl __romstrap_end + +__romstrap_end: +.previous diff --git a/src/northbridge/via/vx900/romstrap.lds b/src/northbridge/via/vx900/romstrap.lds new file mode 100644 index 0000000..b51c979 --- /dev/null +++ b/src/northbridge/via/vx900/romstrap.lds @@ -0,0 +1,27 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007 AMD + * (Written by Yinghai Lu yinghai.lu@amd.com for AMD) + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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 3 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, see http://www.gnu.org/licenses/ + */ + +SECTIONS { + . = (0x100000000 - 0x2c) - (__romstrap_end - __romstrap_start); + .romstrap (.): { + *(.romstrap) + } +} diff --git a/src/northbridge/via/vx900/vx900.h b/src/northbridge/via/vx900/vx900.h new file mode 100644 index 0000000..2137daf --- /dev/null +++ b/src/northbridge/via/vx900/vx900.h @@ -0,0 +1,26 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Alexandru Gagniuc mr.nuke.me@gmail.com + * + * 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 3 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, see http://www.gnu.org/licenses/. + */ + +#define VX800_ACPI_IO_BASE 0x0400 + +#define VX900_NB_IOAPIC_ID 0x2 +#define VX900_NB_IOAPIC_BASE 0xfecc000 + +#define VX900_SB_IOAPIC_ID 0x1 +#define VX900_SB_IOAPIC_BASE 0xfec0000 \ No newline at end of file