aboutsummaryrefslogtreecommitdiff
path: root/src/northbridge/intel/i82810/raminit.c
blob: f0d5cb71e655a5ff0f3ea20336f23a51f8fb8221 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/*
 * This file is part of the LinuxBIOS project.
 *
 * Copyright (C) 2007 Uwe Hermann <uwe@hermann-uwe.de>
 * Copyright (C) 2007 Corey Osgood <corey@slightlyhackish.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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include <spd.h>
#include <sdram_mode.h>
#include <delay.h>
#include "i82810.h"

/*-----------------------------------------------------------------------------
Macros and definitions.
-----------------------------------------------------------------------------*/

/* Uncomment this to enable debugging output. */
// #define DEBUG_RAM_SETUP 1

/* Debugging macros. */
#if defined(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

/* DRAMT[7:5] - SDRAM Mode Select (SMS). */
#define RAM_COMMAND_SELF_REFRESH 0x0	/* IE disable refresh */
#define RAM_COMMAND_NORMAL	 0x1	/* Normal refresh, 15.6us/11.7us for 100/133MHz */
#define RAM_COMMAND_NORMAL_FR	 0x2	/* Fast refresh, 7.8us/5.85us for 100/133MHz */
#define RAM_COMMAND_NOP		 0x4
#define RAM_COMMAND_PRECHARGE	 0x5
#define RAM_COMMAND_MRS		 0x6
#define RAM_COMMAND_CBR		 0x7

/*-----------------------------------------------------------------------------
SDRAM configuration functions.
-----------------------------------------------------------------------------*/

/**
 * Send the specified RAM command to all DIMMs.
 *
 * @param Memory controller
 * @param TODO
 * @param TODO
 */
static void do_ram_command(const struct mem_controller *ctrl, uint32_t command,
			   uint32_t addr_offset, uint32_t row_offset)
{
	uint8_t reg;

	/* TODO: Support for multiple DIMMs. */

	/* Configure the RAM command. */
	reg = pci_read_config8(ctrl->d0, DRAMT);
	reg &= 0x1f;		/* Clear bits 7-5. */
	reg |= command << 5;
	pci_write_config8(ctrl->d0, DRAMT, reg);

	/* RAM_COMMAND_NORMAL affects only the memory controller and
	   doesn't need to be "sent" to the DIMMs. */
	/* if (command == RAM_COMMAND_NORMAL) return; */

	PRINT_DEBUG("    Sending RAM command 0x");
	PRINT_DEBUG_HEX8(reg);
	PRINT_DEBUG(" to 0x");
	PRINT_DEBUG_HEX32(0 + addr_offset);	// FIXME
	PRINT_DEBUG("\r\n");

	/* Read from (DIMM start address + addr_offset). */
	read32(0 + addr_offset);	//first offset is always 0
	read32(row_offset + addr_offset);
}

/*-----------------------------------------------------------------------------
DIMM-independant configuration functions.
-----------------------------------------------------------------------------*/

static void spd_set_dram_size(const struct mem_controller *ctrl,
			      uint32_t row_offset)
{
	/* The variables drp and dimm_size have to be ints since all the
	 * SMBus-related functions return ints, and its just easier this way.
	 */
	int i, drp, dimm_size;

	drp = 0x00;

	for (i = 0; i < DIMM_SOCKETS; i++) {
		/* First check if a DIMM is actually present. */
		if (smbus_read_byte(ctrl->channel0[i], 2) == 4) {
			print_debug("Found DIMM in slot ");
			print_debug_hex8(i);
			print_debug("\r\n");

			dimm_size = smbus_read_byte(ctrl->channel0[i], 31);

			/* WISHLIST: would be nice to display it as decimal? */
			print_debug("DIMM is 0x");
			print_debug_hex8(dimm_size * 4);
			print_debug("MB\r\n");

			/* The i810 can't handle DIMMs larger than 128MB per
			 * side. This will fail if the DIMM uses a
			 * non-supported DRAM tech, and can't be used until
			 * buffers are done dynamically.
			 * Note: the factory BIOS just dies if it spots this :D
			 */
			if (dimm_size > 32) {
				print_err("DIMM row sizes larger than 128MB not"
					  "supported on i810\r\n");
				print_err
				    ("Attempting to treat as 128MB DIMM\r\n");
				dimm_size = 32;
			}

			/* Set the row offset, in KBytes (should this be
			 * Kbits?). Note that this offset is the start of the
			 * next row.
			 */
			row_offset = (dimm_size * 4 * 1024);

			/* This is the way I was doing this, it's provided
			 * mainly as an alternative to the "new" way.
			 */

#if 0
			/* 8MB */
			if (dimm_size == 0x2)
				dimm_size = 0x1;
			/* 16MB */
			else if (dimm_size == 0x4)
				dimm_size = 0x4;
			/* 32MB */
			else if (dimm_size == 0x8)
				dimm_size = 0x7;
			/* 64 MB */
			else if (dimm_size == 0x10)
				dimm_size = 0xa;
			/* 128 MB */
			else if (dimm_size == 0x20)
				dimm_size = 0xd;
			else
				print_debug("Ram Size not supported\r\n");
#endif

			/* This array is provided in raminit.h, because it got
			 * extremely messy. The above way is cleaner, but
			 * doesn't support any asymetrical/odd configurations.
			 */
			dimm_size = translate_spd_to_i82810[dimm_size];

			print_debug("After translation, dimm_size is 0x");
			print_debug_hex8(dimm_size);
			print_debug("\r\n");

			/* If the DIMM is dual-sided, the DRP value is +2 */
			/* TODO: Figure out asymetrical configurations. */
			if ((smbus_read_byte(ctrl->channel0[i], 127) | 0xf) ==
			    0xff) {
				print_debug("DIMM is dual-sided\r\n");
				dimm_size += 2;
			}
		} else {
			print_debug("No DIMM found in slot ");
			print_debug_hex8(i);
			print_debug("\r\n");

			/* If there's no DIMM in the slot, set value to 0. */
			dimm_size = 0x00;
		}

		/* Put in dimm_size to reflect the current DIMM. */
		drp |= dimm_size << (i * 4);
	}

	print_debug("DRP calculated to 0x");
	print_debug_hex8(drp);
	print_debug("\r\n");

	pci_write_config8(ctrl->d0, DRP, drp);
}

