summaryrefslogtreecommitdiff
path: root/config/coreboot/haswell/patches/0010-haswell-NRI-Collect-SPD-info.patch
blob: 4c2a267052c9462bb46f9219fb8d16a5d610011f (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
339
340
341
342
343
344
From 354969af4361bcc7dc240ef5871d169728f7f0cc Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sat, 7 May 2022 13:48:53 +0200
Subject: [PATCH 10/26] haswell NRI: Collect SPD info

Collect SPD data from DIMMs and memory-down, and find the common
supported settings.

Change-Id: I4e6a1408a638a463ecae37a447cfed1d6556e44a
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
 .../intel/haswell/native_raminit/Makefile.inc |   1 +
 .../haswell/native_raminit/raminit_main.c     |   1 +
 .../haswell/native_raminit/raminit_native.h   |  57 +++++
 .../haswell/native_raminit/spd_bitmunching.c  | 206 ++++++++++++++++++
 4 files changed, 265 insertions(+)
 create mode 100644 src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c

diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.inc b/src/northbridge/intel/haswell/native_raminit/Makefile.inc
index 90af951c5a..ebf7abc6ec 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.inc
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.inc
@@ -2,3 +2,4 @@
 
 romstage-y += raminit_main.c
 romstage-y += raminit_native.c
+romstage-y += spd_bitmunching.c
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 9b42c25b40..2d2cfa48bb 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -20,6 +20,7 @@ struct task_entry {
 };
 
 static const struct task_entry cold_boot[] = {
+	{ collect_spd_info,                                       true, "PROCSPD",    },
 };
 
 /* Return a generic stepping value to make stepping checks simpler */
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index 885f0184f4..1a0793947e 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -3,6 +3,15 @@
 #ifndef HASWELL_RAMINIT_NATIVE_H
 #define HASWELL_RAMINIT_NATIVE_H
 
+#include <device/dram/ddr3.h>
+#include <northbridge/intel/haswell/haswell.h>
+
+#define SPD_LEN 256
+
+/* 8 data lanes + 1 ECC lane */
+#define NUM_LANES		9
+#define NUM_LANES_NO_ECC	8
+
 enum raminit_boot_mode {
 	BOOTMODE_COLD,
 	BOOTMODE_WARM,
@@ -12,6 +21,8 @@ enum raminit_boot_mode {
 
 enum raminit_status {
 	RAMINIT_STATUS_SUCCESS = 0,
+	RAMINIT_STATUS_NO_MEMORY_INSTALLED,
+	RAMINIT_STATUS_UNSUPPORTED_MEMORY,
 	RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/
 };
 
@@ -21,14 +32,60 @@ enum generic_stepping {
 	STEPPING_C0 = 3,
 };
 
+struct raminit_dimm_info {
+	spd_raw_data raw_spd;
+	struct dimm_attr_ddr3_st data;
+	uint8_t spd_addr;
+	bool valid;
+};
+
 struct sysinfo {
 	enum raminit_boot_mode bootmode;
 	enum generic_stepping stepping;
 	uint32_t cpu;		/* CPUID value */
 
 	bool dq_pins_interleaved;
+
+	/** TODO: ECC support untested **/
+	bool is_ecc;
+
+	/**
+	 * FIXME: LPDDR support is incomplete. The largest chunks are missing,
+	 * but some LPDDR-specific variations in algorithms have been handled.
+	 * LPDDR-specific functions have stubs which will halt upon execution.
+	 */
+	bool lpddr;
+
+	struct raminit_dimm_info dimms[NUM_CHANNELS][NUM_SLOTS];
+	union dimm_flags_ddr3_st flags;
+	uint16_t cas_supported;
+
+	/* Except for tCK, everything is eventually stored in DCLKs */
+	uint32_t tCK;
+	uint32_t tAA;			/* Also known as tCL */
+	uint32_t tWR;
+	uint32_t tRCD;
+	uint32_t tRRD;
+	uint32_t tRP;
+	uint32_t tRAS;
+	uint32_t tRC;
+	uint32_t tRFC;
+	uint32_t tWTR;
+	uint32_t tRTP;
+	uint32_t tFAW;
+	uint32_t tCWL;
+	uint32_t tCMD;
+
+	uint8_t lanes;			/* 8 or 9 */
+	uint8_t chanmap;
+	uint8_t dpc[NUM_CHANNELS];	/* DIMMs per channel */
+	uint8_t rankmap[NUM_CHANNELS];
+	uint8_t rank_mirrored[NUM_CHANNELS];
+	uint32_t channel_size_mb[NUM_CHANNELS];
 };
 
 void raminit_main(enum raminit_boot_mode bootmode);
 
+enum raminit_status collect_spd_info(struct sysinfo *ctrl);
+
 #endif
diff --git a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
new file mode 100644
index 0000000000..dbe02c72d0
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <cbfs.h>
+#include <commonlib/clamp.h>
+#include <console/console.h>
+#include <device/dram/ddr3.h>
+#include <device/smbus_host.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <northbridge/intel/haswell/raminit.h>
+#include <string.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+static const uint8_t *get_spd_data_from_cbfs(struct spd_info *spdi)
+{
+	if (!CONFIG(HAVE_SPD_IN_CBFS))
+		return NULL;
+
+	printk(RAM_DEBUG, "SPD index %u\n", spdi->spd_index);
+
+	size_t spd_file_len;
+	uint8_t *spd_file = cbfs_map("spd.bin", &spd_file_len);
+
+	if (!spd_file) {
+		printk(BIOS_ERR, "SPD data not found in CBFS\n");
+		return NULL;
+	}
+
+	if (spd_file_len < ((spdi->spd_index + 1) * SPD_LEN)) {
+		printk(BIOS_ERR, "SPD index override to 0 - old hardware?\n");
+		spdi->spd_index = 0;
+	}
+
+	if (spd_file_len < SPD_LEN) {
+		printk(BIOS_ERR, "Invalid SPD data in CBFS\n");
+		return NULL;
+	}
+
+	return spd_file + (spdi->spd_index * SPD_LEN);
+}
+
+static void get_spd_for_dimm(struct raminit_dimm_info *const dimm, const uint8_t *cbfs_spd)
+{
+	if (dimm->spd_addr == SPD_MEMORY_DOWN) {
+		if (cbfs_spd) {
+			memcpy(dimm->raw_spd, cbfs_spd, SPD_LEN);
+			dimm->valid = true;
+			printk(RAM_DEBUG, "memory-down\n");
+			return;
+		} else {
+			printk(RAM_DEBUG, "memory-down but no CBFS SPD data, ignoring\n");
+			return;
+		}
+	}
+	printk(RAM_DEBUG, "slotted ");
+	const uint8_t spd_mem_type = smbus_read_byte(dimm->spd_addr, SPD_MEMORY_TYPE);
+	if (spd_mem_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
+		printk(RAM_DEBUG, "and not DDR3, ignoring\n");
+		return;
+	}
+	printk(RAM_DEBUG, "and DDR3\n");
+	if (i2c_eeprom_read(dimm->spd_addr, 0, SPD_LEN, dimm->raw_spd) != SPD_LEN) {
+		printk(BIOS_WARNING, "I2C block read failed, trying SMBus byte reads\n");
+		for (uint32_t i = 0; i < SPD_LEN; i++)
+			dimm->raw_spd[i] = smbus_read_byte(dimm->spd_addr, i);
+	}
+	dimm->valid = true;
+}
+
+static void get_spd_data(struct sysinfo *ctrl)
+{
+	struct spd_info spdi = {0};
+	mb_get_spd_map(&spdi);
+	const uint8_t *cbfs_spd = get_spd_data_from_cbfs(&spdi);
+	for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+		for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
+			struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot];
+			dimm->spd_addr = spdi.addresses[channel + channel + slot];
+			if (!dimm->spd_addr)
+				continue;
+
+			printk(RAM_DEBUG, "CH%uS%u is ", channel, slot);
+			get_spd_for_dimm(dimm, cbfs_spd);
+		}
+	}
+}
+
+static void decode_spd(struct raminit_dimm_info *const dimm)
+{
+	/** TODO: Hook up somewhere, and handle lack of XMP data **/
+	const bool enable_xmp = false;
+	memset(&dimm->data, 0, sizeof(dimm->data));
+	if (enable_xmp)
+		spd_xmp_decode_ddr3(&dimm->data, dimm->raw_spd, DDR3_XMP_PROFILE_1);
+	else
+		spd_decode_ddr3(&dimm->data, dimm->raw_spd);
+
+	if (CONFIG(DEBUG_RAM_SETUP))
+		dram_print_spd_ddr3(&dimm->data);
+}
+
+static enum raminit_status find_common_spd_parameters(struct sysinfo *ctrl)
+{
+	ctrl->cas_supported = 0xffff;
+	ctrl->flags.raw = 0xffffffff;
+
+	ctrl->tCK  = 0;
+	ctrl->tAA  = 0;
+	ctrl->tWR  = 0;
+	ctrl->tRCD = 0;
+	ctrl->tRRD = 0;
+	ctrl->tRP  = 0;
+	ctrl->tRAS = 0;
+	ctrl->tRC  = 0;
+	ctrl->tRFC = 0;
+	ctrl->tWTR = 0;
+	ctrl->tRTP = 0;
+	ctrl->tFAW = 0;
+	ctrl->tCWL = 0;
+	ctrl->tCMD = 0;
+	ctrl->chanmap = 0;
+
+	bool yes_ecc = false;
+	bool not_ecc = false;
+
+	for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+		ctrl->dpc[channel] = 0;
+		ctrl->rankmap[channel] = 0;
+		ctrl->rank_mirrored[channel] = 0;
+		ctrl->channel_size_mb[channel] = 0;
+		for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
+			struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot];
+			if (!dimm->valid)
+				continue;
+
+			printk(RAM_DEBUG, "\nCH%uS%u SPD:\n", channel, slot);
+			decode_spd(dimm);
+
+			ctrl->chanmap |= BIT(channel);
+			ctrl->dpc[channel]++;
+			ctrl->channel_size_mb[channel] += dimm->data.size_mb;
+
+			/* The first rank of a populated slot is always present */
+			const uint8_t rank = slot + slot;
+			assert(dimm->data.ranks);
+			ctrl->rankmap[channel] |= (BIT(dimm->data.ranks) - 1) << rank;
+
+			if (dimm->data.flags.pins_mirrored)
+				ctrl->rank_mirrored[channel] |= BIT(rank + 1);
+
+			/* Find common settings */
+			ctrl->cas_supported &= dimm->data.cas_supported;
+			ctrl->flags.raw &= dimm->data.flags.raw;
+			ctrl->tCK  = MAX(ctrl->tCK,  dimm->data.tCK);
+			ctrl->tAA  = MAX(ctrl->tAA,  dimm->data.tAA);
+			ctrl->tWR  = MAX(ctrl->tWR,  dimm->data.tWR);
+			ctrl->tRCD = MAX(ctrl->tRCD, dimm->data.tRCD);
+			ctrl->tRRD = MAX(ctrl->tRRD, dimm->data.tRRD);
+			ctrl->tRP  = MAX(ctrl->tRP,  dimm->data.tRP);
+			ctrl->tRAS = MAX(ctrl->tRAS, dimm->data.tRAS);
+			ctrl->tRC  = MAX(ctrl->tRC,  dimm->data.tRC);
+			ctrl->tRFC = MAX(ctrl->tRFC, dimm->data.tRFC);
+			ctrl->tWTR = MAX(ctrl->tWTR, dimm->data.tWTR);
+			ctrl->tRTP = MAX(ctrl->tRTP, dimm->data.tRTP);
+			ctrl->tFAW = MAX(ctrl->tFAW, dimm->data.tFAW);
+			ctrl->tCWL = MAX(ctrl->tCWL, dimm->data.tCWL);
+			ctrl->tCMD = MAX(ctrl->tCMD, dimm->data.tCMD);
+
+			yes_ecc |=  dimm->data.flags.is_ecc;
+			not_ecc |= !dimm->data.flags.is_ecc;
+		}
+	}
+
+	if (!ctrl->chanmap) {
+		printk(BIOS_ERR, "No DIMMs were found\n");
+		return RAMINIT_STATUS_NO_MEMORY_INSTALLED;
+	}
+	if (!ctrl->cas_supported) {
+		printk(BIOS_ERR, "Could not resolve common CAS latency\n");
+		return RAMINIT_STATUS_UNSUPPORTED_MEMORY;
+	}
+	/** TODO: Properly handle ECC support and ECC forced **/
+	if (yes_ecc && not_ecc) {
+		/** TODO: Test if the ECC DIMMs can be operated as non-ECC DIMMs **/
+		printk(BIOS_ERR, "Both ECC and non-ECC DIMMs present, this is unsupported\n");
+		return RAMINIT_STATUS_UNSUPPORTED_MEMORY;
+	}
+	if (yes_ecc)
+		ctrl->lanes = NUM_LANES;
+	else
+		ctrl->lanes = NUM_LANES_NO_ECC;
+
+	ctrl->is_ecc = yes_ecc;
+
+	/** TODO: Complete LPDDR support **/
+	ctrl->lpddr = false;
+
+	return RAMINIT_STATUS_SUCCESS;
+}
+
+enum raminit_status collect_spd_info(struct sysinfo *ctrl)
+{
+	get_spd_data(ctrl);
+	return find_common_spd_parameters(ctrl);
+}
-- 
2.39.2