static void set_dram_timing(const struct mem_controller *ctrl)
{
	/* TODO, for now using default, hopefully safe values. */
	// pci_write_config8(ctrl->d0, DRAMT, 0x00);
}

static void set_dram_buffer_strength(const struct mem_controller *ctrl)
{
	/* TODO: This needs to be set according to the DRAM tech
	 * (x8, x16, or x32). Argh, Intel provides no docs on this!
	 * Currently, it needs to be pulled from the output of
	 * lspci -xxx Rx92
	 */
	pci_write_config16(ctrl->d0, BUFF_SC, 0x77da);
}

/*-----------------------------------------------------------------------------
Public interface.
-----------------------------------------------------------------------------*/

/**
 * TODO.
 *
 * @param Memory controller
 */
static void sdram_set_registers(const struct mem_controller *ctrl)
{
	unsigned long val;

	/* TODO */
	pci_write_config8(ctrl->d0, GMCHCFG, 0x60);

	/* PAMR: Programmable Attributes Register
	 * Every pair of bits controls an address range:
	 * 00 = Disabled, all accesses are forwarded to the ICH
	 * 01 = Read Only
	 * 10 = Write Only
	 * 11 = Read/Write

	 * Bit  Range
	 * 7:6  000F0000 - 000FFFFF
	 * 5:4  000E0000 - 000EFFFF
	 * 3:2  000D0000 - 000DFFFF
	 * 1:0  000C0000 - 000CFFFF
	 */

	/* Ideally, this should be R/W for as many ranges as possible. */
	pci_write_config8(ctrl->d0, PAM, 0xff);

	/* Enabling the VGA Framebuffer currently screws up the rest of the boot.
	 * Disable for now */
	
	/* Enable 1MB framebuffer. */
	//pci_write_config8(ctrl->d0, SMRAM, 0xC0);

	//val = pci_read_config16(ctrl->d0, MISSC);
	/* Preserve reserved bits. */
	//val &= 0xff06;
	/* Set graphics cache window to 32MB, no power throttling. */
	//val |= 0x0001;
	//pci_write_config16(ctrl->d0, MISSC, val);

	//val = pci_read_config8(ctrl->d0, MISSC2);
	/* Enable graphics palettes and clock gating (not optional!) */
	//val |= 0x06;
	//pci_write_config8(ctrl->d0, MISSC2, val);
}

/**
 * TODO.
 *
 * @param Memory controller
 */
static void sdram_set_spd_registers(const struct mem_controller *ctrl)
{
	/* spd_set_dram_size() moved into sdram_enable() to prevent having
	 * to pass a variable between here and there.
	 */
	set_dram_buffer_strength(ctrl);

	set_dram_timing(ctrl);
}

/**
 * Enable SDRAM.
 *
 * @param Number of controllers
 * @param Memory controller
 */
static void sdram_enable(int controllers, const struct mem_controller *ctrl)
{
	int i;

	/* Todo: this will currently work with either one dual sided or two
	 * single sided DIMMs. Needs to work with 2 dual sided DIMMs in the
	 * long run.
	 */
	uint32_t row_offset;

	spd_set_dram_size(ctrl, row_offset);

	/* 1. Apply NOP. */
	PRINT_DEBUG("RAM Enable 1: Apply NOP\r\n");
	do_ram_command(ctrl, RAM_COMMAND_NOP, 0, row_offset);
	udelay(200);

	/* 2. Precharge all. Wait tRP. */
	PRINT_DEBUG("RAM Enable 2: Precharge all\r\n");
	do_ram_command(ctrl, RAM_COMMAND_PRECHARGE, 0, row_offset);
	udelay(1);

	/* 3. Perform 8 refresh cycles. Wait tRC each time. */
	PRINT_DEBUG("RAM Enable 3: CBR\r\n");
	do_ram_command(ctrl, RAM_COMMAND_CBR, 0, row_offset);
	for (i = 0; i < 8; i++) {
		read32(0);
		read32(row_offset);
		udelay(1);
	}

	/* 4. Mode register set. Wait two memory cycles. */
	PRINT_DEBUG("RAM Enable 4: Mode register set\r\n");
	do_ram_command(ctrl, RAM_COMMAND_MRS, 0x1d0, row_offset);
	udelay(2);

	/* 5. Normal operation (enables refresh) */
	PRINT_DEBUG("RAM Enable 5: Normal operation\r\n");
	do_ram_command(ctrl, RAM_COMMAND_NORMAL, 0, row_offset);
	udelay(1);

	PRINT_DEBUG("Northbridge following SDRAM init:\r\n");
	DUMPNORTH();
}